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

+ Recent posts