추가할 것: 지네릭 타입 제한
Generic
지네릭은 왜 필요한가
지네릭 vs 오브젝트 타입 차이점?
generic은 굉장히 중요하다.
어떤 타입이 들어올지 미리 정하지 않고 객체를 만들거나 호출 시 어떤 타입으로 할 것인지 설정
지네릭(또는 제네릭, 제네릭스)은 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스의 컴파일 단계에서 타입 체크(compile-time type check)를 해주는 기능이다.
인스턴스 별로 다르게 동작하려고 만든 기능이므로 static멤버에 타입 변수 지정 불가
즉 클래스와 메서드에서 쓸 데이터 타입을 나중에 정하는 방식이다. 매개변수와 비슷해 보여 헷갈리지만 매개변수는 값에 연관이 되어있고 generic은 데이터 타입에 포커스 되어있다.
객체의 타입을 런타임이 아닌 컴파일 단계에서 확인하기 때문에 객체의 타입 안정성을 높이고 형 변환을 생략 가능하여 간결한 코드를 작성할 수 있다.
// generic은 클래스 및 인터페이스 이름 뒤 < > 안에 타입 파라미터를 넣는다.
public class 클래스명 <타입 매개변수> { ~~~~ }
public interface 인터페이스명 <타입 매개변수> { ~~~~ }
타입 매개변수로는 정해진 규칙이 없으며 아무 알파벳 한 글자로 설정할 수 있다. 다만 통상적으로 상황에 맞게 의미 있는 문자를 선택해서 사용하는 것이 좋다.
<T> | <E> | <K> | <N> | <V> | <R> |
Type | Elements | Key | Number | Value | Result |
기호의 종류만 다를 뿐이지 '임의로 정한 참조형 타입'을 의미하는 것은 똑같다.
class Container
{
Object box;
void setBox(Object box){
this.box = box;
}
Object getBox(){
return box;
}
}
위 클래스를 generic 클래스로 변경하면 클래스 옆에 <타입>을 붙이면 된다. 여기선 <T>로 해보았다.
class Container <T> //generic 타입 T를 선언
{
T box;
void setBox(T box){
this.box = box;
}
T getBox(){
return box;
}
}
사용 & 미사용 예시
지네릭을 사용 안 했을 때 클래스에 다양한 타입을 넣는다면 대략 아래와 같이 나타낼 수 있다.
타입 변경을 위해 수동 타입 변환을 해야 한다. 번거롭기도 하고 실수할 가능성도 높은데 에러는 실행 전까지 뜨지 않는다.
public class generic_basic {
public static void main(String[] args) {
Box box = new Box(); //객체 생성
box.set("This is String");
//box.set(1);
//box.set('P');
System.out.println(box.get()); //결과값 "This is String"
String a = (String) box.get(); // 문자열 - 문자열 형변환 가능
Integer integer = (Integer) box.get(); // 실행시 ClassCastException 에러
}
}
class Box {
private Object object;
public void set(Object object){ //setter 로 Object 타입의 object 변수값 설정
this.object = object;
}
public Object get(){ // Object 타입의 object 변수값 반환
return object;
}
}
이런 문제점들을 타파하기 위해 generic이 탄생하게 되었다. 위의 코드를 아래처럼 나타낼 수 있다.
public class generic_basic {
public static void main(String[] args) {
Box<String> stringBox = new Box<String>();
stringBox.set("String 4 stringBox");
System.out.println(stringBox.get()); //String 4 stringBox
Box<Integer> integerBox = new Box<Integer>();
integerBox.set(12345);
System.out.println(integerBox.get()); // 12345
}
}
class Box<T>
{
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
}
Box 클래스에 지네릭 타입 T를 설정했다. 그리고 String 타입과 Integer 타입의 인스턴스를 각각 생성했다.
지네릭 클래스 Box의 모습이 아래와 같이 변경된 것이나 마찬가지가 된다.
class Box { //String 타입
private String t;
public String get() { return t; }
public void set(String t) { this.t = t; }
}
class Box { //Integer 타입
private Integer t;
public Integer get() { return t; }
public void set(Integer t) { this.t = t; }
}
정리하면 지네릭을 씀으로써의 이득은:
- 타입 체크와 형 변환의 생략 가능 -> 코드가 간결함
- 클래스와 메서드 안에서 쓰이는 객체의 타입 안정성 확보
지네릭 클래스 단계별 정의
1. 지네릭 클래스의 틀 정의하기
class GenericClass <T> {
}
2. 정의된 지네릭 클래스의 틀 안에 속성과 메서드 정의
class GenericClass <T> {
private T t;
public T get() { return t; } // setter로 받은 t 값 getter로 리턴
public void set(T t) { this.t = t; } //t 변수에 setter값 대입
}
3. 지네릭 클래스의 인스턴스 생성
GenericClass<String> genClass = new GenericClass<String>();
// <T> 타입을 <String>타입으로 설정
4. 인스턴스를 참조 변수를 통해 접근해서 사용
genClass.set("This is String Type!"); //setter를 통해 GenericClass 필드변수 값 변경
System.out.println(genClass.get()); //getter를 통해 필드변수 값 반환받아 출력
//결과: This is String Type!
타입 매개변수
generic클래스 < > 안에 있는 것을 매개변수 혹은 타입 변수라고 한다. 변수의 이름과 타입의 이름이 헷갈릴 수 있기 때문에 보통 하나의 대문자로 표현한다.
용어들의 설명:
class Box <T> { ... }
Box //원시 타입 (raw type)
Box <T> // T Box
T // 타입 매개변수 or 타입 변수
다이아몬드 연산자
자바 7부터 생성자에 자료형을 넣지 않을 수 있다. 타입 매개변수를 컴파일러가 문맥을 통해 추측하여 넣는다. 이를 다이아몬드 연산자(Diamond Operator)라고도 한다
Box<T> boxType = new Box<>();
와일드카드
와일드카드(wild card)는 제한을 두지 않는 기호를 의미하며 '?'로 표기한다.
<? extends T> // 와일드 카드의 상한 제한: T타입과 T타입의 하위 클래스만 사용
<? super T> // 와일드 카드의 하한 제한: T타입과 T타입 상위 클래스만 사용
<?> // 제한 X: 타입 변수에 모든 타입 사용 가능 <? extends Object> 와 동일
*와일드 카드에는 & 사용 불가. <? extends T & E> 처럼은 사용 안된다.
지네릭 메서드
지네릭 클래스와 마찬가지로 앞에 지네릭 타입이 선언된 메서드면 지네릭 메서드가 된다. 클래스 내부의 특정 메서드만 선택적으로 지네릭 타입으로 선언이 가능하다.
지네릭 클래스 | 지네릭 메서드 | |
타입 지정 시기 | 인스턴스 생성시 | 지네릭 메서드 호출시 |
타입 특징 | 타입이 전역 변수처럼 사용됨 | 타입이 메서드 내 지역 변수처럼 사용됨 |
주의) 지네릭 클래스에 선언된 타입 변수와 지네릭 메서드의 타입 변수는 다른 것이다. (단지 모양만 같을 뿐)
예제
public class generic_method {
public static void main(String[] args) {
NormalClass normal = new NormalClass();
String strInput = normal.accept("DANC"); //String 값을 타입변수로 넣음.
String strInput2 = normal.accept("DANC2");
normal.<String, Integer> getPrint(strInput, 3);
}
}
class NormalClass{ // 일반 클래스 안에서 지네릭 메서드 선언
// T accept 에서 T는 리턴타입 (예 int accept (int t)
public <T> T accept(T t){ //String 을 받았으니 public String accept(String t)
return t;
}
// 리턴 타입 앞에 타입변수 선언
public <K, V> void getPrint(K k, V v){
System.out.println(k + ":" + v);
}
}
지네릭 메서드는 메서드가 호출될 때 지네릭 타입이 결정이 된다. 즉 호출될 때까지 무슨 타입인지 알 수 없다. 따라서 특정 타입의 클래스 메서드는 사용할 수 없다. 예를 들면 String 클래스의 length() 메서드가 있다.
class NormalClass{
public <T> void accept(T t){
System.out.println(t.length()); //String의 클래스 메서드 length() 사용 불가
}
하지만 모든 클래스의 상위 클래스인 Object의 메서드는 사용 가능하다.
class NormalClass{
public <T> void accept(T t){
System.out.println(t.equals("Same?"); //Object의 클래스 메서드 equals() 사용 가능
}
Object클래스 메서드 링크
Object (Java Platform SE 7 )
Called by the garbage collector on an object when garbage collection determines that there are no more references to the object. A subclass overrides the finalize method to dispose of system resources or to perform other cleanup. The general contract of fi
docs.oracle.com
'programming > JAVA' 카테고리의 다른 글
Java - Inner Class (0) | 2022.05.18 |
---|---|
[TBC]Java - 컬렉션 프레임워크 (Collections Framework) (0) | 2022.05.17 |
[TBC]Java - Array 정리 (0) | 2022.05.15 |
[TBC]Java - 객체지향언어 (OOP - Object-oriented Programming) (0) | 2022.05.15 |
Java - 추상화 / 인터페이스 (0) | 2022.05.15 |