본문 바로가기

배열과 인덱스

같은 타입의 여러 값을 배열에 저장하고 길이와 마지막 인덱스를 구분해 회원가입의 고정 용량 저장소를 만듭니다.

회원이 늘 때마다 email1, email2, email3 변수를 추가하면 같은 합계·출력 코드도 함께 늘어납니다.

배열은 같은 타입의 원소를 하나의 참조와 인덱스로 관리합니다.

길이는 원소 수이고 마지막 인덱스는 길이보다 하나 작습니다.


배열 경계 예외

lab/LengthAsIndex.java
public final class LengthAsIndex {
    public static void main(String[] args) {
        int[] age = {20, 30, 40};
        System.out.println("length=" + age.length);
        System.out.println(age[age.length]);
    }
}
실패 관찰
length=3
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3

정상 인덱스는 0, 1, 2입니다.

length는 원소 수 3이지 마지막 위치가 아닙니다.

반복 조건은 index < age.length로 둡니다.

length는 개수이고 읽기는 index가 length보다 작을 때만 한다

인덱스 0·1·2를 읽고 3에서 index<length 검사로 종료하는 흐름과 검사 없는 실패를 대비합니다. 조건이 잘못된 원소 접근을 막는 전환을 드러낼 수 있습니다.

길이 3 배열의 유효 인덱스와 검사 없는 범위 밖 접근

age의 길이 3은 원소 수이며 정상 순회는 index 3에서 읽기를 멈춘다. age[length]를 직접 읽으면 범위 예외가 발생한다.

ARRAY · BOUNDARIES

length는 개수, 마지막 인덱스는 length−1

길이3의 배열은 인덱스0·1·2에서 읽습니다. 0부터 시작하는 정상 순회는3에서 멈추지만, age[3]을 직접 읽으면 범위 예외가 납니다.

length는 개수이고 읽기는 index가 length보다 작을 때만 한다세 원소 배열의 인덱스 0부터 2는 읽을 수 있지만 정상 순회는 3에서 멈추며, age[length]의 무조건 접근은 예외가 된다. falsetrueage = {20, 30, 40} · length = 3index 0index 1index 2203040유효 인덱스 0, 1, 2 · 마지막은 2범위 밖 · index 3age[age.length]ArrayIndexOutOfBoundsException검사 없는 접근의 실패index < age.length ?읽기 전에 조건 검사index = 3 · 순회 종료age[3]은 읽지 않음index = 0 / 1 / 2age[index] 읽기
length는 개수, 마지막 인덱스는 length−1 length3의 실제 index0/1/2와값20/30/40, 정상조건3에서는 미읽기 종료; 무검사age[3] 별도예외 age · length=3 20 0 30 1 40 2 아래 숫자는 index 0·1·2 정상 순회 · index=0부터 index < length ? 읽기 전 검사 참 age[index] 읽기 0→20 · 1→30 · 2→40 이후 다음 index 검사 거짓 index=3 · 순회 종료 age[3] 읽지 않음 읽기 경계의 그림 검사 없는 별도 접근 age[age.length] age[3] 직접 읽기 ArrayIndexOutOf BoundsException 존재하지 않는 칸
실제 배열
age={20,30,40}, length=3입니다. index 0→20, 1→30, 2→40이며 마지막 유효 인덱스는 2입니다.
정상 순회
index < age.length를 먼저 검사합니다. 0·1·2에서만 읽고, index=3이면 false여서 읽지 않고 끝냅니다.
검사 없는 접근
age[age.length]는 age[3]이므로 ArrayIndexOutOfBoundsException입니다.

new int[0]에는 유효 인덱스가 없습니다. index < empty.length 반복은 0회지만 empty[0]을 읽으면 예외입니다.

이 예의 조건

  • age={20,30,40}, length=3이며 유효 인덱스는 0, 1, 2입니다.
  • 정상 순회의 조건 검사와 원 실패 예제의 무조건 age[age.length] 접근을 같은 index=3 경계에서 비교합니다.
