개발 학습 실습실

이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.

공부 › Java › 기본 문법

변수와 타입

변수를 다른 변수에 대입하면 무엇이 복사되나

변수는 무언가를 담아 두는 칸입니다. int 같은 기본형 변수의 칸에는 값 자체가 들어 있고, 클래스 타입 같은 참조형 변수의 칸에는 객체가 있는 곳을 가리키는 참조가 들어 있습니다. 대입할 때도 메서드에 넘길 때도 복사되는 것은 언제나 "칸에 들어 있는 것"입니다. 그래서 기본형은 복사본을 바꿔도 원래 변수가 그대로이고, 참조형은 두 변수가 같은 객체 하나를 함께 보게 됩니다.

실행으로 대조 · JDK 21에서 열다섯 가지 조합을 실행해 대조

이 실습에 나오는 개념

기본형과 참조형
자바의 타입은 기본형과 참조형 두 가지로 나뉜다. 기본형은 byte, short, int, long, float, double, char, boolean의 여덟 가지이며, 변수가 값 자체를 담는다. 참조형은 클래스, 인터페이스, 배열처럼 그 밖의 모든 타입이며, 변수가 객체 자체가 아니라 객체를 가리키는 참조를 담는다. 그래서 기본형 변수를 대입하면 값이 복사되어 두 변수가 서로 영향을 주지 않지만, 참조형 변수를 대입하면 참조가 복사되어 두 변수가 같은 객체를 가리키게 된다. 참조형 변수는 아무 객체도 가리키지 않는다는 뜻의 null을 담을 수 있고, 기본형 변수는 null을 담을 수 없다.
값에 의한 전달
메서드를 호출할 때 인자로 적은 변수 자체가 아니라 그 변수에 들어 있는 것을 복사해 매개변수에 넣어 주는 방식이다. 영어로는 pass by value 또는 call by value라고 하며, 자바는 언제나 이 방식으로만 인자를 넘긴다. 기본형이면 값이 복사되고, 참조형이면 참조가 복사되어 매개변수와 호출한 쪽 변수가 같은 객체를 가리킨다. 그래서 메서드 안에서 넘겨받은 객체의 내용을 바꾸면 호출한 쪽에서도 바뀐 내용이 보이지만, 매개변수에 다른 객체를 대입해도 호출한 쪽 변수는 바뀌지 않는다.
불변 객체
생성된 뒤에는 상태가 바뀌지 않는 객체다. 값이 바뀌지 않으므로 여러 스레드가 동시에 사용해도 안전하고, 다른 곳에 넘겨도 변경될 걱정이 없으며, 캐싱하거나 해시 컬렉션의 키로 쓰기에 적합하다. 필드를 private final로 선언하고 상태를 바꾸는 메서드를 두지 않으며, 클래스를 final로 해 하위 클래스가 동작을 바꾸지 못하게 한다. 필드가 가변 객체(List, Date 등)를 가리키면 생성자와 getter에서 복사본을 주고받아(방어적 복사) 밖에서 내부를 바꾸지 못하게 한다.
힙(Heap) 메모리
new로 만든 모든 객체와 배열이 저장되는 메모리 영역으로, 모든 스레드가 공유한다. 가비지 컬렉터가 관리하며 더 이상 쓰이지 않는 객체를 회수한다. 크기는 -Xms(초기), -Xmx(최대) 옵션으로 정하고, 공간이 부족하면 OutOfMemoryError가 발생한다.

이렇게 바꿔 보세요

처음 설정으로 실행하면

  1. Point a = new Point(1): new가 힙에 Point 객체를 하나 만듭니다(x는 1). 변수 a의 칸에는 객체 자체가 아니라 그 객체가 있는 곳을 가리키는 참조가 들어갑니다.
  2. Point b = a: a의 칸에 들어 있는 참조를 복사해 b의 칸에 넣습니다. 객체는 복사되지 않습니다. 객체는 여전히 하나이고, a와 b가 같은 객체를 가리킵니다.
  3. b.x = 99: b의 칸에 든 참조를 따라가서, 그 객체의 x를 99로 바꿉니다. b의 칸 자체는 바뀌지 않습니다. a도 바로 이 객체를 가리키고 있습니다.
  4. System.out.println(b.x): b가 가리키는 객체의 x를 읽습니다. 출력은 99입니다.
  5. System.out.println(a.x): a가 가리키는 객체의 x를 읽습니다. 출력은 99입니다. b로 바꾼 객체가 a가 가리키는 바로 그 객체이기 때문입니다.

실행 결과

코드

class Point {
    int x;
    Point(int x) { this.x = x; }
}

Point a = new Point(1);
Point b = a;                        // a에 든 참조가 복사된다
b.x = 99;                           // 가리키는 객체의 내용을 바꾼다

System.out.println(b.x);
System.out.println(a.x);

이 모형의 전제

열다섯 가지 조합의 출력(복사본에서 읽은 값, a에서 읽은 값)을 이 PC의 JDK 21에서 같은 구조의 코드를 실행한 결과와 대조했습니다. "참조"는 객체가 있는 곳을 가리키는 값이라는 뜻으로 썼습니다. 자바에서는 이 값을 숫자로 꺼내 볼 수 없고, 실제로 메모리 주소 그대로인지도 JVM마다 다릅니다. 변수의 칸이 스택에, 객체가 힙에 놓인다는 그림은 지역 변수에 대한 일반적인 설명이며, JIT 컴파일러가 최적화하면 객체를 힙에 만들지 않을 수도 있지만 프로그램의 결과는 같습니다. 문자열 "hi"는 실제로는 String Pool에 있는 객체인데, 여기서는 힙의 객체 하나로만 그렸습니다. 메서드에 넘길 때 생기는 호출 프레임은 "스택과 힙" 실습에서, ==와 equals의 차이는 "==와 equals" 실습에서 다룹니다.

다음 실습: 연산자와 형변환

이 실습에 나오는 개념

이 실습에서 보는 것

설정

코드


      
    

그림

실행 비교

이렇게 바꿔 보세요