728x90
반응형
- Object 타입을 파라미터로 갖는 경우 모든 데이터타입을 전달받을 수 있고 모든 객체를 전달할 수 있기 때문에 데이터 저장 시 편리함
- 그러나, 객체를 꺼내서 사용해야할 경우 형변환이 필요하며 잘못된 변환 수행 시 ClassCastException 이 발생할 수도 있다
class Toy {
String toyName;
}
class Icecream {
String icecreamName;
}
class NormalBox {
Object item;
public NormalBox() {}
public NormalBox(Object item) {
this.item = item;
}
public Object getItem() {
return item;
}
public void setItem(Object item) {
this.item = item;
}
}
NormalBox box = new NormalBox();
box.setItem(new Toy());
Object item = box.getItem();
// Toy toy = (Toy) item; // 형변환을 통해 Toy 타입으로 변환
// => 만약, Toy 가 아닌 다른 객체일 경우 문제 발생하므로
// 형변환 전 변환 가능 여부를 판별해야한다!
// => instanceof 연산자를 통해 변환 가능 여부 판별
if(item instanceof Toy) { // item 객체를 Toy 타입으로 변환 가능한가?
Toy toy = (Toy) item;
} else if(item instanceof Icecream) { // Icecream 타입으로 변환 가능한가?
Icecream icecream = (Icecream)item;
} else {
System.out.println("변환 불가능한 객체!");
}
제네릭 Generic
- 컴파일 시점에 사용 가능한 객체의 타입을 체크하는 것
- 클래스 또는 인터페이스 정의할 때 해당 클래스 또는 인터페이스에서 사용하기 위한 어떤 타입을 미리 지정하는 것이 아니라 제네릭 타입으로 선언한 후 실제 객체 사용을 위해 인스턴스를 생성하는 시점에서 사용할 타입을 결정하는 것
- 자바에서 제공하는 컬렉션 프레임워크 등의 클래스 및 인터페이스에는 제네릭이 적용된 경우가 많으며, 이 클래스 등의 인스턴스 생성 시 실제 사용할 데이터타입을 지정해줘야 한다
주의사항!
1. 제네릭 타입은 static 멤버에 지정 불가
인스턴스 생성 시점에 타입이 결정되어야 하는데 static 멤버는 인스 턴스 생성 시점보다 먼저 로딩되기 때문
2. private static T some2;제네릭타입은 static 멤버에 사용 불가
제네릭 타입을 적용한 클래스 정의
- 클래스 또는 인터페이스 선언 시 이름 뒤에 <> 기호를 쓰고 해당 기호 사이에 알파벳 대문자 한 글자를 지정(제네릭 타입 지정) (클래스 내에서 사용하게 될 임시 데이터타입 이름 지정) 주로 대문자 T(Type) 또는 E(Element) 를 사용
- 지정된 임시 데이터타입은 객체 생성 시점에서 실제 데이터타입으로 변경됨 (선언 시점에서는 실제 존재하지 않는 데이터타입)
class Toy {
String toyName;
}
class Icecream {
String icecreamName;
}
class NormalBox {
// Toy, Icecream 등 다양한 타입을 저장하기 위해 Object 타입 변수를 선언
private Object some;
public Object getSome() {
return some;
}
public void setSome(Object some) {
this.some = some;
}
}
제네릭 클래스 정의
class GenericBox<T> { // 임시 데이터타입으로 T 를 지정
private T some; // 변수 some 의 데이터타입은 무엇으로도 변할 수 있음
// private static T some2; // 제네릭타입은 static 멤버에 사용 불가
// T obj = new T(); // 제네릭타입으로 인스턴스 생성 불가
public T getSome() {
return some;
}
public void setSome(T some) {
this.some = some;
}
public void method() {
// 제네릭타입은 instanceof 연산자의 타입 파라미터로 사용 불가
Object o = new Object();
// if(o instanceof T) {}
}
}
// 제네릭 타입이 적용된 GenericBox 클래스의 인스턴스 생성
// => 주의! 제네릭 타입에 실제 데이터타입 지정 시
// 반드시 참조 데이터타입을 사용해야한다!
// GenericBox<int> gb; // 기본데이터타입을 제네릭 타입에 지정 불가!
// GenericBox<Integer> gb; // Wrapper 클래스 타입으로 사용 가능!
// GenericBox 인스턴스의 제네릭 타입을 Toy 타입으로 지정하기 위해
// 클래스명 뒤에 <Toy> 타입을 지정하고
// 생성자 호출 코드의 생성자명 뒤에도 <Toy> 타입을 지정
// => 클래스 내의 임시 데이터타입(T)이 모두 Toy 타입으로 바뀜
GenericBox<Toy> toyBox = new GenericBox<Toy>();
// 파라미터 타입이 Toy 타입으로 변경되었으므로
// 다른 데이터타입은 사용 불가능하게 바뀐다!
// => 즉, 데이터를 저장하는 시점에서 미리 타입 검사가 수행됨
// toyBox.setSome(new Icecream()); // Toy 타입 객체만 전달 가능
toyBox.setSome(new Toy()); // Toy 타입 전달 가능
// 저장된 객체를 꺼내올 때 별도의 변환 없이
// 원본 그대로의 타입을 사용 가능(문제 발생 소지 없음)
Toy toy = toyBox.getSome();
// 만약, 다른 데이터타입의 객체를 저장해야할 경우
// 새로운 객체를 생성하는 시점에서 해당 데이터타입을 지정하면 됨
GenericBox<Icecream> icecreamBox = new GenericBox<Icecream>();
// 제네릭타입 T 가 Icecream 타입으로 변경됨
icecreamBox.setSome(new Icecream());
Icecream icecream = icecreamBox.getSome();
// =====================================================
// 만약, Object 타입을 사용해야하는 경우
// GenericBox<Object> objectBox = new GenericBox<Object>();
// Object 타입의 경우 제네릭타입 지정을 생략해도 자동으로 적용됨
GenericBox objectBox = new GenericBox();
objectBox.setSome(new Icecream());
objectBox.setSome(new Toy());
제네릭 타입을 적용한 클래스 상속
- 부모로부터 상속받을 때 제네릭타입도 상속받아 표현하려면 서브클래스에도 부모의 제네릭 타입을 반드시 명시해야한다! 단, 서브클래스만의 제네릭 타입을 추가하는 것은 상관없음
interface Useable<D> {}
class SuperClass<P> {
protected P product;
}
class SubClass<P, D, M> extends SuperClass<P> implements Useable<D> {
}
제네릭 타입의 파라미터 타입 제한
- 제네릭 타입으로 사용된 타입 파라미터에는 Object 타입을 비롯한 모든 타입을 지정 가능한데 만약, 특정 타입만 지정하도록 강제하려면 extends 키워드를 사용하여 타입 파라미터의 강제성을 부여 가능
- 인터페이스를 타입 제한용으로 지정할 때에도 extends 사용
기본 문법
class 클래스명<제네릭타입명 extends 클래스타입> {}
인스턴스 생성 시점에서 지정 가능한 제네릭타입은 Number 클래스와 Number 클래스의 서브클래스 타입만 지정
ex. Integer, Double 등
public class Ex2 {
public static void main(String[] args) {
// NumberBox 클래스의 제네릭타입은 Number 타입까지만 사용 가능하므로
// String 등 Number 타입이 아닌 타입 지정 불가능
// NumberBox<String> box = new NumberBox<String>(); // 오류 발생
// NumberBox<Object> box = new NumberBox<Object>(); // 오류 발생
NumberBox<Integer> box = new NumberBox<Integer>();
}
}
class NumberBox<T extends Number> {}
// => 제네릭타입으로 지정 가능한 타입은 Number 와 그 서브클래스 타입만 가능
제네릭 타입이 적용된 메서드
- 특정 메서드 내에서만 사용 가능한 타입 지정
- 메서드 리턴타입 앞에 제네릭 타입을 지정
- 리턴타입 또는 파라미터 타입으로 사용 가능
기본 문법
[제어자] <제네릭타입> 리턴타입 메서드명([파라미터...]) {}
제네릭타입 지정 방법
메서드 호출 시 메서드명 앞에 제네릭 타입을 명시
참조변수명.<데이터타입>메서드명([파라미터...]);
class GenericMethod {
public void normalMethod() {
System.out.println("일반 메서드");
}
public <P> void genericMethod1(P p) {
// 메서드 파라미터로 전달받을 데이터타입을 제네릭타입 P 로 지정
// => 리턴타입 앞에 제네릭타입 <P> 지정 필수!
System.out.println("파라미터 타입 : " + p.getClass().getName());
System.out.println("전달받은 데이터 : " + p);
}
public <P> P genericMethod2(P p) {
// 메서드 파라미터와 리턴타입을 제네릭타입 P 로 지정
// => 리턴타입 앞에 제네릭타입 <P> 지정 필수!
System.out.println("파라미터 타입 : " + p.getClass().getName());
System.out.println("전달받은 데이터 : " + p);
return p;
}
}
GenericMethod gm = new GenericMethod();
gm.<String>genericMethod1("홍길동");
gm.<Integer>genericMethod1(10);
int num = gm.<Integer>genericMethod2(20);
System.out.println("리턴받은 데이터 : " + num);
String str = gm.<String>genericMethod2("홍길동");
System.out.println("리턴받은 데이터 : " + str);
제네릭 메서드에서의 타입 파라미터 제한
// 제네릭 메서드에서의 타입 파라미터 제한
WildcardGenericType wgt = new WildcardGenericType();
// 메서드 내의 파라미터 타입에 제네릭 타입을 ? 로 지정했을 경우
// extends Object 와 동일하게 취급되므로 모든 타입을 제네릭 지정 가능
wgt.method1(new GenericBox3<Object>());
wgt.method1(new GenericBox3<Person>());
wgt.method1(new GenericBox3<SpiderMan>());
// wgt.method2(new GenericBox3<Object>()); // 오류 발생
// Person 또는 Person 클래스의 서브클래스 타입(SpiderMan)만 가능
wgt.method2(new GenericBox3<Person>());
wgt.method2(new GenericBox3<SpiderMan>());
wgt.method3(new GenericBox3<Object>());
wgt.method3(new GenericBox3<Person>());
// wgt.method3(new GenericBox3<SpiderMan>()); // 오류 발생
// Person 또는 Person 클래스의 슈퍼클래스 타입(Object)만 가능
System.out.println("=========================");
// 제네릭 타입을 메서드 파라미터에서 제한하는 예
ArrayList<Number> list = new ArrayList<Number>();
// list.addAll(c);
// public boolean addAll(Collection<? extends Number> c)
// => addAll() 메서드 파라미터로 Collection 객체를 전달 가능하나
// 이 때, Collection 객체의 제네릭 타입은 Number 타입 또는
// Number 클래스의 서브클래스 타입만 사용 가능하다!
728x90
반응형
'프로그래밍 언어 > JAVA' 카테고리의 다른 글
JUnit 4 테스트에서 특정 예외가 발생할 때 (0) | 2021.06.02 |
---|---|
한 줄로 ArrayList 초기화 (0) | 2021.06.02 |
[JAVA] 자바의 스택 & 큐 (Stack & Queue) (0) | 2021.01.19 |
[JAVA] 자바 컬렉션 프레임워크 - Map 편 (0) | 2021.01.19 |
[JAVA] 자바 컬렉션 프레임워크 - List 편 (0) | 2021.01.19 |