관계도의 보충 설명
원 도표의 전체 관계·라벨·조건
구분내용
22index = 0
원 도표의 설명과 주석
구분내용
전체 읽기 정보length는 개수이고 읽기는 index가 length보다 작을 때만 한다 길이 3 배열의 유효 인덱스와 검사 없는 범위 밖 접근 age의 길이 3은 원소 수이며 정상 순회는 index 3에서 읽기를 멈춘다. age[length]를 직접 읽으면 범위 예외가 발생한다. ARRAY · BOUNDARIES length는 개수, 마지막 인덱스는 length−1 길이 3의 배열은 인덱스 0·1·2에서 읽습니다. 0부터 시작하는 정상 순회는 3에서 멈추지만, age[3]을 직접 읽으면 범위 예외가 납니다. 실제 배열 age={20,30,40} , length=3 입니다. index 0→20 , 1→30 , 2→40 이며 마지막 유효 인덱스는 2입니다. 정상 순회 index < age.length 를 먼저 검사합니다. 0·1·2에서만 읽고, index=3 이면 false여서 읽지 않고 끝냅니다. 검사 없는 접근 age[age.length] 는 age[3] 이므로 ArrayIndexOutOfBoundsException 입니다. new int[0] 에는 유효 인덱스가 없습니다. index < empty.length 반복은 0회지만 empty[0] 을 읽으면 예외입니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
다음 인덱스 후보

정상 순회는 유효 위치를 읽은 뒤 3을 검사하고 실패 예제는 length=3을 직접 인덱스로 씁니다.

  • 배열 값: age[0]=20, age[1]=30, age[2]=40
  • 조건 있는 순회: 다음 후보 index=3
  • 검사 없는 예제: 요청 index=age.length=3
  • length는 원소 개수이므로 값 3은 마지막 유효 인덱스 2를 벗어납니다.
읽기 허용 여부

순회는 3<3을 검사하지만 실패 예제는 사전 조건 없이 3번 위치를 읽으려 합니다.

  • 순회 검사: 3<3 → false, 읽기 안 함
  • 무조건 접근: age[3] 요청, 범위 밖
  • 유효 범위: 0≤index<3
  • 읽기 앞의 조건은 순회를 종료시키고 무조건 접근은 배열의 범위 검사에서 실패합니다.
각 경로의 종료

조건 있는 순회는 정상 종료하고 무조건 접근은 예외로 끝납니다.

  • 정상 순회: age[3] 읽기 없이 종료
  • 실패 예제: ArrayIndexOutOfBoundsException
  • length 출력: 원 실패 예제에서 length=3 출력 후 예외
  • 정상 종료 뒤 별도 실패가 이어지는 흐름이 아니라 두 접근 정책의 결과를 비교합니다.

length는 개수이고 읽기는 index가 length보다 작을 때만 한다

인덱스 0·1·2를 읽고 3에서 index<length 검사로 종료하는 흐름과 검사 없는 실패를 대비합니다. 조건이 잘못된 원소 접근을 막는 전환을 드러낼 수 있습니다.

길이 3 배열의 유효 인덱스와 검사 없는 범위 밖 접근

age의 길이 3은 원소 수이며 정상 순회는 index 3에서 읽기를 멈춘다. age[length]를 직접 읽으면 범위 예외가 발생한다.

ARRAY · BOUNDARIES

length는 개수, 마지막 인덱스는 length−1

길이3의 배열은 인덱스0·1·2에서 읽습니다. 0부터 시작하는 정상 순회는3에서 멈추지만, age[3]을 직접 읽으면 범위 예외가 납니다.

