타입 안정성 보장
형변환의 번거로움 해결
단점
1) 타입 안정성 X // instaceof 연산자사용
2) 형변환의 번거로움
이를 해결하기위해 Object 클래스 사용
👉 컴파일러가 알수 있는 형태로 자료형을 결정
(모든 클래스는 Object의 하위 클래스임을 알고 있다. 👉 자료형을 Object로 결정)
객체를 생성하는 시점에 타입 힌트를 통해서 형변환이 발생(Object 👉 Apple)
예) Box<Apple> appleBox = new Box<Apple>();
public class Ex01 {
public static void main(String[] args) {
Box appleBox = new Box();
appleBox.setItem(new Apple());
Apple apple = (Apple)appleBox.getItem(); //1. 형변환의 번거로움 //
System.out.println(apple.get());
Box grapeBox = new Box();
grapeBox.setItem(new Grape());
if(grapeBox.getItem() instanceof Apple){ //2. 타입안정성이 떨어짐.
Apple grape = (Apple)grapeBox.getItem();
System.out.println(grape.get());
}
}
}
public class Apple {
public String get() {
return "사과";
}
}
public class Grape {
public String get() {
return "포도";
}
}
public class Box<T> {
private T item;
public void setItem(T item){
this.item = item;
}
public T getItem(){
return item;
}
}
public class Ex01 {
public static void main(String[] args) {
Box<Apple> appleBox = new Box<Apple>();
appleBox.setItem(new Apple());
Apple apple = appleBox.getItem();
System.out.println(apple.get());
}
}
Class Box<T> {}
1) 제네릭 클래스 : Box<T>
2) 타입변수 T : T
3) 원시타입 : Box : 원시 타입(raw type)
Box<Apple> appleBox = new Box<Apple>(); // OK, Apple 객체만 저장가능
Box<Grape> grapeBox = new Box<Grape>(); // OK, Grape 객체만 저장가능
처음부터 공간이 필요한 정적 변수는 사용불가
private static T item( X )
지네릭 배열을 생성할 수 없는 것은 new 연산자 때문.
이 연산자는 컴파일 시점에 타입 T가 뭔지 정확히 알아야 한다.
private static T[] nums
public class Box<T> { // Box<T> : 제네릭 클래스, T 타입 매개변수, Box : 원시타입
private T item;
//private static T item2;
//사용 불가, 처음부터 공간을 할당 받으므로 자료형이 명확해야한다.
//private static T[] nums = new T[3];
//사용불가, 배열에서 공간 생성을 위해서는 자료형이 명확해야한다.
public void setItem(T item){
this.item = item;
}
public T getItem() {
return item;
}
}
Box<Apple> appleBox = new Box<>()👉Object👉 Apple타입 문자로 사용할 타입을 명시하면 한 종류의 타입만 저장할 수 있도록 제한할 수 있지만, 여전히 모든 종류의 타입을 지정할 수 있는 것에는 변함이 없다. 타입 매개변수 T에 지정할 수 있는 타입의 종류를 제한할 수 있는 방법이 있다.
- 메서드 내부에서 접근 가능한 제네릭 타입 인스턴스 자원은 모두 Object형
- 형변환된 객체의 정의된 인스턴스 자원 접근 X
- 공통된 틀(abstract)을 정해서 타입 매개변수의 하위 클래스임을 정의
<T extends 타입> -> T 는 타입의 하위 클래스 <T extends 타입1 & 타입2> -> T는 타입1의 하위 클래스 이고 타입2 인터페이스의 구현 클래스T는 Fruit의 하위 클래스임을 알 수 있음 👉 Object 보다는 Fruit로 변환
1) extends 를 사용하면, 특정 타입의 하위클래스만 대입할 수 있게 제한
class FruitBox<T extends Fruit> { // Fruit의 하위클래스만 타입으로 지정가능
ArrayList<T> list = new ArrayList<T>();
...
}
FruitBox<Apple> appleBox = new FruitBox<Apple>(); // OK
FruitBox<Toy> toyBox = new FruitBox<Toy>(); // 에러. Toy는 Fruit의 하위클래스가 아님
2) 인터페이스를 구현해야 한다는 제약이 필요하다면, 이때에도 extends를 사용
interface Eatable {}
class FruitBox<T extends Eatable> { ... }
3)클래스 Fruit의 하위 클래스이면서 Eatable 인터페이스도 구현해야 한다면 아래와 같이 &기호로 연결
class FruitBox<T extends Fruit & Eatable> { ... }
👉 FruitBox에는 Fruit의 하위클래스면서 Eatable을 구현한 클래스만 타입 매개변수 T에 대입될 수 있다.
//T는 Fruit의 하위클래스로 한정.
public class Box<T extends Fruit> { // Box<T> : 제네릭 클래스, T 타입 매개변수, Box : 원시타입
private T item;
public void setItem(T item){
this.item = item;
}
public T getItem() {
return item;
}
public String toString(){
// T == Fruit Fruit를 상속받았으니 Object가 아니라 Fruit
return item.get();
}
}
public abstract class Fruit {
public abstract String get();
}
<?> : <? extends Object>
<? extends 타입> -> T는 타입의 하위 클래스 : 타입으로 상한 제한
<? extends 클래스형 $ 인터페이스형> : 사용불가, 제니릭 클래스에서만 가능.
<? super 타입> -> T는 타입의 상위 클래스 : 타입으로 하한 제한
public class Jucier {
public static void make(Box<Apple> box){
ArrayList<Apple> fruits = box.getItems();
System.out.println(fruits);
}
public static void make(Box<Grape> box){
//오류 발생
//컴파일시 <Grape>제거되기 때문에 메서드가 중복정의되어진다.
}
}
와일드 카드 ?사용
public class Jucier {
public static void make(Box<?> box){
ArrayList<?> fruits = box.getItems();
System.out.println(fruits);
}
}
public class Ex02 {
public static void main(String[] args) {
Box<Grape> grapeBox = new Box<>();
grapeBox.add(new Grape());
grapeBox.add(new Grape());
Jucier.make(grapeBox);
}
}
<? extends 타입>
public class Jucier {
// 상한제한 ? : Fruit, Apple, Grape
public static void make(Box<? extends Fruit> box){
ArrayList<?> fruits = box.getItems();
System.out.println(fruits);
}
// 하한제한 ? : Apple,Fruit,Object
public static void make2(Box<? super Apple> box){
ArrayList<?> fruits = box.getItems();
System.out.println(fruits);
}
}
public class Ex03 {
public static void main(String[] args) {
Box<Toy> toyBox = new Box<>();
toyBox.add(new Toy());
//Jucier.make(toyBox); 오류
}
}
clss Box<T>: T의 자료형은 객체가 생성될 때 결정public <T,U> String method(T str1, U str2); : T,U의 자료형은 함수가 호출될때 결정. public static <T extends Fruit> void make3(Box<T> box){
}
제네릭 클래스 : Box가 객체가 될 때 T의 자료형 결정.
제네릭 매서드 : 메서드 호출시 T의 자료형 결정
public class Box<T> {
private T item;
public void method1(T str1, T str2){ //제네릭 클래스, Box가 객체가 될 때 T의 자료형 결정.
}
public <T> void method2(T str1, T str2){ // 제네릭 매서드, 메서드 호출시 T의 자료형 결정
}
}