ShovelingLife
A Game Programmer
ShovelingLife
전체 방문자
오늘
어제
  • 분류 전체보기 (1067) N
    • 그래픽스 (57)
      • 공통 (19)
      • 수학 물리 (22)
      • OpenGL & Vulkan (1)
      • DirectX (14)
    • 게임엔진 (180) N
      • Unreal (69)
      • Unity (100) N
      • Cocos2D-X (3)
      • 개인 플젝 (8)
    • 코딩테스트 (221)
      • 공통 (7)
      • 프로그래머스 (22)
      • 백준 (162)
      • LeetCode (19)
      • HackerRank (2)
      • 코딩테스트 알고리즘 (8)
    • CS (235)
      • 공통 (21)
      • 네트워크 (44)
      • OS & 하드웨어 (55)
      • 자료구조 & 알고리즘 (98)
      • 디자인패턴 (6)
      • UML (4)
      • 데이터베이스 (7)
    • 프로그래밍 언어 (346)
      • C++ (167)
      • C# (88)
      • Java (9)
      • Python (33)
      • SQL (30)
      • JavaScript (8)
      • React (7)
    • 그 외 (9)
      • Math (5)
      • 일상 (5)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

  • Source Code 좌측 상단에 복사 버튼 추가 완료
  • 언리얼 엔진 C++ 빌드시간 단축 꿀팁
  • 게임 업계 코딩테스트 관련
  • 1인칭 시점으로 써내려가는 글들

인기 글

태그

  • Unity
  • 프로그래머스
  • 함수
  • string
  • 클래스
  • 알고리즘
  • 백준
  • 티스토리챌린지
  • 문자열
  • 오블완
  • 파이썬
  • 그래픽스
  • C
  • c#
  • 포인터
  • 배열
  • C++
  • SQL
  • 언리얼
  • 유니티

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
ShovelingLife

A Game Programmer

프로그래밍 언어/Java

[Java] Generic 제네릭

2024. 10. 22. 11:55

사용법

다이아몬드 연산자 (<>)에 타입을 정의해주면 되지만 명명 규칙이 존재한다.

  • E : 요소(element)
  • K : 키(key)
  • N : 숫자(Number)
  • T : 타입(type)
  • V : 값(value)
  • S, U, V : 2,3,4번째 선언된 타입

장점

  1. 컴파일 타임때 타입추론을 통한 타입에러를 사전에 잡을 수 있다.
  2. 컴파일러가 타입추론을 통한 타입 캐스팅을 자동으로 해준다.
  3. 코드의 재사용성이 매우 높아진다.

컴파일 타임 때 타입 추론을 통한 타입 에러를 사전에 잡을 수 있다

public interface List<E> extends Collection<E> {...}

 

List를 사용할 때 우리는 다이아몬드 연산자 (<>) 안에 어떠한 타입을 지정하고 사용한다. Java 7 버전 이후로 도입됐다.

List<String> string = new ArrayList<String>();
Map<String, List<String>> map = new HashMap<String, List<String>>();

에서

List<String> string = new ArrayList<>();
Map<String, List<String>> map = new HashMap<>();

 

타입을 지정함으로써 컴파일러는 타입을 추론할수 있게된다. 따라서 List 요소는 String 인 객체만 들어올 수 있다.

 

해당 예제 처럼 String 타입 외 다른 타입이 들어온다면 개발자는 사전에 오류를 잡아낼 수 있다. 만약 타입을 지정하지 않고 로 타입(raw type)으로 사용시 컴파일러는 타입추론을 하지 못한다 따라서 리스트 내부에 어떠한 객체를 넣어도 개발자는 오류가 발생한걸 인지하지 못한다.

public static void main(String[] args) {
  List rawTypeList = new ArrayList(); //로 타입 리스트
  rawTypeList.add("String");
  rawTypeList.add(10);
}

이처럼 컴파일러는 제네릭을 통한 타입추론을 할 수 없기 때문에 해당 코드는 컴파일오류가 나지않지만 제네릭을 통한 타입추론이 가능해짐으로써 우리는 리스트를 믿고 사용할 수 있게된다. 즉 안정성을 보장할 수 있게된다는 뜻인데 만약 로 타입 리스트를 사용하게된다면 리스트 내부에 어떠한 객체가 있는지 알 수 없게된다. 이러한 문제점은 안정성이 없고 얼마든지 개발자의 실수로 인해 리스트내부에 다른 객체가 혼합하여 들어갈 수 있다.

컴파일러가 타입 추론을 통한 타입 캐스팅을 자동으로 해준다

public static void main(String[] args) {
  List rawTypeList = new ArrayList();
  rawTypeList.add("String");
  rawTypeList.add(10);

  for (Object o : rawTypeList) {
    System.out.println(o);
  }
}