length는 개수이고 읽기는 index가 length보다 작을 때만 한다세 원소 배열의 인덱스 0부터 2는 읽을 수 있지만 정상 순회는 3에서 멈추며, age[length]의 무조건 접근은 예외가 된다. falsetrueage = {20, 30, 40} · length = 3index 0index 1index 2203040유효 인덱스 0, 1, 2 · 마지막은 2범위 밖 · index 3age[age.length]ArrayIndexOutOfBoundsException검사 없는 접근의 실패index < age.length ?읽기 전에 조건 검사index = 3 · 순회 종료age[3]은 읽지 않음index = 0 / 1 / 2age[index] 읽기
length는 개수, 마지막 인덱스는 length−1 length3의 실제 index0/1/2와값20/30/40, 정상조건3에서는 미읽기 종료; 무검사age[3] 별도예외 age · length=3 20 0 30 1 40 2 아래 숫자는 index 0·1·2 정상 순회 · index=0부터 index < length ? 읽기 전 검사 참 age[index] 읽기 0→20 · 1→30 · 2→40 이후 다음 index 검사 거짓 index=3 · 순회 종료 age[3] 읽지 않음 읽기 경계의 그림 검사 없는 별도 접근 age[age.length] age[3] 직접 읽기 ArrayIndexOutOf BoundsException 존재하지 않는 칸
실제 배열
age={20,30,40}, length=3입니다. index 0→20, 1→30, 2→40이며 마지막 유효 인덱스는 2입니다.
정상 순회
index < age.length를 먼저 검사합니다. 0·1·2에서만 읽고, index=3이면 false여서 읽지 않고 끝냅니다.
검사 없는 접근
age[age.length]는 age[3]이므로 ArrayIndexOutOfBoundsException입니다.

new int[0]에는 유효 인덱스가 없습니다. index < empty.length 반복은 0회지만 empty[0]을 읽으면 예외입니다.

이 예의 조건

  • age={20,30,40}, length=3이며 유효 인덱스는 0, 1, 2입니다.
  • 정상 순회의 조건 검사와 원 실패 예제의 무조건 age[age.length] 접근을 같은 index=3 경계에서 비교합니다.

관계도의 보충 설명

원 도표의 전체 관계·라벨·조건
구분내용
22index = 0
원 도표의 설명과 주석
구분내용
전체 읽기 정보length는 개수이고 읽기는 index가 length보다 작을 때만 한다 길이 3 배열의 유효 인덱스와 검사 없는 범위 밖 접근 age의 길이 3은 원소 수이며 정상 순회는 index 3에서 읽기를 멈춘다. age[length]를 직접 읽으면 범위 예외가 발생한다. ARRAY · BOUNDARIES length는 개수, 마지막 인덱스는 length−1 길이 3의 배열은 인덱스 0·1·2에서 읽습니다. 0부터 시작하는 정상 순회는 3에서 멈추지만, age[3]을 직접 읽으면 범위 예외가 납니다. 실제 배열 age={20,30,40} , length=3 입니다. index 0→20 , 1→30 , 2→40 이며 마지막 유효 인덱스는 2입니다. 정상 순회 index < age.length 를 먼저 검사합니다. 0·1·2에서만 읽고, index=3 이면 false여서 읽지 않고 끝냅니다. 검사 없는 접근 age[age.length] 는 age[3] 이므로 ArrayIndexOutOfBoundsException 입니다. new int[0] 에는 유효 인덱스가 없습니다. index < empty.length 반복은 0회지만 empty[0] 을 읽으면 예외입니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
다음 인덱스 후보

정상 순회는 유효 위치를 읽은 뒤 3을 검사하고 실패 예제는 length=3을 직접 인덱스로 씁니다.

  • 배열 값: age[0]=20, age[1]=30, age[2]=40
  • 조건 있는 순회: 다음 후보 index=3
  • 검사 없는 예제: 요청 index=age.length=3
  • length는 원소 개수이므로 값 3은 마지막 유효 인덱스 2를 벗어납니다.
읽기 허용 여부

순회는 3<3을 검사하지만 실패 예제는 사전 조건 없이 3번 위치를 읽으려 합니다.

  • 순회 검사: 3<3 → false, 읽기 안 함
  • 무조건 접근: age[3] 요청, 범위 밖
  • 유효 범위: 0≤index<3
  • 읽기 앞의 조건은 순회를 종료시키고 무조건 접근은 배열의 범위 검사에서 실패합니다.
각 경로의 종료

조건 있는 순회는 정상 종료하고 무조건 접근은 예외로 끝납니다.

  • 정상 순회: age[3] 읽기 없이 종료
  • 실패 예제: ArrayIndexOutOfBoundsException
  • length 출력: 원 실패 예제에서 length=3 출력 후 예외
  • 정상 종료 뒤 별도 실패가 이어지는 흐름이 아니라 두 접근 정책의 결과를 비교합니다.

