Generic

이재현·2024년 7월 31일

Java

목록 보기
12/15

🩵 Generic이란?

ArrayList나 LinkedList의 사용시에 객체<타입> 객체명 = new 객체<타입>(); 의 형태로 사용한다.
이때 저 <> 안에 들어가는 타입이 바로 Generic이다.

Generic은 클래스 내부에서 지정하는 것이 아닌 외부에서 사용자에 의해 지정되는 것을 의미한다.
즉, 특정 타입을 지정해두는 것이 아니라 필요에 의해 지정할 수 있도록 하는 일반 타입이라는 의미로 볼 수 있다.

💙 Generic 장점

제네릭을 사용함으로써 잘못된 타입이 들어오는 것을 컴파일 단계에서 방지할 수 있다.

  • 제네릭의 기능 자체가 컴파일 때 작용하는 것이기에 이때 방지할 수 있다.
    또한 클래스 외부에서 타입을 지정해주기 때문에 따로 타입을 체크하고 변환해줄 필요가 없다.
    결과적으로는 관리가 편해지고, 비슷한 기능의 경우 코드의 재사용성이 높아진다.



🩵 Generic 객체

💙 Generic Class

클래스의 선언 앞에 제네릭 타입 매개변수가 쓰이게 되면, 이를 제네릭 클래스라고 한다.

class Sample<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
		this.value = value;
}
}

💙 Generic Interface

클래스와 마찬가지로, 인터페이스 역시 제네릭의 적용이 가능하다.
주의할 점으로는 인터페이스를 Implement한 클래스에서도 Overriding한 메서드를 제네릭 타입에 맞춰 똑같이 구현해주어야 한다.
특히, 제네릭은 람다 표현식의 함수형 인터페이스에서 가장 많이 사용된다.

interface ISample<T> {
public void addElement(T t, int index);
public T getElement(int index);
}

class Sample<T> implements ISample<T> {
private T[] array;

public Sample() {
array = (T[]) new Object[10];
}

@Override
public void addElement(T element, int index) {
array[index] = element;
}

@Override
public T getElement(int index) {
return array[index];
}
}

💙 Generic Method

제네릭 클래스, 혹은 인터페이스와는 다르게 제네릭 타입 매개변수를 사용하는 것이 아니다.

// 틀린 예시… 실제로 처음 쓸 때 저렇게 사용해서 안됐던…
class FruitBox<T> {
public T addBox(T x, T y) {
...
}
}

제네릭 메서드는 메서드의 선언부에 <T> 가 선언된 메서드를 의미한다.
위의 나쁜 예시는 클래스의 제네릭에서 설정된 타입을 받아와서 반환 타입으로 사용하는 그냥 일반 메서드이다.
제네릭 메서드는 직접 메서드에 제네릭을 설정함으로써 동적으로 타입을 받아와서 사용할 수 있는 메서드이다.

// 옳게 된 제네릭 메서드… 독립적으로 타입 할당 운영되는… <T>가 포인트…
class FruitBox<T> {
public static <T> T addBoxStatic(T x, T y) {
...
}
}

이렇게 하게 되면 제네릭 클래스에 정의된 타입 매개변수와 제네릭 메서드에 정의된 타입 매개변수는 별개의 것이 된다.

  • 제네릭 메서드의 제네릭 타입 선언 위치는 반환 타입 바로 앞이다!
  • 메서드 호출 또한 메서드 왼쪽에 제네릭 타입이 위치한다!
    • 제네릭 타입에 들어갈 데이터 타입을 메서드의 매개변수를 통해 추정할 수 있기 때문에, 대부분 제네릭 메서드의 타입 매개변수를 생략하고 호출할 수 있다.

💙 타입 변수

클래스 옆의 <T> 와 메서드 옆의 <T>는 동일한 형태인데 제네릭 메서드만이 독립적으로 운용된다.




🩵 제네릭 범위 제한

제네릭에 타입을 지정해줌으로 클래스의 타입을 외부에서 정하여 안정성을 확보하는 것은 좋다.
하지만 너무 자유롭기 때문에 원하지 않은 클래스들도 대입이 가능하다는 문제점이 있다.
따라서 이를 방지하기 위해 사용되는 것이 제한된 타입 매개변수이다.

💙 extends

제네릭의 타입을 제한하기 위해서 타입 한정 키워드 extends를 사용한다.
<T extends [제한 타입]> 의 형태로 사용하며, 뒤에 오는 제한 타입의 범위에 있는 타입들만 받도록 타입 파라미터 범위를 제한한다.

  • 상속 키워드와 동일하기 때문에 혼동의 여지가 있기에, 제한 키워드는 <> 안에 있어야 한다는 점을 기억하면 좋다.
