public class 클래스명<A> {}
public interface 인터페이스명<A>{}❓자바에서 제네릭을 사용하는 이유와 제네릭 타입의 예시를 들어주세요.
메서드의 선언부에 제네릭 타입이 선언된 메서드이며, 제네릭의 위치는 반환 타입 앞이다.
예)
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
❓제네릭 클래스와 제네릭 인터페이스에 대해 설명해주세요. 또한, 제네릭 타입의 원시 타입(raw type)이란 무엇이고 어떤 문제점이 있을까요?
제네릭 클래스는 매개변수나 함수를 매개변수로 일반화 하여 T타입에 모든유형을 추가할 수 있다.
예제1)
public class Home {
public void turnOnLight() {
System.out.println("전등을 켭니다.");
}
}
public class Car {
public void run() {
System.out.println("자동차가 달립니다.");
}
}
public class HomeAgency implements Rentable<Home> {
@Override
public Home rent() {
return new Home();
}
}
public class CarAgency implements Rentable<Car> {
@Override
public Car rent() {
return new Car();
}
}
public class GenericExample {
public static void main(String[] args) {
HomeAgency homeAgency = new HomeAgency();
Home home = homeAgency.rent();
home.turnOnLight();
CarAgency carAgency = new CarAgency();
Car car = carAgency.rent();
car.run();
}
}
예제2)
public class GenericsTypeOld {
private Object t;
public Object get() {
return t;
}
public void set(Object t) {
this.t = t;
}
public static void main(String[] args) {
GenericsTypeOld type = new GenericsTypeOld();
type.set("test str");
String str = (String) type.get();
}
}
위의 코드는 매번 type casting을 해줘야하며, 만약 맞지않는 casting을 할 경우 ClassCastException이 발생할 수 있다.
public class GenericsTypeOld<T> {
private T t;
public T get() {
return this.t;
}
public void set(T t1) {
this.t = t1;
}
public static void main(String[] args) {
GenericsTypeOld<String> type = new GenericsTypeOld<>();
type.set("test str");
GenericsTypeOld type1 = new GenericsTypeOld();//Raw use of parameterized class 'GenericsTypeOld'
type1.set("Test 2");
type1.set(10);
}
}
클래스 매개변수에 제네릭 타입을 지정하므로써 클래스 생성 시 타입을 지정하여 생성할 수 있다. 그러므로 타입 캐스팅의 불편함이 제거되었다.
반면에 타입을 지정하지 않은 경우 GenericsTypeOld type1 = new GenericsTypeOld();는 RawType(타입 변수 또는 타입 매개변수가 없는 클래스나 인터페이스를 뜻함.)으로 선언되어 Object타입으로 지정된다. set메서드로 String, int가 가능해졌다. Runtime오류를 피하기 위해서 사용할 타입의 지정이 필요하다.
package java.lang;
import java.util.*;
public interface Comparable<T> {
public int compareTo(T o);
}
public interface Fruit <T> {
public void taste(T fruit);
}
public class AnyFruit<T> implements Fruit<T>{
@Override
public void taste(T fruit) {
String fruitName = fruit.getClass().getName();
System.out.println("fruitName = " + fruitName);
}
}
public class Banana {
}
public class TestFruit {
public static void main(String[] args) {
Banana banana = new Banana();
AnyFruit<Banana> fruit1 = new AnyFruit<>();
fruit1.taste(banana);//fruitName = Banana
}
}
제네릭 클래스와 제네릭 인터페이스의 개념과 예시, 그리고 Raw Type의 문제점 등을 잘 설명했습니다. 하지만, Raw Type에 작성된 코드를 사용하는 경우와 실행할 때 발생할 수 있는 문제점을 더 자세히 설명하는 것이 좋을 것 같습니다.
❓ 제네릭 클래스에서 타입 파라미터의 상한과 하한에 대해 설명해주세요. 상한과 하한을 사용하면 어떤 이점이 있을까요?
제네릭에 사용하는 타입 파라미터를 제한하는 방법중 상한과 하한이 있다. 이는 자식 또는 구현관계에 있는 타입만 대체할 수 있다. 이를 통해 제네릭을 다형성있게 사용 할 수 있다.
타입을 제한하므로서, 타입을 받는 쪽에서 타입캐스팅에 대한 범위를 알 수 있다.

