제네릭은 '데이터 형식에 의존하지 않고, 하나의 값이 여러 다른 데이터
타입들을 가질 수 있도록 하는 방법'이다
우리가 흔히 쓰는 ArrayList, HashMap 등을 생성할 때를 생각해보자
객체<타입> 객체명 = new 객체<타입>(); 이렇게 쓴다
ArrayList<Integet> list = new ArrayList<Integer>();
HashMap<String, String> map = new HashMap<String, String>();
이렇게 <>괄호 안에 들어가는 타입을 지정해준다.
이렇든 제네릭은 클래스 내부에서 지정하는 것이 아닌 외부에서 사용자에
의해 지정되는 것을 의미한다. 특정 타입을 미리 지정해주는 것이 아닌
필요에 의해 지정할 수 있도록 일반타입 이라는 것이다.
제네릭 클래스와 제네릭 인터페이스를 작성하는 방법은 클래스명 다음에
<제네릭 타입 변수명(들)>을 삽입하는 것이다.
==제네릭 타입 변수명이 1개 일때==
접근 지정자 class 클래스명<T>{
//타입 T를 사용한 코드
}
접근 지정자 interface 클래스명<T>{
//타입 T를 사용한 코드
}
==제네릭 타입 변수명이 2개일 때==
접근 지정자 class 클래스명<K,V>{
//타입 K, V를 사용한 코드
}
접근 지정자 interface 클래스명<K,V>{
//타입 K, V를 사용한 코드
}
제네릭 타입 변수 | 의미 |
---|---|
T | 타입(Type) |
K | 키(Key) |
V | 값(Value) |
N | 숫자(Number) |
E | 원소(Element) |
public class MyClass<T>{
private T t;
public T get(){
return t;
}
public void set(T t){
this.t = t;
}
}
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();
}
제네릭 클래스인 MyClass는 제네릭 타입 변수 1개를 갖고 있으며,
이 제네릭 타입은 필드타입, getter 메서드의 리턴 타입, setter 메서드의
입력 타입에 사용됐다.
제네릭 인터페이스인 MyInterface는 2개의 제네릭 타입 변수를 갖고 있으며
각 setter, getter 메서드의 입력과 리턴 타입으로 사용됐다.
일반 클래스의 객체 생성 과정과 비슷하다. 다만 클래스명 다음에
<실제 제네릭 타입>을 삽인한다는 점에서만 차이가 있다.
즉 객체를 생성할 때 제네릭 타입 변수에 실제 타입을 대입하는 것이다.
클래스명<실제 제네릭 타입> 참조 변수명 = new 클래스명<실제 제네릭 타입>();
클래스명<실제 제네릭 타입> 참조 변수명 = new 클래스명<>();
class MyClass<T>{
private T t;
public T get() {
return t;
}
public void set(T t) {
this.t = t;
}
}
public class Main {
public static void main(String[] args) {
MyClass<String> mc1 = new MyClass<>();
mc1.set("안녕");
MyClass<Integer> mc2 = new MyClass<>();
mc2.set(100);
}
}
제네릭 클래스는 클래스를 정의하는 시점에 타입을 지정하는 것이 아니라 객체를 생성하는 시점에 타입을 지정한다.
class KeyValue<K, V>{
private K key;
private V value;
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
}
public class Main {
public static void main(String[] args) {
KeyValue<String, Integer> kv1 = new KeyValue<>();
kv1.setKey("사과");
kv1.setValue(1000);
...
}
}
클래스 전체를 제네릭으로 선언하는 대신, 일반 클래스 내부의 특정 메서드만
제네릭으로 선언할 수 있다. 이를 '제네릭 메서드'라고 한다.
리턴 타입 또는 입력매개변수의 타입을 제네릭 타입 변수로 선언한다.
제네릭 메서드는 호출되는 시점에 실제 제네릭 타입을 지정한다.
//제네릭 타입 변수명이 1개일 때
접근 지정자 <T> T 메서드명(T t){
//타입 T를 사용한 코드
}
//제네릭 타입 변수명이 2개일 때
접근 지정자 <T, V> T 메서드명(T t, V v){
//타입 T를 사용한 코드
}
//매개변수에만 제네릭이 사용됐을 때
접근 지정자 <T> void 메서드명(T t){
//타입 T를 사용한 코드
}
//리턴 타입에만 제네릭이 사용됐을 때
접근 지정자 <T> T 메서드명(int a){
//타입 T를 사용한 코드
}
참조 객체.<실제 제네릭 타입>메서드명(입력매개변수);
//일반 클래스 안에 제네릭 메서드
class GenericMethods{
public <T> T method1(T t) {
return t;
}
public <T> boolean method2(T t1, T t2) {
return t1.equals(t2);
}
public <K, V> void method3(K k, V v) {
System.out.println(k + ":" + v);
}
}
public class Main {
public static void main(String[] args) {
GenericMethods gm = new GenericMethods();
//제네릭 타입을 String 으로 지정
String str1 = gm.<String>method1("안녕");
//입력매개변숫값으로 제넨릭 타입 유추 가능 -> 지정 생략 가능
String str2 = gm.method1("하이");
boolean bool1 = gm.<Double>method2(2.5, 3.5);
boolean bool2 = gm.method2(4.5, 3.5);
gm.<String, Integer>method3("영어", 100);
gm.method3("수학", 80);
}
}
(수정 예정)