You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
publicUseBoxTest {
privatestaticvoiduseGenericBox() {
GenericBoxbox = newGenericBox(); // raw 타입 객체 생성은 지양
}
}
publicUseBoxTest {
sprivatestaticvoiduseGenericBox() {
GenericBox<String> box = newGenericBox(); // String 으로 제네릭 타입 명시
}
}
헷갈리는 사용법
publicvoidconfusing() {
GenericBox<Animal> pbox = newGenericBox<>();
pbox.setSome(newAnimal());
pbox.setSome(newDog()); // Dog는 Animal을 상속받으므로 문제 없음GenericBox<Dog> dbox = newGenericBox<>();
pbox = dbox; // GenericBox 자체에는 상속관계가 없음. 사용하지 못함
}
// 예시 코드// 만약 아래 코드가 Collection<? extends E>가 아니라 <E> 라면 상속 관계 없이 들어간 값만 사용할 수 있게 됨publicinterface<E> extendsCollection<E> {
booleanaddAll(intindex, Collection<? extendsE> c);
}
주의사항
타입 파라미터는 객체를 생성하면서 전달됨
타입의 결정 시점은 객체 생성 시점이므로 static 멤버에서는 사용 불가
Generic은 컴파일 타임에 지정한 타입으로 존재. 컴파일러가 이미 타입을 체크했기 때문에 런타임에는 자유롭게 사용
런타임에는 타입 정보 삭제(단순 Object로 관리)
즉, 런타임에는 Generic 타입이 존재하지 않고 컴파일 타임에 지정된 타입으로만 존재
그렇기 때문에 런타임에 타입 안정성을 확보하기 위해 사용하는 타입을 지정해야하고 raw 타입을 사용하면 안됨
런타임에 동작하는 new, instanceof 키워드 사용 불가
Generic을 이용한 배열 생성 불가
배열은 runtime에 객체의 정보를 유지하고 동일한 타입의 객체만 처리함
만약 T[]이 된다고 가정했을 때는 runtime에 Object[]로 변경됨
publicvoidcantUseGeneric() {
//I i = new I();//Cannot instantiate the type IGenericBox<SpiderMan> obj = newGenericBox<>();
// compile error : Type Object cannot be safely cast to GenericBox<String>//if(obj instanceof GenericBox<String>) { }if (objinstanceofGenericBoxgb) { // raw 타입 사용gb.setSome("Hello"); // 타입에 안전하지 않음System.out.println("맞지만 타입에 안전하지 않음: " + gb.getSome());
}
if (objinstanceofGenericBox<?> gb) { // 와일드카드를 썼을 때는 무언가를 세팅할 수 없음// gb.setSome("Hello"); // compile errorSystem.out.println("이것이 최선: 뭐든 담기는 GenericBox");
}
}
// Generic 타입으로는 배열 생성 불가 publicvoidgenericArray() {
// GenericBox<String> [] boxes2 = new GenericBox<>[3]; // compile errorGenericBox<Person>[] boxes3 = (GenericBox<Person>[]) newGenericBox[3];
boxes3[0] = newGenericBox<Person>();
boxes3[1] = (GenericBox) newGenericBox<String>("Hello");
// 위의 경우 값이 담기는 문제가 발생함 -> 배열에서는 제네릭을 사용하면 안됨
}
Generic Method
파라미터와 리턴타입으로 type parameter를 갖는 메서드
메서드 리턴 타입 앞에 타입 파라미터 변수 선언
메서드 호출 시점에 타입이 결정됨
publicclassTypeParameterMethodTest<T> {
Tsome; // 객제 생성 시점에 T 결정publicTypeParameterMethodTest(Tsome) {
this.some = some;
}
public <P> voidmethod1(Pp) { // 메서드 호출 시 타입 결정System.out.printf("클래스 레벨의 T: %s%n", some.getClass().getSimpleName());
System.out.printf("파라미터 레벨의 P: %s%n", p.getClass().getSimpleName());
}
public <P> Pmethod2(Pp) {
System.out.printf("클래스 레벨의 T: %s%n", some.getClass().getSimpleName());
System.out.printf("파라미터 레벨의 P: %s%n", p.getClass().getSimpleName());
returnp;
}
publicstaticvoidmain(String[] args) {
// 객체 생성 시점 - 클래스에 선언된 타입 파라미터 T의 타입 결정TypeParameterMethodTest<String> tpmt = newTypeParameterMethodTest<>("Hello");
// 메서드 호출 시점 - 메서드에 선언된 타입 파라미터 P의 타입 결정tpmt.<Long>method1(20L); // 명시적으로 타입 결정tpmt.method2(10); // 묵시적으로 타입 결정
}
}
Bounded Type Parameter (한정형 형인자)
필요에 따라 구체적인 타입 제한 필요
계산기 프로그램 시 Number 이하의 타입(Byte, Short, Integer...) 로만 제한
type parameter 선언 뒤 extends 와 함께 상위 타입 명시
classNumberBox<TextendsNumber> {
publicvoidaddAll(T... ts) { // T... 은 내부적으로 배열이므로 해당 코드 작성 Xdoublesum = 0;
for(Tt : ts) {
sum += t.doubleValue();
}
}
}
privatestaticvoiduseNumberBox() {
NumberBox<Integer> box = newNumberBox<>();
// 아래 코드의 경우 값이 들어가기 때문에 문제가 발생할 수 있음// box.addAll((Integer[]) new Object[] {11, 2, "Hello"});
}
Wildcard Type
실제 type 파라미터가 무엇인지 모르거나 신경쓰지 않을 경우
와일드 카드도 제네릭의 일종으로 유연성을 위한 키워드. 와일드 카드도 마찬가지로 런타임시 타입이 고정됨
비 한정형 와일드 카드 자료형 (unbounded wildcard type)
Generic type<?> : 타입에 제한이 없음
한정형 와일드 카드 자료형 (bounded wildcard type)
Generic type<? extends T> : T 또는 T를 상속받은 타입들만 사용 가능
Generic type<? super T> : T 또는 T의 조상 타입만 사용 가능
// boxPerson만 사용 가능voidnotUseWildCardType(GenericBox<Person> boxPerson) {}
PECS
와일드 카드를 사용한 방법을 PECS 로 부름
와일드 카드 자료형을 파라미터로 하면 위와 같이 자료형을 가져올 수 있으나 블록 안에서 값을 지정하는 것은 다른 문제임
PE (Producer Extends) : 제네릭 타입이 데이터를 생산하여 외부로 제공하는 역할
Generic이 제공하니 코드에서 Person으로 사용 가능
CS (Consumer Super) : 제네릭 타입이 데이터를 쓰는 역할(추가, 수정)
Generic이 소비하니 코드에서 Person 자식은 저장 가능
// 들어오는 값은 Person을 상속받은 Person 하위의 클래스가 들어올 수 있음 -> 들어왔을 경우 런타임 시 해당 타입으로 지정이됨 -> 그런데 Peson 하위인 SpiderMan이 들어왔다면 Person을 넣을 수 없음 -> 마찬가지로 그 하위에 클래스가 계속 생길 수 있으므로 어떤 값도 넣을 수 없음// 들어올 수 있는 최상위의 객체는 Person 이므로 getSome()시 Person 타입으로 반환됨publicvoiduseWildCardType2(GenericBox<? extendsPerson> boxExtendsPerson) { // GenericBox<?>도 마찬가지로 어떤 값도 박싱해서 들어오지만 값을 넣을 수는 없음// null을 제외한 어떤 값도 넣을 수 없음// boxExtendsPerson.setSome(new Person("사람"));// boxExtendsPerson.setSome(new SpiderMan("피터파커"));Personperson = boxExtendsPerson.getSome();
}
// 들어오는 값은 Person 상위의 클래스만 들어올 수 있음 -> 들어왔을 경우 가장 좁은 객체는 Person 까지의 객체가 들어 올 수 있음 -> 런 타임 시 최대 Object일 수 이며 최소 Person 이므로 Person과 Person을 상속받은 객체를 값에 넣을 수 있음// Person 최대 상위 객체는 Object 이므로 getSome() 메서드 호출 시 Object 타입으로 반환됨publicvoiduseWildCardType3(GenericBox<? superPerson> boxSuperPerson) {
// 파라미터로 Person의 보다 상위 값만 들어오고 넣을 수 있는 값은 이를 상속 받는 객체만 넣을 수 있음boxSuperPerson.setSome(newPerson("사람"));
boxSuperPerson.setSome(newSpiderMan());
boxSuperPerson.getSome();
}
// 사용 예제publicclassCollections {
// src는 공급자, dest는 소비자로 동작publicstatic <T> voidcopy(List<? superT> dest, List<? extendsT> src) {...}
}
와일드 카드 사용 예시 코드2
// Number의 하위 타입을 가질 수 있는 두 개의 GenericBox를 파라미터로 받고 세번째 파라미터는 원하는 결과의 타입으로 I or D이다.// 파라미터에 따라 intValue, doubleValue의 합을 반환하는데 이외의 타입이 오면 '타입오류'라고 반환한다.publicGenericBox<? superNumber> addAll(GenericBox<? extendsNumber> gb1,
GenericBox<? extendsNumber> gb2, chartype) {
Numbernum1 = gb1.getSome();
Numbernum2 = gb2.getSome();
returnswitch (type) {
case'I' -> newGenericBox<Number>(num1.intValue() + num2.intValue());
case'D' -> newGenericBox<Number>(num1.doubleValue() + num2.doubleValue());
default -> newGenericBox<Object>("타입 오류");
};
}
추가 질문 : 위의 코드에서 <? super Number>라면 최상위 객체는 Object이고 반환 시 해당 객체를 받는 인스턴스는 Number 일지 Object인지 모르는데 해당 제네릭을 사용하는 이유는?
제네릭 타입을 사용할 때는 타입의 안전성과 유연성을 모두 고려해야하므로
타입 안전성 (Type Safety)
타입 안전성은 코드가 컴파일 타임에 타입 오류를 방지하도록 하는 것을 의미
는 Number의 상위 타입을 나타내므로 Number를 상속 받는 모든 타입을 안전하게 추가할 수 있음
Object를 사용하면 Number 타입만이 아닌 모든 타입의 객체를 추가할 수 있어 타입 안전성이 떨어짐
원하지 않는 타입의 객체가 추가되는 것을 막기 위한 장치
유연성 (Flexibility)
유연성은 코드가 다양한 타입을 처리할 수 있는 능력을 의미. 특정한 타입에 고정되지 않고 여러 타입을 다룰 수 있게 하여 재사용성과 범용성을 높임
제네릭 타입을 사용하면 특정 타입의 객체들만 다루는 메서드로 제한할 수 있음
addAll() 메서드가 Number 또는 그 하위 타입만을 다루도록 제한하고 싶을 때 사용
Object를 사용하면 타입의 유연성이 없어지고 모든 타입의 객체를 허용하게 되어 예상치 못한 결과를 초래할 수 있음