열심히 끝까지

Java2 day14 본문

Java2(주말)

Java2 day14

노유림 2022. 5. 22. 21:38

[14일차 수업내용]
제네릭스(generics)
---------------------------------------------------------------------------------------
1. 제네릭스(generics)
            - 제네릭스는 다양한 타입의 객체들을 다루는 메소드나
              컬렉션클래스에 컴파일 시 타입체크(Compile-time type check)를 해주는 기능
            - 객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고
              형변환의 번거로움이 줄어듬
            - 타입안정성을 높인다는 것은 의도하지 않은 타입의 객체가 저장되는 것을 막고
              저장된 객체를 꺼내올 때 원래의 타입과 다른 타입으로 잘못 형변환되어
              발생할 수 있는 오류를 줄여준다는 것


            Ex) 
                        ArrayList list = new ArrayList();
                                    list.add(10);
                                    list.add("abc");

                        for(int i = 0; i < list.size(); i++){
                                    int num = (Integer)list.get(i);
                        }

                        ArrayList<Integer> list = new ArrayList<Integer>(); // 참조형만 올 수 있음(기본형 못옴)
                                                     = new ArrayList<>(); // 위와 동일(앞에서 언급해줬다면)
                                    list.add(10);
                                    list.add("abc"); // 빨간줄 그어짐

                        for(int i = 0; i < list.size(); i++){
                                    int num = list.get(i);  // 어차피 Integer만 오기 때문에 굳이 형변환 시킬 필요 없음
                        }

                        * 제네릭스의 장점
                                    ① 타입안정성을 제공
                                    ② 타입체크와 형변환을 생략할 수 있으므로 코드가 간결
                                                ==> 다룰 객체의 타입을 미리 명시해줌으로써 

                                                       번거로운 형변환을 줄여준다는 의미!

2. 제네릭 클래스의 선언
            - 제네릭 타입은 클래스와 메소드에 선언할 수 있는데, 
              먼저 클래스에 선언하는 제네릭 타입에 대해서 알아보자.

            class Box{
                        Object item;
                        void setItem(Object Item){}
                        Object getItem(){}
            }

            - 위 클래스를 제네릭 클래스로 변경하고자 한다면 
              아래와 같이 '<T>'를 붙이고 Object는 모두 T로 변경

            class Box<T>{
                        T item;
                        void setItem(T Item){}
                        T getItem(){}
            }

            - Box<T>에서 'T'는 타입변수(type variable)라고 하며 'Type'에서 첫글자를 따온 것
              ( 타입변수는 T가 아닌 다른 것을 사용해도 됨 )
            - 타입변수가 여러개인 경우에는 Map(K, V)처럼 콤마를 구분자로 나열
            - T, K, V 이들은 기호의 종류만 다를 뿐, '임이의 참조형 타입'을 의미하는 것은 모두 같음
            - 기존에는 다양한 종류의 타입을 다루는 메소드의 매개변수나 리턴타입으로 
              Object타입의 참조변수를 많이 사용했고, 그로인해 형변환이 불가피했지만
              이젠 Object타입 대신 원하는 타입을 지정하기만 하면 되는 것

            Box<String> b = new Box<String>();

            class Box{
                        String item;
                        void setItem(String Item){}
                        String getItem(){}
            }

            b.setItem(new Object());            // 에러 O String item = new Object();
            b.setItem("abc");                     // 에러 X String item = "abc";

            2-1) 제네릭스 용어
                        class Box<T>{}
                                    - Box<T>              제네릭 클래스, 'T의 Box' 혹은 T Box라고 읽음
                                    - T                      타입변수 또는 타입 매개변수(T는 타입문자)
                                    - Box                   원시타입(raw type)

                        - 타입문자 T는 제네릭 클래스 Box<T>의 타입 변수 또는 타입 매개변수라고 하는데
                          메소드의 매개변수와 유사한 점이 있기 때문
                        - 그래서 아래와 같이 타입 매개변수에 타입을 지정하는 것을 '제네릭 타입 호출'이라 하고
                          지정된 타입을 '매개변수화된 타입(parameterized type)'이라고 함

                                    Box<String> b = new Box<String>();
                                    Box<Integer> b1 = new Box<>();

                        - 위처럼 Box<String>, Box<Integer>는 서로 다른 타입을 대입하여 호출하는 것일 뿐
                          이 둘이 별개의 클래스를 의미하는 건 아님

            2-2) 제네릭스 제한
                        - 제네릭 클래스 Box의 객체를 생성할 때는 객체별로 다른 타입을 지정해주는 것이 적절
                                    왜? 제네릭은 인스턴스별로 다르게 동작하도록 만든 것이니까!
                        - 모든 객체에 대해 동일하게 동작해야 하는 static 멤버에 타입변수 T를 사용할 수 없음
                                    왜? T는 인스턴스 변수로 간주되게 때문

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

                        - static 멤버는 타입 변수에 지정된 타입, 
                          즉 대입된 타입의 종류에 관계없이 동일한 것이어야 하기 때문
                        - 또한 제네릭타입의 배열을 생성하는 것도 허용되지 않음
                        - 제네릭배열 타입의 참조변수 선언하는 것은 가능하지만(T[]),
                          new T[10];과 같은 배열을 생성하는 것은 안됨
                                    ==> new 연산자로 인해 불가능한 것인데, 
                                           new 연산자는 컴파일 시점에 타입 T가 무엇인지 정확히 알아야 하기 때문

