개발/자바

참조 타입 배열과 기본 타입 배열의 차이: 이해와 정리

핸생이 2024. 12. 27. 20:54

문제 - https://school.programmers.co.kr/learn/courses/30/lessons/42895?language=java

풀이 블로그 - https://isshosng.tistory.com/173

 

 

 

 

코딩테스트 연습을 위해 “N으로 표현”이라는 DP 문제를 공부하던 중 블로그에서 아래와 같은 코드로 DP 저장 배열을 만들었습니다.

ArrayList<Set<Integer>> list = new ArrayList<>();

 

그러나 이를 바탕으로 저만의 코드를 작성하려다 고민을 하게 되었습니다. “굳이 ArrayList를 사용하지 않고 입력값도 1에서 9로 정해져 있으니 고정된 배열 형태로 만들어보면 어떨까?” 라는 생각이 들었고 아래와 같은 코드를 작성했습니다.

Set<Integer>[] list = new HashSet[9];

 

 


 

의도

중복을 제거하기 위해 Set 자료구조를 사용했고 숫자 N이 몇 번 사용되었는지 나타내는 인덱스에 가능한 숫자 조합들을 저장하고자 했습니다.

문제 상황

위 코드가 의도대로 작동하는지 확인하기 위해 예시 데이터를 넣어 테스트했습니다.

  Set<Integer>[] list = new HashSet[9];
 
  list[0].add(1);
  list[0].add(1);
  list[1].add(1);
  list[1].add(2);
  list[1].add(2);
  list[1].add(3);
  list[1].add(3);
  
  
  for (int i = 0; i < list.length; i++) {
      System.out.println("list[" + i + "]: " + list[i]);
  }

결과: 프로그램 실행 시 NullPointerException이 발생했습니다.

 

 


 

 

문제 원인 분석

NullPointerException은 참조하려는 객체가 존재하지 않을 때 발생합니다.

코드를 검토하면서 제가 아래 내용을 잘못 이해하고 있었음을 깨달았습니다

  • new HashSet[9]; 은 배열 안에 HashSet 객체를 생성하는 것이 아닙니다.
  • 이는 단지 Set<Integer> 타입의 참조를 저장할 공간을 할당했을 뿐입니다.
  • 배열의 각 요소(list[0], list[1], ...)는 초기값으로 null 을 가지며 객체를 참조하지 않습니다.

따라서 list[0].add(1);에서 list[0]이 null인 상태에서 메서드를 호출했기 때문에 NullPointerException이 발생한 것입니다.

수정된 코드

배열의 각 요소에 HashSet 객체를 명시적으로 할당한 뒤, 데이터를 추가하도록 수정했습니다.

  Set<Integer>[] list = new HashSet[9];
  
  for (int i = 0; i < 9; i++) {
      list[i] = new HashSet<>();  
  }
  
  list[0].add(1);
  list[0].add(1);
  list[1].add(1);
  list[1].add(2);
  list[1].add(2);
  list[1].add(3);
  list[1].add(3);
  
  for (int i = 0; i < list.length; i++) {
      System.out.println("list[" + i + "]: " + list[i]);
  }

결과: 각 HashSet 객체에 데이터가 정상적으로 추가되고 콘솔 출력 결과도 예상대로 나타났습니다.

 


 

 

핵심 정리

참조 타입 배열공간만 할당하며 각 인덱스에서 참조할 객체를 명시적으로 초기화해야 합니다.

이를 도식화하면 다음과 같습니다

  • 초기화 전: [0]번째 공간 -> null
  • 초기화 후: [0]번째 공간 -> HashSet 객체 참조

 

 

반면 기본 타입 배열공간을 할당하며 기본값까지 자동으로 초기화됩니다. 이를 도식화하면:

  • [0]번째 공간 -> 기본값 (예: 0)

 

 

 

 


추가 공부: 왜 참조 타입은 null로 초기화되고 기본 타입은 기본값이 설정될까?

1. 참조 타입 배열이 null로 초기화되는 이유

  1. 객체 생성 비용 최소화
    • 객체 생성은 메모리와 성능에 영향을 미칩니다.
    • 불필요한 객체 생성을 방지하기 위해, 참조 타입 배열은 초기값으로 null을 설정하고 필요한 시점에 객체를 명시적으로 생성하도록 설계되었습니다.
  2. 유연성 제공:
    • 참조 타입 배열은 다양한 객체를 참조할 수 있습니다. 예를 들어
    • Set<Integer>[] list = new HashSet[9]; list[0] = new HashSet<>(); // HashSet 객체 참조 list[1] = new TreeSet<>(); // TreeSet 객체 참조
    • 개발자가 각 요소에 어떤 객체를 할당할지 유연하게 결정할 수 있습니다.
  3. 불필요한 메모리 낭비 방지:
    • 배열 크기가 클수록 모든 요소를 초기화하는 데 비용이 많이 듭니다.
    • 예를 들어 new HashSet[1000]이라면 1000개의 객체를 생성하는 대신 필요한 인덱스에만 객체를 생성할 수 있습니다.

2. 기본 타입 배열이 기본값으로 초기화되는 이유

  1. 쓰레기 값 방지:
    • 기본 타입 배열의 각 요소는 값을 직접 저장합니다. 초기화를 하지 않으면 메모리에 예측 불가능한 쓰레기 값이 남아 오류를 유발할 수 있습니다.
    • Java는 이를 방지하기 위해 각 기본 타입의 기본값(0, 0.0, false 등)을 자동으로 설정합니다.
  2. 즉시 사용 가능:
    • 기본 타입 배열은 생성 직후 바로 사용할 수 있습니다.
    • 예를 들어:
    • int[] list = new int[3]; System.out.println(list[0]); // 출력: 0

3. 설계 의도

  • 참조 타입 배열: 객체 생성 비용과 유연성을 고려하여 null로 초기화. 객체는 필요할 때만 생성하도록 설계.
  • 기본 타입 배열: 값 자체를 저장하므로 기본값을 자동으로 지정하여 예측 가능한 동작과 개발 편의성을 제공.