본문 바로가기

Java/Java 3

[Java] Chapter 21. 제네릭 1

반응형

1. 제네릭의 이해

제네릭은 C++에서의 템플릿과 같은 개념으로

기본틀이며, 자료형을 제외한 틀을 만들어 두는 것을 의미

제네릭 이전의 코드

class Apple{
}

class Orange{
}

class AppleBox{
}

class OrangeBox{
}

class FruitAndBox{
}

 

동일한 성격과 내용을 가지는 클래스를 하나의 클래스로 대체

class Box{
    private Object ob;
    public void set(Object ob){
    	ob = o;
    }
    
    public Object get(){
    	return ob;
    }
}

 

제네릭 이전의 코드의 사용 예

class FruitAndBox2{
    Apple ap = (Apple)aBox.get();
    Orange og = (Orange)oBox.get();
}

이러한 무엇이든 담을 수 있는 박스를 만들더라도 형 변환 과정이 필요하며

형 변환 과정을 통한 결과는 컴파일러가 책임을 지지 않기 때문에, 컴파일러의 오류 발견 가능성이 떨어진다.

 

제네릭 이전 코드가 갖는 문제점1

class FruitAndBox2{

    aBox.set("Apple");
    oBox.set("Orange");
    
    Apple ap = (Apple)aBox.get();
    Orange og = (Orange)oBox.get();
}

문자열을 담았는데도 컴파일 과정에서 발견되지 않고, 형 변환 과정에서 예외가 발생한다.

제네릭 이전 코드가 갖는 문제점2

class FruitAndBox2{

    aBox.set("Apple");
    oBox.set("Orange");
    
}

프로그래머의 실수가 실행 과정에서조차 발견되지 않을 수 있다.

 

제네릭 기반의 클래스 정의하기

class Box<T> {
   private T ob;
   public void set(T o){
   	ob = o;
   }
   public T get(){
   	return ob;
   } 
}

인스턴스 생성 시 T의 자료형을 결정하기 위해 제네릭을 이용하고,

클래스 이름 뒤에 <T>를 붙여 T는 인스턴스 생성 시 자료형을 결정하기 위한 표식임을 표시한다.

 

즉, T는 인스턴스 생성시 결정이 되는 자료형의 정보이다.

제네릭 클래스 기반 인스턴스 생성

Box<Apple> aBox = new Box<Apple>();
Box<Orange> oBox = new Box<Orange>();

타입 매개변수 (Type Parameter)   Box<T>에서 T

타입 인자 (Type Argument)   Box<Apple>에서 Apple

매개변수화 타입 (Parameterized Type)   Box<Apple>

 

제네릭 이후의 코드 : 개선된 결과

class Box<T> {
   private T ob;
   public void set(T o){
   	ob = o;
   }
   public T get(){
   	return ob;
   } 
}

class FruitAndBox Fault_Generic{
	public static void main(String[] args){
  		Box<Apple> aBox = new Box<Apple>();
		Box<Orange> oBox = new Box<Orange>();
        
        Apple ap = aBox.get();
        Orange og = oBox.get();
  }
}

인스턴스 생성 시 Box<T>가 Apple과 Orange로 각각 결정되므로, 인스턴스의 get 메서드 반환형도 각각 결정된다

따라서, get 메서드 호출문에서 형 변환도 필요 없다.

 

실수가 컴파일 오류로 이어진다.

class Box<T> {
   private T ob;
   public void set(T o){
   	ob = o;
   }
   public T get(){
   	return ob;
   } 
}

class FruitAndBox Fault_Generic{
	public static void main(String[] args){
         aBox.set("Apple");
    	 oBox.set("Orange");
  }
}

인스턴스를 전달해야하는데 문자열을 전달했다 -> 타입 불일치

컴파일 에러를 일으켜서 코드 안정성이 높아진다.


2. 제네릭의 기본 문법

다중 매개변수 기반 제네릭 클래스의 정의

자료형을 여러 개로 결정

class DBox<L, R> {
   private L left;   // 왼쪽 수납 공간
   private R right;   // 오른쪽 수납 공간

