개발 학습 실습실

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

공부 › Java › 기본 문법

배열

배열 변수를 대입하면 배열이 복사되나

배열은 같은 타입의 칸을 정해진 개수만큼 나란히 붙여 놓은 것입니다. 칸의 번호(인덱스)는 0부터 시작하고, 길이는 만들 때 정해져 바뀌지 않으며, 만들자마자 모든 칸이 기본값으로 채워집니다. 배열도 객체라서 변수에는 배열을 가리키는 참조가 들어갑니다. 그래서 b = a는 배열을 복사하지 않고, 2차원 배열은 "배열을 칸에 담은 배열"이 됩니다.

실행으로 대조 · JDK 21에서 일흔두 가지 조합을 실행해 대조

이 실습에 나오는 개념

배열
같은 타입의 값을 정해진 개수만큼 나란히 담는 객체다. int[] a = new int[3]처럼 만들며, 칸의 번호(인덱스)는 0부터 시작해 길이 - 1까지다. 길이는 만들 때 정해지고 바뀌지 않으며 a.length로 읽는다. 만들자마자 모든 칸이 기본값(숫자는 0, boolean은 false, 참조형은 null)으로 채워지고, 범위를 벗어난 번호를 쓰면 실행 중에 ArrayIndexOutOfBoundsException이 발생한다. 배열도 객체이므로 배열 변수에는 배열의 참조가 들어 있고, 다른 변수에 대입하면 두 변수가 같은 배열을 가리키게 된다.
얕은 복사와 깊은 복사
얕은 복사는 객체나 배열의 겉 한 겹만 새로 만들고, 그 안에 든 참조는 그대로 옮겨 담는 것이다. 그래서 복사본과 원본이 안쪽 객체를 함께 가리키고, 한쪽에서 안쪽 객체를 바꾸면 다른 쪽에서도 바뀐 것이 보인다. 배열의 clone()과 Arrays.copyOf가 얕은 복사이며, 2차원 배열에 쓰면 바깥 배열만 새로 만들어지고 행 배열은 함께 쓰게 된다. 깊은 복사는 안쪽 객체까지 모두 새로 만들어 원본과 완전히 따로 두는 것으로, 2차원 배열이라면 행마다 clone을 해야 한다. 칸에 든 것이 기본형 값이거나 불변 객체라면 얕은 복사만으로도 서로 영향을 주지 않는다.
기본형과 참조형
자바의 타입은 기본형과 참조형 두 가지로 나뉜다. 기본형은 byte, short, int, long, float, double, char, boolean의 여덟 가지이며, 변수가 값 자체를 담는다. 참조형은 클래스, 인터페이스, 배열처럼 그 밖의 모든 타입이며, 변수가 객체 자체가 아니라 객체를 가리키는 참조를 담는다. 그래서 기본형 변수를 대입하면 값이 복사되어 두 변수가 서로 영향을 주지 않지만, 참조형 변수를 대입하면 참조가 복사되어 두 변수가 같은 객체를 가리키게 된다. 참조형 변수는 아무 객체도 가리키지 않는다는 뜻의 null을 담을 수 있고, 기본형 변수는 null을 담을 수 없다.
ArrayList
내부에 배열을 두고 가득 차면 더 큰 배열로 옮겨 담는 리스트다. 인덱스로 원소에 접근하는 속도가 O(1)로 빠르고, 끝에 추가하는 것도 분할 상환으로 O(1)이다. 중간에 삽입하거나 삭제하면 뒤의 원소들을 한 칸씩 옮겨야 해서 O(n)이 걸린다.

이렇게 바꿔 보세요

처음 설정으로 실행하면

  1. int[] a = new int[3]: 힙에 int 칸 3개가 나란히 붙은 배열이 만들어집니다. 칸에는 앞에서부터 0, 1, 2라는 번호(인덱스)가 붙습니다. 값을 넣지 않았지만 모든 칸이 기본값으로 채워져 있습니다(int의 기본값은 0). 변수 a에는 이 배열을 가리키는 참조가 들어갑니다.
  2. int[] b = a: a에 든 참조가 b로 복사됩니다. 배열은 새로 만들어지지 않습니다. a와 b가 같은 배열 하나를 가리킵니다.
  3. System.out.println(a == b): 두 변수가 같은 배열을 가리키는지 비교합니다. 출력은 true입니다.
  4. b[2] = 7: b가 가리키는 배열의 2번 칸에 값을 넣습니다. 길이가 3이면 번호는 0부터 2까지이므로 2번이 마지막 칸입니다.
  5. System.out.println(Arrays.toString(a)): a가 가리키는 배열의 내용을 출력합니다. 출력은 [0, 0, 7]입니다. b로 넣은 값이 a에서도 보입니다. a와 b가 같은 배열을 가리키기 때문입니다.
  6. System.out.println(Arrays.toString(b)): b가 가리키는 배열의 내용을 출력합니다. 출력은 [0, 0, 7]입니다.

실행 결과

코드

import java.util.Arrays;

int[] a = new int[3];                   // 칸 3개. 기본값으로 채워진다
int[] b = a;                            // 참조만 복사된다

System.out.println(a == b);
b[2] = 7;                               // 2번 칸에 넣는다
System.out.println(Arrays.toString(a));
System.out.println(Arrays.toString(b));

이 모형의 전제

일흔두 가지 조합의 결과(a == b, 예외와 그 메시지, 두 번의 출력)를 이 PC의 JDK 21에서 같은 구조의 코드를 실행한 결과와 대조했습니다. 요약에 적은 내용 가운데 Arrays.copyOf로 더 긴 배열을 얻는 것, 행마다 clone하면 행을 함께 쓰지 않는 것, 메서드에 넘긴 배열을 메서드 안에서 바꾸면 밖에서도 보이는 것, i <= a.length로 돌면 예외가 나는 것, null인 칸에 메서드를 부르면 NullPointerException이 나는 것, a.length와 a[0].length의 값도 함께 실행해 확인했습니다. 예외 메시지의 문장은 JDK 버전에 따라 다를 수 있으며 여기 적은 것은 JDK 21의 것입니다. 배열의 칸이 메모리에서 실제로 나란히 붙어 있는지는 실행으로 확인한 것이 아니라 JVM의 일반적인 구현 방식을 따른 설명입니다. 그림에서 String 값 "A"는 칸 안에 바로 적었지만, 실제로는 칸에 String 객체의 참조가 들어 있습니다. { 1, 2, 3 }처럼 값을 적어 만드는 방법, 행마다 길이가 다른 2차원 배열, 향상된 for문은 다루지 않았습니다.

앞 실습: 조건문과 반복문 · 다음 실습: 클래스, 객체, 생성자

이 실습에 나오는 개념

이 실습에서 보는 것

설정

코드


      
    

그림

실행 비교

이렇게 바꿔 보세요