자바

자바의정석 CH 12 - 1 지네릭스, 열거형, 애너테이션

JANNNNNN 2023. 4. 20. 16:16

1. 지네릭스

- 컴파일 시 타입을 체크해주는 기능

Object는 모든 객체를 저장할 수 있지만 새로운 객체를 생성 시 지네릭스로 <Tv>라고 하면 Tv객체만 저장 가능하다.

지네릭스 전

list.get(2)는 String으로 Integer로 형변환이 불가능하다. 하지만 컴파일은 되고 난 후 실행 시에 에러가 발생한다.(=런타임에러) 즉, 런타임에 발생한 에러를 컴파일 할 때 수정할 수 있게 생성된 것이 지네릭스이다.

=> 실행 시 에러가 발생하면 프로그램이 죽기 때문에 가능하면 컴파일할 때 에러를 잡아내는 것이 중요하다.

지네릭스 후

그럴 땐 애초에 객체를 만들 때 Integer만 받겠다는 뜻으로 <Integer>를 적어주면 된다.

list.add(10) = list.add(new Integer(10))이 생략된 것이다.

Integer i = (Integer)list.get(2)에서도 앞에서 지네릭스를 사용했기 때문에 (Integer)를 생략할 수 있다.

=> 그렇게 되면 빨간줄로 컴파일할 때 에러를 잡아낼 수 있다. 

+ 모든 객체를 받기 위해선 ArrayList<Object> list = new ArrayList<Object>로 선언해주어야한다.

지네릭스의 장점

1. 타입 안정성을 제공한다.

2. 타입체크와 형변환을 생략할 수 있어 코드가 간결해진다.

(하나의 컬렉션에는 대부분 한 종류의 객체만 저장한다)

1-2 타입변수 

- 클래스를 작성할 때 Object 타입 대신 타입 변수 E를 선언하여 사용.

- 객체를 생성 시, 타입변수 (E) 대신 실제 타입(Tv)를 지정해 대입해야한다.

참조변수와 생성자에 모두 넣어주어야 하고 일치해야한다.

- 타입변수 대신 실제 타입이 지정되면 형변환이 생략 가능하다.

왼쪽에서는 tvList가 Object 이기 때문에 Tv의 형식과 달라 생략불가능.

But 오른쪽은 Tv타입의 객체만 저장가능하기 때문에 형변환 생략 가능

지네릭스 용어

=> 지네릭스 클래스 선언

=> 참조변수와 생성자에 지네릭스 타입을 호출해야하고 일치해야한다.

=>지네릭 클래스 간의 다형성은 성립한다. (여전히 대입된 타입은 일치해야 한다)

=> 타입이 Product면 매개변수의 다형성도 성립되어 Tv와 Audio도 가능하다.

But 꺼낼 때는 Tv이기 때문에 타입이 Product로 되어 있어 Tv로 형변환 후 출력해주어야한다.

1-3 Iterator<E>

일반클래스일 때 Iterator은 Object 타입이었는데 지네릭스로 넘어오면서 타입변수가 E가 되었다.

=> 지네릭스 클래스가 된 후 타입이 일치하기 때문에 형변환이 필요없다.

Iterator은 참조변수에만 타입을 지정해주면 된다.

HashMap<K, V>

- 여러 개의 타입 변수가 필요한 경우, 콤마()를 구분자로 선언한다.

HashMap<Key = String, Value = Student>는 객체 선언와 동일해야하며 저장할 때 타입도 동일해야한다.

=> 옛날에는 get이 Object라서 형변환이 필요했는데 지네릭스 사용 후엔 타입이 Student로 일치되었다.

T extends Fruit이기 때문에 Fruit의 자손만 T에 FruitBox<여기>에 들어올 수 있는 것이다.

=>원래는 모든 타입이 가능했지만 Fruit의 자손만 대입 가능하게 되었다.

=> 인터페이스인 경우에도 구현할 때 implements를 사용하지 않고 지네릭 클래스에서는 extends를 사용

extends Fruit & Eatable 

=> 상속을 2개 이상 받고 싶을 땐 콤마(,)가 아니라 &를 사용해야 한다.

+ 사실 Fruit는 Eatable를 구현한 객체이기 때문에 Fruit만 사용해줘도 OK, Eatable를 조상으로 갖고있는 것과 동일

Fruit의 자손이 Apple, Grape이기 때문에 지네릭스에서 오류가 발생하지 않는 것

FruitBox.add(new Apple());도 가능! add(T item)이 add(Apple item)이 되면서 item이 박스에 추가됨

=> But 애초에 add(Apple item)이면 FruitBox.add(new Grape());는 불가능.

 

지네릭스의 제약

- 타입 변수에 대입은 인스턴스 별로 다르게 가능

- static 멤버에 타입 변수 사용 불가

=> static은 모든 인스턴스에 공통으로 사용하는 멤버이기 때문에 인스턴스마다 다르게 사용할 수 있게 되면 안된다.

- 배열을 생성할 때 타입 변수 사용 불가. 타입 변수로 배열 선언은 가능

=> new T가 안되는 것 즉, 객체를 생성하거나 배열을 생성할 때 안됨