-----------------------------------------------------------
package example01;

import java.util.ArrayList;

public class GenericsEx {
            public static void main(String[] args) {
                        // 1. 제네릭스를 사용하지 않는 경우
                        /*
                        ArrayList list = new ArrayList();
                        list.add(10);
                        list.add("abc");

                        for(int i = 0; i < list.size(); i++) {
                                    int num = (Integer)list.get(i);
                                    System.out.println("num : " + num);
                        }
                        */

                        // 2. 제네릭스를 사용한 경우
                        ArrayList<Integer> list = new ArrayList();
//                      ArrayList<Integer> list = new ArrayList<Integer>(); 위와 동일

                        list.add(10);
//                      list.add("abc");
                        list.add(20);

                        for(int i = 0; i < list.size(); i++) {
                                    int num = list.get(i);
                                    System.out.println("num : " + num);
                        }
            }
}

-----------------------------------------------
package example01;

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"; }}

class Box<T>{     // 과일을 담는 Box 클래스
            ArrayList<T> list = new ArrayList<>();

            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(); }
}

public class FruitBoxEx01 {
            public static void main(String[] args) {
                        Box<Fruit> fruitBox = new Box<>();
                        Box<Apple> appleBox = new Box<>();
                        Box<Toy> toyBox = new Box<>();
//                      Box<Grape> grapeBox = new Box<Apple>(); compile error ==> 제네릭 타입 불일치
//                      Box<Fruit> fruitBox2 = new Box<Apple>(); 상속관계여도 제네릭 타입이 일치하지 않는다면 에러

                        Fruit f = new Fruit();
                        fruitBox.add(f);
                        fruitBox.add(new Apple()); // Fruit item = new Apple(); 다형성 및 상속
                        fruitBox.add(new Grape()); // Fruit item = new Grape(); 다형성 및 상속
//                      fruitBox.add(new Toy()); // Fruit item = new Toy(); Fruit와 Toy는 상속관계가 아니다.

//                      appleBox.add(new Fruit()); // Apple item = new Fruit(); 다형성 적용 X
                        appleBox.add(new Apple());
                        appleBox.add(new Apple());

                        toyBox.add(new Toy());

                        System.out.println("fruitBox : " + fruitBox);
                        System.out.println("appleBox : " + appleBox);
                        System.out.println("toyBox : " + toyBox);
                        System.out.println("fruitBox[0] : " + fruitBox.get(0));
                        System.out.println("fruitBox.size : " + fruitBox.size());
            } 
}

-----------------------------------------------

3. 제한된 제네릭 클래스
            - 타입문자로 사용할 타입을 명시하면 한 종류의 타입만 지정할 수 있도록 제한할 수 있지만,
              그래도 여전히 모든 종류의 타입을 지정할 수 있다는 것에는 변함이 없음
            - 타입 매개변수 T에 지정할 수 있는 타입의 종류를 제한할 수 있는 방법은 없을까?

                        Box<Toy> toyBox = new Box<>();
                        toyBox.add(new Toy()); // 에러는 안나지만 내 마음에 안듬
                                                            ==> 과일 상자에 장난감을 담음

            - 아래와 같이 제네릭 타입에 extends를 사용하면 특정 타입의 자손들만 대입할 수 있게
              제한할 수 있음

                        class FruitBox<T extends Fruit> { /* 내용 생략 */ }

            - 여전히 한 종류의 타입만 담을 수 있지만, Fruit클래스의 자손들만 담을 수 있다는
              제한이 더 추가된 것

                        FruitBox<Toy> toyBox = new FruitBox<>();            // 에러남!!!
                        FruitBox<Fruit> fruitBox = enw FruitBox<>();
                        fruitBox.add(new Apple());
                        fruitBox.add(new Grape());

            - 다형성에서 조상타입의 참조변수로 자손타입의 객체를 가리킬 수 있는 것 처럼,
              매개변수화된 타입의 자손 타입도 가능한 것
            - 만약 클래스가 아니라 인터페이스를 구현해야 한다는 제약이 필요하다면,
              이 때도 implements가 아닌 extends를 사용

                        interface Eatable{}
                        class FruitBox<T implements Eatable> (X)
                        class FruitBox<T extends Eatable> (O)
                        class FruitBox<T extends Eatable & Fruit> (O)

