[Java] 지네릭스 (Generics)
지네릭스를 통해 타입안정성을 확보하고 코드 작성 시 형변환을 생략해 코드를 간결하게 작성할 수 있다.
지네릭스는 다양한 타입의 객체들을 다루는 메서드나 클래스 (List같은 것들) 에 컴파일 시 타입체크를 해 주는 기능을 한다. 실제로 큰 프로젝트를 진행할 때 타입 안정성때문에 쓰레기값이 나오는 경우가 있으면 안 되니까.. 런타임체크가 아니라 컴파일체크인 것도 알아놓자.
계속 봐도 모르겠으면 코드 짜면서 공부하고, 지금은 지네릭스는 타입체크를 위한 이름표구나~ 라고만 생각하자.
타입 안정성 + 형변환 생략(성능 향상)
지네릭 클래스는 클래스나 메서드에 선언할 수 있다.
왼쪽 코드를 지네릭 클래스로 변환하면
왼쪽과 같이 변한다.
여기서 T는 타입 변수로, 아무거나 써도 되고 <> 안에는 comma를 사용해 여러 가지 타입을 선언할 수 있다.
지네릭 클래스로 변환한 클래스를 왼쪽과 같이 생성하면
(지네릭 클래스의 객체를 생성할 때는 <>를 써줘야함)
왼쪽 코드처럼 생성한 것과 같다.
아 그러면 Box<Object>로 설정하면 뭐든 다 담을 수 있나?
Object는 모든 클래스의 조상이니까.. Box<Object> 로 객체를 생성하면 클래스의 내용물을 String으로, int로, char로.. 등등 여러 가지 타입으로 다룰 수 있을 것 같은데?
라고 생각할 수 있는데..
맞다. 사실 코드가 어떻게 써있는지에 따라 다르지만.. 일단 객체를 오류 없이 생성하고 나면 상속 관계를 적용해서 클래스 안의 요소들을 다룰 수 있다.
지네릭스는 앞서 말했듯 컴파일 시 타입 안정성에 도움을 주기 위한 장치이고, 컴파일을 마친 후에는 Box<String> 은 원시 타입인 Box로 바뀐다. (지네릭 타입이 제거된다.)
왼쪽과 같이 객체별로 다른 타입을 지정하는 것도 가능하다.
객체 생성 시에는 참조변수와 생성자에 대입된 타입은 무조건 일치해야 한다.
사실 생성자에 대입된 타입은 생략해도 된다.
(어차피 일치해야 하기 때문.. JDK 1.7부터 생략가능)
그리고 당연히, static멤버에 타입변수 T는 사용할 수 없다. (T는 인스턴스 변수로 간주됨.)
T는 타입 안정성을 위해 컴파일 시 체크해주지만, 타입을 결정하지는 않는다.
타입으로 뭘 받는지 알아야 메모리 공간에 올릴텐데.. 타입을 모르니 올릴 수 없다.
지네릭 타입의 배열 <T> 은 선언할 수는 있지만 생성은 할 수 없다.
지네릭 클래스에서는 선언만 해 놓고 메인메서드에서 객체 생성하고 이어서 쓰는 방식으로 작성해야한다.
( Arrays클래스의 newInstance로 T타입의 배열을 생성할 수 있긴 하지만 비추. )
지네릭스는 컴파일 시 타입체크를 해 주는 기능이라고 했다.
배열을 생성할 때 사용하는 new 연산자는 컴파일하는 시점에 타입을 제대로 알아야 하는데, 타입변수로 쓰인 T는 그 시점에 타입이 정해지지 않았기 때문이다.
rulerbox 라는 클래스로 객체를 생성하려 하면 당연히 생성이 안된다. 하지만 rulerbox가 Box의 자손이라면?
왼쪽 코드를 추가해주면 (지네릭 클래스끼리만 작용)
오류가 사라진다.
이렇듯, 지네릭스에서 상속 관계는 매우 중요하다.
이렇게 동작하지 않을까? 라고 생각하지만 생각처럼 동작하지 않는 경우가 많다.
<>로 타입을 확인하는 부분에서는 상속관계를 무시하는 것 처럼 보이는데,
일단 클래스를 작성하고 객체를 생성하고 나서, 인자를 조작할 때는 상속 관계를 활용할 수 있다.
지네릭 클래스에서도 타입을 제한할 수 있다.
다음과 같이 지네릭스의 선언부에 extends를 추가하면 그 클래스의 자손 클래스만 담을 수 있다.
이 때 만약 pencilcase가 interface여도 extends로 제한조건을 표현한다.
인터페이스와 클래스의 조합으로는 &을 통해 여러 가지 제한조건을 만들 수 있지만, 클래스의 조합으로는 불가능하다. 클래스로 제한 조건을 만드려면 포함 관계를 잘 이용하자.
와일드 카드 "?"
사실 Object에 기능 몇 개 추가한거다.
와일드카드는 어떤 타입도 될 수 있음 + extends로 상한 (그 자손들만 가능) + super로 하한 (그 조상들만 가능) + static메서드에서 T를 못쓰는 대신 ?는 사용할 수 있음
(와일드카드에는 & 사용 불가능. 지네릭과 다름.)
? extends Object로 작성하면 모든 종류의 FruitBox가 매개변수로 가능해진다.
자바의 sort 메서드는 ? super T 와 같이 지네릭 타입에 하한 제한을 걸어놓는다.
앞에서 말한 new연산자에 의해 매개변수에 ? 를 작성하면 오류가 발생한다.
지네릭 메서드
매개변수의 타입이 복잡할 때 유용하다. 코드를 간략하게 작성할 수 있음.
왼쪽처럼 반환타입 앞에 <T>를 붙여서 사용한다.
아니 근데 스태틱 메서드에서 T는 못쓰는거 아니였나??
static 메서드의 매개변수에 T를 사용할 수 없는건 맞지만, 클래스에서 정의된 T와 완전히 다른 개념인 T를 새로 정의해서 스태틱 메서드를 만드는 건 가능하다.
메서드에 선언된 지네릭 타입은 지역변수처럼 생각하자. 그래서 static도 지네릭 메서드와 함께 쓰일 수 있다.
지네릭 메서드에서도 지네릭의 역할은 컴파일 시 타입을 체크해 주는 역할을 한다.
그냥 코드를 볼 때는 지네릭스 표기인 <T>를 통해 아 저 메서드에서 <>안의 T와 인자로 받는 T는 서로 관련이 있겠구나 라고 생각하면 된다. (앞의 조건이 더 강력해야 함.)
지네릭 메서드를 호출 시 타입 변수에 타입을 대입해야하는데, 대부분의 경우 컴파일러가 알아서 해 준다.
지네릭 타입의 형변환
왼쪽처럼 코드를 작성했을 때, 원시 타입은 Box가 되고 지네릭 타입은 Box<pen>이 된다.
대입된 타입이 같은 지네릭 타입과 non지네릭 타입간의 형변환은 항상 성립한다.
대입된 타입이 다른 경우는 형변환이 성립하지 않는다.
위의 코드가 오류를 나타내는 것처럼,
Object라고 해도 Box<Object> 가 Box<String>으로 변환될 수 없다.
와일드 카드가 사용된 지네릭 타입으로는 형변환 가능함.
컴파일러는 지네릭 타입을 이용해서 소스파일을 체크하고, 필요한 곳에 형변환을 넣어준다. 컴파일 후에는 지네릭 타입을 제거한다.
컴파일된 파일에는 지네릭에 대한 정보가 없다.
지네릭스는 JDK1.5부터 도입돼 적용되고 있고, 지네릭이 도입되기 이전 소스코드와의 호환성을 유지하기 위해 지네릭 타입을 제거하는 것이다. 하지만 자바가 언제 호완성을 포기할지 모르니 원시 타입을 사용하지 말고 코드를 작성하자.
'Programming Language > Java' 카테고리의 다른 글
[Java] 스트림 (Stream) (0) | 2021.10.25 |
---|---|
[Java] 람다식 (Lambda expression) (0) | 2021.10.25 |
[Java] 컬렉션 프레임웍 (Collections Framework) (0) | 2021.10.19 |
[Java] 열거형 (enums) (0) | 2021.10.18 |
[Java] Scanner클래스의 nextLine()과 next() 사용 시 주의점 (0) | 2021.10.05 |
댓글
이 글 공유하기
다른 글
-
[Java] 람다식 (Lambda expression)
[Java] 람다식 (Lambda expression)
2021.10.25 -
[Java] 컬렉션 프레임웍 (Collections Framework)
[Java] 컬렉션 프레임웍 (Collections Framework)
2021.10.19 -
[Java] 열거형 (enums)
[Java] 열거형 (enums)
2021.10.18 -
[Java] Scanner클래스의 nextLine()과 next() 사용 시 주의점
[Java] Scanner클래스의 nextLine()과 next() 사용 시 주의점
2021.10.05