JAVA 9일차

Lucy in the Sky with Diamond·2023년 6월 23일

Generic

Generic은 무엇이며 왜 쓰냐?

  • 제네릭(Generic)이란?

자바에서 ArrayList와 같이 <> 꺽쇠 안에 클래스 타입이 명시되어있는 것을 확인할 수 있다. [그림 1]은 ArrayList의 내부이다. ArrayList처럼 'E'라고 표시된 것을 확인할 수 있다.

ArrayList<Integer> list = new ArrayList<Integer>();
ArrayList<String> list2 = new ArrayList<String>();

위와 같이 ArrayList에 저장할 변수 타입을 Integer, String 등으로 지정할 수 있다.

이처럼 데이터의 타입을 일반화(Generalize)하는 것을 제네릭(Generic)이라고 할 수 있다.

  • Generic의 장점
  1. 컴파일 시간에 더 강력한 타입 검사.

    자바 컴파일러는 제너릭 코드에 강력한 타입 검사를 적용하고 코드가 타입 안전을 위반하는 경우 오류를 발행합니다. 컴파일 타임 오류를 수정하는 것은 찾기 어려울 수 있는 런타임 오류를 수정하는 것보다 쉽습니다.

  2. 캐스트 제거

    List list = new ArrayList(); // 타입이 정해져있지 않으면
    list.add("hello");
    String s = (String) list.get(0); // 여기서 캐스팅을 해야한다.(string부분)
    
     List list = new ArrayList();
     list.add("hello");
     String s = (String) list.get(0);//여기서는 캐스팅을 할 필요가 없다!
  • Generic의

     Box<Integer> integerBox VS public class Box<T>
     ///왼쪽은 타입 아규먼트 오른쪽은 타입 파라미터 
     
    Box<Integer> integerBox = new Box<Integer>();
    위의 소스가 정석이지만 아래의 코드를 써도 JVM에서 알아서 처리함 
    Box<Integer> integerBox = new Box<>();
    //<>는 Empty diamond라고도 말함 
    
    
  • Multiple Type Parameters

      public interface Pair<K, V> {
        public K getKey();
        public V getValue();
    }
    public class OrderedPair<K, V> implements Pair<K, V> {
        private K key;
        private V value;
        public OrderedPair(K key, V value) {
    	this.key = key;
    	this.value = value;
        }
        public K getKey()	{ return key; }
        public V getValue() { return value; }
    }

위에서 만들어진 소스코드를 아래의 두가지 경우로도 객체 생성이 가능하다.

Pair<String, Integer> p1 = new OrderedPair<String, Integer>("Even", 8);
Pair<String, String>  p2 = new OrderedPair<String, String>("hello", "world");
  
Pair<String, Integer> p1 = new OrderedPair<>("Even", 8);
Pair<String, String>  p2 = new OrderedPair<>("hello", "world");  
 

다이아몬드에서 언급했듯이, 자바 컴파일러는 OrderedPair<String, Integer> 선언에서 K와 V 타입을 추론할 수 있기 때문에 가능한 이야기.

  • Parameterized Types

타입 파라미터(즉, K 또는 V)를 파라미터화된 타입(즉, List)로 대체할 수도 있습니다. 예를 들어, OrderedPair<K, V> 예제를 사용하면:

OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));
  • Raw Types

    public class Box<T> {
      public void set(T t) { /* ... */ }
      // ...
    }
  
