제네릭

강진구·2024년 3월 26일

JAVA고급

목록 보기
1/4

제네릭(Generic)이란?

  • 타입 매개변수(Type Parameter)를 이용하여 클래스, 인터페이스, 메서드를 정의할 때 타입을 일반화시켜주는 기능.
  • 코드의 유연성과 재사용성을 높이고, 컴파일 시 타입 안전성을 보장함.

제네릭의 효용

  • 타입 언어에서 “중복되거나 필요없는 코드를 줄여주는 것”

여기서 중복되는 코드가 의미하는게 뭘까?

function plusReturnFunction(a, b) {
    return a + b;
}

const a = 1;
const b = 2;
const c = 1.1;
const d = "hello";

plusReturnFunction(a + b); // 3
plusReturnFunction(a + c); // 2.1
plusReturnFunction(a + d); // 1hello

자바스크립트를 예로들어 설명하면
기본적으로 타입을 지정 해 줄 필요가 없기에 하나의 함수만 구현하면 된다

public class Generic {
    public String plusReturnFunction(int a, int b) { ... }

    public String plusReturnFunction(int a, long b) { ... }

    public String plusReturnFunction(int a, String b) { ... }
}

하지만 자바는 일반적으로 타입을 지정해줘야한다
그래서 똑같은 로직을 수행하는 함수를 타입을 지정해야 한다는 이유로 세 차례나 구현해야 한다

  • 그러면서도 타입 안정성을 해치지 않는 것

그렇다면 타입의 안정성을 해치는 것은 뭘까?

public class Generic {
    public Object plusReturnFunction(Object a,Object b) { ... }
}
  • 자바의 “거의 모든 것”은 객체이고, 객체라는 것은 Object 클래스를 상속한다
  • Object를 상속받기 때문에 위의 코드와 같이 작성을 한다면,실제로 타입과 상관 없이 메서드 안에 두 파라미터를 전달하는 것은 가능하다
  • 하지만 이런 경우 타입 안정성이 침해받게 된다
  • 메서드를 위와 같이 구현했기 때문에, 지금은 아주 작아보이는 { … } 블럭 안에 모든 경우의 수를 대비해야 한다
  • 또한 메서드 내부는 타입에 지배받고 있다
  • 결론적으로는 다음과 같은 문제들이 발생한다
    • 예를 들어 a 객체와 b 객체에 단항 연산자를 사용 할 수 있을까?
    • 또는 두 타입이 다르다면 연산자를 사용하기 위해 같은 타입으로 맞춰줘야 하는데, 어떠한 타입으로 맞출 수 있을까? int? long?
    • 마지막으로 순서는 어떻게 할까? int long을 형변환 해서 처리하는 로직의 코드를 작성했는데, long과 int로 들어오면 어떻게 할까?
  • 타입의 논리로 동작하는 세상에서, 타입 안정성을 침해하는 행위를 했다 그러한 댓가로 형변환과 같은 부수적인 코드는 오히려 늘어나게된다

Generic문법

public class Generic<T> { ... }

Generic<String> stringGeneric = new Generic<>();
  • Generic의 클래스처럼, 제네릭을 사용한 클래스를 제네릭 클래스라고 한다
  • 제네릭에서 <>사이에 들어가는 변수명 T는 타입 변수라고 한다
  • Generic 클래스를 원시 타입 이라고 한다

다수의 타입변수를 사용 할 수 있다

public class Generic<T, U, E> {
  public E multiTypeMethod(T t, U u) { ... }
}


Generic<Long, Integer, String> instance = new Generic();
instance.multiTypeMethod(longVal, intVal);
  • 다형성, 즉 상속과 타입의 관계는 그대로 적용된다
    • 대표적으로 부모 클래스로 제네릭 타입변수를 지정하고, 그 안에 자식클래스를 넘기는 것은 잘 동작한다

와일드 카드를 통해 제네릭의 제한을 구체적으로 정할 수 있다

  • 제네릭에서 와일드카드(Wildcard)는 특정 타입을 대체할 수 있는 기호
    • 주로 메서드의 매개변수나 반환 타입으로 사용되며, 불특정한 타입을 나타낸다.
    • 와일드카드는 ? 기호로 표현
public class ParkingLot<T extends Car> { ... }

ParkingLot<BMW> bmwParkingLot = new ParkingLot();
ParkingLot<Iphone> iphoneParkingLot = new ParkingLog(); // error!

1.상한 와일드카드(Upper Bounded Wildcards) <? extends T>
: T와 그 자손들만 사용 가능
2.하한 와일드카드(Lower Bounded Wildcards)<? super T>
: T와 그 조상들만 가능
3.와일드카드(Wildcard)<?>
: 제한 없음

이렇게 제한을 하는 이유는 다형성 때문이다
위의 코드에서, T는 Car의 자손클래스들이라고 정의했기 때문에,
해당 클래스 내부에서 최소 Car 객체에 멤버를 접근하는 코드를 적을 수 있다
반대로 그러한 코드들이 있을 여지가 있기 때문에,
Car 객체의 자손이 아닌 클래스는 제한한다

메서드를 스코프로 제네릭을 별도로 선언 할 수 있다 - 잘 이해가 안가는 부분

// 또는 ..
static <T> void sort(List<T> list, Comparator<? super T> c) { ... }
  1. 이렇게 반환 타입 앞에 <> 제네릭을 사용한 경우, 해당 메서드에만 적용되는 제네릭 타입변수를 선언 할 수 있다
  2. 타입변수를 클래스 내부의 인스턴스 변수 취급하기 때문에 제네릭 클래스의 타입변수를 static 메서드에는 사용 할 수 없었지만, 제네릭 메소드의 제네릭 타입 변수는 해당 메소드에만 적용되기 때문에 메소드 하나를 기준으로 선언하고 사용 할 수 있다
  3. 같은 이름의 변수를 사용했다고 해도 제네릭 메소드의 타입변수는 제네릭 클래스의 타입변수와 다르다
public class Generic<T, U, E> {
		// Generic<T,U,E> 의 T와 아래의 T는 이름만 같을뿐 다른 변수
    static <T> void sort(List<T> list, Comparator<? super T> c) { ... }
}
profile
기록하고,발전하자

0개의 댓글