[JAVA] generics

yoon·2023년 12월 7일
0

java

목록 보기
10/19
post-thumbnail

✅ Generics

다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능이다.
객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어든다.

예를 들어 ArrayList의 경우 다양한 종류의 객체를 담을 수 있지만 보통 한 종류의 객체를 담는다.

  • generics를 사용하지 않는 경우
ArrayList tvList = new ArrayList();
tvList.add(new Tv());
Tv t = (Tv)tvList.get(0);
  • generics를 사용하는 경우
ArrayList<Tv> tvList = new ArrayList<Tv>();
tvList.add(new Tv());
Tv t = tvList.get(0); //형변환 불필요

✔ 타입 변수

ArrayList클래스의 선언에서 클래스 이름 옆의 '<>'안에 있는 E를 타입 변수라고 한다.

public class ArrayList<E> extends AbstractList<E> {
	private transient E[] elementData;
    public boolean add (E o){}
    public E get (int index){}
 }

generic클래스를 생성할 때는 위의 예시 처럼 E 대신 Tv와 같은 실제 타입을 지정해주어야한다.

✔ generics 타입과 다형성

generic 클래스의 객체를 생성할 때, 참조 변수에 지정해준 지네릭 타입과 생성자에 지정해준 지네릭 타입은 일치해야한다.

// Product와 Tv가 상속 관계에 있더라도 일치해야함
ArrayList<Tv> list = new ArrayList<Tv>(); //OK
ArrayList<Product> list = new ArrayList<Tv>(); //ERROR

그러나 지네릭 타입이 아닌 클래스 타입 간에 다형성을 적용하는 것은 가능하다.


List<Tv> list = new ArrayList<Tv>(); // OK
List<Product> list = new LinkedList<Tv>(); //OK

ArrayList에 Product 자손 객체만 저장할 수는 있지만, 객체를 꺼낼 때 형변환이 필요하다.


ArrayList<Product> list = new ArrayList<Product>();
list.add(new Product());
list.add(new Tv());

Product p = list.get(0);
Tv t = (Tv)list.get(1); // 자손 객체들은 형변환 필요

✔ Iterator< E >

public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove();
 }

✔ HashMap<K,V>

HashMap처럼 데이터를 key와 value형태로 저장하는 컬렉션 클래스는 지정해 줘야 할 타입이 두 개이다. 여기서 k, v는 임의의 참조형 타입을 의미한다.


HashMap<String, Student> map = new HashMap<String, Student>(); // OK
map.put("학생1", new Student("학생1",1,100));

Student s1 = map.get("1-1"; //형변환 필요없음

✔ generics 제약

  • 객체별로 다른 타입을 지정하는 것은 가능
Box<Apple> list = new Box<Apple>(); //OK
Box<Orange> list = new Box<Orange>(); //OK
  • 모든 인스턴스가 동일하게 동작해야하는 static 멤버에는 타입 변수 T를 사용할 수 없다
//static 멤버는 타입의 종류에 관계없이 동일해야하기 때문
class Box<T>{
	static T item; //ERROR
}
  • generic 타입의 배열을 생성할 수 없다.
    • generic 배열 타입의 참조 변수를 선언하는 것은 가능하지만, 배열 생성은 불가능
class Box<T>{
	T[] itemArr; // OK, T타입의 배열을 위한 참조변수
    T[] toArray(){
    	T[] tmpArr = new T[itemArr.length] //생성 불가능
    }
}

new, instanceof 연산자는 컴파일 시점에 타입을 정확히 알아야하지만, Box클래스는 컴파일 하는 시점에 T가 어떤 타입이 될 지 알 수 없기 때문

✔ 와일드카드

generic 타입에 다형성을 적용하기 위한 기호이다.

  • < ? extends T > 와일드 카드의 상한 제한, T와 그 자손들만 가능
ArrayList<? extends Product > list = new ArrayList<Tv>(); //OK
  • < ? super T > 와일드 카드의 하한 제한, T와 그 조상들만 가능
  • < ? > 제한없음, 모든 타입 가능

✔ generic method

메서드의 선언부에 지네릭 타입이 선언된 메서드

class FruitBox<T>{
	...
    static <T> void sort(List<T> list, Comparator<? extends super T> c){
    	...
    }
}

static멤버에는 타입 매개변수를 사용할 수 없지만, 메서드에 지네릭 타입을 선언하고 사용하는 것은 가능하다.
메서드에 선언된 지네릭 타입은 메서드 내에서만 지역적으로 사용될 것이므로 지역 변수를 선언한 것이라고 생각하면 쉽다.

✔ generic 타입의 형변환

지네릭 타입과 원시 타입간의 형변환은 가능하지만, 경고를 발생한다.

Box box = null;
Box<Object> objBox = null;

box = (Box)objBox; //지네릭 > 원시
objBox = (Box<Object>)box; //원시 > 지네릭

대입된 타입이 다른 지네틱 타입 간에는 형변환이 불가능하다.

Box<Object> objBox = null;
Box<String> strBox = null;

objBox = (Box<Object>)strBox; //ERROR

와일드카드를 사용하면 형변환이 가능하다.

Box<? extends Object> wBox = new Box<String>();

✔ generic 타입의 제거

컴파일러는 지네릭 타입을 이용해서 소스파일을 체크팍, 필요한 곳에 현변환을 넣어준다.
그리고 지네릭 타입을 제거한다.
즉, 컴파일 된 파일(*.class)에는 지네릭 타입에 대한 정보가 없다.

✅ 열거형(enum)

여러 상수를 선언해야 할 때 편리하게 선언할 수 있는 방법이다.

class Card {
//              0,      1,       2,      3
	enum Kind {CLOVER, HEART, DIAMOND, SPADE}
    
    final Kind kind; // **타입이 Kind이다**
}

열거형을 이용해서 상수를 정의한 경우는 값을 비교하기 전에 타입을 먼저 비교한다.
그렇기 때문에 값이 같더라고 타입이 다르다면 컴파일 에러가 발생한다 (타입이 다르면 비교 불가)

enum Direction {EAST, SOUTH, NORTH, WEST}

class Unit {
	int x,y;
    Direction dir;
    
    void init(){
    	dir = Direction.SOUTH;
    }
}

열거형 상수간의 비교에는 == 연산자를 사용할 수 있다.
하지만 부등호는 사용할 수 없기 때문에 compareTo() 함수를 사용해야한다.
두 비교 대상이 같으면 0, 왼쪽이 크면 양수, 오른쪽이 크면 음수를 반환한다.

✔ 열거형에 멤버 추가하기

열거형 상수의 값이 불규칙적인 경우 값을 ()와 함께 적어주면 된다.

enum Direction {EAST(1), SOUTH(-5), NORTH(10), WEST(-1)}

그리고 지정된 값을 저장할 수 있는 인스턴스 변수와 생성자를 새로 추가해줘야한다.
이때, 먼저 열거형 상수를 모두 정의한 다음 다른 멤버들을 추가해야한다.

enum Direction {
	EAST(1), SOUTH(-5), NORTH(10), WEST(-1); // ;필수로 붙여줘야함
    
    private final int value; // 꼭 final일 필요는 없다.
    Direction(int value) {this.value = value;} //생성자 추가
    
    public int getValue() {return value; }
 }
 
 Direction d = new Direction(1); // Error > 열거형 생성자는 외부 호출 불가

열거형 생성자는 외부에서 호출할 수 없다. 제어자가 묵시적으로 private이기 때문이다.

profile
하루하루 차근차근🌱

0개의 댓글