   public void set(L o, R r) {
      left = o;
      right = r;
   }

   @Override
   public String toString() {
      return left + " & " + right;
   }
}

class MultiTypeParam{
  public static void main(String[] args) {
   DBox<String, Integer> box = new DBox<String, Integer>();
   box.set("Apple", 25);
   System.out.println(box);
 }
}

타입 매개변수의 이름 규칙

  • 한 문자
  • 대문자

보편적인 이름

  • E
    • Element
  • K
    • Key
  • N
    • Number
  • T
    • Type
  • V
    • Value

기본 자료형에 대한 제한 그리고 래퍼 클래스

제네릭 클래스에 대해 매개변수화 타입을 구성할 때 기본 자료형의 이름은 타입인자로 사용 불가능

Box<int> box = new Box<int>();

타입인자는 클래스의 이름만 가능하다

 

기본자료형에 대한 레퍼 클래스가 존재하고, 필요한 경우 오토 박싱과, 오토 언박싱이 이루어진다.

class Box<T> {
   private T ob;

   public void set(T o) { 
      ob = o;
   }
   public T get() {
      return ob;
   }
}

class PrimitivesAndGeneric {
   public static void main(String[] args) {
      Box<Integer> iBox = new Box<Integer>();
      iBox.set(125);   // 오토 박싱 진행
      int num = iBox.get();   // 오토 언박싱 진행
      System.out.println(num);
   }
}

iBox.set(125);

: 인스턴스가 와야하는데 값이 왔기 때문에 오토 박싱 진행 
int num = iBox.get(); 

: int형이 와야하는데 integer형 인스턴스가 왔기 때문에 오토 언박싱 진행

 

125를 저장하는 int형 인스턴스를 생성하고 참조변수 ob가 참조한다.

그 참조 값이 set 메서드를 인자로 전달한다. 즉, 박스가 참조하는 형태

 

다이아몬드 기호

Box<Apple> aBox = new Box<Apple>();

Box<Apple> aBox = new Box<>();

참조 변수 선언을 통해 컴파일러가 사이에 Apple을 유추한다.

 

'매개변수화 타입'을 '타입 인자'로 전달

   Box<String> sBox = new Box<>();
   sBox.set("I am so happy.");
   
   Box<Box<String>> wBox = new Box<>();
   wBox.set(sBox);

   Box<Box<Box<String>>> zBox = new Box<>();
   zBox.set(wBox);

매개변수화 타입 : Box<String>

타입인자 : String

 

매개변수화 타입 : Box<Box<String>>

타입인자 : Box<String>

 

매개변수화 타입 : Box<Box<Box<String>>>

타입인자 : Box<Box<String>>

 

System.out.println(zbox.get().get().get());

따라서, 3개의 get메서드가 필요하다.

 

제네릭 클래스의 타입 인자 제한하기

특성과 용도에 따라 담고 싶은 것을 제한한다.

class Box<T extends Number> {
    private T ob;

    public void set(T o) {
        ob = o;
    }
    public T get() {
        return ob;
    }
}

class BoundedBox {
    public static void main(String[] args) {
        Box<Integer> iBox = new Box<>();
        iBox.set(24);

        Box<Double> dBox = new Box<>();
        dBox.set(5.97);

        System.out.println(iBox.get());
        System.out.println(dBox.get());
    }
}

class Box<T extends Number> {...}

   → 인스턴스 생성 시 타입 인자로 Number 또는 이를 상속하는 클래스만 올 수 있음

 

즉, T로 전달이 되는 것은 Number 클래스를 상속하거나, Number 클래스의 인스턴스이다.

 

Box<Integer> iBox = new Box<>();

iBox.set(24);

: Integer는 Number를 상속

 

Box<Double> dBox = new Box<>();

dBox.set(5.97);

: Double는 Number를 상속

 

타입 인자 제한의 효과

제한한 클래스의 메서드를 호출 가능