배열의 선언·생성·초기화

src/ArrayCreation.java
public final class ArrayCreation {
    public static void main(String[] args) {
        int[] age;
        age = new int[3];

        System.out.println(age[0]);
        System.out.println(age[1]);
        System.out.println(age[2]);
    }
}
0
0
0

int[] age는 int 배열을 가리킬 변수 선언이고 new int[3]은 길이 3의 배열을 생성합니다.

숫자 원소는 0, boolean은 false, 참조 원소는 null 기본값을 받습니다.

기본값이 저장된 회원을 뜻하지는 않으므로 실제 데이터 수를 별도로 관리해야 합니다.

배열 리터럴로 값과 길이를 함께 정할 수도 있습니다.

int[] age = {20, 30, 40};
String[] emails = {"variables", "conditions", "loops"};

생성 뒤 길이는 바뀌지 않습니다.

더 큰 용량이 필요하면 새 배열을 만들고 값을 옮기거나 뒤에서 컬렉션을 사용합니다.


인덱스 기반 반복

src/ArrayTraversal.java
public final class ArrayTraversal {
    public static void main(String[] args) {
        int[] age = {20, 30, 40};
        int total = 0;

        for (int index = 0; index < age.length; index++) {
            total += age[index];
            System.out.println(index + "=" + age[index]);
        }
        System.out.println("total=" + total);
    }
}
0=20
1=30
2=40
total=90

인덱스는 원소를 읽고 바꾸는 위치입니다.

age[1] = 35는 둘째 원소만 변경합니다.

배열 변수에 새 배열을 대입하지 않는 한 같은 배열의 나머지 원소는 유지됩니다.

빈 배열도 유효합니다.

int[] empty = new int[0];

길이는 0이고 정상 인덱스가 하나도 없습니다.

index < empty.length 반복은 0회 실행하지만 empty[0]은 예외입니다.


배열 참조 대입

배열 변수에는 배열을 가리키는 참조 값이 들어 있습니다.

다른 배열 변수에 대입하면 원소 전체가 복제되는 것이 아니라 같은 배열을 가리키는 참조가 복사됩니다.

src/ArrayAliasing.java
import java.util.Arrays;

public final class ArrayAliasing {
    public static void main(String[] args) {
        int[] original = {20, 30, 40};
        int[] alias = original;

        alias[1] = 99;

        System.out.println("original=" + Arrays.toString(original));
        System.out.println("alias=" + Arrays.toString(alias));
        System.out.println("same=" + (original == alias));
    }
}
original=[20, 99, 40]
alias=[20, 99, 40]
same=true

두 변수는 서로 다른 이름이지만 같은 배열 객체를 가리킵니다.

따라서 alias를 통해 둘째 원소를 바꾸면 original로 읽어도 99입니다.

기본형 int 변수의 값 복사와 배열 참조 복사를 같은 방식으로 생각하면 이 동작을 놓치기 쉽습니다.

독립된 배열이 필요하면 새 배열을 만들고 원소를 복사해야 합니다.

src/IndependentArrayCopy.java
import java.util.Arrays;

public final class IndependentArrayCopy {
    public static void main(String[] args) {
        int[] original = {20, 30, 40};
        int[] copied = Arrays.copyOf(original, original.length);

        copied[1] = 99;

        System.out.println("original=" + Arrays.toString(original));
        System.out.println("copied=" + Arrays.toString(copied));
        System.out.println("same=" + (original == copied));
    }
}
original=[20, 30, 40]
copied=[20, 99, 40]
same=false

Arrays.copyOf는 지정한 새 길이의 배열을 만들고 기존 원소를 옮깁니다.

참조 대입은 같은 배열을 공유하고 복사는 새 배열을 만든다

alias는 같은 배열을 가리키지만 copyOf는 새 배열을 만든 뒤 각 예제에서 index 1을 바꿉니다. 두 독립 실행을 대응 단계로 유지하여 참조와 변경 범위를 보여 줄 수 있습니다.

ArrayAliasing과 IndependentArrayCopy의 서로 독립된 실행에서의 참조 관계

