package ex_generic;
public class Ex1 {
public static void main(String[] args) {
/*
* 사용자 정의 클래스에 대한 제네릭 타입 적용
*
*/
NormalIntegerClass nic = new NormalIntegerClass();
nic.data = 10; // 정수 저장 가능
// nic.data = 3.14; // 실수 저장 불가능
// nic.data = "홍길동"; // 문자열 저장 불가능
// => 여러 데이터타입 데이터를 모두 저장하려면 최소한 Object 타입 변수 선언해야함
NormalObjectClass noc = new NormalObjectClass();
noc.data = 10;
noc.data = 3.14;
noc.data = "홍길동";
// => Object 타입 변수는 모든 데이터타입 저장 가능
// => 단, 객체 내의 데이터를 꺼내서 사용할 때 타입 판별이 필수!
// System.out.println(noc.data + 10); // 오류 발생!
if (noc.data instanceof Integer) {
System.out.println((int) noc.data + 10);
}
// -------------------------------------------------------------------
// 제네릭을 사용한 클래스의 인스턴스 생성
// => 제네릭 타입 지정 시 반드시 클래스 타입(참조형)으로 명시!
// 1. 제네릭 타입 T를 Integer 타입으로 지정
GenericClass<Integer> gc = new GenericClass<Integer>();
// => GenericClass 클래스 내의 모든 T 타입이 Integer 타입으로 변경됨
gc.setData(10);
// gc.setData("홍길동"); // 오류 발생! Integer 타입만 전달 가능!
System.out.println(gc.getData()); // 리턴타입이 Integer 타입으로 바뀌어 있음
int num = gc.getData();
System.out.println(num);
// 2. 제네릭 타입 T를 Double 타입으로 지정
GenericClass<Double> gc2 = new GenericClass<Double>();
// => GenricClass 클래스 내의 모든 T 타입이 Double 타입으로 변경됨
gc2.setData(3.14);
// gc2.setData("홍길동"); // 오류 발생!
double dNum = gc2.getData();
System.out.println(dNum);
System.out.println("==================================================");
// 제네릭타입 T를 Person 타입으로 지정 인스턴스 생성(GenericClass)
// Setter 호출하여 Person 객체 전달하고(각자 이름, 나이)
// Getter 호출하여 Person 객체 내의 데이터를 출력(ex. 박장범, 20)
GenericClass<Person> gc3 = new GenericClass<Person>();
// Person p = new Person("박장범", 20);
// gc3.setData(p);
gc3.setData(new Person("박장범", 20));
// gc3.setData("박장범"); // 오류 발생!
// Person p = gc3.getData();
// System.out.println(p.getName() + ", " + p.getAge());
System.out.println(gc3.getData().getName() + ", " + gc3.getData().getAge());
System.out.println("==========================================");
System.out.println(gc3.getData()); // toString() 메서드 생략!
}
}
/*
* 제네릭을 사용한 클래스 정의
* - 클래스 정의 시점에서 클래스명 뒤에 <> 기호를 쓰고, 기호 사이에 '가상의 데이터타입' 명시
* => 보통 1글자 영문자 사용(주로 E(Element), T(Type) 등을 사용)
* => 가상의 데이터타입이므로 실제 데이터타입으로 사용은 불가능하나 제네릭 타입에서 '임시'로 설정하여 관리함
* - 지정된 가상의 자료형은 클래스 내부에서 실제 데이터타입 명시하는 부분에 대체가 가능함
*
*/
class GenericClass<T> {
// 제네릭타입 T 지정 시 클래스 내의 데이터타입 부분을 T로 지정하여 임시 데이터타입으로 클래스 정의 가능
// (실제 사용 가능한 데이터타입은 아니다!)
// => 차후, 객체 생성 시점에서 제네릭타입에 대한 실제 데이터타입을 명시할 경우
// 현재 클래스 내의 제네릭타입(T) 부분이 지정된 데이터타입으로 대체됨
// => 실제 인스턴스 생성 시점에서 어떤한 데이터타입으로도 변경이 가능!
private T data;
// 멤버변수의 데이터타입이 제네릭타입 T이므로 멤버변수에 접근하는 Getter/Setter 등의 데이터타입도
// 제네릭타입 T로 지정됨
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
// Person 클래스 정의
// 멤버변수 : 이름, 나이 => 접근제한자 private
// 생성자 정의 => 멤버변수의 데이터를 전달받아 초기화
// 멤버변수에 대한 Getter / Setter
class Person {
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return name + ", " + age;
}
}
// 제네릭을 적용하지 않는 일반 클래스 정의
// 1) 사용할 데이터타입을 특정 타입으로 관리하는 일반 클래스
class NormalIntegerClass {
int data; // data 변수에는 정수형 데이터만 저장이 가능함
}
// 2) 사용할 데이타입을 Object 타입으로 관리하는 일반 클래스
class NormalObjectClass {
Object data; // data 변수에는 모든 데이터타입 데이터 저장 가능
}
package ex_generic;
public class Ex2 {
public static void main(String[] args) {
/*
* 제네릭 타입 사용 시 주의사항
* 1. static 멤버 내에서 제네릭 타입 파라미터 사용 불가
* => 제네릭 타입은 인스턴스 생성 시점에서 실제 데이터타입으로 변환되는데
* static 멤버는 인스턴스 생성 시점보다 먼저 로딩(클래스 로딩 시점)되므로
* 데이터타입이 지정되지 않은 상태이기 때문에 사용이 불가능함!
* 2. new 연산자 사용 시 제네릭 타입 파라미터 사용 불가
* 3. instanceof 연산자 사용 시 제네릭 타입 파라미터 사용 불가
*
*/
}
}
class GenericClass2<T> {
private T data;
// private static T staticMember; // 오류 발생! static 멤버변수에 제네릭타입 파라미터 사용 불가!
// => 인스턴스 생성 시점보다 먼저 메모리에 로딩되므로 타입 변경 불가능
// public static void staticMethod(T data) {} // 오류 발생!
// T instance = new T(); // 인스턴스 생성 시 제네릭타입 파라미터로 생성자 호출 불가!
// => 컴파일 시점에서 생성자 타입이 확인 불가능하므로 사용할 수 없다!
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public void compare() {
Object o = new Object();
// if (o instanceof T) {} // instanceof 연산자에 제네릭타입 파라미터 사용 불가!
// => 컴파일 시점에서 T의 데이터타입 확인이 불가능하므로 true, false를
// 미리 판별할 수 없으며, 형변환 등의 수행이 불가능!
}
}
package ex_generic;
public class Ex3 {
public static void main(String[] args) {
/*
* 제네릭 타입의 상속과 구현
*
*/
GenericClass3<Integer> gc; // Integer는 Number의 하위타입이므로 지정 가능!
// GenericClass3<String> gc2; // Number 계열이 아니므로 지정 불가!
// GenericClass3<Object> gc3; // Number 의 상위 타입이므로 지정 불가!
}
}
class Class1<P> {}
interface Interface1<Q> {}
// 부모 타입에 제네릭타입이 지정되 있을 경우
// 서브클래스에서 상속받을 때 부모의 타입 파라미터를 서브클래스 타입파라미터로 명시!
class SubClass<P, Q, R> extends Class1<P> implements Interface1<Q> {
// => Class1<P>, Interface1<Q>를 상속받으로면 최소한 SubClass 뒤에 P와 Q를 명시 필수!
// 또한, 서브클래스 자신만의 제네릭타입도 추가 가능!
P var1; // 슈퍼클래스 Class1의 타입 P
Q var2; // 슈퍼클래스 Interface1의 타입 Q
R var3; // 자신의 타입 R
}
/*
* 제네릭 타입에 대한 사용 가능한 파라미터 타입 제한
* < 기본 문법 >
* 1. 파라미터에 대한 서브클래스 타입으로 제한하는 경우
* class 클래스명<타입파라미터 extends 클래스타입> {}
* => 타입파라미터(E 또는 T 등)는 extends 뒤의 클래스타입이거나 하위 타입만 지정 가능
*
*/
//class GenericClass3<E> {} // 타입 파라미터 E는 어떤 타입으로 변경 가능
class GenericClass3<E extends Number> {}
// => Number 타입 또는 Number 클래스 하위 타입(Integer, Double 등)으로만 변환 가능