-------------------------------------------
package example02;

import java.util.ArrayList;

interface Eatable{}

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"; }}
class Box<T>{
            ArrayList<T> list = new ArrayList<>();

            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(); }
}

class FruitBox<T extends Fruit & Eatable> extends Box<T>{}

public class FruitBoxEx02 {
            public static void main(String[] args) {
                        FruitBox<Fruit> fruitBox = new FruitBox<>();
                        FruitBox<Apple> appleBox = new FruitBox<>();
                        FruitBox<Grape> grapeBox = new FruitBox<>();

//                      FruitBox<Toy> toyBox = new FruitBox<>();
//                      FruitBox<Grape> grapeBox2 = new FruitBox<Apple>();
//                      FruitBox<Fruit> fruitBox2 = new FruitBox<Apple>();
//                      FruitBox<Apple> fruitBox2 = new FruitBox<Fruit>();

                        fruitBox.add(new Fruit());
                        fruitBox.add(new Apple());
                        fruitBox.add(new Grape());

                        appleBox.add(new Apple());
//                      appleBox.add(new Grape());

                        grapeBox.add(new Grape());

                        System.out.println("fruitBox : " + fruitBox);
                        System.out.println("appleBox : " + appleBox);
                        System.out.println("grapeBox : " + grapeBox);
            }
}

-----------------------------------------------------------

4. 와일드카드 ( wild card )
            - 매개변수에 과일박스(FruitBox)를 대입하면 
              주스를 만들어 반환하는 Juicer 클래스가 있고,
              이 클래스에는 과일을 주스로 만들어서 반환하는 
              static 메소드 makeJuice()가 있다고 가정

            class Juicer{
                        static Juice makeJuice(FruitBox<T> box){}
            }

            - Juicer클래스는 제네릭클래스가 아닌데다가 제네릭클래스라고 해도
              static 메소드에서는 타입매개변수 T를 사용할 수 없으므로
              아예 제네릭스를 적용하지 않던가 타입매개변수 대신 특정타입을 정해줘야 함

            class Jucier{
                        static Juice makeJuice(FruitBox<Fruit> box){}
            }

            - FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
            - FruitBox<Apple> appleBox = new FruitBox<Apple>();

            Juicer.makeJuice(fruitBox);   // 에러 안남 FruitBox<Fruit> box = new FruitBox<Fruit>();
            Juicer.makeJuice(appleBox); // 에러가 남 FruitBox<Fruit> box = new FruitBo<Apple>();

            - 이렇게 제네릭타입을' FruitBox<Fruit>'로 고정해 놓으면 위 코드에서도 알 수 있듯이
              FruitBox<Apple>타입의 객체는 메소드의 매개변수가 될 수 없으므로
             해당 메소드를 사용하기 위해선 메소드를 오버로딩 해야함
                        ==> "제네릭타입이 다른 것만으로는 오버로딩이 성립하지 않는다"라는 규칙 때문에
                               우리가 생각한 것처럼 오버로딩을 하면 에러가 발생

            - 이럴 때 사용하는 것이 와일드카드이다. 와일드 카드는 기호 ? 를 사용

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

            - FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
            - FruitBox<Apple> appleBox = new FruitBox<Apple>();

            Juicer.makeJuice(fruitBox);
                        FruitBox<? extends Fruit> box ==> FruitBox<Fruit> box;

            Juicer.makeJuice(appleBox);
                        FruitBox<? extends Fruit> box ==> FruitBox<Apple> box;

            - 와일드카드는 기호 ?로 표현하는데 와일드카드는 어떠한 타입도 될 수 있음
            - ? 만으로는 Object타입과 다를게 없으므로 아래와 같이 제한을 할 수 있음

            *            < ? extends T >           와일드카드 상한제한, T와 그 자손들만 가능
            *            < ? super T >              와일드카드 하한제한, T와 그 조상들만 가능
            *            < ? >                        제한없음, 모든 타입 가능 
                                                        ( < ? extends Object > 와 동일한 의미)