class Calculator<T extends Number> { … }
public class Main {
	public static void main(String[] args) {
	Calculator<Number> cal1 = new Calculator<>();
	Calculator<Integer> cal2 = new Calculator<>();
	Calculator<Double> cal3 = new Calculator<>();
	
	// 오류!
	Calculator<Object> cal4 = new Calculator<>();
	Calculator<String> cal5 = new Calculator<>();
}
}

💙 인터페이스 타입 제한

Extends 키워드 뒤에 올 타입으로 일반 클래스, 추상 클래스, 인터페이스 모두 올 수 있다.

interface Readable { … }

// 인터페이스를 구현하는 클래스
public class Student implements Readable { … } 
// 인터페이스를 Readable를 구현한 클래스만 제네릭 가능
public class School <T extends Readable> { … }

public static void main(String[] args) {
// 타입 파라미터에 인터페이스를 구현한 클래스만이 올수 있게 됨
School<Student> a = new School<Student>();
}

💙다중 타입 제한

2개 이상의 타입을 동시에 구현한 경우로 타입을 제한하고자 한다면, & 연산자를 사용한다.
그렇게 되면 해당 인터페이스들을 동시에 구현한 클래스가 제네릭 타입의 대상이 된다.
단, 자바에서는 다중 상속을 지원하지 않기 때문에 클래스로는 다중 extends가 불가하다.
(오직 인터페이스만!)

interface Readable {}
interface Closeable {}

class BoxType implements Readable, Closeable {}
class Box<T extends Readable & Closeable> {
List<T> list = new ArrayList<>();
public void add(T item) {
list.add(item);
}
}

public static void main(String[] args) {
// Readable 와 Closeable 를 동시에 구현한 클래스만이 타입 할당이 가능하다
Box<BoxType> box = new Box<>();

// 다른 클래스는 할당 불가능하다
Box<Object> box2 = new Box<>(); // ! Error
}

여기에 더해 다중 타입 파라미터의 경우 각각 다중 제한을 거는 것도 가능하다.


💙 재귀적 타입 제한

자기 자신이 들어간 표현식을 사용하여 타입 매개변수의 허용 범위를 제한하는 것을 의미한다.
주로 Comparable Interface와 함께 사용된다.
예를 들어서 <E extends Comparable<E>> 제네릭 타입 E의 타입범위를 Comparable<E>로 한정한다는 중첩표현식을 사용할 수 있다.

  • Comparable은 객체끼리 비교할 때 compareTo() 메서드를 Overriding할 때 구현하는 인터페이스이다.



🩵 제네릭 형 변환

배열과 같은 일반 변수 타입과 다르게 제네릭 서브 타입 간의 형변환은 불가능하다.
사실상 제네릭은 전달받은 타입으로만 서로 캐스팅이 가능하다.

Object[] arr = new Integer[1]; // 문제 없음
List<Object> list = new ArrayList<Integer>(); // 에러 발생

List<Object> listObj = null; 
List<String> listStr = null; 

listObj = (List<String>) listStr;  // 에러. List<String> -> List<Object>
listStr = (List<Object>) listObj;  // 에러. List<Object> -> List<String>

💙 제네릭 와일드 카드

제네릭의 형 변환을 성립하게 해주는 와일드카드 문법 ? 을 사용해야 한다.

  • ? : Unbounded Wildcards (제한 없음)
  • 타입 파라미터를 대치하는 구체적 타입으로 모든 클래스나 인터페이스 타입이 올 수 있다.
  • <? Extends 상위타입> : Upper Bounded Wildcards (상위 클래스 제한)
  • 상위 타입이나 상위 타입의 하위타입만 올 수 있다.
  • <? Super 하위타입> : Lower Bounded Wildcards (하위 클래스 제한)
  • 하위 타입이나 하위 타입의 상위 타입만 올 수 있다.

솔직히 잘 모르겠다. 추후에 상세하게 파고들어야 할 것 같다.




🩵 제네릭 타입 소거

제네릭은 컴파일되면 제네릭 타입은 사라지게 된다. 좀 더 상세하게는 이전 자바와의 호환성을 위해서 제네릭 코드는 컴파일 되면 사라지게 된다.
클래스 파일 .class에는 제네릭 타입에 대한 정보는 존재하지 않게 되는 것이다.
잘 모르겠다..

0개의 댓글