즉, Number 클래스의 intValue 클래스 호출 가능 -> ob가 참조하는 인스턴스는 intValue 메서드를 가지고 있다는 점을 보장 가능하기 때문에

class Box<T extends Number> {
   private T ob;
   ....
   public int toIntValue() {
      return ob.intValue();   // OK!
   }
}

 

제네릭 클래스의 타입 인자를 인터페이스로 제한하기

클래스 뿐만 아니라 타입 인자를 인터페이스로 제한할 수 있다.

 

interface Eatable { public String eat(); }

class Apple implements Eatable {
   public String eat() {
      return "It tastes so good!";
   }  
   . . . .
}

class Box<T extends Eatable> {
   T ob;

   public void set(T o) { ob = o; }
   public T get() {
      System.out.println(ob.eat());    // Eatable로 제한하였기에 eat 호출 가능
      return ob;
   }
}

class Apple implements Eatable 

: Eatable 인터페이스를 Apple 클래스가 구현

 

class Box<T extends Eatable>

: T로 전달되는 클래스 이름은

Eatable 인터페이스를 구현했거나, 직/간접 구현한 클래스만 가능

 

하나의 클래스와 하나의 인터페이스에 대해 동시 제한

class Box<T extends Number & Eatable>

: Number 클래스를 상속하면서 동시에 Eatable 인터페이스를 구현한 클래스로 타입 인자 제한

 

제네릭 메소드의 정의

클래스 전부가 아닌 일부 메서드에 대해서만 제네릭으로 정의

또한 인스턴스 메서드 뿐만 아니라, 클래스 메서드에 대해서도 정의가 가능

 

즉, static 선언의 유뮤에 상관없이 제네릭 메서드의 정의가 가능

public static Box<T> makeBox(T o) { ... }

메서드 이름 : makeBox

반환형        : Box<T>

 

T에 대해 컴파일러가 모르기 때문에 완전한 메서드 정의가 아니다

 

public static <T> Box<T> makeBox(T o) { ... }

static <T> : T가 타입 매개변수임을 알리는 표시

 

제네릭 메서드 호출방법

class BoxFactory {
   public static <T> Box<T> makeBox(T o) {
      Box<T> box = new Box<T>();   // 상자를 생성하고,
      box.set(o);   // 전달된 인스턴스를 상자에 담아서,
      return box;   // 상자를 반환한다.
   }
}

반환형           : Box<T>

메서드이름     : makeBox

매개변수 선언 : (T o)

 

<T>의 결정 시기 [자료형 결정 시기]

제네릭 클래스 : 인스턴스 생성 시 자료형 결정

제네릭 메서드 : 메서드 호출 시 자료형 결정

 

Box<String> sBox = BoxFactory.<String>makeBox("Sweet");
Box<Double> dBox = BoxFactory.<Double>makeBox(7.59);   // 7.59에 대해 오토 박싱 진행됨

Box<String> sBox = BoxFactory.<String>makeBox("Sweet");

: Static이므로 인스턴스 생성없이 호출 가능

 

Box<String> sBox = BoxFactory.makeBox("Sweet");
Box<Double> dBox = BoxFactory.makeBox(7.59);       // 7.59에 대해 오토 박싱 진행됨

Box<String> sBox = BoxFactory.makeBox("Sweet");

: makeBox 호출하면서 String 인스턴스의 참조값 전달

 

제네릭 메소드의 제한된 타입 매개변수 선언

// <T extends Number>는 타입 인자를 Number를 상속하는 클래스로 제한함을 의미
public static <T extends Number> Box<T> makeBox(T o) {
   ....
   // 타입 인자 제한으로 intValue 호출 가능
   System.out.println("Boxed data: " + o.intValue());
   return box;
}

 

// 타입 인자를 Number를 상속하는 클래스로 제한
public static <T extends Number> T openBox(Box<T> box) {
   // 타입 인자 제한으로 intValue 호출 가능
   System.out.println("Unboxed data: " + box.get().intValue());
   return box.get();
}

마찬가지로 제한된 타입 인자의 경우 클래스의 메서드를 호출 가능하다.

반응형