참조 대입 뒤에는 두 변수가 하나의 배열을 보지만 Arrays.copyOf를 사용한 별도 실행에서는 서로 다른 배열을 본다.

ARRAY · BOUNDARIES

참조 대입은 공유, 배열 복사는 독립

별도 실행 A와 B를 비교합니다. alias는 원본 배열을 함께 가리키고, Arrays.copyOf는 새 배열을 만들어 복사본의 변경을 분리합니다.

참조 대입은 같은 배열을 공유하고 복사는 새 배열을 만든다참조 대입은 두 변수가 하나의 변경된 배열을 공유하지만 독립 복사는 새 배열만 바뀌고 원본 배열 값은 그대로 남긴다. 실행 A · ArrayAliasing실행 B · IndependentArrayCopyalias = originalArrays.copyOf(original, original.length)original참조 Aalias참조 Aalias[1] = 99같은 int[] 객체[20, 99, 40]original참조 Bcopied참조 C원본 int[][20, 30, 40]copied[1] = 99새 int[][20, 99, 40]original == alias → trueoriginal == copied → false
참조 대입은 공유, 배열 복사는 독립 A original/alias 같은 참조A→하나의 [20,99,40]; 독립B original참조B→[20,30,40],copied참조C→새[20,99,40] 독립 실행 A · 참조 대입 original · 참조 A alias=original alias · 같은 참조 A alias[1]=99 뒤 하나의 int[] 객체 [20,99,40] 둘 다 같은 값을 읽음 original == alias true 독립 실행 B · 값 복사 Arrays.copyOf로 새 배열 original · 참조 B B의 새 시작 배열 copied · 참조 C copied[1]=99 뒤 원본 int[] 객체 [20,30,40] 복사본 변경과 분리 새 int[] 객체 [20,99,40] 복사본만 변경 original == copied false 선은 참조 · 실행 순서 아님
실행 A · ArrayAliasing
alias=original이므로 두 변수가 같은 배열을 참조합니다. alias[1]=99 뒤 둘 다 [20,99,40]을 읽고 same=true입니다.
실행 B · IndependentArrayCopy
다시 original=[20,30,40]으로 시작합니다. Arrays.copyOf(original,original.length)는 새 배열을 만듭니다. copied[1]=99 뒤 원본은 [20,30,40], 복사는 [20,99,40], same=false입니다.

화살표는 변수의 참조입니다. 두 패널은 독립 실행이며 B는 A의 변경된 원본을 이어받지 않습니다. 이 예제는 int[] 원소 복사입니다.

이 예의 조건

  • ArrayAliasing과 IndependentArrayCopy는 독립 실행입니다.
관계도의 보충 설명
원 도표의 설명과 주석
구분내용
전체 읽기 정보참조 대입은 같은 배열을 공유하고 복사는 새 배열을 만든다 ArrayAliasing과 IndependentArrayCopy의 서로 독립된 실행에서의 참조 관계 참조 대입 뒤에는 두 변수가 하나의 배열을 보지만 Arrays.copyOf를 사용한 별도 실행에서는 서로 다른 배열을 본다. ARRAY · BOUNDARIES 참조 대입은 공유, 배열 복사는 독립 별도 실행 A와 B를 비교합니다. alias는 원본 배열을 함께 가리키고, Arrays.copyOf는 새 배열을 만들어 복사본의 변경을 분리합니다. 실행 A · ArrayAliasing alias=original 이므로 두 변수가 같은 배열을 참조합니다. alias[1]=99 뒤 둘 다 [20,99,40] 을 읽고 same=true 입니다. 실행 B · IndependentArrayCopy 다시 original=[20,30,40] 으로 시작합니다. Arrays.copyOf(original,original.length) 는 새 배열을 만듭니다. copied[1]=99 뒤 원본은 [20,30,40] , 복사는 [20,99,40] , same=false 입니다. 화살표는 변수의 참조입니다. 두 패널은 독립 실행이며 B는 A의 변경된 원본을 이어받지 않습니다. 이 예제는 int[] 원소 복사입니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
초기 배열

