반응형
Jake Seo
제이크서 위키 블로그
Jake Seo
전체 방문자
오늘
어제
  • 분류 전체보기 (715)
    • 일상, 일기 (0)
    • 백준 문제풀이 (1)
    • 릿코드 문제풀이 (2)
    • 알고리즘 이론 (10)
      • 기본 이론 (2)
      • 배열과 문자열 (8)
    • 데이터베이스 (15)
      • Planet Scale (1)
      • MSSQL (9)
      • 디비 기본 개념 (1)
      • SQLite 직접 만들어보기 (4)
    • 보안 (7)
    • 설계 (1)
    • 네트워크 (17)
      • HTTP (9)
      • OSI Layers (5)
    • 회고 (31)
      • 연간 회고 (2)
      • 주간 회고 (29)
    • 인프라 (52)
      • 도커 (12)
      • AWS (9)
      • 용어 (21)
      • 웹 성능 (1)
      • 대규모 서비스를 지탱하는 기술 (9)
    • 깃 (7)
    • 빌드 도구 (7)
      • 메이븐 (6)
      • 그레이들 (0)
    • Java (135)
      • 이펙티브 자바 (73)
      • 자바 API (4)
      • 자바 잡지식 (30)
      • 자바 디자인 패턴 (21)
      • 톰캣 (Tomcat) (7)
    • 프레임워크 (64)
      • next.js (14)
      • 스프링 프레임워크 (28)
      • 토비의 스프링 (6)
      • 스프링 부트 (3)
      • JPA (Java Persistence API) (5)
      • Nest.js (8)
    • 프론트엔드 (48)
      • 다크모드 (1)
      • 노드 패키지 관리 매니저 (3)
      • CSS (19)
      • Web API (11)
      • tailwind-css (1)
      • React (5)
      • React 새 공식문서 요약 (1)
      • HTML (Markup Language) (5)
    • 자바스크립트 (108)
      • 모던 자바스크립트 (31)
      • 개념 (31)
      • 정규표현식 (5)
      • 코드 스니펫 (1)
      • 라이브러리 (6)
      • 인터뷰 (24)
      • 웹개발자를 위한 자바스크립트의 모든 것 (6)
      • 팁 (2)
    • Typescript (49)
    • 리눅스와 유닉스 (10)
    • Computer Science (1)
      • Compiler (1)
    • IDE (3)
      • VSCODE (1)
      • IntelliJ (2)
    • 세미나 & 컨퍼런스 (1)
    • 용어 (개발용어) (16)
      • 함수형 프로그래밍 용어들 (1)
    • ORM (2)
      • Prisma (2)
    • NODEJS (2)
    • cypress (1)
    • 리액트 네이티브 (React Native) (31)
    • 러스트 (Rust) (15)
    • 코틀린 (Kotlin) (4)
      • 자바에서 코틀린으로 (4)
    • 정규표현식 (3)
    • 구글 애널리틱스 (GA) (1)
    • SEO (2)
    • UML (2)
    • 맛탐험 (2)
    • 리팩토링 (1)
    • 서평 (2)
    • 소프트웨어 공학 (18)
      • 테스팅 (16)
      • 개발 프로세스 (1)
    • 교육학 (1)
    • 삶의 지혜, 통찰 (1)
    • Chat GPT (2)
    • 쉘스크립트 (1)
    • 컴파일 (2)
    • Dart (12)
    • 코드팩토리의 플러터 프로그래밍 (4)
    • 플러터 (17)
    • 안드로이드 스튜디오 (1)
    • 윈도우즈 (1)
    • 잡다한 백엔드 지식 (1)
    • 디자인 패턴 (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • Next.js
  • item7
  • 자료구조
  • pnpm
  • 슬로우 쿼리
  • 싱글톤 패턴
  • 느린 쿼리
  • 서버리스 컴퓨팅
  • serverless computing
  • 스프링 검증
  • MSSQL
  • 알고리즘
  • 팩터리 메서드 패턴
  • 자바
  • 자바스크립트 인터뷰
  • 자바스크립트
  • 메이븐 라이프사이클
  • 객체복사
  • prerendering
  • next js app
  • 이펙티브 자바
  • 이펙티브자바
  • 자바 디자인패턴
  • item9
  • 빈 검증
  • 토비의 스프링
  • 외래키 제약조건
  • item8
  • 메이븐 페이즈
  • Pre-rendering
  • 디자인패턴
  • Java
  • 프로그래머의 뇌
  • 싱글턴
  • 이펙티브 자바 item9
  • 싱글톤
  • bean Validation
  • 작업기억공간
  • NEXT JS
  • try-with-resources
  • 플라이웨이트패턴
  • 자바 검증
  • 참조 해제
  • 자바스크립트 면접
  • 러스트
  • 메이븐 골
  • 추상 팩터리 패턴
  • Javadoc 자바독 자바주석 주석 Comment
  • rust
  • 도커공식문서

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Jake Seo

제이크서 위키 블로그

Java/이펙티브 자바

이펙티브 자바, 쉽게 정리하기 - item 28. 배열보다는 리스트를 사용하라

2022. 1. 9. 14:06

이펙티브 자바, 쉽게 정리하기 - item 28. 배열보다는 리스트를 사용하라

공변(covariant)과 불공변(invariant) 개념

  • 공변: 함께 변한다.
    • 배열은 공변이 적용된다.
  • 불공변: 함께 변하지 않는다. (공변이 아니다.)
    • 리스트는 불공변이 적용된다.

공변(covariant) 불공변(invariant) 예제 테스트 코드

@Test
public void covariantTest() {
    Object[] objects = new Long[1];
    objects[0] = "String"; // ArrayStoreException
}
  • Object[]의 공변 특성을 이용하여 objects를 선언 후에 Long[] 저장소를 만들어 할당했다.
  • 해당 저장소 타입이 Object[]로 정의되어 있어 클라이언트는 아무 타입이나 된다는 가정 하에 문자열을 넣은 경우이다.
    • 런타임에 ArrayStoreException 예외가 발생한다.
@Test
public void invariantTest() {
    List<Object> ol = new ArrayList<Long>(); // 불공변이라 타입 자체가 호환이 안된다. 이미 에러가 뜨고 있다.
    ol.add("아아");
    // 제네릭의 타입 정보가 런타임에는 이미 사라져버리기 때문에 컴파일 타임에 검사한다.
    // 런타임에 제네릭 타입을 소거시키는 이유는 레거시 코드와 제네릭 타입을 함께 이용할 수 있게 해주기 위해서이다.
}
  • 불공변 특성을 가지는 리스트에서는 하위 타입과 같은 관계 자체가 존재하지 않고, 모든 관계가 그저 다른 타입일 뿐이므로 에러가 발생한다.
  • 이미 IDE에서 에러가 잡히기 때문에, 런타임에 심각한 에러로 발전할 가능성이 없다.
    • 당연히 에러는 컴파일 타임에 잡는 것이 훨씬 이상적이다.

배열은 실체화(reify)된다.

  • 배열이 실체화된다는 것은 자신이 담기로 한 원소의 타입을 인지하고 확인한다는 뜻이다.
  • 제네릭은 실체화되지 않는다. 타입 정보가 런타임에는 소거된다.
    • 이러한 차이로 제네릭과 배열은 어울릴 수 없다.
    • 이를테면 배열은 제네릭 타입, 매개변수화 타입, 타입 매개변수로 사용할 수 없다.
      • 코드를 new List<E>[], new List<String>[], new E[] 식으로 작성하면 컴파일할 때 제네릭 배열 생성 오류를 일으킨다.
@Test
public void genericArrayTest() {
    List<String>[] stringLists = new List<String>[1]; // IDE 내 generic array creation 컴파일 에러
    List<Integer> intList = List.of(42);
    Object[] objects = stringLists;
    objects[0] = intList;
    String s = stringLists[0].get(0); // * 문제가 되는 부분
}
  • 위의 코드에서 만일 generic array creation 컴파일 에러가 동작하지 않는다면, 컴파일 에러가 전혀 나지 않을 것이라는게 문제다.
    • 결국 런타임에 다다라서 String s = stringLists[0].get(0)에서 ClassCastException이 뜨긴 할 것이다.

제네릭은 실체화(reify)되지 않는다.

  • E, List<E>, List<String> 같은 타입은 실체화 불가(reify) 타입이라고 한다.
  • 런타임에 컴파일타임보다 타입 정보를 적게 가지는 타입이다.
  • 소거 매커니즘 때문에, 매개변수화 타입 가운데 실체화될 수 있는 타입은 List<?>와 Map<?, ?> 같은 비한정적 와일드카드 타입 뿐이다.
    • 배열을 비한정적 와일드카드 타입으로도 만들 수는 있지만 크게 쓸모는 없다.

이러한 이유로 배열과 함께 쓰여서도 안되고, 가변인수 메서드와도 함께 쓰일 때도 배열의 원소가 실체화 불가 타입이라면 경고가 발생한다.
후자의 경우 @SafeVarargs 애너테이션으로 대처할 수 있다.

배열 형변환 시 에러 해결하기

  • 배열로 형변환하는 경우, 제네릭 뱅려 생성 오류나 비검사 형변환 경고가 뜨는 경우 대부분 배열인 E[] 대신 컬렉션인 List<E>를 사용하면 해결된다.
    • 코드는 조금 복잡해지고 성능이 안좋아질 수 있지만, 타입 안정성과 상호 운용성이 좋아진다.

제네릭으로 코드 개선해보기

제네릭을 사용하지 않는 Chooser 클래스

public class Chooser {
  private final Object[] choiceArray;

  public Chooser(Collection choices) {
    choiceArray = choices.toArray();
  }

  public Object choose() {
    Random rnd = ThreadLocalRandom.current();
    return choiceArray[rnd.nextInt(choiceArray.length)];
  }
}
  • 랜덤하게 원소를 하나 골라 반환해주는 역할을 하는 클래스이다.
  • choose() 메서드를 호출 시에 매번 반환된 Object를 원하는 타입으로 형변환해야 한다.
  • 공변(covariant)이 적용되어, 혹여나 타입이 다른 원소가 들어있었다면, 런타임에 ClassCastException이 났을 것이다.

제네릭으로 만들기 위한 첫 시도

public class Chooser<T> {
  private final T[] choiceArray;

  public Chooser(Collection<T> choices) {
    choiceArray = choices.toArray();
  }

  public Object choose() {
    Random rnd = ThreadLocalRandom.current();
    return choiceArray[rnd.nextInt(choiceArray.length)];
  }
}
  • 위 경우 한가지 에러가 뜰텐데, 바로 incompatible types: Object[] cannot be converted to T[] 와 같은 메세지가 뜰 것이다.
  • collection.toArray()는 Object 타입을 반환하도록 되어있기 때문에 (T[])로 캐스팅을 해주어야 한다.
  • choiceArray = (T[]) choices.toArray() 여야 한다.
  • 다 고친 이후에도 unchecked cast 경고가 뜰 것이다. 그 이유는 런타임에는 제네릭 타입 정보가 전부 소거되었기 때문에 이게 안전한 캐스팅인지 런타임에 알 수 없기 때문이다.

배열보다는 List를 적극 활용하여 완성시키자

public class Chooser<T> {
  private final List<T> choiceList;

  public Chooser(Collection<T> choices) {
    choiceList = new ArrayList<T>(choices);
  }

  public Object choose() {
    Random rnd = ThreadLocalRandom.current();
    return choiceList.get(rnd.nextInt(choiceArray.length));
  }
}
  • 동작은 정확히 같은 동작을 한다.
  • 다만 배열을 빼고 제네릭을 활용하는 리스트를 사용함으로써, 잠재적인 에러를 모두 잡았다.
  • 공변은 불공변보다 약간 위험하다는 사실을 깨달았다.

정리

  • 배열은 공변이고 실체화된다는 점에서 잘못 사용했을 때 불안정한 코드가 나온다.
  • 제네릭은 불공변이고 타입 정보가 소거된다.
  • 결과적으로 배열은 런타임에 에러 가능성이 존재하고, 제네릭은 그렇지 않다.
  • 그래서 둘을 섞어 쓰긴 어렵다.
  • 둘을 섞어 쓰다가 컴파일 오류를 만나면, 과연 이 작업에 진짜 배열이 필요한지 생각해보고 아니라면 바로 제네릭을 사용하는 리스트로 변경하자.
반응형
저작자표시 (새창열림)

'Java > 이펙티브 자바' 카테고리의 다른 글

이펙티브 자바, 쉽게 정리하기 - item 30. 이왕이면 제네릭 메서드로 만들라  (0) 2022.01.09
이펙티브 자바, 쉽게 정리하기 - item 29. 이왕이면 제네릭 타입으로 만들라  (0) 2022.01.09
이펙티브 자바, 쉽게 정리하기 - item 27. 비검사 경고를 제거하라  (0) 2022.01.09
이펙티브 자바, 쉽게 정리하기 - item 26. 로 타입은 사용하지 말라  (0) 2022.01.09
이펙티브 자바, 쉽게 정리하기 - item 25. 톱 레벨 클래스는 한 파일에 하나만 담으라  (0) 2022.01.09
    'Java/이펙티브 자바' 카테고리의 다른 글
    • 이펙티브 자바, 쉽게 정리하기 - item 30. 이왕이면 제네릭 메서드로 만들라
    • 이펙티브 자바, 쉽게 정리하기 - item 29. 이왕이면 제네릭 타입으로 만들라
    • 이펙티브 자바, 쉽게 정리하기 - item 27. 비검사 경고를 제거하라
    • 이펙티브 자바, 쉽게 정리하기 - item 26. 로 타입은 사용하지 말라
    Jake Seo
    Jake Seo
    ✔ 잘 보셨다면 광고 한번 클릭해주시면 큰 힘이 됩니다. ✔ 댓글로 틀린 부분을 지적해주시면 기분 나빠하지 않고 수정합니다. ✔ 많은 퇴고를 거친 글이 좋은 글이 된다고 생각합니다. ✔ 간결하고 명료하게 사람들을 이해 시키는 것을 목표로 합니다.

    티스토리툴바