@LEELEE
dev note
@LEELEE
  • 분류 전체보기 (29)
    • Project (10)
    • Study (19)
      • Java (9)
      • Spring (3)
      • Test (2)
      • DB (2)
      • Infra (1)
      • ETC (2)
      • CS (0)
    • 노트 (0)

인기 글

최근 글

태그

  • Leetcode
  • Java
  • was
  • transaction
  • 예외처리
  • springboot
  • 이펙티브자바
  • 배포자동화
  • DB
  • Til
  • oracle
  • junit
  • Redis
  • Spring
  • HTTP
  • AWS
  • PostgreSQL
  • 객체지향
  • test
  • PS
전체 방문자
오늘
어제
hELLO · Designed By 정상우.
@LEELEE

dev note

Study/Java

[Java] 제네릭

2023. 2. 1. 23:48

들어가기 전

에러가 발생하는 시간을 기준으로 컴파일 에러와 런타임 에러로 나눌 수 있다.

둘 중에 어떤 게 더 치명적일까?

 

컴파일 에러는 IDE가 바로 알려주기 때문에 고치기 쉽다.

반면 런타임 에러는 그 코드가 실제 사용되기 전까지는 발견하기가 어렵다.

게다가 그 에러로 인해 실제 데이터에 영향을 준다면 더 치명적일 것이다.

런타임 에러가 발생할 여지가 있는 코드를 컴파일 단계에서 검사를 한다면 치명적인 에러를 줄일 수 있다.

 

 

제네릭이란

클래스, 인터페이스, 메서드를 정의할 때 타입(클래스 및 인터페이스)을 매개변수로 사용하는 기능

 

 

사용하는 이유?

1. 컴파일 시 타입 검사를 해 코드의 안정성을 높임

2. 형변환 번거로움이 줄어듦

 

여기까지만 보면 설명이 추상적이라 명확하게 이해가 되지 않을 수 있다.

제네릭이 사용되지 않은 코드와 사용된 코드를 비교해 보면 이해에 도움이 될 것이다

 

 

제네릭이 사용되지 않은 코드

Box: 과일을 담고, 돌려주는 박스

Apple, Banana: 과일

public class Box {
    Object item;
    
    public void setItem(Object item) {
        this.item = item;
    }

    public Object getItem() {
        return item;
    }
}

public class Apple {
    public void printApple() {
        System.out.println("apple");
    }
}

public class Banana {
    public void printBanana() {
        System.out.println("banana");
    }
}
    @Test
    void test() {
        Box box = new Box(); 
        box.setItem(new Banana()); // Box에 Banana 객체를 집어넣는다
        Apple apple = (Apple) box.getItem(); // Box에서 꺼내어 Apple로 형변환을 한다
        apple.printApple(); //apple 클래스에만 있는 printApple 메서드를 호출한다
        // 컴파일 에러는 나타나지 않았다	
    }

컴파일 에러가 나타나지 않았으므로 테스트를 실행해 본다.

 

테스트 결과

java.lang.ClassCastException: class Banana cannot be cast to class Apple

Banana는 Apple로 형변환 할 수 없다는 ClassCastException 에러가 나타났다.

ClassCastException은 RuntimeException을 상속했다.

앞서 말한 런타임 에러가 발생한 것이다.

 

 

제네릭을 사용한 코드

public class Box<T> {
    T item;
    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }
}

 

컴파일 에러 발생

Inconvertible types; cannot cast 'Banana' to 'Apple' 메시지가 나온다.

 

 

컴파일 에러를 수정하여 Box에서 Banana를 꺼내본다.

(Banana) box.getItem(); 로 형변환할 필요가 없이 바로 Banana 객체가 꺼내진다.

 

제네릭을 사용하여 1. 기존의 런타임 에러를 컴파일 단계에서 검사할 수 있고 2. 별도의 형변환을 할 필요가 없어졌다

이러한 특징으로 여러 타입을 혼용하여 담을 경우에 유용하게 쓰인다. 대표적으로 자바 컬렉션이 있다.

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

 

 

 

제한된 제네릭 타입 파라미터 Bounded Type Parameters

  • <T extends ..>를 사용하면 특정 클래스의 자식 타입 또는 특정 인터페이스의 구현 타입만 매개변수로 사용할 수 있다
  • extends라서 오해할 수 있지만 인터페이스의 구현체도 사용 가능하다
public class Box<T extends Fruits> {
    private List<T> list = new ArrayList<>();          

    public void add(T t) {
        list.add(t);
    }
}

public static void main(String[] args) {
	Box<Fruit> box = new Box<>();
	box.add(new Apple());    
	box.add(new Banana());
}

 

 

 

와일드카드

1. 제네릭 클래스가 아닌 경우
2. static 메서드(아래에서 언급하지만 static에는 제네릭 매개변수를 사용할 수 없다) 경우

 

위 두 경우에도 제네릭을 사용할 수 있다. 바로 와일드카드 기능 덕분이다.

class Juicer { 
	static Juice makeJuice(FruitBox<Fruit> box) {
		String tmp = '';
		for(Fruit f : box.getList()) tmp += f + " ";
		return new Juice(tmp);
	}
}


FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
	...
System.out.println(Juicer.makeJuice(fruitBox));	//OK. FruitBox<Fruit>
System.out.println(Juicer.makeJuice(appleBox));	//에러. FruitBox<Apple>


// 그럼 오버로딩으로 이 문제를 해결할 수 있을까?

