제네릭프로그래밍 방법이란 여러 참조 자료형이 쓰일 수 이는 곳에
특정한 자료형을 지정하지 않고, 클래스나 메서드를 정의한 후 사용하는 시점에
어떤 자료형을 사용할 것인지 지정하는 방식이다.
제네릭 프로그래밍 방법에서 특정한 자료형을 대신하지 않고 일반화하는 자료형 매개변수와, 제네릭 클래스, 제네릭을 인스턴스화 해서 사용하는 방법.
🤔 앞에서 제네릭 프로그래밍은 사용하는 시점에, 어떤 자료형을 사용할지 지정한다고 했다.그렇다면 그 전에 클래스를 생성할 때, 클래스내부에서는 어떻게 자료형을 표현할까??
👉 바로 자료형 매개변수 T 를 사용한다.
👍 좋은 자료형 매개변수
타입 | 설명 |
---|---|
T | 타입(Type) |
E | 요소(Element), 예를 들어 List |
K | 키(Key), 예를 들어 Map<k, v> |
V | 리턴 값 또는 매핑된 값(Variable) |
N | 숫자(Number) |
S, U, V | 2번째, 3번째, 4번째에 선언된 타입 |
<T>
를 붙여 제네릭 클래스를 선언할 수 있다. 클래스 내부의 멤버 변수의 자료형과 매소드의 매개변수 타입은 식별자 기호 T로 쓴다. class SchoolMembers<T> { //클래스명 옆에 <T>를 붙임.
T schoolMember ; //클래스 멤버변수의 자료형을 T를 쓴다.
}
//Teacher이란 클래스가 있다고 가정
SchoolMembers<Teacher> mat = new SchoolMembers<>();
<>
내에 할당해서 인스턴스화한다.👉 타입의 구체화가 일어난다.
자료형 매개변수 T가 전달받은 타입으로 변환되어 생성된다!!
class Apple {}
class Banana {}
class FruitBox<T, U> {
List<T> apples = new ArrayList<>();
List<U> bananas = new ArrayList<>();
public void add(T apple, U banana) {
apples.add(apple);
bananas.add(banana);
}
}
public class Main {
public static void main(String[] args) {
// 복수 제네릭 타입
FruitBox<Apple, Banana> box = new FruitBox<>();
box.add(new Apple(), new Banana());
box.add(new Apple(), new Banana());
}
}
제네릭 자료 매개변수에 참조자료형인 클래스가 올 수 있고, 클래스끼리 상속을 통해 관계를 맺는 객체 지향 프로그래밍의 다형성 원리가 그대로 적용이 된다.
즉 업캐스팅이 적용이 된다는 것뜻이다.
class Fruit { }
class Apple extends Fruit { }
class Banana extends Fruit { }
class FruitBox<T> {
List<T> fruits = new ArrayList<>();
public void add(T fruit) {
fruits.add(fruit);
}
}
public class Main {
public static void main(String[] args) {
FruitBox<Fruit> box = new FruitBox<>();
// 제네릭 타입은 다형성 원리가 그대로 적용된다.
box.add(new Fruit());
//Apple과 Banana는 Fruit를 상속받아 업캐스팅이 적용된다.
box.add(new Apple());
box.add(new Banana());
}
}
public static void main(String[] args) {
// LinkedList<String>을 원소로서 저장하는 ArrayList
ArrayList<LinkedList<String>> list = new ArrayList<LinkedList<String>>();
LinkedList<String> node1 = new LinkedList<>();
node1.add("aa");
node1.add("bb");
LinkedList<String> node2 = new LinkedList<>();
node2.add("11");
node2.add("22");
list.add(node1);
list.add(node2);
System.out.println(list);
}
👉 실행화면
[[aa, bb], [11, 22]]
🧐 제네릭이 생기기 전 클래스의 가장 상위클래스인 Object
타입으로 업캐스팅을 이용해 다양한 참조 자료형을 사용했다.
하지만! 클래스의 메소드로 Object
객체를 반환할 경우 형변환을 해야하고, 런타임 에러가 발생할 수 있다.
⚠️ 런타임 오류로 이어진다.(컴파일 단계에서 에러가 발생하지 않고 런타임때 에러 발생) → 타입이 안전하지 않다.
👉 제네릭을 사용하면서 불필요한 형변환을 줄여 성능을 줄이고, 런타임 에러를 방지할 수 있다.
//이렇게 사용할 수는 없음
T t = new T();
⚠️ 제네릭 메소드는 static이 가능하다.
class Sample<T> {
}
public class Main {
public static void main(String[] args) {
//이건 오류가 나지만
Sample<Integer>[] arr1 = new Sample<>[10];
//이건 오류가 나지 않는다..?! 이왜진?
Sample<Integer>[] arr2 = new Sample[10];
// 제네릭 타입을 생략해도 위에서 이미 정의했기 때문에 Integer 가 자동으로 추론됨
arr2[0] = new Sample<Integer>();
arr2[1] = new Sample<>();
}
}
인터페이스에서도 인터페이스명 옆에 제네릭 타입 매개변수를 붙여 제네릭 인터페이스를 사용할 수 있다.
<T>
가 선언된 메서드이다. <T>
로 메서드에 직접 타입을 지정하면서, 독립적으로 운용가능한 제네릭 메서드이다.⚠️ 제네릭 클래스에서 제네릭 메소드가 지정되었다고 하더라도 서로의 제네릭 자료형매개변수는 관련이 없다. 제네릭 메소드는 자신의 <T>
를 통해서 독립적으로 운용한다.
👉 제네릭 클래스의 <T>
≠ 제네릭 메서드의 <T>
class FruitBox<T> {
// 클래스의 타입 파라미터를 받아와 사용하는 일반 메서드
public T addBox(T x, T y) {
// ...
}
// 독립적으로 타입 할당 운영되는 제네릭 메서드
public static <T> T addBoxStatic(T x, T y) {
//이때의 T는 제네릭 메서드의 T와 달라도 상괍없다.
}
}
class FruitBox<T, U> {
static <T> void printFruitBox(T t){
System.out.println(t);
}
}
public class GenericTest {
public static void main(String[] args) {
FruitBox.<String>printFruitBox("과일박스");
}
}
하지만! 제네릭메서드에 들어가는 매개변수를 통해 메서드의 자료형매개변수를 추정할 수 있기 때문에 이런경우 생략할 수 있다.
FruitBox.printFruitBox("과일박스");
위의 예시에서 보듯이 제네릭 메서드는 static을 붙일 수 있다.
제네릭 메서드는 독립적으로 운용되기 때문이다.
⚠️ 제네릭 메서드는 static 가능!!
제네릭 클래스에 올 수 있는 참조자료형을 extends
를 통해 제한할 수 있다.
이때 extends
는 클래스 상속의 뜻이 아닌 해당 참조 자료형으로 한정한다는 뜻으로 뒤에 일반 클래스, 추상클래스, 인터페이스 모두 올 수 있다.
➕ 제네릭을 쓰는 이유는 참조 자료형을 자유롭게 하기 위한것으로 당연히 한 클래스로만 한정하면 제네릭을 쓰는 이유가 없을 것이다.
따라서 제네릭의 가능한 찹조 자료형을 하위클래스를 여러개 가진 상위클래스로 한정시키거나, 인터페이스를 구현한 클래스만 가능하게 한다.
interface Readable {
}
// 인터페이스를 구현하는 클래스
public class Student implements Readable {
}
// 인터페이스를 Readable를 구현한 클래스만 제네릭 가능
public class School <T extends Readable> {
}
&
연산자를 사용할 수 있다. &
연산자로 묶이는 참조자료형은 인터페이스만 가능하다.⚠️ 한 클래스를 동시에 상속한 클래스는 존재하지 않는다.