두 실행의 원본은 20,30,40입니다.

  • A: alias=original
  • B: Arrays.copyOf로 새 배열
  • 참조 대입과 객체 복사의 대상이 다릅니다.
인덱스 1 변경

alias 또는 copied의 1번을 99로 바꿉니다.

  • A 공유: [20,99,40]
  • B copied: [20,99,40]
  • 같은 변경식도 참조 대상에만 적용됩니다.
원본 비교

A 원본은 바뀌고 B 원본은 유지됩니다.

  • A: original==alias true
  • B: 원본 [20,30,40], ==copied false
  • 새 배열은 독립 저장 공간이므로 원본 변경을 막습니다.

참조 대입은 같은 배열을 공유하고 복사는 새 배열을 만든다

alias는 같은 배열을 가리키지만 copyOf는 새 배열을 만든 뒤 각 예제에서 index 1을 바꿉니다. 두 독립 실행을 대응 단계로 유지하여 참조와 변경 범위를 보여 줄 수 있습니다.

ArrayAliasing과 IndependentArrayCopy의 서로 독립된 실행에서의 참조 관계

참조 대입 뒤에는 두 변수가 하나의 배열을 보지만 Arrays.copyOf를 사용한 별도 실행에서는 서로 다른 배열을 본다.

ARRAY · BOUNDARIES

참조 대입은 공유, 배열 복사는 독립

별도 실행 A와 B를 비교합니다. alias는 원본 배열을 함께 가리키고, Arrays.copyOf는 새 배열을 만들어 복사본의 변경을 분리합니다.

참조 대입은 같은 배열을 공유하고 복사는 새 배열을 만든다참조 대입은 두 변수가 하나의 변경된 배열을 공유하지만 독립 복사는 새 배열만 바뀌고 원본 배열 값은 그대로 남긴다. 실행 A · ArrayAliasing실행 B · IndependentArrayCopyalias = originalArrays.copyOf(original, original.length)original참조 Aalias참조 Aalias[1] = 99같은 int[] 객체[20, 99, 40]original참조 Bcopied참조 C원본 int[][20, 30, 40]copied[1] = 99새 int[][20, 99, 40]original == alias → trueoriginal == copied → false
참조 대입은 공유, 배열 복사는 독립 A original/alias 같은 참조A→하나의 [20,99,40]; 독립B original참조B→[20,30,40],copied참조C→새[20,99,40] 독립 실행 A · 참조 대입 original · 참조 A alias=original alias · 같은 참조 A alias[1]=99 뒤 하나의 int[] 객체 [20,99,40] 둘 다 같은 값을 읽음 original == alias true 독립 실행 B · 값 복사 Arrays.copyOf로 새 배열 original · 참조 B B의 새 시작 배열 copied · 참조 C copied[1]=99 뒤 원본 int[] 객체 [20,30,40] 복사본 변경과 분리 새 int[] 객체 [20,99,40] 복사본만 변경 original == copied false 선은 참조 · 실행 순서 아님
실행 A · ArrayAliasing
alias=original이므로 두 변수가 같은 배열을 참조합니다. alias[1]=99 뒤 둘 다 [20,99,40]을 읽고 same=true입니다.
실행 B · IndependentArrayCopy
다시 original=[20,30,40]으로 시작합니다. Arrays.copyOf(original,original.length)는 새 배열을 만듭니다. copied[1]=99 뒤 원본은 [20,30,40], 복사는 [20,99,40], same=false입니다.

화살표는 변수의 참조입니다. 두 패널은 독립 실행이며 B는 A의 변경된 원본을 이어받지 않습니다. 이 예제는 int[] 원소 복사입니다.

이 예의 조건

  • ArrayAliasing과 IndependentArrayCopy는 독립 실행입니다.

관계도의 보충 설명

