Developer.

[멋사 백엔드 19기] TIL 3일차

📂 목차


📚 본문

Java = 강형 언어이며, 변수에 항상 데이터의 형태가 뭔지 알려줘야 변수 선언이 가능한
타입에 엄격한 언어라는 말이다.

Java 변수

변수는 데이터를 쓸 수 있는 영역을 마련해주는 공간, 쓸 변수마다 그 공간이 달라진다.
변수를 선언할 때 대입연산자(=)literal 을 통해 변수에 값을 할당해줄 수 있다.

literal: 데이터 그 자체, 실제 값

// 10 은 리터럴 특히 정수형 리터럴이라고 함
int a = 10;

정수 리터럴

  1. 10진수: int x = 10
  2. 8진수: int y = 010
  3. 16진수: int z = 0xA
  4. 2진수: int b = 0b1010

10진수 리터럴은 _를 사용하여 숫자를 쉽게 보게 할 수 있다.

기타 리터럴

  1. long 리터럴: int a = 10000000000000L
  2. 실수 리터럴
    • 3.193
    • 10f
    • 3.29F
    • 3d - d 혹은 D 생략 가능
    • 1.2e3 - 지수 표기법
  3. 논리 리터럴: true, false
  4. null 리터럴: null
  5. 문자 리터럴:
    • ‘A’
    • ‘\n’ - 이스케이프 문자(\t, ", ', \, \r 등등)
    • ‘\u0041’
    • “String”

char 은 한 문자를 의미, 여기는 숫자(유니코드 내의 숫자) 와 홑따옴표로 한 개의 문자가 들어가도록 리터럴을 넣어줄 수 있음

Java 변수 유형

자바 변수는 선언하는, 내용의 특징에 따라 혹은 선언하는 위치에 따라 수식어가 붙는다.

데이터 타입에 따른 분류

  • 기본형(Primitive): 실제 값 자체를 저장하며 byte, short, int, long, float, double, char, boolean 8가지가 있다.
    • Stack 이라는 메모리 영역에 저장
  • 참조형(Reference): 그 이외에
    • Heap 이라는 영역에 저장

범위에 따른 분류

클래스 범위와 함수 범위와 변수 할당의 차이

public class Exam {
    int intValue;

    public static void main(String[] args) {
        // 초기화하지 않으면 사용이 불가함
        // 직접 리터럴을 대입해줘야 함
        int intValue_;
    }
}
  • intValue: 클래스 변수, 필드 변수, 멤버 변수라고 하며 기본적으로 값이 초기화(0으로)된다.
  • intValue_: 지역 변수라고 하며 메서드 내에서 자동으로 초기화가 되지 않고, 초기화를 해줘야 한다.

이를 더 자세히 보자.

Local Variable

메서드/생성자/블록 안에서 선언하는 변수이다.

Field

클래스 범위(스코프) 내에서 선언한 변수이다. 보통 클래스 범위의 맨 위에 선언한다.

  • Instance Variable: 객체가 생성될 때 함께 생성되며, 값이 자동 초기화되는 특징이 있다.
  • Static Variable: static 키워드로 선언하며, 클래스 로딩시 딱 한 번만 메모리에 올라가고, 모든 객체가 공유를 한다.
Parameter

메서드 호출 시 전달되는 값이며, 일종의 지역변수이지만 특별히 구분해서 부르게 된다.
파라미터에서 선언된 변수명을 로컬 변수로 다시 선언할 수 없다.

final 키워드

재할당, 재정의 금지라는 뜻 이 이후에는 변수를 바꿀 수 없음

final double PI = 3.141592d

final의 primitive 는 대문자로 쓰는게 관례(Convention)이다. final의 String 도 마찬가지

Java 변수 할당 방식

위 변수들은 전부 JVM이 메모리에 할당하여 사용할 수 있게 된다.

Java 프로그램이 실행되면 JVM은 크게 Heap, Stack, Method Area(=Metaspace) 세 부분을 사용하며, 각 영역마다 다음 특징이 있다.

메모리 영역

  • Heap: new 키워드로 생성된 instance 들이 저장
    • ⭐️ 모든 스레드가 공유하는 메모리 영역
    • ⭐️ GC 의 대상이 된다.
  • Stack: 메서드 호출 시마다 Stack Frame이 생성된다.
    • 지역 변수가 저장되며, 메서드가 끝나면 해당 Stack Frame 삭제
  • Method Area(Metaspace)
    • 클래스 정보, static 변수, 상수 풀(Constant Pool) 저장
    • JVM 시작 시 로딩되는 영역이다(static 으로 선언된 변수가 포함됨)

Constant Pool

  • 클래스 상수 풀(Class Constant Pool): 클래스 메타데이터, 상수 값, 필드, 메서드, 참조 등을 저장
  • 문자열 상수 풀(String Constant Pool): 문자열 리터럴을 효율적으로 관리하기 위해 사용하는 특수 저장공간이며, 문자열 리터럴이 생성될 때마다 JVM 은 해당 문자열이 문자열 상수 풀에 존재하는지 확인하고, 존재하지 않으면 추가하여 메모리를 최적화 함

Java 형 변환

연산자들은 생략하고, 얻어갈 수 있는 형 변환에 대한 것들을 적는다.

Implicit Type Casting

자동 형변환은 작은 타입에서 큰 타입으로 변환이 필요할 때 컴파일러가 자동으로 해주는 것을 의미한다.

  1. 작은 타입 → 큰 타입 가능
  2. 정수형 → 실수형 가능

하지만 이때 long → float 의 경우에는 변환 시에 값 손실을 감안하고 형변환을 진행한다(소수점으로 표현하고 지수자리로 표현됨에 따라 2진법을 사용하는 컴퓨터는 손실이 됨).

Explicit Type Casting

큰 타입 → 작은 타입 으로 변환할 때 사용하며, 손실을 감안하고 변환한다.

long l1 = 12345678910L;
int i1 = (int) l1;

System.out.println(i1);
// 출력: -539222978

Arithmetic Promotion

숫자들을 다룰 때, byte, short, char 등으로 표현했다고 치자. 그러고 두 수를 더하고 싶을 때 일반적으로 덧셈 연산을 통해 수행을 한다. 하지만 여기서 short + short 는 short 로 나와버리면 short 로는 표현할 수 없는 값이 생기고, 이는 음수로 바뀔 것이다.

이를 방지하고 값의 신뢰성을 높이기 위해 덧셈이나 곱셈 등과 같이 크기가 늘어나는 연산에 대해 산술 승격이 자동으로 이루어지게 된다.

  • byte, short, char는 연산 시 자동으로 int로 승격 → int
  • 연산 중 더 큰 타입과 만나면 큰 타입으로 승격

Java 공식 문서 파헤치기

자주 사용되는 클래스들은 전부 java.lang 에 포함되어 있으며,
import를 쓰지 않아도 java.lang의 모듈에 있는 기능들은
전부 컴파일러가 알아서 추가해준다.

자동 추가 클래스 목록

  • String
  • Math
  • Number
  • Object
  • System

여기서 주의할 점은 java.lang 에 있는 클래스들만이지, java.lang.reflect 와 같은 패키지는 자동 import 가 되지 않는다.

더 많은 내용은 공식 문서를 참고하자.

Number

Number 추상 클래스는 byte, double, float, int, long 타입의 수퍼 클래스이다.

주로 숫자 데이터를 다룰 수 있게하고, 정의한다.

메서드

  • (primitive)Value(): Number 를 통해 위 primitive 타입 들로 변환할 수 있다.

String

String 은 immutable 타입이고, reference 형태인 특이한 데이터타입이다.

즉, 한 번 생성되면 변하지 않고 변하지 않으면 안전하게 공유를 할 수 있다는 뜻이 된다(이전의 String Constant Pool 참고).

Field

  • static Comparator<String> CASE_INSENSITIVE_ORDER: 문자열을 정렬할 때에 대소문자를 구분하지 않고 정렬을 하고 싶을때 사용한다.

보통 Collections.sort, List.sort 등에 넘겨서 많이 쓰이며, 정렬을 하는 행위에서 두 값을 비교할 때 어떤 정책을 따라, 어떤 규칙을 따라 비교할지에 Comparator 를 넣어준다.

함수들은 너무 많기에 코드 문제를 풀면서 익히는 것이 좋을 듯하다.

System

멤버 변수

  • err: 표준 에러 출력 스트림
  • in: 표준 입력 스트림
  • out: 표준 출력 스트림

여기서 스트림이란 데이터를 문자나 바이트 형태로 전달하는 통로를 의미하며,

  • 출력스트림: 데이터를 바이트 단위로 외부로 내보낼 수 있는 스트림
  • 입력스트림: 데이터를 바이트 단위로 읽어오는 최상위 추상 클래스

Runtime

JVM 환경을 나타내는 클래스이며 운영체제와 JVM 사이의 직접적인 시스템 작업을 수행할 수 있게 해주는 클래스이다.

가장 핵심 클래스이며, Singleton 패턴으로 다른 클래스들에게 제공된다. 기본적으로 Runtime.getRuntime() 으로 접근하면 된다.

주요 메서드

  • exec(String command): 지정된 환경과 작업 디렉토리에서 별도의 프로세스로 지정된 문자열 명령을 실행(ex. notepad, ls 등등)
    Deprecated 되어서 이젠 쓰이지 않고 ProcessBuilder 를 통해 Process 를 만들어 사용하게 된다.
  • gc(): 가비지 컬렉션 요청
  • totalMemory() / freeMemory(): JVM 이 가용한 메모리 상태 확인
  • exit(int status): JVM status code 의 상태로 종료
  • addShutdownHook(Thread hook): JVM 종료 시 실행할 코드 등록(이런걸 훅이라고 한다)

훅: 특정 이벤트나 시점에서 사용자가 원하는 코드를 끼워 넣을 수 있는 지점

Process

Runtime 으로 exec 을 할 수 있지만, 이는 들어갈 수 있는 인자의 제어를 효과적으로 못하여 Process 라는 실행 가능한 클래스를 통해 실행하게 된다.

ProcessBuilder pb = new ProcessBuilder("echo", "Hello");
Process process = pb.start();  // 프로세스 실행

위 코드 처럼 ProcessBuilder 는 네이티브 프로세스(운영체제의 개념)를 Builder 패턴으로 어떤 프로세스를 만들지 정의를 해주고 프로세스를 만들 수 있다(재사용성 증가).

이를 Runtime 에게 주어서 exec()으로 실행하게 된다.

주요 메서드

  • InputStream getInputStream(): 프로세스가 표준 출력에 쓴 데이터 읽기
  • InputStream getErrorStream(): 프로세스가 표준 에러에 쓴 데이터 읽기
  • OutputStream getOutputStream(): 프로세스가 표준 입력에 쓰기
  • void destroy(): 하위 프로세스 종료
  • getRuntime(): 현재 Java Application 과 연결된 런타임 객체 반환

Object

모든 클래스가 상속받는 최상위 클래스이다.
보통 오버라이드하여 객체 동작을 커스터마이징한다.

메서드

  • toString(): 객체를 문자로 표현하는 법 정의
  • equals(Object obj): 객체 비교 방법 정의
  • hashCode(): 객체 해시코드 반환
  • getClass(): 객체의 Runtime 클래스 정보 반환
  • clone(): 객체 복제 방법 정의(깊은/얕은 복사 가능)
  • finalize(): GC가 객체를 수거하기 전에 호출
  • wait(), notify(), notifyAll(): 스레드 동기화에 사용

Object에 스레드 관련 메서드가 있는 이유는,
자바에서 모든 객체를 Monitor로 사용할 수 있기 때문이다.
synchronized 키워드를 사용하면 객체 단위로 Monitor Lock이 걸리며,
synchronized는 이 추상적 클래스 모니터의 구현체인 동기화 Monitor를 기준으로 이루어진다.

모니터란 한 번에 하나의 Thread만이
모니터 내의 임계 구역(critical section)에 들어올 수 있도록 보장하는 장치이다.
따라서 공유 자원에 대한 접근은 반드시 Monitor Lock을 획득한 스레드만 가능하다.

즉, 객체가 모니터 락을 얻어서 동기화된 코드 블록(또는 메서드)을 실행할 수 있게 되는 것이
synchronized (모니터 락을 획득할 대상) 구문이다.

보통 this 자체가 모니터 락 대상이 되는 경우가 많지만,
별도의 Object 멤버 변수를 선언해서 락으로 쓰기도 한다.
또는 메서드에 synchronized를 직접 선언할 수도 있다.

class Counter {
    private static int count = 0;

    public static synchronized void increment() {
        count++;
        // 이 시점에 Counter.class 모니터 락을 보유 중이므로
        // wait() / notify() 호출 가능
    }
}

이는 운영체제에서 더 다루겠다. 또한 더 많은 기능들과 기본적인 자바 문법들은 전부 타 블로그에 정보가 매우 많기에 생략한다.

Wrapper Variables

자바의 기본형 타입을 객체로 다룰 수 있게 해주는 클래스이며 reference 타입으로 감싸는 역할을 한다.

Integer, Short, Byte, Boolean, Float, Double …

다음 이점을 위해 사용한다:

  1. 객체만 다룰 수 있는 API 사용
    • Collection 은 기본형을 직접 담을 수 없기 때문에 다양한 유틸 기능을 가지는 Collection 을 사용하기 위해 형변환을 수행한다.
  2. 편리한 형 변환 유틸 기능을 public static 함수로 제공
    • Integer.parseInt(), Double.toString 등 편리한 기능 제공
  3. nullable
    • 아무 값도 존재하지 않는 그런 값을 표현할 수 있다.

Auto Boxing

Java 5 이후에 오토 박싱이라는 것으로 primitive 변수를 Integer 선언한 변수에 담을 때 자동으로 컴파일러가 형변환을 시행하여 들어가게 되는게 Auto Boxing 이다.

int n = 10;

// Boxing
Integer obj1 = Integer.valueOf(n);

// Unboxing
int m = obj1.intValue();

// 오토 박싱 & 언박싱
Integer obj2 = n;     // int → Integer (자동 박싱)
int k = obj2;         // Integer → int (자동 언박싱)