- 백기선 - JAVA STUDY/WEEK142021년 03월 06일 18시 18분 18초에 업로드 된 글입니다.작성자: jCurve728x90반응형
목표
자바의 제네릭에 대해 학습하세요.
학습할 것 (필수)
- 제네릭 사용법
- 제네릭 주요 개념 (바운디드 타입, 와일드 카드)
- 제네릭 메소드 만들기
- Erasure
제네릭
제네릭은 java5부터 추가된 기능이다.
제네릭 타입을 사용하면, 잘못된 타입에 대한 에러를 런타임이 아니라 컴파일 타임에 잡을 수 있다.
(에러는 언제나 컴파일 타임에 잡는게 제일 좋음)
중복 코드 제거
public static void main(String[] args){ List list = new ArrayList(); list.add("white"); list.add("ship"); for (Object o : list){ String str = (String) o; System.out.println(str); } }
위 와 같이 List를 그냥 사용하는 raw 타입 방식으로 코드를 작성하면 타입을 지정하지 않고 Object 형으로 데이터를 가지기 때문에 String으로 출력하기 위해서 명시적으로 타입 캐스팅을 해주어야하는데
명시적 타입 캐스팅은 실수의 가능성도 있으면 로 타입 컬렉션은 Object 타입을 넣을 수 있기 때문에 String만 들어가리라는 보장을 할 수 없다. 혹시 누가 정수 1을 넣는다면 런타임시에 ClassCastException,UncheckedException이 발생한다.
로 타입은 위와 같은 실수를 유발할 수 있으므로 되도록 사용하지 말아야한다.
사용하지 말아야함에도 로 타입을 지원하는 이유는 이전 버전에서의 사용도 가능하도록 하위 호환성을 유지하기 위해서 이다.
이제 위 코드를 제네릭을 사용한 코드로 바꾸어 보면
public static void main(String[] args){ List<String> list = new ArrayList<>(); list.add("white"); list.add("ship"); //list.add(1); <- 컴파일 타임에 오류를 잡아준다. for(String s: list){ System.out.println(s); } }
이렇게 간단하게 변환이 가능하며 코드도 훨씬 짧아진다.
추가로 2번째 라인 ArrayList<>();는 다이아몬드 연산자로 java 7버전 부터 추가되어서 타입 추론을 해서 넣어준다.
제네릭 사용
제네릭은 크게 클래스와 메서드, 인터페이스를 만들 때 사용할 수 있다.
제네릭 타입
제네릭은 타입 파라미터로 <T>를 지정하여 사용 범위 내부에서 '타입 매개변수'를 대표하는 값으로 사용하며, 굳이 T가 아니라 다른 어떤 문자가 와도 가능하나 convention이 보통 이렇다
T(type),R(return type),S(stirng),E(element),K(key),V(value),N(number)
public class Box<T>{ private T in; public void push(T element){ in = element; } public T pop(){ return in; } }
멀티 타입 파라미터
타입 파라미터가 두 개 이상 필요한 경우가 있을땐 타입 파라미터를 <>안에 쉼표로 필요한 만큼 선언하면 된다.
public class Box<T,S>{ private T in; private S name; public void push(T element, S elementName){ in = element; name = elementName; } public S pop(){ return name; } }
제네릭 메소드
제네릭 메소드는 파라미터와 반환 타입으로 제네릭 타입을 사용하는 것을 말한다.
class에서는 선언할 때 타입 파라미터를 사용하여 내부에서 사용할 타입을 알려주었다면 제네릭 메소드는 메소드를 정의할 때 , 해당 메소드 내부에서 사용 할 타입 파라미터가 무엇인지 알려주기 위해 리턴 타입을 명시하기 전에 타입 파라미터를 작성해주어야 한다.
public static <T> void printAll(List<T> list){ for(T t : list) { System.out.println(t); } }
제네릭을 사용할 수 없는 경우
위 메서드에서는 static 키워드를 사용하였는데, 제네릭은 static 멤버 필드에는 사용이 불가능하다.
static 메서드는 그 기능을 사용한 것이라 가능했지만, static 키워드를 사용한 멤버 필드의 경우, 특정 객체에 종속되지 않고 클래스 이름으로 접근해서 사용할 수 있는데 제네릭 타입을 사용하면 인스턴스마다 사용하는 타입을 달리 할 수 있어야 하는데 static으로 선언한 변수는 그게 불가능하기 때문에 static 멤버 필드에는 사용이 불가능하다.
primitive 타입에서도 사용이 불가능 하다.
그 이유는, 제네릭을 사용하지 않고 raw type으로 객체를 생성해서 컴파일 후 바이트 코드를 보면 내부적으로 Object 타입으로 취급해서 처리가 되는데 (type Erasure) 타입 소거는 제네릭 타입이 특정 타입으로 제한 되어 있을 경우 해당 타입에 맞춰 컴파일시 타입 변경이 발생하고 타입 제한이 없을 경우 Object 타입으로 변경된다.
근데 primitive 타입은 Object 클래스를 상속받고 있지 않기 때문에 사용이 불가능하고, 사용하고 싶다면 Wrapper클래스를 사용해야 한다.
제네릭 타입을 사용해서 배열을 생성할 때.
배열 생성 시 new 연산자를 사용하는데 이는 heap 영역에 동적으로 메모리 할당하여 객체를 생성하지만 제네릭은 컴파일 타임에 동작하는 문법이다 따라서 컴파일 타임에 타입 파라미터를 특정 지을 수 없기 때문에 Object 타입으로 생성한 다음 타입 캐스팅을 해주어야 사용할 수 있다. (혹은 reflection api의 newInstance() 사용)
Erasure
- 제네릭은 타입의 정보가 런타임에는 소거 된다.
- 원소의 타입을 컴파일 타임에만 검사하고 보증함으로써, 런타임에는 타입 정보를 알 수 조차 없게 한다.
- 이를 실체화가 되지 않는다라고 한다.
- 이는 제네릭이 생기기 이전의 레거시코드가 호환될 수 있도록 한 조치이다.
- 반면에 배열은 타입 정보를 런타임에도 가지고 있으며, 이를 실체화 된다고 한다.
위에서 잠시 언금한 Erasure에 대한 설명이다.
제네릭 주요 개념(바운디드 타입, 와일드 카드)
바운디드 타입
- 타입 파라미터는 아무거나 들어올 수 있다.
- 하지만 "~중에 아무거나" 처럼 제한을 주려면?
- 이를 가능캐하는 것이 제한된 타입 파라미터이다.
- 바운디드 타입은 extends 키워드를 사용할 수 있다.
public static <T extends String> LIst<Integer> printListSize(List<T> list){ List<Integer> result = new ArrayList<>(); for(T t : list) { result.add(t.split(" ").length); } return result; }
위 코드는 타입 파라미터 T를 String 타입으로 제한하기 때문에 해당 메서드를 사용할 때 다른 타입이 들어오는 것을 미연에 방시할 수 있다.
와일드 카드
와일드 카드는 어떤 용도로도 사요이 가능한 비장의 카드라는 뜻으로 <>안에 ? 를 넣어서 <?>로 표현한다.
즉, 어떤 용도로든 아무 타입이나 올 수 있다.
그렇다면 일반 타입 파라미터 T와 와일드 카드 ? 는 어떤 차이가 있는거지?
public static void printList(List<?> list){ list.stream().forEach(System.out :: println); } public static <T> void printList(List<T> list){ list.stream().forEach(System.out :: println); }
와일드 카드는 캐개변수의 타입을 지정할 때 주로 쓰인다.
List<?> list의 타입은 인자를 넘기는 타이밍에 정해지지만, 그 타입이 list라는 리스트의 타입 외에 메서드에서 영향을 미치는 곳은 존재 하지 않는다.
<T> 타입은 반환값에도 , 매개변수 타입에도, 메서드 코드 중에서도 사용할 수 있다.
제네릭에서 타입을 제한하는 방법
- <?> : 모든 종류의 클래스나 인터페이스 타입 사용 가능
- <? extends 상위타입>: 상위타입 타입 또는 이 타입의 하위타입만 사용가능
- <? super 하위타입>:하위타입 타입 또는 이 타입의 상위타입만 사용 가능
제네릭 타입의 제한을 줄 때는 extends를 사용하고 interface를 구현할때도 implements를 사용하는게 아니라 extends를 사용하며 여러개의 인터페이스를 구현해야되면 '&'기호로 연결한다.
반응형'JAVA' 카테고리의 다른 글
생성자 대신 정적 팩터리 메서드를 고려 (0) 2021.03.14 백기선 - JAVA STUDY/WEEK15 (0) 2021.03.06 백기선 - JAVA STUDY/WEEK13 (0) 2021.03.06 백기선 - JAVA STUDY/WEEK12 (0) 2021.03.06 백기선-JAVA STUDY/WEEK11 (0) 2021.02.21 댓글