■ 제네릭
"Generic"의 사전적인 의미는 "일반적인" 이라는 의미를 가지고있다. 제네릭을 클래스내부에서 사용할 데이터 타입를 외부에서 파라미터 형태로 지정하면서 데이터 타입을 일반화 해준다.
제네릭 없이 객체의 여러 자료형을 받을 수 있드록 제작하려면 int, float, double등 모든 자료형의 클래스들을 각각 만들어 주어야한다.
제네릭은 클래스 및 인터페이스를 선언할 때 <>안에 타입 파라미터를 넣어 작성한다.
//제네릭 선언방법
public class 클래스명<타입 매개변수> { ... }
public interface 인터페이스명<타입 매개변수> { ... }
자주 사용하는 매개변수의 타입은 아래 표와 같다. 선언방법이 정해진 것은 아니지만, 일반적으로 대문자 알파벳 하나로 선언해준다.
타입인자 | 설명 |
<T> | Type |
<E> | Element |
<K> | Key |
<N> | Number |
<V> | Value |
<R> | Result |
■ 제네릭의 필요성
제네릭은 클래스 최상단에 속해있는 Object 클래스를 상속받아 확장한다.
public class Test { //object 클래스 암시적 상속
...
}
public class Test extents Object{
...
}
//위 두개의 클래스 모두 같은 클래스이다
//모든 클래스에서는 Object 클래스의 메서드를 상속 받을 수 있다.
제네릭을 사용하지 않고 다양한 값을 넣어보자.
다양한 값들을 넣어줄 것이기 때문에 Object 클래스를 상속 받아준다.
public class Test{
private Object object;
public void set(Object object) { this.object = object};
public Object get(){ return object };
}
set 메서드를 사용해 다양한 타입을 넣어 줄 수 있다.
Test test = new Test();
test.set("String");
test.set("1");
test.set('A');
Test 클래스에 생성자를 작성해준다.
public class Test{
private Object object;
Test(Object object){
this.object = object;
}
public void set(Object object) {
this.object = object;
}
public Object get(){
return object;
}
}
Test 객체를 만들기 위해서, "String"이라는 문자열 생성자 매개변수로 넣어준다.
public class main{
public static void main(String args[]){
Test test = new Test("String");
String a = (String) test.get();
//가능한 변수선언
String b = (Interger) test.get();
//문자열을 정수로 수동 타입 변환 할 수 없기 때문에 ClassCastExpection 발생
}
}
Test 클래스는 여러가지 타입을 선언하기 위하여 Object 클래스를 상속받았는데, 이 코드는 비효율적으로 길며 오류도 수반하는 코드이다. 이러한 점을 해결하기 위해서 Java 5에서부터 제네릭이 추가 되었다.
■ 제네릭 클래스 정의
제네릭은 여러 참조 자료형을 사용해야하는 경우 Object 가 아닌 하나의 문자로 표현한다.
//선언방법
접근제어자 클래스 클래스명<T>{ //제네릭 타입의 매개변수가 1개인 경우
//T(Type)타입을 사용한 코드
}
접근제어자 클래스 클래스명<V,R>{ //제네릭 타입의 매개변수가 2개인 경우
//V(Value)와 R(Result)를 사용한 코드
}
제네릭의 필요성에서 작성한 코드에 예시를 제네릭 선언 방법으로 표현하면 다음과 같다.
<>안에는 제네릭의 매개변수명으로 type에 첫번째 글자인 T를 많이 사용한다.
//제너럴 타입에 클래스 선언
public class Test<T>{}
//제너럴 타입에 인터페이스 선언
public interFace<T>{}
제네릭 타입을 선언해주고, 필드와 메서드, 리턴값을 T로 설정해준다.
//제네릭 타입에 Test 클래스
public class Test<T>{
private T t;
public T get() { return t};
public T set(T t){
this.t = t;
}
}
이제 Test 타입의 객체를 생성할때 어떤 값을 넣어줄 것인지 지정만 해주면 된다.
public class Main{
public static void main(String args[]){
Test<String> test1 = new Test<String>();
test1.set("String");
String Str = test1.get();
Test<Interger> test2 = new Test<Interger>();
test2.set(1234);
int value = test2.get();
}
}
//위처럼 객체를 생성했을 시 Test 클래스의 동작 예시
// String
class Test<T>{
private String t;
public String get(){
return t;
}
public void set(String t){
this.t = t;
}
}
//Interger
class Test<T>{
private Interger t;
public Interger get(){
return t;
}
public void set(Interger t){
this.t = t;
}
}
■ 제네릭 클래스의 사용단계
제네릭 클래스를 선언하고 객체를 생성하는 과정을 4단계로 표현하면 다음과 같다.
1. 템플릿 형태의 제네릭 클래스 정의
class GenericClass<T>{}
2. 템플릿 형태의 제네릭 클래스에 속성과 메서드 정의
class GenericClass<T>{
private T t;
public T Tget(){
return t;
}
public T Tset(T t){
this.t = t;
}
}
3. 제네릭 객체 생셩
Generic<String> gc = new Generic<String>();
4. 제네릭 객체 사용
gc.set("안녕하세유?");
System.out.println(gc.get());
//결과값
안녕하세유?
■ 타입 매개변수
제너럴 클래스에 <>안에 있는 변수명을 타입 매개변수 혹은 타입변수 라고 한다. 변수의 이름과 타입의 이름을 구분하기 위해서 "하나의 대문자"로 작성한다. 제너럴 클래스는 여러개의 타입 변수(타입 매개변수)를 가질 수 있다.
class Test<T>{
Test //원시타입, 여기서 원시타입은 primitive가 아닌 raw 타입.
Test<T> // T Test
T //매개변수 타입 or 변수타입
}
//raw 타입이란? 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때를 뜻한다.
다이아몬드 연산자 <>
- 자바 7부터 제네릭 클래스를 생성 할 때, 생성자의 자료형을 명시하지 않을 수 있다. 생성자 타입 매개변수를 컴파일러가 문백을 통해 추론한다.
- 다이아몬드 연산자 == <>
//기존
ArrayList<String> list = new ArrayList<String>();
//자바7
ArrayList<String> list = new ArrayList<>();
와일드카드(wild Card)
- 와일드카드란 "제한을 두지 않는다"는 기호를 의미한다.
- 제네릭에서는 <?>를 통해 와일드카드를 사용한다
<?> // 타입 매개변수의 모든 타입을 허용
<? extends T> //T타입과 T타입을 상속받는 하위클래스만 사용가능
<? super T> //T타입과 T타입을 상속받은 상위클래스만 사용가능
■ 제네릭 클래스
클래스 전체를 제네릭 클래스로 만들 수 있지만, 클래스 내부에 특정 메서드만 제네릭으로 선언 할 수 있다. 이를 제네릭 메서드라고 한다. 제네릭 클래스가 객체를 생성하는 시점에서 실제 타입을 지정하는 것 과 달리 제네릭 메서드는 호출 되는 시점에서 타입을 지정해준다. 제네릭 클레스 타입이 전역변수처럼 사용된다면, 제네릭 메서드는 지역변수처럼 사용되는 것과 같다.
package generic;
public class genericTest {
public static void main(String[] args) {
TestClass testClass = new TestClass();
String str1 = testClass.<String>accept("Kim Coding");
String str2 = testClass.accept("김코딩"); //입력 매개변수 값으로 타입이 유추가 가능하면 제네릭 타입 지정 생략가능
System.out.println(str1);
System.out.println(str2);
testClass.getPrint("김코딩",1);
testClass.<String, Integer>getPrint("김코딩",2);
}
}
class TestClass{ //일반클래스 내부에 제네릭 메서드 선언
public <T> T accept(T t){
return t;
}
public <V,K> void getPrint(V v, K k){ //리턴 타입앞에 타입 매개변수 언언
System.out.println(v + ":" + k);
}
}
제네릭 메서드는 메서드가 호출되는 시점에서 타입이 지정된다, 제네릭 메서드를 정의하는 동안에는 제네릭 메서드가 실제 어떤 타입이 입력되는지 알 수 없다. 그래서 length()같은 String 클래스 메서드는 사용할 수 없다.
//선언이 불가능한 제네릭 메서드
class Impossible{
private <T> void print(T t){
System.out.printfln(t.length())
//length() 사용불가
}
}
히지만 자바의 최상위 클래스인 Object 클래스의 메서드는 사용가능하다
class PossibleCode{
public <T> void print(T t){
System.out.println(T.equals("김건휘"))
}
//가능
}
차 후 참고자료
'Programming > java' 카테고리의 다른 글
[Java] 열거형과 에노테이션 (0) | 2022.05.19 |
---|---|
[Java] Field 와 static keyword (0) | 2022.05.10 |
[Java] Type (0) | 2022.05.04 |
[JAVA] IF문 IF ELSE 문 중첩 실행 (0) | 2022.04.26 |