chapter 12 지네릭스, 열거형, 애너테이션

byeol·2022년 8월 11일
0
post-thumbnail
전체적인 flow
1. 지네릭스
-1.1 지네릭스란?
-1.2 지네릭 클래스의 선언
-1.3 지네릭 클래스의 객체 생성과 사용
-1.4 제한된 지네릭 클래스
-1.5 와일드 카드
-1.6 지네릭 메서드
-1.7 지네릭 타입의 형변환
-1.8 지네릭 타입의 제거
2. 열거형(enums)
-2.1 열거형이란?
-2.2 열거형의 정의와 사용
-2.3 열거형에 멤버 추가하기
-2.4 열거형의 이해
3. 애너테이션(annotation)
-3.1 애너테이션이란?
-3.2 표준 애너테이션
-3.3 메타 애너테이션
-3.4 애너테이션 타입 정의하기

1. 지네릭스(Generics)

1.1 지네릭스란?

붕어빵 틀(클래스)이 있는데
3번째 붕어빵(인스턴스 생성) 칸에서는 <팥>(지네릭스)이 들어가는 붕어빵만 만들겠습니다.
비유를 하자만 그렇다.
일단 지네릭스를 왜 이용하는 것인지 알면 이게 무엇을 의미하는지 알게된다.
사실 java API를 보면 꼭 <>이렇게 된 부분이 있었는데
이제야 정확히 이해할 수 있을거같다.

지네릭스는 다양한 타입의 객체들을 다루는 메서드(Object)나 컬렉션 클래스에 컴파일 시의 타입체크를 해주는 기능이다.
ArrayList의 경우 우리는 숫자, String을 섞어 객체를 담는 것보다는 한 종류의 객체만을 담는 경우가 많다. 그런데도 ① 꺼낼때마다 타입체크를 하고 형변환을 하는 것
ArrayList에 String만 저장하기로 했으나 ② 원하지 않은 종류의 객체(int)가 포함되는 것
✔️ 이 두가지를 해결할 수 있는 것은 지네릭스이다.

지네릭스의 장점
1. 타입 안정성을 제공한다.
2. 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해 진다.

1.2 지네릭 클래스의 선언

지네릭 타입은 클래스와 메서드에 선언할 수 있다.

BeforeAfter
class Box{class Box <T>{
Object item;T item;
void setItem(Object item){this.item=item;}void setItem(T item){this.item=item;}
Object getItem(){return item;}}T getItem(){return item;}}

Box<T>의 T는 타입변수라고 하며 Type의 첫글자에서 따온 것이다. 타입변수는 T가 아닌 다른것 사용해도 된다. 그냥 변수의 이름을 붙이는 것처럼 우리가 마음대로 해도 된다. 단, 상황에 맞게 이름을 지어주자.

  • ArrayList<E>의 경우는 Element(요소)의 첫글자를 따서 사용했다.
  • 여러개의 타입변수의 경우 Map<K,V>와 같이 ',' 콤마를 구분자로 나열하면 된다.

이들은 기호의 종류만 다를 뿐 임의의 참조형 타입을 의미한다는 것은 모두 같다
f(x,y)=x+y ,f(k,v)=k+v 처럼

실제 사용 예시를 보자

Box<String> b = new Box<String>();
b.setItem(new Object())//에러발생. String 이외의 타입 지정불가
b.setItem("ABC");
String item =(String) b.getItem();//형변환 필요없음


이렇게 우리는 String으로 지정해주었다. 이는 지네릭 클래스 Box<T>가 다음과 같이 정의된 것과 같다.

class Box {
String item;
void setItem(String item){this.item=item;}
String getItem(){return item;}}

👀 사실 지네릭 도입 되기 이전 코드와의 호환성을 위해서 지네릭 클래스인데도 예전의 방식으로 객체 생성을 허용한다. 하지만 지네릭 타입을 지정하지 않아서 안전하지 않다는 경고가 발생한다.

Box b=new Box();//OK. T는 Object로 간주
b.setItem("ABC");//경고. unchecked or unsafe operation
b.setItem(new Object());//경고. unchecked or unsafe operation

아래의 경우는 알고 T에 Object를 지정한 것이기 때문에 경고가 발생하지 않는다.

Box<Object> b=new Box<Object>();//OK. T는 Object로 간주
b.setItem("ABC");//경고 발생안함.
b.setItem(new Object());//경고 발생안함.



지네릭스의 용어


지네릭스의 제한

① 타입변수 T는 인스턴스 변수로 간주된다.
따라서 모든 객체 대해 동일하게 동작하는 static멤버에 타입변수 T를 사용할 수 없다.
이는 너무 당연한 말이다.
어떤 객체는 매개변수화 된 타입이 String, 다른 것은 Interger이라고 하자.
근데 static 멤버에 T를 했다고 치자. 서로 충돌된다.

class Box<T<T>>{
   static T item ; //에러
   static int compare(T t1, T t2){...}//에러
   ...  
 }

② 지네릭 타입의 배열을 생성하는 것도 허용되지 않는다.
단, 지네릭 배열 타입의 참조변수 선언까지만 가능
그러나 new T[10] 처럼 생성은 불가능!!

