제네릭 클래스와 상속
제네릭 클래스도 상속이 가능하다.
class Box{
protected T ob;
public void set(T o){
ob = o;
}
public T get(){
return ob;
}
}
class SteelBox extends Box{
public SteelBox(T o){
ob = o;
}
}
public class Main1 {
public static void main(String[] args) {
Box iBox = new SteelBox<>(7959);
Box sBox = new SteelBox<>("simple");
System.out.println(iBox.get());
System.out.println(sBox.get());
}
}
제네릭 상속을 설명하기 위해
class SteelBox<T> extends Box<T>{
public SteelBox(T o){
ob = o;
}
}
두 제네릭스가 상속 관계를 구성하면
Box<Integer> iBox = new SteelBox<>(7959);
Box<String> sBox = new SteelBox<>("simple");
매개변수 타입 또는 제네릭 타입
Box<Integer> iBox
Box<String> sBox
타입이란 단어가 포함된 것은 Box<Integer>를 일종의 자료형으로 정확히는 클래스 이름을 간주함. -> 상속 관계가 형성 될 수 있다.
하지만
Box<Number> box = new Box<Integer>는 상속 관계 성립 X
Number를 Integer가 상속하지만, Box<Number> 와 Box<Integer>는 상속관계 성립 하지 않는다.
타겟 타입
class Box1{
private T ob;
public void set(T o){
ob = o;
}
public T get() {
return ob;
}
}
class EmptyBoxFactory{
public static Box1 makeBox(){
Box1 box = new Box1<>();
return box;
}
}
public class Main2 {
public static void main(String[] args) {
Box1 ibox = EmptyBoxFactory.makeBox();
ibox.set(25);
System.out.println(ibox.get());
}
}
제네릭 메소드 정의
public static <T> Box1<T> makeBox(){
Box1<T> box = new Box1<>();
return box;
}
Box1 클래스의 makeBox 메소드와 달리 인자를 전달 받지 않는다. 따라서 compiler로 T 유추 불가능. 따라서 T에 관한 인자를 전달해줘야 한다.
Box1<Integer> ibox = EmptyBoxFactory.<Integer>makeBox();
makeBox 메소드는 Box<Integer> 인스턴스의 참조 값을 반환해야 한다고 판단 할 수 있다, 따라서 makeBox 메소드 호출시, T는 Integer가 되어야 한다는 것을 알 수 있다. T의 유추 정보를 타겟 타입이라고 한다.
와일드 카드
class Box2{
private T ob;
public void set(T o){
ob = o;
}
public T get(){
return ob;
}
@Override
public String toString(){
return ob.toString();
}
}
class Unboxer{
public static T openBox(Box2 box2){
return box2.get();
}
public static <T> void peekBox(Box2<T> box){
System.out.println(box);
}
public static void peekBox2(Box2<?> box){
System.out.println(box);
}
}
public class Main3 {
public static void main(String[] args) {
Box2 box = new Box2<>();
box.set("simple string");
Unboxer.peekBox(box);
}
}
상자의 내용물들을 반환하지 않고, 무엇이 들었는지만 확인 할 정도
public static <T> void peekBox(Box2<T> box){
System.out.println(box);
}
Box<Integer> Box<String>의 인스턴스를 인자로 받게 하기 위함
Box<Object> 와 Box<String>/Box<Integer>는 상속 관계 형성하지 않는다.
class Box3<T>{
private T ob;
public void set(T ob){
ob = ob;
}
public T get(){
return ob;
}
@Override
public String toString(){
return ob.toString();
}
}
class Unboxer2{
public static void peekBoxt(Box3<? extends Number> box3){
System.out.println(box3);
}
public static void peekBox(Box3<? super Integer> box4){
System.out.println(box4);
}
}
public class Main4 {
public static void main(String[] args) {
Box3<Integer> box = new Box3<>();
box.set(1234);
Box3<Number> box3 = new Box3<>();
box3.set(new Integer(9995));
Box3<Object> box4 = new Box3<>();
box4.set("My Simple Instance");
Box3<Double> box1 = new Box3<>();
box1.set(1.12);
Unboxer2.peekBoxt(box);
Unboxer2.peekBoxt(box1);
Unboxer2.peekBox(box3);
Unboxer2.peekBox(box4);
}
}
와일드 카드 라는 것을 사용하면 상속 관계 성립 시킬 수 있다.
public static void peekBoxt(Box3<? extends Number> box3){
System.out.println(box3);
}
public static void peekBox(Box3<? super Integer> box4){
System.out.println(box4);
}
}
Box<T>로 기반으로 생성된 Box<Integer> 인스턴스나 Box<String> 인스턴스들을 인자로 받는다
코드가 조금 더 간결하다는 이유로 와일드카드 기반 메소드를 더 선호
와일드카드의 상한선 과 하한선
상한 제한, 하한 제한을 문법적 측면에서 보면,
public static void peekBox2(Box2<?> box){
System.out.println(box);
}
Box<T>에서 T가 Number 또는 Number의 하위 클래스인 제네릭 타입의 인스턴스만 전달. 제한할 때 상한 제한된 와일드카드라는 것을 사용
Box <? Extends Number> Java
Box<T> 인스턴스를 참조하는 참조변수이다.
이때 Box<T> 인스턴스의 T는 Number 또는 이를 상속하는 하위 클래스이어야 한다.
public static void peekBox(Box3<? super Integer> box4){
System.out.println(box4);
}
peekBox의 매개 변수에 제한을 걸어 Box<Integer>, Box<Double>과 제네릭 타입의 인스터스만 인자로 전달.
하한 제한된 와일드카드
Box<? Super Integer>
Box<T> 인스턴스 T의 Integer 또는 Integer가 상속하는 클래스 이어야 한다.
언제 와일드카드에 제한을 걸어야 할까?
Box<T>의 T를 Number 또는 Number를 직접적으로 상속하는 클래스로 제한하기 위한 것 /
Box<T>의 T를 Integer 또는 Integer가 직간접으로 상속하는 클래스로 제한
Public static<T> void copy(List<? Super T>, dest, List<? Extends T src)
Collections 클래스의 복사 메소드
상한제한의 목적
class Box5<T>{
private T ob;
public void set(T ob){
ob = ob;
}
public T get(){
return ob;
}
}
class Toy {
@Override
public String toString(){
return "I'm Toy";
}
}
class BoxHandler{
public static void outBox(Box5<? extends Toy> box){
Toy t = box.get();
System.out.println(t);
}
public static void inBox(Box5<? super Toy> box1, Toy n){
box1.set(n);
}
}
public class Main5 {
public static void main(String[] args) {
Box5<Toy> box = new Box5<>();
BoxHandler.inBox(box, new Toy());
BoxHandler.outBox(box);
}
}
필요한 만큼만 기능을 허용하여, 코드의 오류가 컴파일 과정에서 최대한 발견
Box를 대상으로 get 또는 set 호출도 가능
Box.set(new Toy()) 넣는 것도 ok
단 set의 빈번한 이용은 하지 않는게 좋다. 오류 발생 가능성이 있다. = 필요한 만큼만 매개변수 선언. 상자에서 꺼내는 것은 가능, 수정을 불가능.
Set 메소드의 호출이 불가능 한 이유는 Toy 인스턴스를 저장할 수 있는 상자만 인스턴스만 전달된다.
상속관계를 맺으면 outBox 메소드에 Box<car> 또는 Box<robot> 인스턴스가 인자로 전달 가능.
public static void outBox(Box5<? extends Toy> box){
Toy t = box.get();
System.out.println(t);
}
여기서 box.set(new Toy()) -> Error
따라서 Toy 인스턴스를 저장하는 메소드 호출은 불가능함.
class Box5<T>{
private T ob;
public void set(T ob){
ob = ob;
}
public T get(){
return ob;
}
}
class Toy {
@Override
public String toString(){
return "I'm Toy";
}
}
class BoxHandler{
public static void outBox(Box5<? extends Toy> box){
Toy t = box.get();
System.out.println(t);
}
public static void inBox(Box5<? super Toy> box1, Toy n){
box1.set(n);
}
}
public class Main5 {
public static void main(String[] args) {
Box5<Toy> box = new Box5<>();
BoxHandler.inBox(box, new Toy());
BoxHandler.outBox(box);
}
}
하한제한의 목적
필요한기능 만큼 허용, 코드 오류가 컴파일 과정에서 최대한 발견
상자에 인스턴스를 저장하는 것이 목적, get 메소드를 호출하는 코드가 삽입된다면 프로그래머의 실수
public static void outBox(Box5<? extends Toy> box){
Toy t = box.get();
System.out.println(t);
}
public static void inBox(Box5<? super Toy> box1, Toy n){
box1.set(n);
}
}
get 매소드 호출문에서 컴파일 오류가 발생.
반환형을 Toy로 결정 할 수 없기 떄문.
따라서 box가 참조하는 인스턴스를 대상으로 꺼내는 기능의 메소드 호출은 불가능하다.
와일드카드를 언제 제한을 걸어야 하는가?
매개변수 선언
Box<? Extends Toy> box
Box가 참조하는 인스턴스를 대상으로 꺼내는 작업만 허용하겠다는 의미
Box<? Super Toy> box
Box가 참조하는 인스턴스를 대상으로 넣는 작업만 허용하겠다는 의미
class Box6<T>{
private T ob;
public void set(T o){
ob = o;
}
public T get(){
return ob;
}
}
class Toy1{
@Override
public String toString(){
return "Toy";
}
}
class BoxContentMover{
public static void moveBox(Box6<? super Toy1> to, Box6<? extends Toy1> from){
to.set(from.get());
}
}
public class Main6 {
public static void main(String[] args) {
Box6<Toy1> box1 = new Box6<>();
box1.set(new Toy1());
Box6<Toy1> box2 = new Box6<>();
BoxContentMover.moveBox(box2, box1);
System.out.println(box2.get());
}
}
from으로 전달된 상자의 내용물을 to로 전달된 상자로 옮긴다.
public static void moveBox(Box6<? super Toy1> to, Box6<? extends Toy1> from){
to.set(from.get());
}
}
From.set(to.get()) -> 프로그램 오류
<문제>
class Boxt<T>{
private T ob;
public void set(T ob){
ob = ob;
}
public T get(){
return ob;
}
}
class BoundedWildcardDemo{
public static void addBox(Boxt<? super Integer> b1, Boxt<? extends Integer> b2, Boxt<? extends Integer> b3){
b1.set(b2.get() + b3.get());
}
}
public class Main7 {
public static void main(String[] args) {
Boxt<Integer> box = new Boxt<>();
box.set(24);
Boxt<Integer> box1 = new Boxt<>();
box1.set(37);
Boxt<Integer> result = new Boxt<>();
result.set(0);
BoundedWildcardDemo.addBox(result, box, box1);
System.out.println(result.get());
}
}
제한된 와일드카드 선언을 갖는 제네릭 메소드
Toy 클래스를 담은 상자 기준을 InBox와 outbox 메소드를 정의
class Box7<T>{
private T ob;
public void set(T o){
ob = o;
}
public T get(){
return ob;
}
}
class Toy2{
@Override
public String toString(){
return "Toy";
}
}
class Robot{
@Override
public String toString(){
return "robot";
}
}
class BoxHandler2{
public static <T> void outBox(Box7<? extends T> box7){
T t = box7.get();
System.out.println(t);
}
public static <T> void inBox(Box7<? super T> box7, T n){
box7.set(n);
}
}
public class Main8 {
public static void main(String[] args) {
Box7<Toy2> tBox = new Box7<>();
BoxHandler2.inBox(tBox, new Toy2());
BoxHandler2.outBox(tBox);
Box7<Robot> rBox = new Box7<>();
BoxHandler2.inBox(rBox, new Robot());
BoxHandler2.outBox(rBox);
}
}
public static <T> void outBox(Box7<? extends T> box7){
T t = box7.get();
System.out.println(t);
}
public static <T> void inBox(Box7<? super T> box7, T n){
box7.set(n);
}
}
class Robot{
@Override
public String toString(){
return "robot";
}
}
Box<Robot>의 인스턴스를 대상으로 outBox와 InBox 메소드를 호출.
<두 메소드는 오버로딩 인정 안됨>
public static <T> void outBox(Box7<? extends T> box7){
T t = box7.get();
System.out.println(t);
}
기술적 문제가 기인한다. 제네릭 등장 이전에 정의된 클래스들과 상호 호환성 유지를 위해 컴피일 시 제네릭과 와일드카드 관련 정보를 지우는 과정을 거친다. 즉 두 매개변수 선언은 컴파일 과정에서 다음과 같이 수정. 메소드의 오버로딩 성립 불가능
위와 같이 컴파일러가 제네릭 정보를 지우는 행위를 가리켜 Type Erasure라고 한ㄷ,
<두 메소드는 두번째 매개변수로 인해 오버로딩 인정 됨>
public static <T> void inBox(Box7<? super T> box7, T n){
box7.set(n);
}
}
제네릭과 관련 없는 두 번째 매개변수의 자료형이 다르기 때문이다.
<문제>
class Box8<T>{
private T ob;
public void set(T o){
ob = o;
}
public T get(){
return ob;
}
}
class BoundeWildcardGeneric{
public static <T> boolean compBox(Box8<? extends T> box, T com){
T bc = box.get();;
return bc.equals(com);
}
public static void main(String[] args) {
Box8<Integer> box = new Box8<>();
box.set(24);
Box8<String> box1 = new Box8<>();
box1.set("Poly");
if (compBox(box, 25))
System.out.println("상자 안에 25 적용");
if (compBox(box1, "Moly"))
System.out.println("상자안에 Moly 저장");
System.out.println(box.get());
System.out.println(box1.get());
}
}
제네릭 인터페이스의 정의와 구현
인터페이스 역시 제네릭 클래스와 마찬가지로 제네릭으로 정의
interface Getable<T>{
public T get();
}
class Box9<T> implements Getable<String>{//Getable<T>
private T ob;
public void set(T ob){
ob = ob;
}
@Override
public String get(){
return ob.toString();
}
}
class Toy3{
@Override
public String toString(){
return "Toy";
}
}
public class Main10 {
public static void main(String[] args) {
Box9<Toy3> box = new Box9<>();
box.set(new Toy3());
Box9<Toy3> gt = box;
System.out.println(gt.get());
}
}
class Box9<T> implements Getable<String>{
Box<T> 클래스는 Getable <T> 인터페이스를 구현하는 형태
Box9<Toy3> box = new Box9<>();
box.set(new Toy3());
Box9<Toy3> gt = box;
System.out.println(gt.get());
}
Getable<T>형 참조변수로 Box<T>의 인스터스를 참조할 수 있다. 단 T를 대신할 자료형이 동일해야 한다.