[Java] 제네릭

6720·2023년 7월 28일
0

이거 모르겠어요

목록 보기
29/38

Generic

데이터의 타입을 일반화한다

데이터 형식에 의존하지 않고, 하나의 값이 여러 다른 데이터 타입을 가질 수 있도록 하는 방법이며, 클래스나 메서드에서 사용할 내부 데이터 타입을 컴파일 시 미리 지정하는 방법임.

예를 들어서 어떤 자료구조를 만들어 배포할 때, 내부에서 타입을 지정하여 배포하면, 해당 타입에 대한 자료구조로 밖에 사용할 수 없으며, 다른 타입을 사용하고 싶다면 타입만 다르고 내용은 같은 자료구조를 배포해야 할 것임.
→ 자료구조의 타입을 정해두지 않고 외부에서 사용자가 타입을 직접 지정할 수 있도록 배포하면 해당 문제를 해결할 수 있음.

특징

1) 잘못된 타입이 들어올 수 있는 것을 컴파일 단계에서 방지할 수 있음.
컴파일 단계에서 에러가 나는 것은 매우 축복임.
컴파일 단계에서는 어떤 부분이 어떻게 에러가 발생하는지 알려주기 때문에 어떤 코드던 되도록 컴파일 단계에서 먼저 에러가 발생하도록 작성하는 것을 추천함.
(만약 컴파일 단계가 아닌 연산 단계에서 에러가 난다면 원인을 찾는 데 한 세월이 걸릴 것임.)

제네릭 타입을 사용하면 컴파일 단계에서 에러가 발생하기 때문에 연산 단계 전으로 도입하기 전에 잘못된 부분을 수정할 수 있음.

2) 클래스에 외부에서 타입을 지정해주기 때문에 따로 타입을 체크하고 변환해줄 필요가 없음.
유지보수 용이

3) 비슷한 기능을 지원하는 경우 코드의 재사용성이 높아짐

4) 계층구조가 복잡해지면 어려워짐.

타입 변수

타입설명
<T>Type
<E>Element
<K>Key
<V>Value
<N>Number

타입 변수 반드시 한 글자일 필요는 없으며, 설명과 반드시 일치할 필요도 없음.

EX) <Ele> - O

선언 및 생성

[선언]

// 클래스 + 인터페이스 선언
class TestList <T> { ... }
interface Test <T> { ... }

// 타입은 2개일 수도 있음. EX) 맵
class HashMap <K, V> { ... }

// T 타입은 안에서까자 유효함.
class TestList <T> {
	T test;
		
	void setTest(T test) {
		this.test = test;
	}
		
	T getTest() {
		return test;
	}
}

[생성]

TestList<Integer> test1 = new TestList<>();
TestList<String> test2 = new TestList<>();

<> 괄호 안에 들어가는 타입을 지정하여 사용함.

+) 제네릭 메서드

// [접근 제어자] <제네릭타입> [반환타입] [메서드명]([제네릭타입] [파라미터]) { ... }
public <T> T Test(T o) { ... }

클래스와 인터페이스와는 다르게 제네릭 타입이 앞 쪽에 선언됨.

++) 제네릭 메서드는 클래스에 지정한 제네릭 유형과 별도로 독립적으로 제네릭 유형을 선언할 수 있음.
→ 정적 메서드로 선언할 때 필요함.

static이 붙게 되면 프로그램을 실행할 때 메모리에 올라가게 됨.
→ 객체 생성을 통해 접근할 필요 없이 이미 메모리에 올라가 있기 때문에 클래스 이름을 통해 바로 쓸 수 있다는 것.

// 에러 코드
class Test<E> {
	static E test(E o) { // <E>가 아닌 E로 선언되어 있음.
		return o;
	}
}

class Main {
	public static void main(String[] args) {
		Test.test(3); // test 메서드에는 접근할 수 있지만 타입을 지정할 수 없음.
	}
}

다음처럼 static 메서드로 구현되어 있지만 클래스의 타입을 지정할 수 없기 때문에 타입 E는 선언되어 있지 않음.
정적 메서드에 접근한다 하더라도 메서드 타입이 클래스의 제네릭 타입이라서 사용할 수 없음.
(인텔리제이는 다음과 같은 코드를 작성하면 static 타입으로 작성할 수 없도록 막아버림.)

// 정상 코드
class Test<E> {
	private E e;

	void set(E e) {
		this.e = e;
	}

	E get() {
		return e;
	}

	static <E> E test(E o) {
		return o;
	}
}

class Main {
	public static void main(String[] args) {
		// 클래스 선언
		Test<Integer> t = new Test<>();
		t.set(10);   // e = 10;  Integer

		// 메서드 실행
		Test.test(3);   // o = 3;   Integer
		Test.test("3"); // o = "3"; String
		Test.test(t);   // o = 10;  Test
	}
}

제네릭과 와일드카드

와일드카드: 알 수 없는 타입 (?)

제네릭의 타입 범위를 특정 범위 내로 좁히고 싶을 때 사용함.

<K extends T> // T와 T의 자손 타입만 가능 (K는 들어오는 타입으로 지정)
<K super T> // T와 T의 부모 타입만 가능 (K는 들어오는 타입으로 지정)

<? extends T> // T와 T의 자손 타입만 가능
<? super T> // T와 T의 부모 타입만 가능
<?> // 모든 타입 가능 (= <? extends Object>)

// K는 특정 타입으로 지정이 되지만, ?는 타입이 지정되지 않음.
// Number와 이를 상속하는 타입이 지정될 수 있으며, 호출될 시 K는 지정된 타입으로 변환됨.
<K extends Number>

// 타입 참조 X
<? extends T>

제거 시기

자바 코드에서 선언되고 사용된 제네릭 타입은 컴파일 시 컴파일러에 의해 자동으로 검사돼 타입 변환되며,
코드 내의 모든 제네릭 타입은 제거되어, 컴파일된 class 파일에는 어떠한 제네릭 타입도 포함되지 않게 됨.
→ 제네릭을 사용하지 않는 코드와의 호환성을 유지하기 위해서

주의사항

  • 제네릭 타입의 객체는 생성 불가
T t = new T(); // X

참고 링크

https://st-lab.tistory.com/153#comment13265417
http://www.tcpschool.com/java/java_generic_concept
https://inpa.tistory.com/entry/JAVA-☕-제네릭Generics-개념-문법-정복하기

profile
뭐라도 하자

0개의 댓글