제네릭 (generic) 이란 ?
데이터 타입을 일반화(generalize)한다는 것을 의미
클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 하면서 결정이 된다.
class Test<T> { // 제너릭 클래스라 불림
private T element; // 이 부분은 구체화 하지 않음(type 지정x)
public void setElement(T element) {
this.element = element;
}
public T getElement() {
return element;
}
}
<> 이 꺾쇠 괄호가 바로 제네릭 !
private T element;
private int element;
이 부분이 구체화 하지 않았다 말을 하며 (타입 지정 하지 않은 것)
위 코드로 보면 T 가 자료형이구나 알 수 있다 !
♣ Tip
우리가 변수 선언할 때 변수의 타입을 지정해주듯, 제네릭은 객체(Object) 에 타입을 지정해주는 것이라고 생각하면 됨 !
타입 매개변수 (parameter)
제네릭은 <> 꺾쇠 괄호 키워드를 사용하고, 꺾쇠 괄호 안에 식별자 기호를 지정함으로써 파라미터화 할 수 있다.
이것이 마치 메소드가 매개변수를 받아 사용하는 것과 비슷하여 제네릭 타입 매개변수 / 타입 변수 라고 부른다 !
List<T>
-
타입 매개변수
List<String> stringList = new ArrayList<String>();
------ ------
매개변수화된 타입
타입 파라미터 생략
맨 앞에 크래스명과 함께 타입 지정해줬는데 굳이 생성자까지 제네릭 지정해줄 필요는 없지만 !
그래도 코드 직관성을 위해 적어두자
Box<Tv> one = new Box<Tv>();
Box<Tv> one = new Box<>();
타입 파라미터 할당 가능 타입
제네릭에서 할당 받을 수있는 타입은 reference type(참조 자료형) 뿐 !
int나 double 같이 primitive type(기본 자료형) 은 못쓴다 !
Test<int> t2 = new Test<int>(); error !
Test<Integer> t2 = new Test<Integer>();
t2.setElement(5); // boxing 됐다 표현 !
복수 타입 파라미터
제네릭은 반드시 1개만 사용하라는 법 없음 !
만일 타입이 여러개가 필요한 경우 2개든 3개든 얼마든지 만들 수 있다 !
제네릭 타입의 구분은 꺾쇠 괄호 안에서 쉼표(,) 로 하며 <K, V> 와 같은 형식을 통해 복수 타입 파라미터를 지정할 수 있음!
그리고 당연히 클래스 초기화할 때는 제네릭 타입을 2개 넘겨주야함 .
class ClassTwo <K, V> {}
public void add(T apple, U banana) {
apples.add(apple);
bananas.add(banana);
}
제네릭 사용 이유와 장점
1. 컴파일 타임에 타입 검사를 통해 예외 방지
자바에서 제네릭은 JDK 1.5 부터 도입(지원)된 스펙이다.
이전에는 여러 타입을 다루기 위해 인수나 반환값으로 Object 타입을 사용했는데, Object 로 타입을 선언할 경우
반환된 Object 객체를 다시 원하는 타입으로 매번 타입 변환 해야 하고, 런타임 에러가 발생할 가능성이 크다.
package generic;
class Apple {
@Override
public String toString() {
return "사과는 맛있당.";
}
}
class Banana {
@Override
public String toString() {
return "바나나는 멸종 위기이다.";
}
}
class InstanceType {
private int count; // 인스턴스 생성되면서 0으로 저장
// ====================================================
// 1. [parameter type : Apple]
public void showInstanceType(Apple apple) { 애플은 받을 수 있지만, 바나나는 못받음
System.out.println("type >> " + apple);
count++;
}
// 2. [parameter type : Object] 애플과 바나나는 받을 수 있지만 형변환을 해주는 상황들이 생김
public void showInstanceType(Object object) { // 다형성
System.out.println("type >> " + object);
count++;
}
// 3. [parameter type : generic type]
public <T> void showInstanceType(T type) {
System.out.println("type >> " + type);
count++;
}
// ====================================================
public void showCount() {
System.out.println("count >> " + count);
}
}
public class Ex03 {
public static void main(String[] args) {
Apple apple = new Apple();
Banana banana = new Banana();
InstanceType in = new InstanceType();
// in.showInstanceType(apple);
in.<Apple>showInstanceType(apple);
in.showCount();
// in.showInstanceType(banana);
in.<Banana>showInstanceType(banana);
in.showCount();
}
}
2. 불필요한 캐스팅을 없애 성능 향상
반드시 다운 캐스팅을 통해 가져와야할 땐 쓸데 없는 성능 낭비로 이어지는데
package generic;
class TestTwo {
private Object element;
public void setElement(Object element) { // 다형성 구현
this.element = element;
}
public Object getElement() {
return element;
}
}
public class Ex02 {
public static void main(String[] args) {
TestTwo t = new TestTwo();
t.setElement("ㅎㅇ");
System.out.println(t.getElement());
//String t_value = t.getElement(); error ! String t_value / Object t.getElement(), 다운 캐스팅 해줘야함
String t_value = (String)t.getElement();
System.out.println("t_value >> " + t_value);
TestTwo t2 = new TestTwo();
t2.setElement(5);
System.out.println(t2.getElement());
Integer t2_value = (Integer)t2.getElement();
System.out.println("t2_value >> " + t2_value);
}
}
System.out.println(t.getElement());
# 실행 결과
ㅎㅇ
String t_value = t.getElement()
String 타입의 t_value 변수에 Object 타입의 t.getElement() 을 넣으면 당연히 ! 에러가 발생함
다운 캐스팅을 해줘야하는데 이런 런타임 에러가 발생하게 됨 ㅠ
그렇다면 메소드마다 형변환을 해주게 된다면 ?!
package generic;
interface Available {
// 추상 클래스
void selfIntroduction();
}
class Tree {
private String sort;
private int age;
public Tree() {}
public Tree(String sort, int age) {
this.sort = sort;
this.age = age;
}
public void info() {
System.out.printf("[INFO] 종류 : %s, 나이 : %d\n\n", sort, age);
}
}
class Maple extends Tree implements Available {
public Maple(String sort, int age) {
super(sort, age);
}
@Override
public void selfIntroduction() {
System.out.println("나는 단충이다 !");
}
}
class Pine extends Tree implements Available {
public Pine(String sort, int age) {
super(sort, age);
}
@Override
public void selfIntroduction() {
System.out.println("나는 소나무이다 !");
}
}
public class Ex04 {
// ==================================================================================
// [자료형에 안전하지 않은 코드]
// public static <T> void callSelfIntroduction(T t) { // main 메소드에서 호출하기 위해 static
// ((Available)t).selfIntroduction();
// }
//
// public static <T> void callInfo(T t) {
// ((Tree)t).info();
// }
// [자료형에 안전한 코드] 타입 제한 두기
// : class 와 interface 를 구분하지 않고, 키워드 extends 사용
// 내 생각 : 컴파일 할 때 결정이 되기 때문에 상속 개념이 없는듯
// T 에는 Available 포함한 Available 의 자식이 와야함
// T 는 Available 포함한 Available 의 자식이 와야한다고 제한을 두는 거지, 상속 개념 x
public static <T extends Available> void callSelfIntroduction(T t) { // Available interfave
((Available)t).selfIntroduction();
}
public static <T extends Tree> void callInfo(T t) { // Tree class
((Tree)t).info();
}
// ==================================================================================
public static void main(String[] args) {
Maple maple = new Maple("당단풍나무", 25);
Pine pine = new Pine("금강고로쇠", 33);
callSelfIntroduction(maple);
callInfo(maple);
callSelfIntroduction(pine);
callInfo(pine);
// 런타임(실행) error ! 받은 건 String 인데, ((Available)t) 형변환 되지 않음
// 이러한 경우를 [자료형에 안전하지 않은 코드] 라 불림
// 빨간줄 안뜸
//String name = "홍길동";
//callSelfIntroduction(name);
// 컴파일 error !
// 코드에 타입 제한을 뒀으니 이제 빨간줄
//String name = "홍길동";
//callSelfIntroduction(name);
//callInfo(name);
}
}
위 코드에서 말했듯 <자료형에 안전하지 않는 코드>가 되어버린다.
<?> : 와일드 카드
제네릭 간의 형변환을 성립되게 하기 위해서는 제네릭에서 제공하는 와일드 카드 ? 문법을 이용하면 됨 !
<?> | wildcard |
<? extends 상위타입> | 가능 |
<? super 하위타입> | 가능 |
문법
1. 제네릭 인터페이스
interface InterfaceName <T> {}
2. 제네릭 클래스
class ClassOne <T> {}
3. 제네릭 메소드
public static <T> void show(T t) {
System.out.println(t);
}
4. 제네릭 클래스가 오면 제네릭 메소드로 처리
public void three(ClassOne<T> name) {} // error !
public void three(ClassOne<?> name) {}
5. 제네릭 클래스에 상속 클래스가 올 때
// 클래스 상속
class ClassSuper {}
class ClassSub extends ClassSuper {}
// 자기를 포함한 이하
public void four(ClassOne<? extends ClassSuper> name) {}
// 자기를 포함한 이상
public void five(ClassOne<? super ClassSub> name) {}
6. 제네릭 타입(타입 파라미터)을 타입 제한(한정) , 제네릭 클래스가 올 때
public <T extends ClassOne<?>> void six(T t) {}
https://velog.io/@wwe221/generic
'Language > JAVA' 카테고리의 다른 글
JAVA 간단한 프로그램 - 동물병원 프로그램 ver2 (0) | 2024.04.24 |
---|---|
JAVA 간단한 프로그램 - 동물병원 프로그램 ver1 (0) | 2024.04.24 |
JAVA Calendar 클래스 개념과 예제 (0) | 2024.04.23 |
JAVA 자바 활용 백엔드 개발 기초 연습 문제 - 예외 클래스 정의하기 (0) | 2024.04.22 |
JAVA 자바 활용 백엔드 개발 기초 연습 문제 - 일반 클래스에서 인퍼페이스 로 변경하기 (0) | 2024.04.19 |