반응형
제네릭(Generics)
- Java 1.5 부터 사용가능
- 넓은 범위의 의미로 자료형을 결정해놓지 않고 틀(템플릿)만 만들어 놓은 것을 뜻한다. 즉, 클래스의 자료형을 필요할 때 유동적으로 변경할 수 있다.
- 예) 티셔츠(틀), 색깔: 빨강색, 파랑색, ...(자료형)
- 컴파일 시 타입을 체크해 주는 기능(compile-time type check)
- 제네릭을 사용함에 있어서 장점
- 객체의 타입 안정성을 높여 준다. 즉, 설정된 타입 이외의 타입이 들어오는 것을 방지한다.
ArrayList<Integer>
로 타입을 설정하면 해당 배열리스트에는Integer
타입만 요소로 가질 수 있고,String
과 같은 타입은 요소가 될 수 없다.
- 객체의 타입 체크, 형변환의 번거로움을 줄여준다.
- 제네릭 미사용 시, 해당 객체에 들어있는 타입 검증 및 형변환 하기 위해서 추가적인 로직이 필요하다. 또한 형변환은 컴파일러가 아닌 프로그래머가 직접 수행하기 때문에 코드의 안정성이 떨어진다.
- 컴파일러의 실수보다 프로그래머의 실수가 더 많이 발생하며 또한 컴파일러가 명시적 형변환에 대해서는 관여를 거의 하지 않기 때문에 컴파일러의 오류 발견 가능성을 낮추는 결과로 이어져서 코드의 안정성이 떨어진다.
- 제네릭을 이용하면 컴파일 타임에 에러를 발견할 수 있게 해준다.(
ClassCastException
)- 에러 발견 과정
- 컴파일 중에 에러 발생
- 실행 중 예외로 에러 발생
- 컴파일이 정상적으로 되고 실행 중 예외로 발견되지도 않았지만 에러가 발생
- 초기 단계 일수록 에러를 수정하기 쉽다.
2번 단계
에서 발생한 예외를try-catch
문으로 처리하게되면 에러를 찾기 매우 힘들어진다.
- 런타임 에러 보다는 컴파일 에러가 좋다.
- 런타임 에러는 실행 중인 프로그램에 치명적인 문제가 발생한다.
- 컴파일 타임 오류를 수정하는 것은 찾기 어려울 수 있는 런타임 오류를 수정하는 것보다 쉽다.
- 런타임 에러는 프로그래머의 실수로 발생한다. 이런 실수를 줄일려면, 잘못된 방식으로 코드를 작성한 경우 이를 컴파일 단계에서 검출되게 하면 미리 에러를 발견하고 수정할 수 있다.
- 에러 발견 과정
- 객체의 타입 안정성을 높여 준다. 즉, 설정된 타입 이외의 타입이 들어오는 것을 방지한다.
제네릭 이전의 코드
- 문제점
- 프로그래머의 실수가 컴파일 과정에서 발견되지 않는다.
//사과
class Apple {
public String toString() {
return "I am an apple.";
}
}
//오렌지
class Orange {
public String toString() {
return "I am an orange.";
}
}
//무엇이든 저장하고 꺼낼 수 있는 상자
class Box {
private Object ob;
public void set(Object o) {
ob = o;
}
public Object get() {
return ob;
}
}
public class generic01 {
public static void main(String[] args) {
Box aBox = new Box();
Box oBox = new Box();
Box aBox2 = new Box();
Box oBox2 = new Box();
aBox.set(new Apple());
oBox.set(new Orange());
aBox2.set("Apple"); //사과를 담아야하지만 문자열을 담았다.
oBox2.set("Orange"); //오렌지를 담아야하지만 문자열을 담았다.
Apple ap = (Apple)aBox.get(); //명시적 형변환.
Orange og = (Orange)oBox.get(); //명시적 형변환.
Apple ap2 = (Apple)aBox2.get(); //명시적 형변환. 상자에 과일이 담기지 않았는데 꺼내려 한다.
Orange og2 = (Orange)oBox2.get(); //명시적 형변환. 상자에 과일이 담기지 않았는데 꺼내려 한다.
System.out.println(ap);
System.out.println(og);
System.out.println(ap2);
System.out.println(og2);
}
}
- 위의 코드를 실행하니 런타임에 예외(
2번 단계
)로 에러가 발생하였다.
- 더욱 큰 문제는 프로그래머의 실수가 실행 과정에서도 발견되지 않을 수 있다는 것이다.
- 박스에 사과와 오렌지를 담지않고 문자열을 담았다. 박스에 들어있는것은 과일이 아닌 문자열 이지만 사과와 오렌지를 꺼내는 과정에서도 에러가 발생하지 않고 있다.
- 프로그래머는 자신이 실수했다는 사실조차 깨닫지 못할 수 있다.
public class generic01 {
public static void main(String[] args) {
Box aBox2 = new Box();
Box oBox2 = new Box();
aBox2.set("Apple");
oBox2.set("Orange");
System.out.println(aBox2.get());
System.out.println(oBox2.get());
}
}
//결과
Apple
Orange
타입 변수
- 제네릭을 사용한다면, 타입 변수를 사용해야 한다.
- 예)
ArrayList<E>
에서E
가 타입 변수에 해당한다.
- 예)
- 여러 개의 타입 변수가 필요한 경우, 콤마를 구분자로 선언할 수 있다.
- 예)
HashMap<K, V>
- 예)
- 제네릭 클래스임에도 타입 인자를 지정하여 인스턴스를 생성하지 않는 경우, 원시 타입(Raw Type)이다. 즉, 제네릭 클래스가 되기 이전의 일반 클래스의 인스턴스를 의미한다. 원시타입으로 사용하는 것은 권장하지 않는다.
- 제네릭 클래스의 타입 변수는 선언된 블럭 {...} 안에서만 유효하다.
public class ClassName <T> {...}
public Interface InterfaceName <T> {...}
- 다이아몬드 기호(
<>
)- Java 1.7 이상부터 생성자에 타입 지정 생략이 가능하다.
- 참조 변수의 타입(
Box<Integer>
)을 보고 생성자의 타입을 컴파일러가 추론한다.
public class Box<T> {
private T ob;
public void set(T o) {
ob = o;
}
public T get() {
return ob;
}
}
Box box = new Box(); // 원시 타입(Raw Type)
Box<Integer> integerBox = new Box<Integer>(); // 정상
Box<Integer> integerBox = new Box<>(); // 정상. 다이아몬드 기호 사용
Box<String> stringBox = new Box<>(); // 정상. 다이아몬드 기호 사용
용어 정리
- 타입 매개변수(Type Parameter)
- 매개변수는 받는다는 의미. 함수를 정의할 때 사용되는 변수
- Box<T>에서 T
- 보편적으로 사용되는 타입변수 문자
- 타입 매개변수의 이름은 일반적인 관례로 대문자를 사용하여 한 문자로 이름을 짓는다.
E
- 요소(Java Collections Framework에서 광범위하게 사용됨)
K
- 키
N
- 숫자
T
- 유형
V
- 값
- 매개변수는 받는다는 의미. 함수를 정의할 때 사용되는 변수
- 타입 인자(Type Argument)
- 인자는 전달한다는 의미. 함수를 호출할 때 사용되는 변수
- Box<Apple> box = new Box<>(); 에서 Apple
- 인자는 전달한다는 의미. 함수를 호출할 때 사용되는 변수
- 매개변수화 타입(Parameterized Type)
- Box<Apple> box = new Box<>(); 에서 Box<Apple>
- 자바는 매개변수화 타입 자체를 별도의 자료형으로 간주한다.
- Box<Box<Apple>> 과 같이 매개변수화 타입을 타입 인자로 전달하는 것도 가능하다.
- 제네릭 클래스 인스턴스 생성 과정
- 예) Box<Apple> aBox = new Box<Apple>();
- T를 Apple로 결정하여 인스턴스를 생성한다.
- Apple 또는 Apple을 상속하는 하위 클래스의 인스턴스를 저장할 수 있다.
제네릭 기반의 코드
- Box(
틀
)라는 티셔츠를 만들어 놓고 색깔은(T
)나중에 결정한다.
- 색깔(
T
)은 인스턴스 생성 시에 결정한다.
- 제네릭 이전의 코드에서는 프로그래머의 실수가 컴파일 중에 발견되지 않았다. 제네릭 기반의 코드에서는 프로그래머의 실수가 컴파일 중에 발견된다. 형변환도 하지 않아도 된다.
//무엇이든 저장하고 꺼낼 수 있는 상자
public class Box<T> {
private T ob;
public void set(T o) {
ob = o;
}
public T get() {
return ob;
}
}
public class generic02 {
public static void main(String[] args) {
Box<Apple> aBox = new Box<Apple>(); // T를 Apple로 결정
Box<Orange> oBox = new Box<Orange>(); // T를 Orange로 결정
//aBox.set("Apple"); 컴파일 오류 발생
//oBox.set("Orange"); 컴파일 오류 발생
aBox.set(new Apple());
oBox.set(new Orange());
Apple ap = aBox.get(); // 형변환 하지 않는다.
Orange og = oBox.get(); // 형변환 하지 않는다.
System.out.println(ap);
System.out.println(og);
}
}
제네릭 타입과 다형성
- 참조 변수와 생성자의 대입된 타입(매개변수화 타입)은 일치해야 한다. 즉, 다형성은 성립하지 않는다.
class Product {}
class Tv extends Product{}
//Tv는 Product의 하위클래스이다. class Tv extends Product {}
ArrayList<Tv> list = new ArrayList<Tv>(); // 정상
ArrayList<Product> list = new ArrayList<Tv>(); // 에러 발생
- 제네릭 클래스 간의 다형성은 성립한다.
//LinkedList는 List를 구현한다.
List<Tv> list = new ArrayList<Tv>(); // 정상
//ArrayList는 List를 구현한다.
List<Tv> list = new LinkedList<Tv>(); // 정상
- 매개변수의 다형성은 성립한다.
ArrayList<Product> list = new ArrayList<Product>();
list.add(new Product()); //정상
list.add(new Tv()); //정상
제네릭 클래스의 타입 인자 제한하기
- 클래스는
extends
키워드를 사용해서 타입 인자의 범위를 제한할 수 있다.
- 인터페이스도 클래스와 마찬가지로
extends
키워드를 사용한다.
- 제한된 타입 매개변수(Bounded Type Parameters)
- 특정 타입만 타입 인자로 사용할 수 있도록 제한한다.
<T extends Fruit>
<T extends Eatable>
- 다중 경계(Multiple Bounds)
- 여러 조건을 동시에 만족하는 타입만 타입 인자로 사용할 수 있도록 제한한다.
- 클래스와 클래스의 경우는 불가능하다. 자바에서 클래스의 다중상속은 불가능하다.
- 클래스와 인터페이스의 경우
클래스 & 인터페이스
형태로 사용할 수 있다.<T extends Fruit & Eatable>
인터페이스 & 클래스
형태로는 사용 불가능하다.
- 인터페이스와 인터페이스의 경우
인터페이스 & 인터페이스
형태로 사용할 수 있다.<T extends Eatable & Eatable2>
interface Eatable {}
interface Eatable2 {}
class Fruit implements Eatable {}
class Product {}
class Apple extends Fruit {}
class Tv extends Product {}
//FruitBox는 Fruit의 자손이면서 Eatable 인터페이스를 구현한 경우에만 타입으로 설정 가능,
//인터페이스를 같이 사용할 때는 "," 대신 "&" 사용 또한 인터페이스의 경우에도 implemenets가 아닌 extends를 사용해서 제한한다.
class FruitBox<T extends Fruit & Eatable> {
ArrayList<T> list = new ArrayList<T>();
}
FruitBox<Apple> appleBox = new FruitBox<Apple>(); // 정상
FruitBox<Tv> tvBox = new FruitBox<Tv>(); // 에러 발생
- 타입 인자 제한의 효과
- 클래스라면 특정 타입(
Number
)이거나 특정 타입(Number
)의 자손만, 인터페이스라면 해당 인터페이스(Number
)를 직접 혹은 간접적으로 구현한 타입만 타입 인자로 사용하도록 제한했기 때문에 그 타입(Number
)의 메서드도 제네릭 클래스에서 사용할 수 있다.
- 즉, 타입 인자로 사용 가능한 모든 타입은 해당 타입의 메서드를 가지고 있다고 보장할 수 있다.
- 클래스라면 특정 타입(
//타입 인자 제한 미사용
public class Box<T> {
private T ob;
public int toIntValue() {
return ob.intValue(); // 컴파일 에러 발생
}
}
//타입 인자 제한 사용
public class Box<T extends Number> {
private T ob;
public int toIntValue() {
return ob.intValue(); // 정상
}
}
제네릭 메서드
- 클래스 전부가 아니라 특정 메소드만 제네릭을 선언한다.
- 제네릭 메서드는 메서드를 호출할 때마다 다른 제네릭 타입을 대입할 수 있게 한 것이 목적이다.
- 와일드카드와 제네릭 메서드는 목적이 다르다.
- 제네릭 메서드의 타입 변수는 메서드 내에서만 유효하다. (
sort 메서드의 {...}
)
- 제네릭 클래스(
FruitBox<T>
) 의 타입 변수<T>
와 제네릭 메서드(sort
)의 타입 변수<T>
는 동일한 타입 매개변수가 아니며 별개이다.
class FruitBox<T> {
static <T> void sort(List<T> list, Comparator<? super T> c) {
//여기서 사용되는 T는 제네릭 메서드의 T가 사용된다.
}
}
- 제네릭 클래스의 T는 인스턴스 생성 시점에 타입을 결정하지만 제네릭 메서드의 T는 메서드 호출 시점에 결정한다.
class Fruit {}
class Apple extends Fruit {}
class Juicer {
static <T extends Fruit> Juice makekJuice(FruitBox<T> box) {
...
...
...
}
}
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
Juicer.<Fruit>makeJuice(fruitBox); // 정상
Juicer.<Apple>makeJuice(appleBox); // 정상
Juicer.makeJuice(appleBox); // 정상, 참조변수의 형(FruitBox<Apple>)을 기반으로 makeJuice 메소드의 T를 결정하게 된다. 이 때, 참조변수의 형(FruitBox<Apple>)을 가리켜 타겟 타입이라 한다.
//메서드를 호출할 때 타입을 생략하지 않을 때는 클래스 이름 생략 불가
<Fruit>makeJuice(fruitBox); // 에러 발생
this.<Apple>makeJuice(appleBox); // 정상
Juicer.<Apple>makeJuice(appleBox); // 정상
제네릭 제약사항
- 타입 인자에는 기본 자료형(primitive type)을 사용할 수 없다.
int, char
와 같은 기본 자료형은 타입 인자로 사용 불가능.
Integer, Character
과 같은 래퍼 클래스는 타입 인자로 사용 가능.
class Box<T> {
private T ob;
public void set(T o) {
ob = o;
}
public T get() {
return ob;
}
}
//Box<int> box = new Box<int>(); 타입 인자로 기본 자료형이 올 수 없다. 컴파일 오류 발생
//래퍼 클래스는 기본 자료형으로 입력하거나 꺼낼 수 있다.
//내부적으로 오토 박싱, 언박싱이 처리된다.
Box<Integer> iBox = new Box<Integer>();
iBox.set(125); //오토 박싱 진행
int num = iBox.get(); //오토 언박싱 진행
- 타입 인자에 대입은 인스턴스 별로 다르게 가능하다.
Box<String> strBox = new Box<String>();
Box<Integer> intBox = new Box<Integer>();
- static 멤버에 타입 변수 사용 불가능
- 클래스의 정적(static) 멤버는 클래스의 모든 비정적(non static) 개체가 공유하는 클래스 수준 변수이다.
- 타입 인자는 인스턴스 별로 다르게 대입가능하기 때문에 타입 변수가 정적으로 고정되어서는 안된다.
- 객체를 생성할 때 타입 변수 사용불가. 타입 변수로 선언은 가능하다.
- 타입 이레이져로 인해서 제네릭은 컴파일 시 모두 지워진다. 제네릭 정보가 지워지면 생성에 참고할 정보가 없는 것이기 때문에 제네릭으로 생성은 불가능하다.
T[] tmpArr = new T[10]; // 에러 발생
- 일반적인 방법으로는 불가능하다. 그러나 자바 리플렉션 API를 활용하면 타입 변수에 해당하는 인스턴스를 생성할 수는 있다.
public static <E> void append(List<E> list, Class<E> cls) throws Exception { E elem = cls.getDeclaredConstructor().newInstance(); // OK list.add(elem); } List<Apple> ls = new ArrayList<>(); append(ls, Apple.class); System.out.println(ls.get(0));
- 각 오버로딩의 매개변수 유형이 동일한 원시 타입으로 지워지는 메서드는 오버로딩 할 수 없다.
- 컴파일 에러가 발생한다.
//컴파일 전 public class Example { public void print(Set<String> strSet) { } public void print(Set<Integer> intSet) { } } //타입 이레이져 public class Example { public void print(Set strSet) { } // 에러 public void print(Set intSet) { } // 에러 }
와일드카드 <?>
- 제네릭 메서드와 와일드카드는 기능적으로는 동일하다.
- 메서드 정의가 복잡해질수록 와일드카드 기반 메서드 정의가 더 간결하므로 제네릭 메서드 보다 와일드카드 기반 메서드 정의가 권고된다.
- 하나의 참조변수로 서로 다른 타입이 대입된 여러 제네릭 객체를 다루기 위한 것이 목적이다.
- 매개변수화 타입의 다형성을 성립시키기 위해 나왔다.
- 하나의 참조 변수로 대입된 타입이 다른 객체를 참조 가능
class Product {}
class Tv extends Product {}
class Audio extends Product {}
//와일드카드 사용
ArrayList<? extends Product> list = new ArrayList<Tv>(); // 정상
ArrayList<? extends Product> list = new ArrayList<Audio>(); // 정상
//와일드카드 미사용
ArrayList<Product> list = new ArrayList<Tv>(); // 에러 발생
- 메서드의 매개변수에 와일드 카드 사용
class Fruit {}
class Apple extends Fruit {}
class Grape extends Fruit {}
//FruitBox<Fruit> 뿐만 아니라 FruitBox<Apple>, FruitBox<Grape> 같이 Fruit를 상속하는 다른 클래스들도 매개변수로 전달될 수 있다.
class Juicer {
static Juice makeJuice(FruitBox<? extends Fruit> box) {
...
...
...
}
}
와일드 카드 종류
<? extend T>
- 와일드 카드의 상한 제한. T와 그 자손들만 타입으로 가능.
<? super T>
- 와일드 카드의 하한 제한. T와 그 조상들만 타입으로 가능.
<?>
- 제한 없음. 모든 타입이 가능.
<? extends Object>
와 동일
- 제한 없음. 모든 타입이 가능.
- 와일드카드 제한의 효과
- 와일드카드 상한 제한의 경우
<? extend T>
- 제네릭에서 타입 인자를 제한한 효과와 동일한 효과를 얻을 수 있다.
Box<? extends Toy>
: box 대상으로 Toy 넣는 것 불가
- 와일드카드 하한 제한의 경우
<? super T>
Box<? super Toy>
: box 대상으로 Toy 꺼내는 것 불가
- 와일드카드 상한 제한의 경우
- 프로그래머의 의도에서 벗어난 것에 대해서 컴파일 오류를 일으켜주는 것은 매우 좋다.
- 예를 들어 상자에 어떤 물건을 넣는 메서드가 있을 때, 이 메서드 안에서 상자에 물건을 넣는 것이 아니라 상자에 물건을 꺼내는 행위를 하는 경우에는 프로그래머의 실수일 확률이 높다.
- 상자에서 물건을 꺼내는 메서드가 있을 때, 이 메서드 안에서 상자에 물건을 넣는 행위를 하는 경우에도 마찬가지이다. 이러한 실수를 컴파일 단계에서 오류로 발견될 수 있도록 가능하게 한다.
- 와일드카드 제한 미사용 코드 예
class Box<T> {
private T ob;
public void set(T o) {
ob = o;
}
public T get() {
return ob;
}
}
class Toy {
public String toString() {
return "I am a Toy";
}
}
public class BoxHandler {
public static void outBox(Box<Toy> box) { //상자에서 꺼내는 메서드
Toy t = box.get(); //상자에서 꺼내기
System.out.println(t);
// box.set(new Toy()); //상자에서 꺼내는 메서드 안에서 상자에 넣는 연산을 하는 것은 실수일 확률이 높다.
}
public static void inBox(Box<Toy> box, Toy n) { //상자에 넣는 메서드
box.set(n); //상자에 넣기
// Toy myToy = box.get(); //상자에 넣는 메서드 안에서 상자에서 꺼내는 연산을 하는 것은 실수일 확률이 높다.
}
}
- 예1) 와일드카드 제한 사용 코드
- 와일드 카드 제한 사용은 결과적으로 코드의 안정성이 올라간다.
class Box<T> {
private T ob;
public void set(T o) {
ob = o;
}
public T get() {
return ob;
}
}
class Plastic {
public Plastic() {
System.out.println("Plastic Create");
}
}
class Toy extends Plastic {
public String toString() {
return "I am a Toy";
}
}
class Car extends Toy {
public Car() {
System.out.println("Car Create ");
}
}
class Robot extends Toy {
public Robot() {
System.out.println("Robot Create");
}
}
public class BoxHandler {
public static void outBox(Box<? extends Toy> box) { //상자에서 꺼내는 메서드
Toy t = box.get(); //상자에서 꺼내기
System.out.println(t);
// box.set(new Toy()); //해당 실수가 컴파일 오류로 발견된다. Box에 넣는 내용물이 Toy라는 것을 보장할 수 없다. Box에 넣는 내용물이 Toy의 하위 클래스가 온다면 부모인 Toy를 하위 클래스에 담을 수는 없다. 그렇기 때문에 Toy를 Box에 담으려 할 때 컴파일 오류가 발생한다.
}
public static void inBox(Box<? super Toy> box, Toy n) { //상자에 넣는 메서드
box.set(n); //상자에 넣기
// Toy myToy = box.get(); //해당 실수가 컴파일 오류로 발견된다. Box에 들어있는 내용물이 Toy라는 것을 보장할 수 없다. Box에서 꺼낸 내용물이 Toy의 부모 클래스라면 부모 클래스를 자식 클래스에 할당하려고 하는 것과 같은 상황이므로 Box에서 내용물을 꺼내서 할당할 때 컴파일 오류가 발생한다.
}
}
- 예2) 와일드카드 제한 사용 코드
class BoxContentsMover {
public static void moveBox(Box<? super Toy> to, Box<? extends Toy> from) {
// from에서 to로 내용물 이동하는 기능
// from에서는 내용물을 꺼내기만 하면되고 to에서는 내용물을 넣기만 하면된다.
to.set(from.get());
}
}
와일드 카드 사용 지침
- 복사기능을 제공하는 메서드가 존재할 때, 와일드 카드를 어떤식으로 사용해야하는지 알아보자.
src
는 복사에 이용될 데이터,dest
해당 데이터를 이용해서 복사한다고 가정
- 예)
copy(src, dest)
in 변수
- 코드에 데이터를 제공한다.
src
는 복사할 데이터를 제공하기 때문에 in 매개변수라고 생각할 수 있다.
out 변수
- 다른 곳에서 사용할 데이터를 보유한다.
- dest는 데이터를 허용하므로 out 매개변수이다.
in 변수
는extends
키워드 를 사용하여 상한 와일드카드로 정의한다.
out 변수
는super
키워드 를 사용하여 하한 와일드카드로 정의한다.
- Object 클래스에 정의된 메서드를 사용하여
in 변수
에 액세스할 수 있는 경우 무제한 와일드카드를 사용한다.
- 코드가
in 및 out 변수
로 변수에 액세스해야 하는 경우 와일드카드를 사용하면 안된다.
- 이 지침은 메서드의 반환 유형에 적용되지 않는다. 와일드카드를 메서드의 반환 유형으로 사용하는 것은 프로그래머가 코드를 사용하여 와일드카드를 처리하도록 하기 때문에 피해야 한다.
타입 이레이져(Type Erasure)
- 컴파일러는 제네릭 타입을 제거하고, 필요한 곳에 형변환을 넣는다.
- 제네릭 타입의 경계(bound)를 제거
- 하위 호환성을 위해서 제네릭을 컴파일 타임에 제거한다.
//컴파일 전
class Box<T extends Fruit> {
void add(T t) {
...
}
}
//컴파일 전
//class Box<T> {
// void add(T t) {
// ...
// }
//}
//컴파일 후
class Box {
void add(Fruit t) {
...
}
}
//컴파일 후
//class Box {
// void add(Object t) {
// ...
// }
//}
- 제네릭 타입 제거 후에 타입이 불일치하면, 형변환을 추가
//컴파일 전
T get(int i) {
return list.get(i);
}
//컴파일 후
Fruit get(int i) {
return (Fruit)list.get(i);
}
- 와일드 카드가 포함된 경우, 적절한 타입으로 형변환 추가
//컴파일 전
static Juice makeJuice(FruitBox<? extends Fruit> box) {
String tmp = "";
for(Fruit f : box.getList())
tmp += f + " ";
return new Juice(tmp);
}
//컴파일 후
static Juice makeJuice(FruitBox box) {
String tmp = "";
Iterator it = box.getList().iterator();
while(it.hasNext()) {
tmp += (Fruit)it.next() + " ";
}
return new Juice(tmp);
}
와일드카드와 제네릭 기반의 메서드의 결합
- 타입 인자의 다형성도 허용하면서 어떠한 타입도 인자로 대입 가능하게 할 수 있다.
- 와일드카드만 사용한 경우에서는 Box가 Toy와 관련되어야 타입 매개변수로 전달될 수 있었다.
- 와일드카드와 제네릭을 결합함으로써 특정 타입에 종속되지않고 모든 타입을 인자로 받으면서 타입인자의 다형성까지 해결할 수 있게되었다.
//와일드카드만 사용
public class BoxHandler {
public static void outBox(Box<? extends Toy> box) { //상자에서 꺼내는 메서드
Toy t = box.get(); //상자에서 꺼내기
System.out.println(t);
}
public static void inBox(Box<? super Toy> box, Toy n) { //상자에 넣는 메서드
box.set(n); //상자에 넣기
}
}
//와일드카드와 제네릭 메서드 결합
public class BoxHandler {
public static <T> void outBox(Box<? extends T> box) { //상자에서 꺼내는 메서드
T t = box.get(); //상자에서 꺼내기
System.out.println(t);
}
public static <T> void inBox(Box<? super T> box, T n) { //상자에 넣는 메서드
box.set(n); //상자에 넣기
}
}
제네릭 타입의 형변환
- 제네릭 타입과 원시 타입 간의 형변환은 바람직 하지 않다.
- 타입 체크 불가능
Box<Object> objBox = null;
Box box = (Box)objBox; // 가능. 제네릭 타입 -> 원시 타입. 그러나 경고 발생
objBox = (Box<Object>)box // 가능. 원시 타입 -> 제네릭 타입. 그러나 경고 발생
//다른 제네릭 타입의 형변환 불가능. 참조 변수와 생성자의 대입된 타입(매개변수화 타입)은 일치해야 한다는 것과 같은 맥락
Box<Object> objBox = null;
Box<String> strBox = null;
objBox = (Box<Object>)strBox; // 에러 발생
strBox = (Box<String>)objBox; // 에러 발생
//와일드 카드가 사용된 제네릭 타입으로는 형변환 가능
Box<Object> objBox = (Box<Object>)new Box<String>(); // 에러 발생
//Box<String> -> Box<? extends Object>
Box<? extends Object> wBox = (Box<? extends Object>)new Box<String>(); // 정상
Box<? extends Object> wBox = new Box<String>(); // 정상. 위 문장과 동일
// Box<? extends Object> -> Box<String>
Box<String> strbox = (Box<String>)wBox; // 가능. 경고발생
[참고자료]
https://www.youtube.com/watch?v=QcXLiwZPnJQ&list=PLW2UjW795-f6xWA2_MUhEVgPauhGl3xIp&index=135
https://docs.oracle.com/javase/tutorial/java/generics/index.html
https://docs.oracle.com/javase/tutorial/java/generics/QandE/generics-questions.html
https://cafe.naver.com/cstudyjava
윤성우의 열혈 Java 프로그래밍
반응형
'Java > 기본' 카테고리의 다른 글
이펙티브 자바(Effective Java) 아이템 2 - 생성자에 매개변수가 많다면 빌더를 고려하라 (0) | 2024.04.12 |
---|---|
이펙티브 자바(Effective Java) 아이템 1 - 생성자 대신 정적 팩터리 메서드를 고려하라 (0) | 2024.04.08 |
자바8 메모리 관리(Java 8 Memory Management) 변화 - PermGen, Metaspace (0) | 2021.07.05 |
자바 배열 정렬(Java Array Sort) - 정렬(Arrays.sort), 병렬 정렬(Arrays.parallelSort) 비교 (0) | 2021.07.05 |
자바 애노테이션(Java Annotation)의 변화 (0) | 2021.07.05 |