```java
  Box<Integer> intBox = new Box<>();
  Box rawBox = new Box();
  //레거시 코드 = Raw type
  
  Box<String> stringBox = new Box<>();
  Box rawBox = stringBox;    

Raw타입은 제네릭 코드를 클래스로 선언했을때

  • Generic Methods

제너릭 메서드는 자체적인 타입 파라미터를 도입하는 메서드
예시로 이해해보자!

  public class Util {
    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
}
	public class Pair<K, V> {
      private K key;
      private V value;
      public Pair(K key, V value) {
          this.key = key;
          this.value = value;
      }
      public void setKey(K key) { this.key = key; }
      public void setValue(V value) { this.value = value; }
      public K getKey()   { return key; }
      public V getValue() { return value; }
  }

위의 소스코드를 불러오는 코드는 다음과 같다.

Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2);

하지만, 다음과 같이 불러오는것도 가능하다!

Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.compare(p1, p2);
  • Bounded Type Parameters

      public class Box<T> {
        private T t;          
        public void set(T t) {
            this.t = t;
        }
        public T get() {
            return t;
        }
        public <U extends Number> void inspect(U u){
            System.out.println("T: " + t.getClass().getName());
            System.out.println("U: " + u.getClass().getName());
        }
        public static void main(String[] args) {
            Box<Integer> integerBox = new Box<Integer>();
            integerBox.set(new Integer(10));
            integerBox.inspect("some text"); // 에러: 여전히 String입니다!
        }
    }

    public static int countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray)
    if (e > elem) // compiler error
    ++count;
    return count;
    } T[] anArray 얘가 클래스 객체임
    메서드의 구현은 간단하지만 보다 큼 연산자(>)가 short, int, double, long, float, byte 및 char와 같은 기본 타입에만 적용되기 때문에 컴파일이 불가능함. public interface Comparable {
    public int compareTo(T o);
    } public static <T extends Comparable> int countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray)
    if (e.compareTo(elem) > 0)
    ++count;
    return count;
    }

Box 및 Box은 Box의 하위 타입이 아니기 때문입니다.

이것은 제네릭을 사용한 프로그래밍에 관한 일반적인 오해이지만 배워야 할 중요한 개념입니다.


타입 파라미터를 변경하지 않는 한 하위 타입 지정 관계는 타입 간에 유지됩니다.

Type Inference

Box<Integer> integerBox = new Box<Integer>();
Box<Integer> integerBox = new Box<>();
//<>는 Empty diamond라고도 말함 

요기서 아래<>가 없는 이유가 Type Inference때문이다.

static <T> T pick(T a1, T a2) { return a2; }
Serializable s = pick("d", new ArrayList<String>())
Serializable s = <String, ArrayList<String>> pick("d", new ArrayList<String>())

1번쨰 메소드와 아규먼트를 보고 type inference를 함

d와 new ArrayList의 타입은 스트링이다.
이 마지막 요점을 설명하기 위해 다음 예제에서 타입 추론은 pick 메서드에 전달되는 두 번째 아규먼트가 Serializable 유형임을 확인.

마커 인터페이스는 추상메소드가 없는 인터페이스를 의미함

래퍼 클래스(Wrapper class)

프로그램에 따라 기본 타입의 데이터를 객체로 취급해야 하는 경우가 있습니다.

예를 들어, 메소드의 인수로 객체 타입만이 요구되면, 기본 타입의 데이터를 그대로 사용할 수는 없습니다.

Type Inference and Instantiation of Generic Classes

컴파일러가 컨텍스트에서 타입 아규먼트를 유추할 수 있는 한 제네릭 클래스의 생성자를 호출하는 데 필요한 타입 아규먼트를 빈 타입 파라미터 세트 (<>)로 바꿀 수 있다.

Map<String, List<String>> myMap = new HashMap<String, List<String>>();
Map<String, List<String>> myMap = new HashMap<>();

제네릭 클래스 인스턴스화 중에 타입 유추를 활용하려면 다이아몬드를 사용해야 합니다. 다음 예제에서 컴파일러는 HashMap() 생성자가 Map<String, List<String>> 타입이 아닌 HashMap 원시(Raw) 타입을 참조하기 때문에 확인되지 않은 변환 경고를 생성합니다.

Map<String, List<String>> myMap = new HashMap(); // unchecked conversion warning

다이아몬드가 없으면 타입 인퍼런스를 전혀 안하므로 모름.

Type Inference and Generic Constructors of Generic and Non-Generic Classes

class MyClass<X> {
<T> MyClass(T t) {
  // ...
}
}

여기서 MyClass에서 는 제너릭 클래스 타입을 나타내며 MyClass(T t)에서 와 (T t)는 제너릭 메소드 타입의 파라미터를 의미합니다.

  • 와일드카드

    와일드 카드는 제너릭메소드 호출, 제너릭 클래스 인스턴스 생성, 상위 타입에 대한 타입 아규먼트로 사용이 안됨
  • upper bounded wildcards

    List< Integer>,List< Double>,List< Number>에서 작동하는 메소드를 작성한다고 생각하자.
        public static void process(List<? extends Foo> list) {
        for (Foo elem : list) {
            // ...
        	 }
    	}
    	```

Number, Integer, Double 및 Float와 같은 Number의 하위 타입의 List에서 작동하는 메서드를 작성하려면 List<? extends Number> 코드를 정의하는것이 옳다.

  • Example
    public static void process(List<? extends Foo> list) { / ... / }
    에서 List는 리스트문 <? extends Foo>는 Foo타입을 상속받은 와일드카드
  • Unbounded Wildcards

List<?>에서 ?는 오브젝트 타입이다. -> 그러면 < T>와는 무슨 차이지?

Integer, Number 및 Object와 같은 Integer의 상위 타입 및 Integer list에서 작동하는 메서드를 작성하려면 List<? super Integer>을 지정(즉 Integer를 포함한 상위 클래스)

List<? extends Integer> intList = new ArrayList<>();
List<? extends Number> numList = intList; // OK. List<? extends Integer> is a subtype of List<? extends Number>

제한이 없는 애들은 object로 구성된다.
List<?> -> List< Object>로 컴파일러가 취급함

0개의 댓글