ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java-36] 자바 제네릭, < >
    Java 2021. 2. 24. 14:35

    Java Generic

    Generic, 제네릭 기본기

    generic <> 을 알아보기전에, 만약 제네릭이 없었을 때를 생각해보자. 일단 ArrayList <>에서 또한 우리가 알고 있는 제네릭이다. 그렇다면 없었을 경우에는
    ArrayList list = new ArrayList(); 와 같은 방법으로 리스트 선언이 가능했다. 마치 파이썬에서 리스트를 선언하듯이 일정의 타입없이 사용이 가능하다는 것이다. 코드를 보도록 해보자.

        public static void main(String[] args){
            ArrayList list = new ArrayList();
            list.add("Hello");
            String str = (String) list.get(0);
        }

    문제는 이와 같다. list 에서 꺼내야 하는 타입을 타입 캐스팅 해줘야 한다는 것이다. 이는 리스트가 모든 Object를 넣을 수 있기때문에 가능한일이다. 그럼 제네릭에서는 어떻게 편리하게 해결할까?

    제네릭 사용법

    제네릭은 강력한 타입의 규제라고 얘기할 수 있다. 예를들어보자 만약 *ArrayList 에 * String 타입의 문자열을 add(_) 할 수 있을까? 문제가 발생한다. 대신 List는 Integer 로만 한정되어있기 때문에, 불필요한 타입캐스팅을 하지 않아도 된다는 점이다.

    public static void main(String[] args){
            ArrayList<String> strList = new ArrayList<>();
            strList.add("Hello");
            System.out.println(strList.get(0));
        }

    class , interface

    제네릭 타입이라 함은 타입을 매개변수로 받는 클래스 또는 인터페이스를 이뤄 말을 한다. 선언하는 방법은 다음과 같다.

    • Generic Type class : public class 클래스
    • Generic Typer interface : public class 인터페이스
    public class Box <T> {
        T t;
    
        public T get() {return t;}
    
        public  void set(T t){
            this.t = t;
        }
    }
        public static void main(String[] args) { 
            Box<String> box = new Box<>();
            box.set("Hello");
            System.out.println(box.get());
    
            Box<Integer> box2 = new Box<>();
            box2.set(100_000);
            System.out.println(box2.get());
        }

    다음과 같이 사용할 수 있는게 제네릭이다.

    멀티 타입 파라미터 (class<K,V>, interface<K,V>)

    제네릭 타입은 두 개 이상의 멀티 타입 파라미터를 사용할 수 있다. 우리가 자주 사용해봤던,
    Map<K,V> 또한 멀티 타입 파라미터를 사용한 것이다. K는 key 값과 V 는 value 값이다.

    public interface Map<K,V> {
        // Query Operations
    
        int size();
    
        boolean isEmpty();
    
        boolean containsKey(Object key);
    
        //code..
    }
    public class Product<T, M> {
    
        private T kind; // 상품 종류
        private M model; // 모델 명
    
        public T getKind(){return this.kind;}
    
        public M getModel(){return this.model;}
    
        public void setKind(T kind) { this.kind = kind;}
    
        public void setModel(M model){this.model = model;}
    }
        public static void main(String[] args) {
            Product<Tesla, String> product = new Product<>();
            product.setKind(new Tesla());
            product.setModel("모델 x");
            System.out.println(product.getKind().toString());
            System.out.println(product.getModel());
        }
    
    result :
    Tesla@6ed3ef1
    모델 x

    제네릭 메소드 <T,R> R method(T t)

    제네릭 메서드는 메개 타입, 리턴 타입으로 타입 파라미터를 가진다. 간단한 예제를 통해서 알아보도록 하자. 이런식으로 제네릭을 통한 메소드 구현도 가능하다.

        public static void main(String[] args) {
            Box<String> box = packaging("boxing Starting :)");
            Box<Integer> box2 = Main.packaging(100);
            System.out.println(box.get());
            System.out.println(box2.get());
        }
        public static <T> Box<T> packaging(T t){
            Box<T> box = new Box<T>();
            box.set(t);
            return box;
        }   
    //result : boxing String :) 

    와일드 카드

    우리가 코드에서 볼 수 있는 ? 는 와일드 카드 라고 부른다. 제네릭에서 구체적인 타입을 작성하는대신, 와일드 카드를 사용할 수 있다는 것이다. 총 사용할 수 있는 형태는 3가지가 있다.

    • <?> Unbounded WildCard : 클래스와 인터페이스 사용에 있어서, 제한 없이 사용할 수 있다.

    • <? extends a타입> Upper Bounded Wildcard : a타입의 상위클래스를 제한한다.

    • <? super b타입> Lower Bounded Wildcard : b 타입의 하위클래스를 제한한다.

      다음과 같은 클래스 다이어그램이 있다고 하고, 이에 대해서 알아보도록 하자.

      image
    public class AnimalList<T> {
        ArrayList<T> al = new ArrayList<T>();
    
    
        public static void cryingAnimalList(AnimalList<? extends LandAnimal> al) {
            LandAnimal la = al.get(0);
            la.crying();
        }
    
        void add(T animal) { al.add(animal); }
        T get(int index) { return al.get(index); }
        boolean remove(T animal) { return al.remove(animal); }
        int size() { return al.size(); }
    }
    • <? extends LandAnimal> 특성상, LandAnimal 의 하위 , 즉 자식 클래스만 사용할 수 있을 것이다.
        public static void main( String[] args ) {
            AnimalList<Cat> catList = new AnimalList<Cat>();
    
            catList.add(new Cat());
    
            AnimalList<Dog> dogList = new AnimalList<Dog>();
    
            dogList.add(new Dog());
    
            AnimalList<Sparrow> sparrows = new AnimalList<>();
            sparrows.add(new Sparrow());
    
            AnimalList.cryingAnimalList(catList); // 가능 냥냥
    
            AnimalList.cryingAnimalList(dogList); // 가능 월월
    
            AnimalList.cryingAnimalList(sparrows);
            // error 발생
    
        }

    다음과 같은 상속 형태를 가진 클래스가 있다고 가정을 해보자.

    Type Erase

    Erasure 라 함은, 자바의 타입소거를 의미한다. 예를 들어보도록 하자. 만약 다음과 같은 코드가 있다고 하다.

        String str = "Hello";

    여기서 str 변수와 "Hello"의 리터럴은 Stiring 타입정보와 정보를 내제하고 있다. 런타임시 타입체크에서 다음과 같이 두개의 타입이 같은지 확인하고 매칭을 시키는데, 이 둘이 일치하지 않는다면, 매칭 에러가 발생한다는 것이다.

    하지만 제네릭은 그렇지 않다는 것이다. generic을 선언한 뒤 제네릭의 타입토큰은, T 가 아닌 SignatureObject 인 Object 와 비슷 한 형태를 띈다는 것이다.

    public class Test<T> {
        public void foo(T t){
            System.out.println(t.toString());
        }
    }

    라는 java 파일을 타입파라미터에 Integer를 넣는다고 한들, 컴파일 시 Test 의 타입 파라미터가 Test 로 변경되지 않는 다는 것이다.

    package org.example;
    
    public class Test<T> {
        public Test() {
        }
    
        public void foo(T t) {
            System.out.println(t.toString());
        }
    }
    
    
    public class org.example.Test<T> {
      public org.example.Test();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public void foo(T);
        Code:
           0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
           3: aload_1
           4: invokevirtual #13                 // Method java/lang/Object.toString:()Ljava/lang/String;
           7: invokevirtual #17                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
          10: return
    }
    

    디컴파일된 클래스에서도 또는 바이코드에서도 Integer에 대한 정보를 담고있지 않는다는 것이다.

    댓글

Designed by Tistory.