본문 바로가기

개발

Java - Generics 란

반응형

지네릭스(Generics) 란?
 - 컴파일시 타입을 체크해 주는 기능 - JDK1.5
 - 객체의 타입 안정성을 높이고 형변환의 번거로움을 줄여줌
 - 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다.

<형변환 에러>

import java.util.ArrayList;

public class GenericTest {

    public static void main(String[] args){
        ArrayList list = new ArrayList();
        list.add(10);
        list.add(20);
        list.add("30"); // String

        Integer i = (Integer) list.get(2); // 컴파일 OK

        System.out.println(list);
    }
}

- 컴파일러에서는 에러를 찾지 못한다. 실행 했을때 에러가 발생한다.

<형변환 필요>

import java.util.ArrayList;

public class GenericTest {

    public static void main(String[] args){
        ArrayList list = new ArrayList();
        list.add(10);
        list.add(20);
        list.add(30);

        Integer i = (Integer) list.get(2); // 컴파일 OK

        System.out.println(list);
    }
}

 

<형변환 불필요>

import java.util.ArrayList;

public class GenericTest {

    public static void main(String[] args){
        ArrayList<Integer> list = new ArrayList();
        list.add(10);
        list.add(20);
        list.add(30);

        Integer i = list.get(2); // 컴파일 OK

        System.out.println(list);
    }
}

 

타입 변수
 - 클래스를 작성할 때, Object 타입 대신 타입변수(E)를 선언해서 사용

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


 - 객체 생성시 타입 변수(E) 대신 실제 타입(Tv)를 지정(대입)

ArrayList<Tv> tvList = new ArrayList<Tv>();

 - 타입 변수 대신 실제 타입이 지정되면, 형변환 생략 가능
  *위의 <형변환 불필요> 소스 참고

지네릭스 용어
 Box<T> : 지네릭 클래스 'T의 Box' 또는 'T Box'라고 읽는다
  T : 타입 변수 또는 매개변수.(T는 타입문자)
  Box : 원시타입 (raw type)

지네릭 타입과 다형성
 - 참조변수와 생성자의 대입된 타입은 일치해야 한다.

 ArrayList<Tv> list = new ArrayList<Tv>(); // ok
 ArrayList<Product> list = new ArrayList<Tv>() // 에러! 

 - 지네릭 클래스간의 다형성은 성립

 List<Tv> list = new ArrayList<Tv>(); // ok
 List<Tv> list = new LinkedList<Tv>(): // ok

 - 매개변수의 다형성도 성립

class Product{}
class Tv extends Product{}
class Audio extends Product{}

....

void method(){
	ArrayList<Product> list = new ArrayList<Product>();
 	list.add(new Product());
 	list.add(new Tv());
 	list.add(new Audio());

	Product p = list.get(0);
    // get 함수는 E get(int idx) >> Product get(int idx) 이므로
    // 형변환 필요
    Tv t = (Tv)list.get(1);
    
}
....

 ArrayList 의 add 함수에 제네릭 적용 
 boolean add( E e){} >> boolean add(Product e){}

 

Itereator<E>
 - 지네릭 클래스의 하나 
 - it.next() 에서 형변환 불필요 

package gnerics;

import java.util.ArrayList;
import java.util.Iterator;

public class GenericTest {

    public static void main(String[] args){
        ArrayList<Student> list = new ArrayList();
        list.add(new Student("홍길동", "A"));
        list.add(new Student("김철수", "B"));
        list.add(new Student("김영희", "C"));

        // 제네릭 클래스
        Iterator<Student> it = list.iterator();

        while(it.hasNext()){
            // 형변환 불필요
            Student student = it.next();
            System.out.println(student.getName() +" : "+student.getClassName());
        }

    }

}

class Student{
    String name;
    String className;

    public Student(String name, String className) {
        this.name = name;
        this.className = className;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }
}

 

HashMap<K,V> 
 - 여러 개의 타입 변수가 필요한 경우 콤마(,)를 구분자로 선언 

package gnerics;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class GenericTest2 {

    public static void main(String[] args){
        Map<Integer, Student> map = new HashMap<>();
        map.put(1, new Student("홍길동", "A"));
        map.put(2, new Student("김철수", "B"));
        map.put(3, new Student("김영희", "C"));

        Iterator<Integer> iterator = map.keySet().iterator();

        while (iterator.hasNext()){
            // 형변환 불필요
            Student student = map.get(iterator.next());

            System.out.println(student.getName() +" : "+student.getClassName());

        }

    }

}


class Student{
    String name;
    String className;

    public Student(String name, String className) {
        this.name = name;
        this.className = className;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }
}

 

제한된 지네릭 클래스 
 - extends로 대입할 수 있는 타입을 제한 
 - 인터페이스의 경우도 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의 자손이 아니다

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

Box<Apple> appleBox = new Box<Apple>(); // ok 
Box<Grape> grapeBox = new Box<Grape>(); // ok 

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

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

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

