제네릭 정리 (자바의 정석)

2023. 5. 20. 17:04자바

제네릭 : 컴파일시 타입을 체크해 주는 기능

객체의 타입 안정성을 높이고 형변환의 번거로움을 줄여줌

 

제네릭의 장점

1. 타입의 안정성 제공

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

 

 

제네릭 타입과 다형성

 

1. 참조변수와 생성자에 대입된 타입은 일치해야 한다. (생성자에 대입된 타입은 생략 가능)

2. 제네릭 클래스 간의 다형성 성립 (참조변수와 생성자에 대입된 타입은 일치해야 한다. 이거 또한 생성자에 대입된 타입은 생략 가능)

import java.util.*;

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

class Ex12_1 {
    public static void main(String[] args) {
        ArrayList<Product> productList = new ArrayList<>();
        ArrayList<Tv> tvList1 = new ArrayList<>();
//     ArrayList<Product> tvList2 = new ArrayList<Tv>(); // 에러. 이유 : 타입 변수가 불일치하므로
      List<Tv> tvList3 = new ArrayList<>(); // OK. 다형성

        productList.add(new Tv());  // Product를 상속 받은 하위 클래스
        productList.add(new Audio());

        tvList1.add(new Tv());
        tvList1.add(new Tv());

        printAll(productList);
//        printAll(tvList1); // 컴파일 에러가 발생한다. 이유 : 타입 변수가 불일치하므로
    }

    public static void printAll(ArrayList<Product> list) {
        for (Product p : list)
            System.out.println(p);  //toString값 출력
    }
}

 

실행 결과

javaofclassic.generic.Tv@b684286
javaofclassic.generic.Audio@880ec60

 

 

iterator를 사용시에도 제너릭을 사용하면 타입을 체크 할 수 있기 때문에 형변환을 하지 않아도 되므로 사용하기 편해진다.

제네릭을 사용하지 않는다면 Object 타입으로 넘어오기 때문에 형변환을 해줘야 한다.

 

 

HashMap에 제네릭 사용

 

import java.util.*;

class Ex12_2 {
    public static void main(String[] args) {
        ArrayList<Student> list = new ArrayList<>();
        list.add(new Student("자바왕", 1, 1));
        list.add(new Student("자바짱", 1, 2));
        list.add(new Student("홍길동", 2, 1));

        Iterator<Student> it = list.iterator();
        while (it.hasNext()) {
            //  Student s = (Student)it.next(); // 지네릭스를 사용하지 않으면 형변환 필요.
            Student s = it.next();
            System.out.println(s.name);
        }

        System.out.println("======================");

        HashMap<String, Student> map = new HashMap<>();

        map.put("자바왕", new Student("자바왕", 1, 1));
        map.put("자바짱", new Student("자바짱", 1, 2));
        map.put("홍길동", new Student("홍길동", 2, 1));

        Iterator<Map.Entry<String, Student>> it2 = map.entrySet().iterator();

        while (it2.hasNext()) {
            Map.Entry<String, Student> entry = it2.next();
            System.out.println(entry.getKey() + " : " + entry.getValue().no);
        }
    }
}

class Student {
    String name = "";
    int ban;
    int no;

    Student(String name, int ban, int no) {
        this.name = name;
        this.ban = ban;
        this.no = no;
    }
}

실행결과

 

자바왕
자바짱
홍길동
======================
홍길동 : 1
자바왕 : 1
자바짱 : 2

 

제한된 제네릭 클래스

 

import java.util.ArrayList;

class Fruit implements Eatable {
    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"  ;}}

interface Eatable {}

class Ex12_3 {
    public static void main(String[] args) {
        FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
        FruitBox<Apple> appleBox = new FruitBox<Apple>();
        FruitBox<Grape> grapeBox = new FruitBox<Grape>();
//    FruitBox<Grape> grapeBox = new FruitBox<Apple>(); // 에러. 타입 불일치
// 에러. FruitBox클래스의 타입변수는 Fruit클래스와 Eatable인터페이스를 상속받아야 하는데 toy는 해당클래스를 상속 받지 않았다.
//    FruitBox<Toy>   toyBox   = new FruitBox<Toy>();

        fruitBox.add(new Fruit());
        fruitBox.add(new Apple());
        fruitBox.add(new Grape());
        appleBox.add(new Apple());
//    appleBox.add(new Grape());  // 에러. Grape는 Apple의 하위클래스가 아님
        grapeBox.add(new Grape());

        System.out.println("fruitBox-"+fruitBox.list);
        System.out.println("appleBox-"+appleBox.toString());
        System.out.println("grapeBox-"+grapeBox);   //grapeBox.toString()
    }  // main
}

/*
FruitBox 클래스는 Box 제너릭 클래스를 상속받으면서
타입변수는 Fruit클래스와 Eatable 인터페이스
를 상속받는다.
즉 FruitBox의 타입변수에 제한을 줄 수 있다.
*/
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();}
}

 