static Juice makeJuice(FruitBox<Fruit> box) {
	String tmp = "";
	for (Fruit f : box.getList()) {
		tmp += f + " ";
	}
	return new Juice(tmp);
}
    
static Juice makeJuice(FruitBox<Apple> box) {
	String tmp = "";
	for (Fruit f : box.getList()) {
		tmp += f + " ";
	}
	return new Juice(tmp);
}
    
// 컴파일 에러 발생
// 제네릭 타입이 다른 것만으로는 오버로딩에 성립하지 않는다

 

 

와일드 카드 사용

  • ?를 사용한다
  • ?만 단독으로 사용하면 Object와 같기 때문에 extends로 상한, super로 하한 조건을 같이 사용한다
  • <? extends T> 와일드카드의 상항 제한. T와 그 자식들만 가능
    <? super T> 와일드카드의 하한 제한. T와 그 부모들만 가능

 

 

와일드카드를 사용한 코드

class Juicer { 
    static Juice makeJuice(FruitBox<? extends Fruit> box) {
        String tmp = "";
        for (Fruit f : box.getList()) {
            tmp += f + " ";
        }
        return new Juice(tmp);
    }
}

 

<T extends .. >와 다른 점이 뭐냐? 와일드카드는 참조가 불가능하기 때문에 메서드에서 참조될 수 없다.

 

 

 

제네릭 타입 제거 Type Erasure

  1. 컴파일러는 제네릭 타입을 이용해 소스파일을 검사하고
  2. 필요한 곳에 형변환 코드를 넣어주고
  3. 제네릭 타입을 제거한다
  4. 즉, 컴파일된 .class 파일엔 제네릭이 없다!

아래는 제네릭 타입 파라미터를 제거하는 규칙이다.

 

  • 제네릭 타입 파라미터가 상한이 있는 경우에는 타입 파라미터를 부모 타입으로
public class Node<T extends Comparable<T>> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) {
        this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}

// 컴파일러가 Type Erasure를 한다면
// 아래와 같은 코드로 바뀌는 셈이다

public class Node {

    private Comparable data;
    private Node next;

    public Node(Comparable data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Comparable getData() { return data; }
    // ...
}
  • 제네릭 타입 파라미터의 상/하한이 없는 경우에는 타입 파라미터를 Object로 변환
public class Node<T> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) {
        this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}

// 컴파일러가 Type Erasure를 한다면
// 아래와 같은 코드로 바뀌는 셈이다

public class Node {

    private Object data;
    private Node next;

    public Node(Object data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Object getData() { return data; }
    // ...
}
  • type-safety를 유지하기 위해 필요한 경우 타입 캐스팅을 사용
  • 제네릭 타입을 상속받은 클래스에서는 다형성을 유지하기 위해 브릿지 메서드*를 생성

 

브릿지 메서드?

제네릭 클래스 Node와 Node를 상속받은 클래스 MyNode가 있다.

public class Node<T> {
    public T data;
    public Node(T data) { this.data = data; }
    public void setData(T data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

public class MyNode extends Node<Integer> {
    public MyNode(Integer data) { super(data); }
    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

 

Type Erasure 후

  •  이 경우 Mynode의 setData(Integer data)와 Node의 setData(Object data)의 매개변수가 다르다
  • 오버라이딩이라고 볼 수 없다
public class Node<Object> {
    public Object data;
    public Node(Object data) { this.data = data; }
    public void setData(Object data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

public class MyNode extends Node {
    public MyNode(Integer data) { super(data); }
    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

// 이 경우 Mynode의 setData(Integer data)와 Node의 setData(Object data)의 매개변수가 다르다

MyNode mn = new MyNode(5);
Node n = mn;
n.setData("Hello");     // Causes a ClassCastException to be thrown.
Integer x = mn.data;

 

브릿지 메서드 생성 후

  • 위 문제를 해결하기 위해 브릿지 메서드가 생성된다
  • 브릿지 메서드에는 Object로 받은 객체를 Integer로 형변환한 뒤에 setData(Integer data) 메서드를 호출하는 로직이 들어있다
class MyNode extends Node {

    // Bridge method generated by the compiler
    public void setData(Object data) {
        setData((Integer) data);
    }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }

    // ...
}

 

 

 

주의 사항

  • static 키워드에는 제네릭스를 사용할 수 없다
    제네릭스는 인스턴스 변수로 취급되기 때문이다
  • 제네릭의 매개변수명을 임의로 정할 순 있지만 관습적으로 사용되는 것들이 있다
    (T: Type. 보통의 경우, E: Element. 리스트의 요소일 경우, K: Key, V: Value)

 

 

참고

<자바의 정석>

https://docs.oracle.com/javase/tutorial/java/generics/why.html

 

저작자표시 비영리 (새창열림)

'Study > Java' 카테고리의 다른 글

@ExceptionHandler는 어떻게 예외를 처리할 수 있을까?  (0) 2023.07.20
자바의 함수형 프로그래밍 전략, 메서드 참조  (0) 2023.05.04
JVM 명세 - Run-Time Data Areas  (0) 2022.06.25
자바로 간단한 http 웹 서버 구현  (0) 2022.06.11
간단한 자바 TCP 통신 구현  (0) 2022.06.02
    'Study/Java' 카테고리의 다른 글
    • @ExceptionHandler는 어떻게 예외를 처리할 수 있을까?
    • 자바의 함수형 프로그래밍 전략, 메서드 참조
    • JVM 명세 - Run-Time Data Areas
    • 자바로 간단한 http 웹 서버 구현
    @LEELEE
    @LEELEE

    티스토리툴바