class Box<T> {
	T[] itemArr;	// ok
    ...
    T[] toArray(){
    	T[] tmpArr = new T[itemArr.length]; // 에러! 제네릭으로 배열 생성 불가
		...
    }
    
}

 

와일드 카드 <?>
 - 하나의 참조변수로 대입된 타입이 다른 객체를 참조 가능
 - 하나의 참조변수로 서로 다른 타입이 대입된 여러 지네릭 객체를 다루기 위한 것
  <? extends T> 와일드 카드 상한 제한. T와 그 자손들만 가능. T 밑으로 가능
  <? super T> 와일드 카드 하한 제한. T와 그 조상들만 가능. T 위로 가능
  <?> 제한 없음. 모든 타입이 가능 <? extends Object> 와 동일

 ArrayList< ? extends Product> list = new ArrayList<Tv>(); // ok
 ArrayList< ? extends Product> list = new ArrayList<Audio>(); // ok
 ArrayList<Product> list = new ArrayList<Tv>(); // 에러! 대입된 타입 불일치 

 - 메서드의 매개변수에 와일드 카드를 사용

static Juice makeJuice(FruitBox<? extends Fruit> box){
	String tmp ="";
    for(Fruit f : box.getList())
		tmp += f+ " ";
	return new Juice(tmp);
}

...

makeJuice(new FruitBox<Fruit>());
makeJuice(new FruitBox<Apple>());

 

지네릭 메서드
 - 지네릭 타입이 선언된 메서드(타입 변수는 메서드 내에서만 유효)
 - 메서드를 호출할 때마다 다른 지네릭 타입을 대입할 수 있게 하는 것.

static <T> void sort(List<T> list, Comparator<? super T> c)

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

class FruitBox<T> { // 1번 
	...

    static <T> void sort(List<T> list, Comparator<? super T> c){     // 2번
    	....
    }
}

*** 1번 라인의 T와 2번라인의 T와는 다른 T
 1번은 클래스의 타입 매개변수
 2번은 메서드의 타입 매개변수 
 
 지역 변수가 클래스 변수보다 우선 하는 거 처럼
 메서드의 타입 매개변수가 클래스의 타입 매개변수보다 우선

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

FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
	...
Juicer.<Fruit>makeJuice(fruitBox);
Juicer.<Apple>makeJuice(Apple);

	...
    
static <T extends Fruit> Juice makeJuice(FruitBox<T> box){
	...
}

** 메서드 makeJuice 에서는 선언부에 있는 <T extends Fruit>이 영향을 받아 
메서드 인풋 타입의 제네릭에도 <T extends Fruit> 을 따라야 한다.
- 메서드를 호출할 때 타입을 생략하지 않을 때는 클래스 이름 생략 불가

<Fruit>makeJuice(fruitBox)			// 에러 클래스 이름 생략 불가
this.<Fruit>makeJuice(fruitBox)		// ok
Juicer.<Fruit>makeJuice(fruitBox)	// ok

 

static <T extends Fruit> Juice makeJuice(FruitBox<T> box){
	...
}

static Juice makeJuice(FruitBox<? extends Fruit> box){
	...
}

 

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

Box<Object> objBox = null;
Box box = (Box)objBox;		// ok 지네릭 > 원시. 경고 발생
objBox = (Box<Object>)box;	// ok 원시 > 지네릭. 경고 발생

Box<String> strBox = null;
objBox = (Box<Object>)strBox; // 에러 Box<Object> -x-> Box<String>
strBox = (Box<String>)objBox; // 에러 Box<String> -x-> Box<Object>

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

 Box<Object> objBox = (Box<Object>)new Box<String>(); // 에러. 형변환 불가능
 Box< ? extends Object> wBox = ( Box< ? extends Object>)new Box<String>(); // ok
 Box< ? extends Object> wBox = new Box(); // ok 위 문장과 동일

 

지네릭 타입의 제거
 - 컴파일러는 지네릭 타입을 제거하고, 필요한 곳에 형변환을 넣는다.
  (1) 지네릭 타입의 경계(bound)를 제거
   컴파일 후에는 모두 클래스로 바뀌어진다. 하위 호환성 때문에 ! JDK 1.5 이전 버전도 돌아가야 하기 때문에

// 소스
    class Box<T extends Fruit> {
        void add(T t){}
     } 

// 컴파일 후 
    class Box {
        void add(Fruit t){}
     } 

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

// 소스
	T get(int i){
		return list.get(i);
	}

// 컴파일 후 
	Fruit get(int i){
		return (Fruit)list.get(i);
	}

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

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 ="";
    Interator it = box.getList().iterator();
    while(it.hasNext()){
    	tmp += (Fruit)it.next() +" ";
    }
	return new Juice(tmp);
}
반응형

'개발' 카테고리의 다른 글

Java - 애너테이션  (0) 2021.06.27
Java - 열거형(enum)  (0) 2021.06.27
Java - Collections의 유용한 static 메서드  (0) 2021.06.06
Java - HashMap과 Hashtable  (0) 2021.06.06
Java - TreeSet  (0) 2021.06.06