실행 결과

fruitBox-[Fruit, Apple, Grape]
appleBox-[Apple]
grapeBox-[Grape]

 

제네릭의 제한

 

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

- static은 인스턴스를 공통으로 사용하게 쓸 수 있는 예약어이므로 제네릭을 사용시 사용할 수 없다.

 

2. 배열이나 객체를 생성할 때 타입 변수 사용 불가하다.

- 참조변수를 만드는 거까지는 가능하나 배열이나 객체를 생성은 불가하다.

- new 뒤에는 타입이 확정이 되어 이써야 되는데 T인 경우에는 타입이 확정이 되어 있지 않다.

 

와일드카드

 

 

사용할 때 대부분 <? extends T> 형태를 많이 사용한다.

 

메스드의 매개변수에 와일드 카드를 사용하지 않았다면 Apple은 들어갈 수 없다.

 

제네릭 메소드

 

 

와일드 카드와 제네릭 메소드의 차이점

 

와일드 카드는 하나의 참조변수로 서로 다른 타입이 대입된 여러 제네릭 객체를 다루기 위해 사용

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

 

import java.util.ArrayList;

class Fruit2                 { public String toString() { return "Fruit";}}
class Apple2 extends Fruit2    { public String toString() { return "Apple";}}
class Grape2 extends Fruit2    { public String toString() { return "Grape";}}

class Juice {
    String name;

    Juice(String name)       { this.name = name + "Juice"; }
    public String toString() { return name;                }
}

class Juicer {
    /*
    * makeJuice의 매개변수로 FruitBox의 타입변수를 extends를 활용하여
    * 지정해준다.즉 Fruits를 상속받고 있는 클래스는 Fruits클래스의 타입
    * 변수로 사용이 가능하다.
    * */

    static Juice makeJuice(FruitBox2<? extends Fruit2> box) {
        String tmp = "";

        /*
        * box라는 인스턴스 안에 있는 리스트를 tmp로 붙여주는 작업을 하는 중이다.
        * 다 붙이고 나서 Juice의 생성자의 매개변수에 넣어준다.
        * */
        
        for(Fruit2 f : box.getList())
            tmp += f + " ";
        return new Juice(tmp);
    }

    /*
    static <T extends Fruit2> Juice makeJuice(FruitBox2<? extends Fruit2> box) {
        String tmp = "";

        *//*
         * box라는 인스턴스 안에 있는 리스트를 tmp로 붙여주는 작업을 하는 중이다.
         * 다 붙이고 나서 Juice의 생성자의 매개변수에 넣어준다.
         * *//*
        for(Fruit2 f : box.getList())
            tmp += f + " ";
        return new Juice(tmp);
    }
    */

    /*
    static <T extends Fruit2> Juice makeJuice(FruitBox2<T> box) {
        String tmp = "";

        *//*
         * box라는 인스턴스 안에 있는 리스트를 tmp로 붙여주는 작업을 하는 중이다.
         * 다 붙이고 나서 Juice의 생성자의 매개변수에 넣어준다.
         * *//*
        for(Fruit2 f : box.getList())
            tmp += f + " ";
        return new Juice(tmp);
    }
    */
}

class Ex12_4 {
    public static void main(String[] args) {
        FruitBox2<Fruit2> fruitBox = new FruitBox2<>();
        FruitBox2<Apple2> appleBox = new FruitBox2<>();

        fruitBox.add(new Apple2());
        fruitBox.add(new Grape2());
        appleBox.add(new Apple2());
        appleBox.add(new Apple2());

        // makeJuice는 static 메소드이므로 클래스명으로 호출가능
        System.out.println(Juicer.makeJuice(fruitBox).toString());
        System.out.println(Juicer.makeJuice(appleBox));
    }
}

class FruitBox2<T extends Fruit2> extends Box2<T> {}

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

 

실행결과

Apple Grape Juice
Apple Apple Juice

'자바' 카테고리의 다른 글

자바 람다 정리(자바의 정석)  (0) 2023.05.29
어노테이션 정리(자바의 정석)  (0) 2023.05.22
enum 정리(자바의 정석)  (0) 2023.05.21
컬렉션 - hashMap 정리  (0) 2023.05.17
자바 문자열  (0) 2023.04.25