new 다음에 오는 객체는 타입이 확정되어 있어야 하는데 T는 다르니까 안되는 것.

1-4 와일드 카드 <?>

- 하나의 참조변수로 대입된 타입이 다른 객체를 참조 가능

지네릭스는 참조변수와 객체 생성의 타입이 같지 않으면 오류가 난다. 그러나 다형성을 살리고 싶기 때문에 나온 것이 <?>

=> <?>는 불일치해도 괜찮다.

대부분 <? extends T>를 많이 사용

=> 만약 Product와 그 자손이 Tv, Audio라고 하면 <? extends Product>는 다형성처럼 Tv, Audio까지 다룰 수 있다.

- 메서드의 매개변수에도 와일드 카드 사용 가능

=> Fruit와 Apple이 box에 들어올 수 있는 것, 만약 ? extends가 없었다면 Fruit만 들어올 수 있었을 것

와일드 카드로 appleBoc에 Fruit2의 자손까지 들어올 수 있도록 했기 때문에 Apple2, Grape2가 들어올 수 있는 것

+ 만약 와일드 카드가 없었다면 Fruit2만 들어올 수 있었을 것.

Juicer에서 makeJuice에  <? extends Fruit2>로 Fruit2와 그 자손들까지 들어올 수 있게 했기 때문에

fruit2와 apple2가 들어올 수 O

+ for문은 향상된 for문.  box.getList()에서 하나씩 꺼내서 Fruit2에 반환되는 것.

그리고 문자열 결합을 하는 것.

1-5 지네릭 메서드

- 지네릭 타입이 선언된 메서드(타입 변수는 메서드 내에서만 유효)

- 클래스의 타입 매개변수 <T>와 메서드의 타입 매개변수 <T>는 별개이다.

=> 타입 변수가 일치하는 것이지 다른 타입 변수이다. iv와 lv의 이름이 같아도 되는 것처럼 !

+ FruitBox<String T>가 들어와도 되고 static< Integer T>가 들어와도 된다.

- 메서드를 호출할 때마다 타입을 대입해야한다 (대부분 생략 가능)

static 메서드가 Fruit의 자손만 가능하도록 제한이 되어 있다. 즉, FruitBox에 Fruit의 자손만 대입 가능

=> FruitBox<Fruit>, FruitBox<Apple>만 가능하다.

대부분 sysout.(Juicer.<Fruit>makeJuice(fruitBox));에서 <Fruit>를 생략 가능하다.

- 메서드 호출할 때 타입을 생략하지 않을 때는 클래스 이름 생략 불가능하다.

=> 그다지 중요하진 않고 에러가 났을 땐 클래스 이름을 써주면 된다.

핑크색 박스는 지네릭 메서드를 사용한 것이고 하얀색 박스는 와일드 카드를 사용한 것이다.

=> 지네릭 메서드는 메서드를 호출할 때마다 다른 지네릭 타입을 대입할 수 있게 한 것

=> 와일드 카드는 하나의 참조변수로 서로 다른 타입이 대입된 여러 지네릭 객체를 다루기 위한 것

+와일드 카드로 안될 때 지네릭 메서드를 사용하는 경우가 많다. 둘이 다른 것이니 혼동하지말자

1-6 지네릭 타입의 형변환

- 지네릭 타입과 원시 타입 간의 형변환은 바람직 하지 않다. (경고 발생)

Box가 원시 타입. 원시타입을 쓰는 것 자체가 바람직하지 못한 것.

=> Class Box<T>인데 <T>를 선언하지 않고 Box를 사용하는 것은 가능은 한데 바람직하지 못하다는 이야기

=> objBox, strBox는 선언되었다고 가정 할 때 같은 맥락으로 원시타입과 지네릭 타입은 형변환이 불가능하다.

- 와일드 카드가 사용된 지네릭 타입으로는 형변환이 가능하다.

=> object와 그 자손이 가능하기 때문에 String은 object의 자손이라서 가능하다. (형변환 생략 가능)

그래서 FruitBox<Apple> -> FruitBox<? extends Fruit>이 가능해진 것.

=> 형변환은 써주되 생략 가능했는데 에러 안나면 가능한거고 생략 했는데 에러 나면 불가능 한거

 

지네릭 타입의 제거

- 컴파일러는 지네릭 타입을 제거하고, 필요한 곳에 형변환을 넣는다.

1. 지네릭 타입의 경계(bound)를 제거

+ <T extends Fruit> 가 <T>였으면 컴파일할 때 지네릭스를 제거하고 Object로 바뀐다.

void add(Fruit t)에서도 Fruit가 Object로 바뀐다.

2. 지네릭 타입 제거 후에 타입이 불일치하면, 형변환을 추가

=> 지네릭스의 장점인 형변환 생략 가능으로 인해 return (Fruit)list.get(t)가 생략된 것.

3. 와일드 카드가 포함된 경우, 적절한 타입으로 형변환 추가

=> 제일 중요한 것은 컴파일 할 때 지네릭 타입이 제거된다는 것

지네릭스를 이용해서 Object를 타입 <T>로 바꿨는데 컴파일 할 때는 <T>가 다시 Object로 되고

불일치할 때 적절한 형변환을 추가. 또 생략된 부분에 형변환이 추가된다.