제네릭

2024. 7. 30. 20:28JAVA/복습

 

 

제네릭이란?

사용할 타입을 인스턴스 생성 시점에 결정하는 기술 .

생성 시점에 클래스 내부에서 사용할 타입을 결정하는 기술이다.

범용적으로 사용할 수 있어 Generic 이다.

 

제네릭이 필요한 이유

1. 코드 안정성을 위해

2. 코드 재사용성을 위해

 

Integer[] values;

String[] values;

 

값을 담을 자료구조에 타입이 고정 돼 있다면, 용도는 명확하지만 당연스레 재사용성이 떨어진다.

public class ex<T> {
	private Object[] objectValues;
    private T[] genericValues;    
}

다형성 활용

 

Object 를 사용해 다형성을 살려도 되지만 문제가 발생한다.

Object 에는 모든 값이 담기므로,

1. String, Integer, User 등 다양한 값들이 들어갈 수 있다.

2. 값 저장에는 문제가 없으나 꺼낼 때 발생한다.

 

문제 

다운캐스팅 강제 발생

1. 자료구조에 담긴 값을 사용하기 위해선, 온전히 그 모습이어야 한다. 그러나, Obejct로 취급되므로, 다운캐스팅이 강제 된다. 가령 String -> Integer 라던가, Integer -> String 이라던가, Integer -> User 라던가 연관 없는 객체를 캐스팅 하려고 하면 예외가 퍼버버버버벙

-> 제네릭을 사용하면 자료구조 타입이 외부에서 정한 타입이 된다.

-> new ex<Integer>(); 와 같이 생성했다면 아래와 같이 동작한다.

public class ex<Integer> {
	
    private Integer[] genericValues;    
}

-> 이를 통해 외부에서 지정한 값을 넣고, 뺄 수 있게된다. 개발자에게 다운캐스팅이 강제되지 않게 된다.

 

 

사용법

class 이름 뒤에 < > 를 작성하고 그 안에 외부에서 결정한 매개타입 ('T')을 꼭 지정한다.

public class Generic<T> {
	접근제어자 T 변수명;
}

여기서 T 는 매개타입으로, T(type)의 약어이다.

아무 글자나 써도 되지만, 암묵적으로 사용되는 글자가 몇개 있다.

T - 타입(type)

E - 인자(element)

K - 키(key)

V - 값(value)

 

타입 추론

제네릭 클래스를 생성할 때, 생성자 <>에 타입을 넣어줘야하는데,

인스턴스를 담을 참조 변수 타입에 정보가 있다면 생략 가능하다.

Generic<Integer> generic = new Generic<Integer>();	// 이랬던게
	
Generic<Integer> generic = new Generic<>();	// 이렇게 됐습니다.

 

RAW(원시) 타입

제네릭을 사용하기 위해선 <> 와 그 안에 들어갈 매개 타입, 타입 인자를 꼭 써야 한다고 했다.

Generic<T> generic = new Generic<>();
Generic generic = new Generic();

그러나, 위와 같이 <> 도, 타입 인자를 사용하지 않고도 사용 가능하다.

 

그렇게 될 경우, 매개타입 T가 모든 것을 담을 수 있는 Object로 대체된다.

public class Generic<T> {
	// 접근제어자 T 변수명;
       접근제어자 Object 변수명;
}

이는, 제네릭이 없던 시절과의 호환을 위해 제공되는 기능이다. 

하지만, 타입 안전성이 떨어지고 다운 캐스팅을 감수해야 하니, 꼭 필요한 상황이 아니면 사용하지 않는 것이 옳다.

 


타입 매개변수 제한

타입 매개변수 T 에 대해서도 제약을 걸 수 있다.

 

타입 매개변수의 제한은 왜 필요할까?

T에는 Obejct와 같이 모든 타입이 들어갈 수 있다.

T는 인스턴스 생성 시점에, 생성자에 밀어넣은 타입 인자로 결정된다.

즉. 단순 Class 상태일 때,  T = Object 와 다를 바가 없다. 프로그램은 Generic<T> 의 T가 뭔지 모른다. Object 정도로나 알겠지.