---------------------------------------------
package example03;

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 Box<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(); }
}
class FruitBox<T extends Fruit> extends Box<T> {}

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

            public String toString() { return name; }
}

class Juicer{
//          // 1. 와일드카드를 사용하지 않은 메소드
//          static Juice makeJuice(FruitBox<Fruit> box) {
//                      String tmp = "";
//
//                      // 일반적인 for문
//                     /*
//                     for(int i = 0; i < box.getList().size(); i++) {
//                                  tmp += box.getList().get(i) + " ";
//                      }
//                      return new Juice(tmp);
//                      */
//
//                     //향상된 for문
//                      for(Fruit f : box.getList()) {
//                                 tmp += f + " ";
//                      }
//                      return new Juice(tmp);
//          }

// 2. 와일드카드를 이용한 메소드
            static Juice makeJuice(FruitBox<? extends Fruit> box) {
                        String tmp = "";

                        for(Fruit f : box.getList()) {
                                    tmp += f + " ";
                        }
                        return new Juice(tmp);
            }  
}

public class FruitBoxEx03 {
            public static void main(String[] args) {
                        FruitBox<Fruit> fruitBox = new FruitBox<>();
                        FruitBox<Apple> appleBox = new FruitBox<>();

                        fruitBox.add(new Apple());
                        fruitBox.add(new Grape());

                        appleBox.add(new Apple());

                        // 1. 와일드카드를 사용하지 않은 메소드
                        /*
                        System.out.println("[와일드카드 미사용]");
                        System.out.println(Juicer.makeJuice(fruitBox));
//                      System.out.println(Juicer.makeJuice(appleBox));
                        */

                        // 2. 와일드카드를 이용한 메소드
                        System.out.println("[와일드카드 사용]");
                        System.out.println(Juicer.makeJuice(fruitBox));
                        System.out.println(Juicer.makeJuice(appleBox));
            }
}

---------------------------------------------
 
5. 제네릭 메소드
            - 메소드의 선언부에 제네릭 타입이 선언된 메소드를 제네릭 메소드라고 함
            - 제네릭 타입의 선언 위치는 반환타입 바로 앞
            
                        public static <T> void sort(T[] a, Comparator<? super T> c)

            - 제네릭 클래스에 정의된 타입 매개변수와 제네릭 메소드에 정의된 타입 매개변수는 
              별개의 것
            - 같은 타입문자 T를 사용해도 같은 것이 아니라는 것에 주의해야 함

                        class A<T>{
                                    public static <T> void sort(T[] a, Comparator<? super T> c){}
                        }
            
            - 메소드에 선언된 제네릭타입은 지역변수를 선언한 것과 같다고 생각하면 편한데,
              이 타입 매개변수는 메소드 내에서만 지역적으로 사용될 것이므로 
              메소드가 static이건 아니건 상관 없음

                        static Juice makeJuice(FruitBox<? extends Fruits> box){}
                                                            ↓
                                                            ↓제네릭 메소드로 변환
                                                            ↓
                        static <T extends FruitBox> Juice makeJuice(FruitBox<T> box){} 


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


                        Juicer.<Fruit>makeJuice(fruitBox);
                        Juicer.<Apple>makeJuice(appleBox);
                                         ↓
                                         ↓파라미터를 통해 컴파일러가 타입을 추정할 수 있기 때문에 생략 가능
                                         ↓
                        Juicer.makeJuice(fruitBox);
                        Juicer.makeJuice(appleBox);


---------------------------------------------------------------------------
package example03;

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 Box<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(); }
}
class FruitBox<T extends Fruit> extends Box<T> {}

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

            public String toString() { return name; }
}

class Juicer{
//          // 1. 와일드카드를 사용하지 않은 메소드
//          static Juice makeJuice(FruitBox<Fruit> box) {
//                     String tmp = "";
//
//                     // 일반적인 for문
//                    /*
//                     for(int i = 0; i < box.getList().size(); i++) {
//                                  tmp += box.getList().get(i) + " ";
//                      }
//                      return new Juice(tmp);
//                     */
//
//                      //향상된 for문
//                     for(Fruit f : box.getList()) {
//                                  tmp += f + " ";
//                      }
//                      return new Juice(tmp);
//          }

            // 2. 와일드카드를 이용한 메소드
//          static Juice makeJuice(FruitBox<? extends Fruit> box) {
//                      String tmp = "";
//
//                      for(Fruit f : box.getList()) {
//                                  tmp += f + " ";
//                     }
//                     return new Juice(tmp);
//          }