원 도표의 설명과 주석
구분내용
전체 읽기 정보참조 대입은 같은 배열을 공유하고 복사는 새 배열을 만든다 ArrayAliasing과 IndependentArrayCopy의 서로 독립된 실행에서의 참조 관계 참조 대입 뒤에는 두 변수가 하나의 배열을 보지만 Arrays.copyOf를 사용한 별도 실행에서는 서로 다른 배열을 본다. ARRAY · BOUNDARIES 참조 대입은 공유, 배열 복사는 독립 별도 실행 A와 B를 비교합니다. alias는 원본 배열을 함께 가리키고, Arrays.copyOf는 새 배열을 만들어 복사본의 변경을 분리합니다. 실행 A · ArrayAliasing alias=original 이므로 두 변수가 같은 배열을 참조합니다. alias[1]=99 뒤 둘 다 [20,99,40] 을 읽고 same=true 입니다. 실행 B · IndependentArrayCopy 다시 original=[20,30,40] 으로 시작합니다. Arrays.copyOf(original,original.length) 는 새 배열을 만듭니다. copied[1]=99 뒤 원본은 [20,30,40] , 복사는 [20,99,40] , same=false 입니다. 화살표는 변수의 참조입니다. 두 패널은 독립 실행이며 B는 A의 변경된 원본을 이어받지 않습니다. 이 예제는 int[] 원소 복사입니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
초기 배열

두 실행의 원본은 20,30,40입니다.

  • A: alias=original
  • B: Arrays.copyOf로 새 배열
  • 참조 대입과 객체 복사의 대상이 다릅니다.
인덱스 1 변경

alias 또는 copied의 1번을 99로 바꿉니다.

  • A 공유: [20,99,40]
  • B copied: [20,99,40]
  • 같은 변경식도 참조 대상에만 적용됩니다.
원본 비교

A 원본은 바뀌고 B 원본은 유지됩니다.

  • A: original==alias true
  • B: 원본 [20,30,40], ==copied false
  • 새 배열은 독립 저장 공간이므로 원본 변경을 막습니다.

새 길이가 더 크면 남은 칸은 기본값 0으로 채워지고, 더 작으면 뒤 원소가 잘립니다.

배열 자체의 length가 바뀐 것이 아니라 다른 길이의 새 배열이 생긴 것입니다.

int[] expanded = Arrays.copyOf(original, 5);
// [20, 30, 40, 0, 0]

회원가입의 용량을 늘릴 때 emails와 age를 각각 새 배열로 복사할 수 있지만, 두 배열의 새 길이를 같게 유지해야 합니다.

또한 size는 실제 회원 수 그대로 두어 새로 생긴 기본값 칸을 회원 정보로 취급하지 않습니다.

복사 전후에 다음 불변식을 확인합니다.

  • 0 <= size && size <= array.length
  • index 0부터 size - 1까지는 실제 회원입니다.
  • 복사 뒤 같은 index의 기존 값은 그대로입니다.
  • size부터 새 length - 1까지는 아직 사용하지 않는 공간입니다.

병렬 배열의 관계

클래스를 배우기 전에는 이메일과 나이를 같은 길이의 두 배열에 저장할 수 있습니다.

src/ParallelArrays.java
public final class ParallelArrays {
    public static void main(String[] args) {
        String[] emails = {"[email protected]", "[email protected]"};
        int[] age = {40, 50};

        if (emails.length != age.length) {
            throw new IllegalStateException("parallel lengths differ");
        }

        for (int index = 0; index < emails.length; index++) {
            System.out.println(emails[index] + "=" + age[index]);
        }
    }
}

인덱스 0의 두 값이 첫 회원이고 인덱스 1의 두 값이 둘째 회원입니다.

throw new IllegalStateException(...)은 두 배열의 길이가 다를 때 현재 실행을 예외로 중단하는 문장입니다. 한 배열에만 있는 인덱스를 같은 회원으로 취급하는 대신, 관계가 깨졌음을 즉시 드러냅니다. 위의 두 배열은 길이가 모두 2이므로 이 분기는 실행되지 않습니다. 직접 확인하려면 나이 배열의 50을 빼고 실행해 정상 출력 대신 예외가 발생하는지 보세요.

정렬·삭제·삽입 때 한 배열만 움직이면 관계가 깨지는 약점이 있습니다.

ch4에서 하나의 객체로 묶기 전까지 모든 변경이 같은 인덱스를 유지해야 합니다.


용량과 저장 개수