따라서,  Seaon 에 start() 메서드가 있고, 그걸 우리가 알고있다 한들, 자바는 모른다. Object로 인식한다.

 

public class Season{
	public void start(){
    	System.out.print("계절시작");
    }
}

 

그래서 아래와 같이 run()은 컴파일러 에러가 뜬다.

Object로 인식하고 있으니 toString()은 된다.

 

 

public class Generic<T> {
	private T fourSeason;
    
    public void seasonStart(){
    	fourSeason.toString();
        fourSeason.run();
    }
}

 

 

그러나!

아래와 같이 매개타입에 제약을 걸면? 

public class Generic<T extends Season> {
	private Season season;
    /*
    private Spring season;
    private Summer season;
    private Fall season;
    private Winter season;
    */
}

 

 

타입 매개변수에 제약을 걸며 컴파일러에게 정보를 넘겼기 때문에, Seaon 의 메서드를 온전히 사용할 수 있게 된다.

  public void seasonStart(){
    	season.toString();
        season.run();
    }

 

제네릭 메서드

클래스 뿐만 아니라 메서드에도 제네릭을 적용시킬 수 있다.

 

작성법

반환타입 왼쪽에 <> 와 반환할 타입 변수를 넣어준다.

public class ex<T> {
    private T[] values;

    public <T extends Number> T numberMethod(T t) {

        return t;
    }
}

 

사용법

메서드 호출 시, 객체.<타입인자>메서드명(); 을 입력하여 사용

public class exMain {
    public static void main(String[] args) {
        ex<String> stringex = new ex<>();

        Double v = stringex.<Double>numberMethod(10.1);
    }
}

 

제네릭 우선순위

ex< T >  T <- String 을 담아 제네릭 클래스의 인스턴스를 생성했다.

이상하다.

제네릭 인스턴스 생성할 때 <String> 을 넣었는데, 왜 numberMethod("10.1") 이 아니라 10.1 이 들어갈까?

 

"10.1" 을 넣어보자. 에러가 뜬다. 

타입 매개변수 T 가 메서드 레벨에서 덮어쓰기 당했기 때문이다.

제네릭에도 우선순위가 존재한다.

제네릭 메서드 타입 매개 변수 > 제네릭 클래스 타입 매개 변수

 

 

 

메서드에서 제네릭 부분을 제거하면 class 레벨의 타입매개변수T 로 적용된다.

ex<String> stringex = new ex<>();

이제 10.1 이 아닌 "10.1" 이 들어가는 것을 확인할 수 있다.

 

생성 시점

타입인자<T> 가 결정되는 시점은 아래와 같다.

 

타입

객체를 생성하는 시점

 

메서드 

메서드를 실행하는 시점

 

사용 위치

제네릭 메서드는 인스턴스 메서드, static 메서드에 모두 적용 가능하다.

 

다만,

제네릭 타입은 static 메서드에선 사용 불가능 하다.

왜?

제네릭 타입은 객체 생성 시점에 생성된다.

static 키워드 사용 시, 해당 클래스, 메서드 등은 프로그램 실행 초반에 호출된다.

 

매개타입변수 가 뭔지도 모르는 시점에서 사용할 수 없기 때문에 메서드에 T를 도입할 때 에러가 난다.

 

반면,  static 메서드는 실행 시점에 타입이 정해지기 때문에, 이와 무관하게 돌아간다.

!!!

class<T>랑

<T> void method 의 T는 서로 적용 레벨이 다른 T 인거 아시쥬?
알아야 함.

public static <T> void staticGenericMethod() {
}

 

그래도!!

클래스 레벨의 타입 변수 T와 제네릭 메서드 레벨의 타입 변수는 다른 단어를 쓰도록 하자.

헷갈리니까.

 

 

'JAVA > 복습' 카테고리의 다른 글

Set 인터페이스  (1) 2024.08.01
Set - HashSet  (0) 2024.07.31
LinkedList  (0) 2024.07.31
ArrayList  (0) 2024.07.31
와일드 카드  (0) 2024.07.31