            // 3. 제네릭 메소드
            static <T extends Fruit> Juice makeJuice(FruitBox<T> box) {
                        String tmp = "";

                        for(Fruit f : box.getList()) {
                                    tmp += f + " ";
                        }
                        return new Juice(tmp);
            }
}

public class FruitBoxEx03 {
            public static void main(String[] args) {
                        FruitBox<Fruit> fruitBox = new FruitBox<>();
                        FruitBox<Apple> appleBox = new FruitBox<>();

                        fruitBox.add(new Apple());
                        fruitBox.add(new Grape());

                        appleBox.add(new Apple());

                        // 1. 와일드카드를 사용하지 않은 메소드
                        /*
                        System.out.println("[와일드카드 미사용]");
                        System.out.println(Juicer.makeJuice(fruitBox));
                        // System.out.println(Juicer.makeJuice(appleBox));
                        */

                        // 2. 와일드카드를 이용한 메소드
//                      System.out.println("[와일드카드 사용]");
//                      System.out.println(Juicer.makeJuice(fruitBox));
//                      System.out.println(Juicer.makeJuice(appleBox));

                        // 3. 제네릭 메소드 사용
                        System.out.println("[제네릭 메소드 사용]");
                        System.out.println(Juicer.<Fruit>makeJuice(fruitBox));
                        System.out.println(Juicer.makeJuice(fruitBox));
                        System.out.println(Juicer.<Apple>makeJuice(appleBox));
                        System.out.println(Juicer.makeJuice(appleBox));
            }
}

----------------------------------------------------
package example04;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

class Fruit{
            String name;
            int weight;

            public Fruit(String name, int weight) {
                        this.name = name;
                        this.weight = weight;
            }
            public String toString() { return name + "(" + weight + ")"; }
}

class Apple extends Fruit{ public Apple(String name, int weight) { super(name, weight); }}
class Grape extends Fruit{ public Grape(String name, int weight) { super(name, weight); }}
class Box<T>{
            ArrayList<T> list = new ArrayList<>();

            void add(T item) { list.add(item); }

            ArrayList<T> getList() { return list; }

            public String toString() { return list.toString(); }
}
class FruitBox<T extends Fruit> extends Box<T>{}

// 사과정렬기준
class AppleComp implements Comparator<Apple>{
            @Override
            public int compare(Apple o1, Apple o2) {
                        return o2.weight - o1.weight; // 내림차순
            }
}
// 포도정렬기준
class GrapeComp implements Comparator<Grape>{
            @Override
            public int compare(Grape o1, Grape o2) {
                        return o1.weight - o2.weight; // 오름차순
            }
}

// 과일정렬기준
class FruitComp implements Comparator<Fruit>{
            @Override
            public int compare(Fruit o1, Fruit o2) {
                        if(o1 instanceof Apple && o2 instanceof Apple) {
                                    return o2.weight - o1.weight;
                        }else if(o1 instanceof Grape && o2 instanceof Grape) {
                                    return o1.weight - o2.weight;
                        }
                        return -1;
            }
}

public class FruitBox04 {
            public static void main(String[] args) {
                        FruitBox<Apple> appleBox = new FruitBox<>();
                        FruitBox<Grape> grapeBox = new FruitBox<>();

                        appleBox.add(new Apple("GreenApple", 300));
                        appleBox.add(new Apple("GreenApple", 100));
                        appleBox.add(new Apple("GreenApple", 200));

                        grapeBox.add(new Grape("GreenGrape", 200));
                        grapeBox.add(new Grape("GreenGrape", 400));
                        grapeBox.add(new Grape("GreenGrape", 100));

                        System.out.println("[정렬 전 Apple과 Grape]");
                        System.out.println("AppleBox : " + appleBox);
                        System.out.println("GrapeBox : " + grapeBox);

//                      Collections.sort(appleBox.getList(), new AppleComp());
//                      Collections.sort(grapeBox.getList(), new GrapeComp());

                        Collections.sort(appleBox.getList(), new FruitComp());
                        Collections.sort(grapeBox.getList(), new FruitComp());

                        System.out.println("[정렬 후 Apple과 Grape]");
                        System.out.println("AppleBox : " + appleBox);
                        System.out.println("GrapeBox : " + grapeBox);
            }
}

'Java2(주말)' 카테고리의 다른 글

Java2 day16  (0) 2022.05.29
Java2 day15  (0) 2022.05.28
Java2 day13  (0) 2022.05.21
Java2 day12  (0) 2022.05.15
Java2 day11 보충  (0) 2022.05.15