src/FixedMemberRegistry.java
public final class FixedMemberRegistry {
    public static void main(String[] args) {
        String[] emails = new String[3];
        int[] age = new int[3];
        int size = 0;

        emails[size] = "[email protected]";
        age[size] = 40;
        size++;

        emails[size] = "[email protected]";
        age[size] = 50;
        size++;

        for (int index = 0; index < size; index++) {
            System.out.println(emails[index] + "=" + age[index]);
        }
        System.out.println("size=" + size + ", capacity=" + emails.length);
    }
}
[email protected]=40
[email protected]=50
size=2, capacity=3

emails.length는 세 칸의 용량이고 size는 의미 있는 두 회원의 수입니다.

length까지 순회하면 마지막 빈 칸이 null=0으로 출력됩니다.

추가할 때는 먼저 size < emails.length를 확인하고 두 배열을 같은 size 위치에 쓴 뒤 마지막에 size를 증가시킵니다.

FixedMemberRegistry의 두 유효 회원과 미사용 한 칸을 포함하는 용량 3

이메일과 나이를 같은 인덱스로 맞춘 두 배열에서 size 2는 사용 영역이고 length 3은 전체 용량이다.

ARRAY · BOUNDARIES

같은 인덱스가 한 회원, size 뒤는 빈 용량

두 배열의 길이는3이지만 저장된 회원은2명입니다. 조회는 size 앞까지 하고, 추가는 같은 위치에 두 값을 쓴 뒤 개수를 늘립니다.

같은 index의 두 값이 회원이고 size 뒤는 빈 용량이다같은 인덱스의 이메일과 나이가 회원을 이루며 0과 1만 사용 중이고 인덱스 2의 null과 0은 미사용 기본값이다. capacity = 3 · 두 배열의 length는 동일size = 2 · 실제 회원 영역 [0, size)size부터 length 전index 0index 1index 2emailsage[email protected][email protected]null40500조회: index < sizenull / 0은 회원이 아님
같은 인덱스가 한 회원, size 뒤는 빈 용량 capacity3 안의 size2 사용영역 index0/1 email-age쌍, index2null0 미사용기본값은회원아님 capacity=3 · 두 배열 size=2 · 실제 회원 미사용 capacity index 0 emails[0] / age[0] [email protected] 40 index 1 emails[1] / age[1] [email protected] 50 index 2 emails[2] / age[2] null / 0 기본값 · 회원 아님 조회: index < size 0 ≤ size ≤ length=3

size=2, capacity=3

index 0 · 회원
emails[0][email protected] · age[0]=40
index 1 · 회원
emails[1][email protected] · age[1]=50
index 2 · 미사용
emails[2]=null · age[2]=0. 기본값은 세 번째 회원이 아닙니다.

불변식: 두 배열의 길이는 같고 0 <= size && size <= length입니다. 조회는 index < size까지입니다. length까지 출력하면 null=0도 나옵니다.

  1. 추가 전에 용량 검사

    size < emails.length인지 먼저 확인해야 합니다. 현재 예제의 두 번 쓰기에 이 검사 코드가 이미 있다는 뜻은 아닙니다.

  2. 같은 위치에 두 값 쓰기

    emails[size]와 age[size]에 한 회원의 값을 씁니다.

  3. 마지막에 개수 증가

    두 값을 쓴 뒤 size++합니다. 새로 생긴 기본값 칸은 회원 수에 넣지 않습니다.


연습 문제

{25, 55, 35, 40} 배열에서 최댓값과 인덱스를 찾으세요.

첫 원소를 최댓값의 초깃값으로 사용해 실제 배열에 없는 값을 초기화에 쓰지 않습니다.

해설 보기
src/OldestMember.java
public final class OldestMember {
    public static void main(String[] args) {
        int[] age = {25, 55, 35, 40};
        int maxIndex = 0;

        for (int index = 1; index < age.length; index++) {
            if (age[index] > age[maxIndex]) {
                maxIndex = index;
            }
        }

        System.out.println("index=" + maxIndex);
        System.out.println("age=" + age[maxIndex]);
    }
}
index=1
age=55

빈 배열에서는 index 0도 없으므로 이 알고리즘 전에 length가 0인지 검사해야 합니다.