본문 바로가기

Java/문법

16 -2 장 제네릭의 문법

강한 타입 체크(strong type checking)

 -제네릭을 사용하면 모든 타입의 상품을 저장할 수 있으면서도 잘못된 캐스팅을 할 때 문법 오류를 발생시켜 잘못된 캐스팅으로 발생할 수 있는 문제를 사전에 예방

 

1. 제네릭 클래스와 제네릭 인터페이스 정의하기 

 

- 클래스명 다음에 <제네릭 타입 변수명> 삽입

- 제네릭 클래스

//제네릭 타입 변수명이 1개 일때 
접근 지정자 class 클래스명 <T> {
	//타입 T를 사용한 코드 
 }
 
 //제네릭 타입 변수명이 2개 일때
 접근 지정자 class 클래스명 <K,T> {
 	//타입 K,T를 사용한 코드 
 }
public class MyClass<T> {
	private T t;
    public T get(){
    	return t;
    }
    public void set(T t) {
    	this.t=t;
    }
}

- 제네릭 인터페이스

//제네릭 타입 변수명이 1개 일떄
접근 지정자 interface 클래스명 <T> {
	//타입 T를 사용한 코드 
}

//제네릭 타입 변수명이 2개 일때
접근 지정자 interface 클래스명 <T,V> {
	//타입 K,V를 사용한 코드 
}
public interface MyInterface<K,V> {
	public abstract void setKey(K k);
    public abstract void setValue(V v);
    public abstract K getKey();
    public abstract V getValue();
}

- 제네릭 타입 변수의 관례적 표기 및 의미

제네릭 타입 변수 의미
T 타입(Type)
K 키(Key)
V 값(Value)
N 숫자(Number)
E 원소(Element)

 

 

2. 제네릭 클래스의 객체 생성

 

 - 객체를 생성할 떄 제네릭 타입 변수에 실제 타입을 대입

클래스명<실제 제네릭 타입> 참조 변수명 = new 클래스명<실제 제네릭타입>();
클래스명<실제 제네릭 타입> 참조 변수명 = new 클래스명<>();

 

- 제네릭 타입 변수 1개를 가진 제네릭 클래스의 선언 및 활용

 ->제네릭 클래스 MyClass<T>를 정의한 후 객체를 생성해 사용

 

 ->제네릭 클래스는 클래스를 정의하는 시점에 타입을 지정하는 것이 아니라 객체를 생성하는 시점에 타입을 지정하기 때문에 하나의 제네릭 클래스로 다양한 타입의 객체를 저장 및 관리할 수 있는 객체 생성할 수 있음

 (mc1 객체 - string 타입을 저장 및 관리하는 객체

  mc2 객체 - integer 타입을 저장 및 관리하는 객체)

package GenericArguement;

class MyClass<T>{
	private T t;
	public T get() { //getter 메서드	
		return t;
	}
	public void set(T t) { //setter 메서드 
		this.t=t;
	}
}

public class GenericArgument {
	public static void main(String[] args) {
		MyClass<String>mc1 = new MyClass<String>(); //String 타입을 저장하거나 꺼내 올 수 있는 객체로 지정
		mc1.set("안녕");
		System.out.println(mc1.get());
		
		MyClass<Integer>mc2 = new MyClass<>(); //Integer 타입을 저장하거나 꺼내 올 수 있는 객체로 지정
		mc2.set(100);
		System.out.println(mc2.get());
		
		//MyClass<Integer>mc3 = new MyClass<>();
		//mc3.set("안녕"); //강한 타입 체크로 문법 오류 발생
	}
}

 

- 제네릭 타입 변수 2개를 가진 제네릭 클래스의 선언 및 활용

package GenericArguement;

class KeyValue<K,V>{
	private K key;
	private V value;
	public K getKey() {
		return key;
	}
	public V getValue() {
		return value;
	}
	public void setKey(K key) {
		this.key=key;
	}
	public void setValue(V value) {
		this.value=value;
	}
}
public class GenericArgument {
	public static void main(String[] args) {
		KeyValue<String,Integer>kv1 = new KeyValue<>(); //제네릭 타입 변수 K,V가 각각 String,Integer 타입으로 결정
		kv1.setKey("사과");
		kv1.setValue(1000);
		String key1 = kv1.getKey();
		int value1 = kv1.getValue();
		System.out.println("key:"+key1+"value:"+value1);
		
		KeyValue<Integer,String>kv2 = new KeyValue<>(); //제네릭 타입 변수 K,V가 각각 Integer, String 타입으로 결정
		kv2.setKey(404);
		kv2.setValue("Not Found(요청한 페이지를 찾을 수 없습니다.)");
		int key2 = kv2.getKey();
		String value2 = kv2.getValue();
		System.out.println("key:"+key2+"value:"+value2);
		
		KeyValue<String,Void> kv3 = new KeyValue<>(); //void : 해당 제네릭 타입 변수의 필드를 사용하지 않음
		kv3.setKey("키 값만 사용");
		String key3 = kv3.getKey();
		System.out.println("key:"+key3);
	}
}

- 제네릭의 필요성

1) 추가 클래스 생성 없이 어떤 상품도 저장 및 관리 -> object 타입으로 선언

2) setter 메서드에 잘못된 객체를 입력했을 때 바로 문법으로 체크 ->제네릭

3) getter 메서드의 리턴 타입도 다운캐스팅이 필요 없어야 함

 -> 제네릭

 

- 해결책 2. 제네릭 클래스를 사용한 다양한 객체의 저장

 -> goods1일때 제네릭 타입을 Apple타입, goods2일때 제네릭 타입을 Pencil타입으로 설정

package GenericArguement;

class Apple{}
class Pencil{}
class Goods<T>{
	private T t;
	public T get() {
		return t;
	}
	public void set(T t) {
		this.t=t;
	}
}

public class GenericArgument {
	public static void main(String[] args) {
		//1.Goods를 이용해 Apple 객체를 추가하거나 가져오기
		Goods<Apple>goods1 = new Goods<>();
		goods1.set(new Apple());
		Apple apple = goods1.get(); //다운 캐스팅 필요없음
		
		//2.Goods를 이용해 pencil 객체를 추가하거나 가져오기 
		Goods<Pencil>goods2 = new Goods<>();
		goods2.set(new Pencil());
		Pencil pencil =goods2.get();
		
		//3.잘못된 타입 선언
		Goods<Apple>goods3 = new Goods<>();
		goods3.set(new Apple());
		//Pencil pencil2 = goods3.get(); //강한타입체크로 문법 오류 발생
	}
}