https://www.codetab.org/tutorial/java-generics/bounded-wildcards/
제한하지 않는 경우(Unbounded Wildcards)
public void display(List<? extends Pet> list) {
}
display(new ArrayList<Pet>());
display(new ArrayList<Dog>());
display(new ArrayList<Bulldog>());
display(new ArrayList<Cat>());
display(new ArrayList<Persian>());
display(new ArrayList<String>());
display(new ArrayList<Integer>());
❓ 와일드카드를 제네릭 타입 파라미터로 사용할 때, ? extends T와 ? super T의 차이점은 무엇인가요? 이 차이점을 이용하여 어떤 상황에서는 extends를 사용해야 하는지, 어떤 상황에서는 super를 사용해야 하는지 설명해주세요.
상한(Upper Bounded Wildcards) - ? extends T
T 클래스를 상속받은 클래스나 T클래스 자신 클래스를 타입 파라미터로 허용.
타입 파라미터의 범위를 상속 클래스 자체 및 상속클래스의 subtype으로 범위를 제한한다.
public void display(List<? extends Pet> list) {
}
display(new ArrayList<Pet>());
display(new ArrayList<Dog>());
display(new ArrayList<Bulldog>());
display(new ArrayList<Cat>());
display(new ArrayList<Persian>());
display(new ArrayList<String>()); //error
display(new ArrayList<Integer>()); //error
? extends T의 경우 컬렉션 요소를 읽을 경우 컴파일러가 타입 안정성을 보장할 수 있기에 Collection요소를 읽을때 사용하는것이 좋다.
public static void main(String[] args) {
List<Integer> integerList = Arrays.asList(1,2,3,4);
List<Double> doubleList = Arrays.asList(1.1, 2.1, 3.1, 4.1);
printNumberList(integerList);
printNumberList(doubleList);
}
public static void printNumberList(List<? extends Number> numbers) {
for (Number number : numbers) {
System.out.println("number : " + number);
}
}
하한(Lower Bounded Wildcards) - ? super T
T 클래스를 상위 클래스나 T클래스 자신 클래스를 타입 파라미터로 허용.
타입 파라미터의 범위를 상속받는 클래스 supertype 및 super type클래스 범위를 제한한다.
(super 키워드 : 상속받은 자식 클래스와 부모클래스에 동일한 변수명이 있을 경우 부모클래스의 변수를 선택할 경우 사용)
public void display(List<? super Buldog> list) {
}
display(new ArrayList<Pet>());
display(new ArrayList<Dog>());
display(new ArrayList<Bulldog>());
display(new ArrayList<Cat>()); //error
display(new ArrayList<Persian>()); //error
display(new ArrayList<String>()); //error
display(new ArrayList<Integer>()); //error
컬렉션에 요소를 추가할 경우 ? super T 타입을 제한하여 컴파일러가 타입 안정성을 보장 할 수 있다.
public static void main(String[] args) {
List<Number> numberList = new ArrayList<>();
List<Object> objectList = new ArrayList<>();
printNumberList(numberList);
printNumberList(objectList);
}
public static void printNumberList(List<? super Integer> numbers) {
numbers.add(44);
numbers.add(new Integer(10));
}
제네릭이 자바에 추가된 이유는 무엇인가요?
제네릭 타입의 이름은 T나 E 처럼 하나의 캐릭터로만 선언되어야 하나요?
메소드에서 제네릭 타입을 명시적으로 지정하기 애매할 경우에는 < > 안에 어떤 기호를 넣어 주어야 하나요?
메소드에서 제네릭 타입을 명시적으로 지정하기에는 애매하지만, 어떤 클래스의 상속을 받은 특정 타입만 가능하다는 것은 나타내려면 < > 안에 어떤 기호를 넣어 주어야 하나요?
제네릭 선언시 wildcard라는 것을 선언했을 때 어떤 제약사항이 있나요?
메소드를 제네릭하게 선언하려면 리턴타입 앞에 어떤 것을 추가해 주면 되나요?