package ex0728;
// import java.util.Arrays;
public class Ex11_generic {
public static void main(String[] args) {
Object[] obj = new Object[5];
// Object는 모든 클래스 객체를 참조할 수 있다. (up casting)
obj[0] = new String("서울");
obj[1] = new String("부산");
obj[2] = new Integer(30);
obj[3] = new Integer(50);
obj[4] = new String("인천");
// Arrays.sort(obj); // ClassCastException 런타임 오류
// String s = (String)obj[2]; // ClassCastException 런타임 오류
if (obj[1] instanceof String) { // obj[2] 너 String 형이니?
String s = (String) obj[1]; // 스트링 형이면 다운캐스팅하고
System.out.println(s + ":" + s.length()); // 출력하고 : 길이도 출력해
}
for (Object o : obj) {
System.out.println(o);
}
}
}
Object는 모든 클래스들의 조상이므로 (시조라고 해야하나 ㅋㅋ)
자식들은 Object클래스로 up casting이 가능하다. 지금 이 코드는 숫자나 문자열 모두 그냥 Object에 넣었다.
이럴 경우 데이터타입에 관계없이 모든 것을 넣을 수 있다.
하지만 자료형이 다 제각각이므로 Arrays.sort(obj); 과 같은 메소드를 사용하면 런타임 오류가 발생한다.
Integer 30을 강제로 캐스팅 하면 ClassCastException 이라는 런타임 오류가 발생한다. 그래서 A instanceof B 를 써서 배열A에 들어가 있는 데이터타입이 B 이면 강제로 다운캐스팅(다운캐스팅은 업캐스팅한 것만 가능)하고 출력하라고 밑에서 if문을 쓴 것이다.
package ex0728;
public class Ex12_generic {
public static void main(String[] args) {
Test2 t1 = new Test2();
// t1.set(new String("서울"));
t1.set("서울");
String s = (String)t1.get();
System.out.println(s);
// Integer i1 = (Integer) t1.get(); // ClassCastException 런타임 오류.
Test2 t2 = new Test2();
t2.set(30);
Integer i2 = (Integer)t2.get();
System.out.println(i2);
}
}
class Test2 {
private Object ob;
public void set(Object ob) {
this.ob = ob;
}
public Object get() {
return ob;
}
}
t1의 객체는 String형을 저장하고
t2의 객체는 Integer형을 저장했다.
그냥
System.out.println(t1.get());
System.out.println(t2.get());
이면 상관없지만
s, i2에 넣을 때는 s의 데이터타입인 String으로 다운캐스팅 i2의 데이터타입인 Integer로 다운캐스팅을 해야 한다.
본격적으로 Generic에 대해 알아보자.
다양한 타입의 객체를 다루는 클래스나 인터페이스에서 사용할 데이터 타입을 인스턴스를 생성할 때 결정하는 것으로 JDK 5.0부터 지원한다.
- 성능 저하를 유발하는 강제 캐스팅을 줄일 수 있다.
- 컴파일 할 때 타입 오류를 체크하므로 객체 타입의 안정성을 높인다.
- 반복적인 코드를 줄일 수 있으며, 재사용성 증가로 유지보수가 편리하다.
package ex0728;
public class Ex13_generic {
public static void main(String[] args) {
Test3<String> t1 = new Test3<String>();
t1.set("서울");
// t1.set(50); // 컴파일 오류
String s = t1.get();
System.out.println(s);
// Integer i1 = (Integer)t1.get(); // 컴파일 오류. 미연에 방지 할 수 있다!
Test3<Integer> t2 = new Test3<>(); // new 뒤에 <>에서는 자료형 생략가능. new Test3<Integer> 에서 Integer생략
t2.set(30);
Integer n = t2.get();
System.out.println(n);
/*
Test3 t3 = new Test3(); // 가능하지만 경고 발생. 제너릭은 Object로 처리.
t3.set("서울");
t3.set(50);
*/
}
}
class Test3<T> { // <> 안에는 마음대로 이름을 줄 수 있다.
private T t; // T에 자료형은 String, Integer, Long ...등이 올 수 있음. 아직 정해지지 않았다.
public void set(T t) {
this.t = t;
}
public T get() {
System.out.println(t.getClass()); // 무슨 클래스인지 볼 수 있다.
return t;
}
}
객체를 생성할때 <> 안에 어떤 데이터타입만 저장할 것인지 설정을 해서 생성할 수 있다.
Test3<String> t1 = new Test3<String>();
t1.set(50); 으로 주면 컴파일 오류가 생기기 때문에 안정적으로 코딩할 수 있다.
제네릭을 설정하지 않고 그냥
Test3 t3 = new Test3(); 으로 객체를 생성하면 경고가 발생한다. 이때 제네릭은 Object로 처리되기 때문에
t3.set("서울");
t3.set(50);
모두 넣을 수 있다. 하지만 비추천.
제네릭을 만들 때 <> 안에 마음대로 이름을 줄 수 있으나 관습적으로 대문자 알파벳 한 문자를 사용하고
E : Element
K : Key
N : Number
T : Type
V : Value
S, U, V : 2nd, 3rd, 4th types..
으로 사용한다.
마지막에 확인해보면 t1의 클래스는 <String>으로 줬기 때문에 String으로 뜨고, t2는 Integer로 준 것을 확인 할 수 있다.
package ex0728;
public class Ex14_generic {
public static void main(String[] args) {
Test4<String, Integer> ob = new Test4<>();
ob.set("자바", 100);
ob.print();
ob.set("스프링", 200);
ob.print();
}
}
class Test4<T, U> {
private T t;
private U u;
public void set(T t, U u) {
this.t = t;
this.u = u;
}
public void print() {
System.out.println("T : "+t.getClass().getName()+", "+t);
System.out.println("U : "+u.getClass().getName()+", "+u);
}
}
제네릭 클래스에 멀티 타입 파라미터를 준 경우이다. 만들때 <String, Integer>라고 했으니 T의 자료형은 String형이되고 U의 자료형은 Integer형이 된다.
package ex0728;
public class Ex15_generic {
public static void main(String[] args) {
Test5<Integer> ob = new Test5<>();
ob.set(30);
Integer i = ob.get();
System.out.println(i);
// Test5<String> ob2 = new Test5<>(); // 컴파일 오류 발생.
// Number를 상속받은 class가 아니기 때문이다.
}
}
// 제한된(한정된) 타입 파라미터(bounded type parameter)
// Number를 상속받은 클래스만 가능 (Integer, Long, Double ...)
class Test5<T extends Number> {
private T t;
public void set(T t) {
this.t=t;
}
public T get() {
return t;
}
}
제한된 타입 파라미터만을 받도록 설정한 제네릭 클래스.
T extends Number를 통해 Number를 상속받았기 때문에 그 클래스에 포함되는 것들만 파라미터로 받을 수 있다.
package ex0728;
public class Ex16_genericMethod {
public static void main(String[] args) {
Test6 t = new Test6();
t.print("자바");
t.print(20);
t.disp(30);
// t.disp("자바"); // 컴파일 오류
}
}
// generic Method
class Test6 {
public <U> void print(U u) {
System.out.println(u.getClass().getName() + ", " + u);
}
public <U extends Number> void disp(U u) {
System.out.println(u.getClass().getName() + ", " + u);
}
}
제네릭 메소드 타입 파라미터를 하나 이상 갖는 메소드를 말한다.
선언 방법은 리턴 타입 앞에 <> 기호를 추가하고 <> 기호 안에 타입 파라미터를 기술 한 후 리턴 타입과 매개 변수타입에서 타입 파라미터를 사용한다.
disp(U u) 메소드의 경우 받는 자료형을 Number를 상속받은 것들만 가능하게 설정했으므로
t.disp("자바"); 의 경우 컴파일 오류이다.
package ex0728;
public class Ex17_generic {
public static void main(String[] args) {
TestImpl7<Integer> ob1 = new TestImpl7<>();
ob1.print(10);
DemoImpl7 ob2 = new DemoImpl7();
ob2.print("자바");
}
}
// 제네릭 인터페이스
interface Test7<T> {
public void print(T t);
}
// 구현 클래스-1
class TestImpl7<T> implements Test7<T> {
@Override
public void print(T t) {
System.out.println(t);
}
}
// 구현 클래스-2
class DemoImpl7 implements Test7<String> {
@Override
public void print(String t) {
System.out.println(t);
}
}
제네릭 인터페이스를 구현할 때 위의 2가지 방법으로 구현할 수 있다.
1의 경우 자료형을 객체를 생성할 때 결정한 방법이고
2의 경우 구현 클래스에서 자료형을 결정한 방법이다.
'쌍용강북교육센터 > 7월' 카테고리의 다른 글
0729_ConsoleEx_콘솔 입력 (0) | 2021.07.29 |
---|---|
0729_Ex01~Ex05_Generic (0) | 2021.07.29 |
0728_Ex01~Ex03_exception : 예외처리 (0) | 2021.07.29 |
0727_패키지 (0) | 2021.07.27 |
0727_Ex06~Ex08_enum : 열거형 (0) | 2021.07.27 |