이유가 무엇일까?
new 연사자 때문이다.
이 연산자는 컴파일 시점에서 타입 T가 정확히 무엇인지 알아야 한다. 그런데 위의 코드에 정의된 Box<T>클래스를 컴파일하는 시점에서는 T가 어떤 타입이 될지 알 수 없다. 마찬가지 이유로 instanceof연산자도 사용불가이다.
👀 꼭 지네릭 배열을 생성해야 하는 경우에는 new 연산자 대신에 Reflection API의 newInstance()와 같이 동적으로 객체를 생성하는 메서드로 배열을 생성하거나
Object 배열 생성-> 복사-> T[]로 형변경

1.3 지네릭 클래스의 객체 생성과 사용

① 상속관계에 있어도 마찬가지이다.
참조변수와 생성자에 대입된 타입(매개변수화 된 타입)이 일치해야 한다.
두 타입 Apple이 Fruit의 자손이라고 가정하자.
❌ Box<Fruit> appleBox = new Box<Apple>();//에러
② 두 지네릭 클래스가 상속관계라면 다형성으로 인해 아래와 같이 사용할 수 있다. 보다시피 매개변수화 된 타입은 일치한다. (① 만족)
Box<Apple> appleBox = new FruitBox<Apple>();// OK 다형성
③ JDK1.7부터는 추정이 가능한 경우 타입을 생략할 수 있게 되었다.
Box<Apple> applebox = new Box<Apple>();
Box<Apple> applebox = new Box<>();
④ 다형성으로 인해서 매개변수화된 타입인 조상타입이 경우에는 자손타입도 메서드의 매개변수가 될 수 있다.
Box<Fruit> fruitbox = new Box<Fruit> ();
fruitbox.add(new Fruit());
fruitbox.add(new Apple());

import java.util.ArrayList;

class Fruit {public String toString() { return "Fruit";}}
class Apple extends Fruit {public String toString() { return "Apple";}}
class Grape extends Fruit {public String toString() { return "Grape";}}
class Toy {public String toString() { return "Toy";}}

public class FruitBoxEx1 {
	public static void main(String[] args) {
		Box<Fruit> fruitBox = new Box<Fruit>();
		Box<Apple> appleBox = new Box<Apple>();
		Box<Toy> toyBox = new Box<Toy>();
		// Box<Fruit> n_fruitBox =new Box<Apple>(); //에러. 타입 불일치
		
		fruitBox.add(new Fruit());
      fruitBox.add(new Apple());//다형성
      
      appleBox.add(new Apple());
      appleBox.add(new Apple());
      
      toyBox.add(new Toy());
      
      System.out.println(fruitBox);
      System.out.println(appleBox);
      System.out.println(toyBox);
	}

}
class Box<T>{
	ArrayList<T> list = new ArrayList<T>();
	void add(T item) {list.add(item);}
	T get(int index) {return list.get(index);}
	int size() { return list.size();}
	public String toString() {return list.toString();}
}

1.4 제한된 지네릭 클래스

이번에는 타입 매개변수 T에 지정할 수 있는 타입의 종류를 제한하는 방법에 대해서 알아보자.

  class FruitBox<T extends Fruit> {
     ArrayList<T> list = new ArrayList<T>();
     ...
  }

위와 같이 지네릭 타입에 extends를 사용하며 특정타입의 자손들과 자신만 대입하도록 제한된다.
따라서

FruitBox<Apple> appleBox = new FruitBox<>();
FruitBox<Grape> grapeBox = new FruitBox<>();

위와 같은 표현이 가능하다.
또한 다형성으로 인해

FruitBox<Fruit> fruitBox = new FruitBox<>();
fruitBox.add(new Apple());
fruitBox.add(new Grape());

위와 같은 표현도 가능하다.

👀 만일 클래스가 아닌 인터페이스를 구현해야 한다는 제약을 넣는다면??
중요한 것은 implements가 아닌 extends를 사용한다는 것이다.

interface Eatable
class FruitBox<T extends Fruit & Eatable> {
   ArrayList<T> list = new ArrayList<T>();
   ...
}

위와 같이 클래스 Fruit의 자손이면서 Eatable인터페이스도 구현해야 한다면 &기호로 연결한다.

import java.util.ArrayList;

class Fruit implements Eatable {
	public String toString() {return "Fruit";}
}
interface Eatable{}
class Apple extends Fruit {public String toString() {return "Apple";}}
class Grape extends Fruit {public String toString() {return "Grape";}}
class Toy {public String toString() {return "Toy";}}

class FruitBoxEx2 {
	public static void main(String[] args) {
          FruitBox<Fruit> fruitBox = new FruitBox<>();
          FruitBox<Apple> appleBox = new FruitBox<>();
          FruitBox<Grape> grapeBox = new FruitBox<>();
          
          fruitBox.add(new Fruit());
          fruitBox.add(new Apple());
          fruitBox.add(new Grape());
          appleBox.add(new Apple());
          grapeBox.add(new Grape());
          
          
          System.out.println("fruitBox-"+fruitBox);
          System.out.println("appleBox-"+appleBox);
          System.out.println("grapeBox-"+grapeBox);
          
	}

}

//FruitBox의 지네릭에 들어갈 타입변수는 Fruit와 그의 자손 그리고 인터페이스 Eatable와 그의 자손
// 또한 FruitBox자제는 Box<T>를 상속 받는다.
class FruitBox<T extends Fruit & Eatable> extends Box<T> {}

class Box<T>{
	ArrayList<T> list = new ArrayList<T>();
	void add (T item) {list.add(item);}
	T get (int i) {return list.get(i);}
	int size() {return list.size();}
    public String toString() {return list.toString();}
  
}

profile
꾸준하게 Ready, Set, Go!

0개의 댓글

관련 채용 정보