컴파일러는 제네릭을 보고 타입추론을 하지만 현재 예제의 리스트는 로 타입 리스트이기 때문에 컴파일러는 타입 추론을 하지 못한다 따라서 for-each 문을 사용할 때 Object의 객체를 내보내게 된다. (어떠한 객체가 있는지 모르기 때문에) 이러한 문제점은 만약 리스트 내부에 서로 다른 타입의 객체가 들어가 있으면

for (Object o : rawTypeList) {
  if(o instanceof String){
    System.out.println((String) o);
  }
  if(o instanceof Integer){
    System.out.println(o);
  }
}

일일이 비교를 통한 타입캐스팅을 해줘야하지만 제네릭을 사용한다면

List<String> stringList = new ArrayList<>();
for (String s : stringList) {
  System.out.println(s);
}

코드의 재사용성이 매우 높아진다

public static void main(String[] args) {
  List<Integer> intList = new ArrayList<>(List.of(1,2,3,4,5));
  List<String> stringList = new ArrayList<>(List.of("a","b","c","d","f"));

  for (String s : stringList) {
    System.out.println(s);
  }

  for (Integer integer : intList) {
    System.out.println(integer);
  }
}

해당 코드는 단순히 리스트를 순회하면서 콘솔에 찍는 행위만 하지만 타입이 서로 다르기 때문에 어쩔 수 없이 반복하는 코드가 보인다. 해당 문제점은 제네릭 메소드를 만들어서 사용하면 해결할 수 있다.

public class MainRunner {
  static <E> void ListForEach(List<E> list){
    for (E e : list) {
      System.out.println(e);
    }
  }
  public static void main(String[] args) {
    List<Integer> intList = new ArrayList<>(List.of(1,2,3,4,5));
    List<String> stringList = new ArrayList<>(List.of("a","b","c","d","f"));
    ListForEach(intList);
    ListForEach(stringList);
  }
}

제네릭 주요 개념 (바운디드 타입, 와일드 카드)

바운디드 타입 (Bounded Generics)

간단하게 타입을 제한할 수 있는 제네릭이다. 만약 리스트 안에 Number의 하위클래스만 (자기포함)을 받고 싶다면 다음과 같이 해주면된다.

class List<E extends Number>{
 ...생략
}

이와 반대로 super는 하위클래스가 아닌 상위클래스만 (자기포함) 받고 싶을 때 사용한다

와일드 카드 (WildCard)

void Foo(List<?>list list){} // List 내부의 요소들을 Object 타입으로 취급하게됨

즉 타입을 신경쓰지 않으니 타입캐스팅을 해주지 않는다 (Object 타입으로 취급) 하지만 바운디드 타입과 같이 사용하게 된다면

void Foo(List<? extends Number > list){} // 내부의 요소를 Number 타입으로 취급하게됨

Number로 취급하게 된다. 와일드카드는 구체적인 데이터 타입이 필요하지 않을 때 사용한다. 즉 구체적인 타입보단 각각의 데이터가 가지고 공통적인 부분 즉 추상적인 데이터를 필요할 때 사용하는거 같다.

Erasure (소거)

Erasure 란 원소 타입을 컴파일 타임에만 검사하고 런타임에는 해당 정보를 알 수 없는 것이다. 즉 컴파일 타임에만 타입에 대한 제약조건을 적용하고 런타임에는 타입에 대한정보를 소거하는 행위다. 소거는 타입 파라미터가 unbounded 이면 타입파라미터를 object 타입으로 변경한다.

  1. Example<T> ->unbounded 임으로
  2. Example<Object>으로 변경
  1. Example<? extends Number> -> bounded임으로
  2. Example<Number> 으로 변경

https://k3068.tistory.com/76

저작자표시 (새창열림)

'프로그래밍 언어 > Java' 카테고리의 다른 글

[Java] Comparable과 Comparator : 비교를 위한 인터페이스  (0) 2024.10.22
[Java] Collections 클래스  (0) 2024.10.22
[Java] BigDecimal  (1) 2024.09.04
[Java] long float의 값 뒤에 L, F을 붙여야 하는 이유  (0) 2024.09.04
[Java] 자료형 정리  (0) 2024.09.04
    '프로그래밍 언어/Java' 카테고리의 다른 글
    • [Java] Comparable과 Comparator : 비교를 위한 인터페이스
    • [Java] Collections 클래스
    • [Java] BigDecimal
    • [Java] long float의 값 뒤에 L, F을 붙여야 하는 이유
    ShovelingLife
    ShovelingLife
    Main skill stack => Unity C# / Unreal C++ Studying Front / BackEnd, Java Python

    티스토리툴바