Java Type Safety 이슈 경험

죠랭이·2021년 9월 5일
0

Java

목록 보기
2/2
post-thumbnail

며칠전 1956-운동 문제를 풀면서 처음 보는 컴파일 Warning 메시지를 발견하였다.

Type safety: The expression of type ArrayList[] needs unchecked conversion to conform to ArrayList[]Java(16777748)

Java에서 2차원 동적배열을 사용하고자 아래와 같이 작성하였다.

ArrayList<Integer> [] arr = new ArrayList[10];

🤔 ArrayList 동적배열과 1차원 정적배열의 콜라보로 이루어진 이 코드는 Type Safety를 경고하는데...왜일까?

여기서 우리가 생각해볼 수 있는 부분은 배열에 넣을 ArrayList 안에 들어있는 원소의 Type이 반드시 Integer라는 보장이 없다. 만약, Integer가 아닌 Long이나 Double형을 가진 ArrayList가 배열에 있을 경우엔 어떻게 할 것인가? 그래서, 필자는 아래의 코드로 해결시도하였으나 컴파일 에러가 났다.

ArrayList<Integer> [] arr = new ArrayList<Integer>[10];

Error Message: Cannot create a generic array of ArrayList Java(16777751)

🤔 Generic Type의 배열 생성이 불가한데...이건 왜그런걸까?

아래 참조 링크에 공유된 내용을 요약하면 다음과 같다.

1. 배열은 공변, Generic은 불공변

무슨 말인지 이해가 잘 안갈 것이라고 확신(?)하기에 다음 코드를 보며 이해하기 바란다.

  • 1차원 배열
Object [] arr = new String[10]; 
  // String은 Object 클래스의 하위 클래스로 컴파일 시 에러가 발생하지 않음. 하지만, 만약 배열값에 Integer Type이 오면 컴파일에서는 에러가 없었을지라도 런타임 에러가 발생함. 따라서, 1차원 배열이 Type safe를 보장하지 않음.
  • ArrayList
ArrayList<Object> arrList = new ArrayList<String> 
  // Object가 String 클래스의 상위 클래스일지라도 컴파일 에러 발생. 따라서, 제너릭은 Type safe를 보장함.

2. 배열은 런타임에 실체화, 제너릭은 런타임에 소멸

이것 또한 무슨 말인지 이해가 잘 안갈 수 있기에...코드로 설명하고자 한다.

ArrayList<String> str = new ArrayList<String>
ArrayList<Integer> integ = new ArrayList<Integer>

// 런타임 시 제너릭 타입 사라짐. 결국, 두 ArrayList는 같아짐.
ArrayList str = new ArrayList;
ArrayList integ = new ArrayList;

🤔 여기서 잠깐, 그렇다면 만약 위에 컴파일에러가 나는 코드가 제대로 동작한다고 가정하면 어떤 문제가 발생할 수 있을까?

// 실제로는 컴파일 에러로 인해 프로그램 실행이 안된다. 이 점 명심!
ArrayList<Integer> [] arr = new ArrayList<Integer>[10];

ArrayList<String> comp = new ArrayList<String>; // 런타임 시점에서 제너릭 타입 소멸로 인한 ArrrayList comp
comp.add("Runtime Error occurred!");
arr[0] = comp;
System.out.println(arr[0].get(0)); // 프로그램 실행 시, Integer != String 으로 인한 강제 실행 종료

필자는 제너릭 타입의 특징을 모른 상태에서 다음과 같이 썼었는데 해당 특징을 신경쓰면서 이제부턴 코딩해야겠다...다들 조심하시길!
앞서 에러 원인에 대해 알게 되었으니 이제는 올바른 활용법에 대해 알아보도록 하자!

👩‍💻 올바른 제너릭 타입 배열 사용법

  1. 와일드카드 타입을 활용한 제너릭 배열 생성
ArrayList<?> [] arr = new ArrayList<?>[10];

이런 식으로 와일드카드 타입을 지정해주면 컴파일 에러없이 배열 선언 및 초기화가 가능하다. 와일드카드로 선언하면 어떤 타입의 데이터 타입을 사용해도 문제가 되지 않는다는 전제를 깔고 시작하는 것이다. 데이터 타입보다 여러 데이터가 담긴 배열을 가지고 동작하는 부분에 초점을 둘 때 사용하길 추천한다.

  1. 강제 형변환 이용하기
@SuppressWarnings("unchecked")
ArrayList<Integer> arr[] = (ArrayList<Integer>[]) new ArrayList[10];

흠...이 방식은 근데 개인적으로 별로 추천하고 싶진 않다. 강제 형변환으로 할 순 있으나 여전히 Unchecked Warning 메시지가 있기에 웬만하면 와일드카드를 사용하기를 추천한다.

p.s. 아래 출처 블로그 글을 보니 만약 소스코드 상에서 타입 안정성 보장이 가능하다면 문제 없이 사용이 가능하다고 한다. 이땐, 정신차리고 코딩해야할 듯...!

이 글이 조금이나마 도움이 되길 바라며...즐겁게 코딩하고 즐겁게 배우며 크게 성장하기를 바란다:)

출처: https://pompitzz.github.io/blog/Java/whyCantCreateGenericsArray.html#%E1%84%8C%E1%85%A6%E1%84%82%E1%85%A6%E1%84%85%E1%85%B5%E1%86%A8%E1%84%80%E1%85%AA-%E1%84%87%E1%85%A2%E1%84%8B%E1%85%A7%E1%86%AF%E1%84%8B%E1%85%B4-%E1%84%8E%E1%85%A1%E1%84%8B%E1%85%B5%E1%84%8C%E1%85%A5%E1%86%B7
profile
슈퍼 개발자를 목표로 하는 주니어

0개의 댓글