-
[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 타입의 하위클래스를 제한한다.
다음과 같은 클래스 다이어그램이 있다고 하고, 이에 대해서 알아보도록 하자.
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에 대한 정보를 담고있지 않는다는 것이다.
'Java' 카테고리의 다른 글
내가 선택한 DB 동시성 해결방법 (6) 2021.12.28 [Java-35] Lombok @Getter, @Setter 직접 만들어 보자 (1) 2021.02.10 [Java-34] Enum 기본 (열거 타입) (0) 2021.01.27 [Java-33] java 스레드(Thread) 기본기 (14) 2021.01.19 [Java-32] java 예외처리 기본기 (0) 2021.01.12