chapter 12 지네릭스, 열거형, 애너테이션---1.5 와일드카드~

byeol·2022년 8월 13일
0

1.5 와일드 카드

사실 나는 이 파트 너무 헷갈린다.
이 와일드 카드가 제한된 지네릭 클래스와 어떤 차이가 있다는 말일까?
계속 찾아봤는데
✔️ 제한된 지네릭 클래스(메서드)는 선언되는 것이어서 대표적이고( 좀 더 무거운 느낌)
✔️ 와일드 카드는 클래스(메서드)가 아닌 참조변수의 타입에 추가적으로 하한,상한 등의 제한을 주는 것 ( 제한된 지네릭 클래보다 가벼운 느낌) 이다.

개념이 명확해지면 추가하도록 하겠다.
복습하면서 개념이 명확해져서 추가한다
+) 일단 제한된 지네릭 클래스는 클래스(메서드) 선언할 때 즉 맨 처음 우리가 이런 역할로 사용하는 클래스나 메서드를 만들 때 그 틀에서 적용한다. T는 누구를 상속받은 객체여야 한다...인터페이스를 구현해야 한다 등 처음에 하는 것
+) 와일드 카드는 매개변수로 들어가는 참조변수가 이 선언된 클래스의 객체를 받는 경우 즉, 활용할 때 와일드 카드를 이용한다. 이미 지정된 타입으로 콕 집지 않고 범위를 넣게끔 만들어 준것!
static Juice makeJuice(FruitBox<Apple> box)->Apple로 콕 찍었다.
static Juice makeJuice(FruitBox<? extends Fruit> box)->Fruite의 자손이므로 콕에서 범위로 바뀜..

아래 예시를 보자
static 메서드의 매개변수인 참조형 변수 box에 <Fruit> 지네릭을 사용하였다.
앞 서 말했지만 static 메서드에는 T(인스턴스 변수 성격)를 매개변수로 사용할 수 없다. 따라서 여기 예시는 아예 특정타입 Fruit를 지정해주었다.

class Juicer {
  static Juice makeJuice(FruitBox<Fruit> box) {
    String tmp ="";
    for(Fruit f:box.getList()) tmp+=f+" ";
    return new Juice(tmp);
   }
  }

그렇다면 만약에 우리가 <Fruit>가 아닌 <Apple>이 필요하다면
저 Juicer 클래스에 static Juice makeJuice(FruitBox<Apple> box) 메서드를 하나 더 정의해야만 한다.

class Juicer {
static Juice makeJuice(FruitBox<Fruit> box) {
  String tmp ="";
  for(Fruit f:box.getList()) tmp+=f+" ";
  return new Juice(tmp);
 }
 static Juice makeJuice(FruitBox<Apple> box) {
  String tmp ="";
  for(Fruit f:box.getList()) tmp+=f+" ";
  return new Juice(tmp);
 }
}

우리는 앞서 이것을 오버로딩이라고 배웠다. 하지만 이는 오버로딩이 아닌 에러이다.
왜냐하면 지네릭타입은 컴파일러가 컴파일할 때마 이용하고 제거해버리기 때문에
중복된 메서드 정의이며 에러이다.

그렇다면 어떻게 해야할까?
참조변수에 무언가 제한을 줄 수 있는 방법이 없을까?
이 때 우리는 와일드 카드를 이용한다.
와일드 카드는 '?' 라는 기호를 이용한다.

< ? extends T > : 와일드 카드의 상한 제한. T와 그 자손들만 가능
< ? super T > : 와일드 카드의 하한 제한. T와 그 조상들만 가능
< ? > : 제한 없음. 모든 타입이 가능. < ? extends Object >와 동일
참고로 &을 사용할 수 없다.

따라서 우리는 makeJuice 메서드의 참조형 매개변수를 아래와 같이 바꾸자.

static Juice makeJuice(FruitBox<? extends Fruit> box) {
  String tmp ="";
  for(Fruit f:box.getList()) tmp+=f+" ";
  return new Juice(tmp);
 }

예시를 통해 더 자세히 알아보자

import java.util.*;

class Fruit {
	String name;
	int weight;
	
	Fruit(String name, int weight) { //생성자
		this.name=name;
		this.weight = weight;
	}
	public String toString() {return name+"("+weight+")";}
}
class Apple extends Fruit {
	Apple(String name, int weight){
		super(name, weight);
	}
}
class Grape extends Fruit {
	Grape(String name, int weight) {
		super (name, weight);
	}
}

class AppleComp implements Comparator<Apple>{
	public int compare(Apple a1, Apple a2) {
		return a2.weight - a1.weight;
	}
}
class GrapeComp implements Comparator<Grape>{
	public int compare(Grape g1, Grape g2) {
		return g2.weight-g1.weight;
	}
}

class FruitComp implements Comparator<Fruit>{
	public int compare(Fruit f1, Fruit f2) {
		return f1.weight-f2.weight;
	}	
}

class FruitBoxEx2 {
	public static void main(String[] args) {
          FruitBox<Apple> appleBox = new FruitBox<>();
          FruitBox<Grape> grapeBox = new FruitBox<>();
          
          appleBox.add(new Apple("GreenApple",300));
          appleBox.add(new Apple("RedApple",200));
          appleBox.add(new Apple("YellowApple",100));
          
          grapeBox.add(new Grape("GreenGrape",300));
          grapeBox.add(new Grape("RedGrape",200));
          grapeBox.add(new Grape("YellowGrape",100));
          
          Collections.sort(appleBox.getList(),new AppleComp());
          Collections.sort(grapeBox.getList(),new GrapeComp());
          System.out.println("appleBox-"+appleBox);
          System.out.println("grapeBox-"+grapeBox);

          System.out.println();
          
          Collections.sort(appleBox.getList(),new FruitComp());
          Collections.sort(grapeBox.getList(),new FruitComp());
          System.out.println("appleBox-"+appleBox);
          System.out.println("grapeBox-"+grapeBox);       
          
	}
}

class FruitBox<T extends Fruit> extends Box<T> {}

class Box<T>{
	ArrayList<T> list = new ArrayList<T>();
	void add (T item) {list.add(item);}
	T get (int i) {return list.get(i);}
	ArrayList<T> getList(){return list;}
	int size() {return list.size();}
    public String toString() {return list.toString();}
  
}




👆 Collections 클래스의 sort메서드를 java API에 들어가서 캡처했다.
공식적으로 아래와 같다.

static <T> void sort(List list, Comparator<? super T> c)

이렇게 설정되었기 때문에 List에 요소로 Apple이 들어가 있어도 Comparator를 구현한 클래스가 Apple요소를 객체로 쓰거나 혹은 Apple의 조상을 요소로 쓰는 객체까지도 가능하도록 sort 메서드가 구현되어져 있다.

1.6 지네릭 메서드

static <T> void sort(List list, Comparator<? super T> c)

메서드 선언부에 지네릭 타입이 선언된 메서드를 지네릭 메서드라고 하며
반환타입 바로 앞(!)에 위치한다.

주의할 것이 있다면
지네릭 메서드의 T는 지역변수의 의미가 있기 때문에
지네릭 클래스에 정의된 타입 매개변수 T(멤버 변수 중 인스턴스 변수의 의미)와 다른 것이다.
따라서 그 둘은 변수 T라는 이름은 같이 사용하지만 서로 다른 박자로, 다른 용도로, 전혀 다르게 사용된다는 것을 알아두자. (지역변수와 인스턴스변수)

class FruitBox <T>{
   ...
  static <T> void sort(List<T> list, Comparator<? super T> c) {
   ...
  }
}

한가지 더 짚고 넘어가자.
static멤버와 사용할 수 없는 타입 매개변수 T
하자민 static 메서드와는 사용가능하다. 그 이유는 앞서 말했던 주의해야할 점과 관련이 있는데 static멤버에 사용되는 T는 지네릭 클래스의 T이기 때문에 인스턴스 변수여서 사용이 불가능하다. 어떻게 인스턴스 변수 T에 static을 붙일 수 있겠는가?
그리고 static 메서드의 T는 메서드의 지역변수 의미가 있기 때문에 가능하다.
공부하다 보면 이 static에 집착하여 뭔가 static과 T가 연관이 깊은 것처럼 보이지만 그냥 T도 변수이기 때문에 변수와의 관계랑 똑같다!

지네릭 메서드로 바꾸는 방법

static Juice makeJuice(FruitBox<? extends Fruit> box) {
  String tmp ="";
  for(Fruit f : box.getList()) tmp+=f+"";
  return new Juice(tmp);
 }

⬇️

static <T extends Fruit> Juice makeJuice(FruitBox<T> box) {
  String tmp ="";
  for(Fruit f : box.getList()) tmp+=f+"";
  return new Juice(tmp);
 }

자 몇 가지 선언할 때 규칙들이 있다.

FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Aplle> appleBox = new FruitBox<Apple>();
System.out.println(Juicer.<Fruit>makeJuice(fruitBox));
System.out.println(Juicer.<Apple>makeJuice(appleBox));

② 아래와 같이 컴파일러가 타입을 추정할 수 있는 경우 생략 가능

FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Aplle> appleBox = new FruitBox<Apple>();
System.out.println(Juicer.makeJuice(fruitBox));
System.out.println(Juicer.makeJuice(appleBox));

③ 단, 지네릭 메서드를 호출할 때 대입된 타입을 생략할 수 없는 경우에는
참조변수나 클래스 이름을 생략할 수 없다.

FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Aplle> appleBox = new FruitBox<Apple>();

System.out.println(<Fruit>makeJuice(fruitBox));//에러
System.out.println(this.<Fruit>makeJuice(appleBox));
System.out.println(Juicer.<Apple>makeJuice(appleBox));

지네릭 메서드는 매개변수의 타입이 복잡할 때도 유용하다.

public static void printAll(ArrayList <? extends Product> list,
                           ArrayList <? extends Product> list2) {

   for(Unit u: list) 
    System.out.println(u);
}

⬇️

public static void <T extends Product> printAll(ArrayList <T> list,
                                             ArrayList <T> list2) {
   for(Unit u: list) 
    System.out.println(u);
}

1.7 지네릭 타입의 형변환

① 원시타입과 지네릭 타입 간의 형변환 : 가능 하지만 경고 발생
Box box = null;
Box objBox = null;

box = (Box) objBox; // 가능, 하지만 경고는 발생
objBox = (Box) box ; // 가능, 하지만 경고는 발생

② 대입된 타입이 다른 지네릭 타입 간의 형변환 : 에러 Error!
Box objBox = null;
Box strBox = null;

objBox = (Box)strBox;//Error
strBox = (Box)objBox;//Error

③ 참조변수와 생성자에 대입된 타입이 일치해야 한다.( 앞서 배운 내용 1.3)
Box objBox = new Box();//Error

④ 다형성 제공
Box<? extends Object> objBox = new Box();//다형성으로 인한 형변환 가능

👀 앞 서 배웠던 내용과 같다.
static Juice makeJuice(FruitBox<? extends Fruit> box){...}

FruitBox<? extends Fruit> box = new FruitBox();//OK
FruitBox<? extends Fruit> box = new FruitBox();//OK
FruitBox<? extends Fruit> box = new FruitBox();//OK

모두 다형성으로 인해 가능했다.

⑤ ④와 반대의 경우 : 가능, 하지만 경고 발생
FruitBox<? extends Fruit> box = null;
FruitBox appleBox = (FruitBox)box;//<? extends Fruit>에 대입될 수 있는 타입 여러개

⑥ 실질적인 예를 통해 더 자세히 알아보자

위에 파란 칸은 이렇게 3가지 방법으로 표현될 수 있으나 저렇게 빈 Optional<?>로 Empty를 한 이유는 형변환 때문이다.

Optional<?> wopt = new Optional();
Optional oopt = new Optional();

Optional sopt = (Optional) wopt;//OK
Optional sopt = (Optional) oopt;//Error

즉 정리하자면 Optional를 Optional으로 직접 형변환 하는 것은 불가능하지만 와일드 카드가 포함된 지네릭 타입으로 형변환하면 가능하다.

2 열거형(enums)

2.1 열거형이란?

✔️ 서로 관련된 상수를 편리하게 선언하기 위한 것
✔️ JDK1.5부터 추가된 기능

beforeAfter
class Card{class Card{
static final int CLOVER =0;enum Kind {CLOVER, HEART, DIAMOND, SPADE};
static final int HEART =1;enum Value {TWO, THREE, FOUR};
static final int DIAMOND =2;final Kind kind;
static final int SPADE = 3;final Value value; }
static final int TWO =0;
static final int THREE =1;
static final int FOUR=4;
final int kind;
final int num; }

자바의 열거형은 '타입에 안전한 열거형' 즉 실제값이 같아도 타입이 다르면 컴파일 에러가 발생 ==> 값 뿐만 아니라 타입까지 체크하여 타입에 안전하다!

상수의 값이 바뀌면 해당 상수를 참조하는 모든 소스를 다시 컴파일 해야한다는 것
하지만 열거형 상수를 사용하면 기존의 소스를 다시 컴파일 하지 않아도 된다!

2.2 열거형의 정의와 사용

열거형에 대한 정의

enum 열거형이름 {상수명1, 상수명2, ...}
정의된 상수 사용법 : '열거형이름.상수명'

① 열거형 상수간의 비교에는 equals()가 아닌 '=='를 사용
② '<','>'와 같은 비교 연산자가 아닌 comporeTo() 사용
같으면0, 왼쪽이 크면 양수, 작으면 음수
③ switch 조건식에도 열거형 사용, 이 때 주의할점은 case문에 열거형의 이름은 적지 않고 상수의 이름만 적는다.

모든 열거형의 조상 -java.lang.Enum

열거형에 정의된 모든 상수를 출력하려면 values()메서드 이용
이 메서드는 열거형의 모든 상수를 배열에 담아 반환
이 메서드는 모든 열거형이 가지고 있는 것으로 컴파일러가 자동으로 추가해준다.

마찬가지로 static E valueOf(String name) 이 메서드도 자동적으로 추가해주는데
열거형 상수의 이름으로 문자열 상수에 대한 참조를 얻을 수 있게 해준다.

메서드설명
Class getDeclaringClass()열거형의 Class 객체를 반환한다.
String name()열거형의 상수의 이름을 문자열로 반환한다.
int ordinal()열거형 상수가 정의된 순서를 반환한다.(0부터 시작)
T valueOf(Class enumType,String name)지정된 열거형에서 name과 일치하는 열거형 상수를 반환한다.
enum Direction {EAST, SOUTH, WEST, NORTH}
public class EnumEx1 {
	public static void main(String[] args) {
		Direction d1= Direction.EAST;
		Direction d2 =Direction.valueOf("WEST");
		Direction d3 = Enum.valueOf(Direction.class,"EAST");
	   
		System.out.println("d1="+d1);
		System.out.println("d2="+d2);
		System.out.println("d3="+d3);
		
		System.out.println("d1==d2 ? "+(d1==d2));
		System.out.println("d1==d3 ? "+(d1==d3));
		System.out.println("d1.equals(d3) ? "+(d1.equals(d3)));
		// System.out.println("d2>d3 ? "+(d2>d3)); 에러
		System.out.println("d1.compareTo(d3) ? "+d1.compareTo(d3));//같으면 0, 왼쪽이 크면 양수, 작으면 음수
		System.out.println("d1.compareTo(d2) ? "+d1.compareTo(d2));
		
		switch(d1) {
		case EAST : 
			System.out.println("The direction is EAST.");break;
		case SOUTH: 
			System.out.println("The direction is SOUTH.");break;
		case WEST : 
			System.out.println("The direction is WEST.");break;
		case NORTH : 
			System.out.println("The direction is NORTH.");break;
		default : 
			System.out.println("Invalid direction.");break;
		
		}
		
		Direction [] Arr = Direction.values();
		
		for(Direction d : Arr)
			System.out.printf("%s=%d%n",d.name(),d.ordinal());
	}

}

2.3 열거형에 멤버 추가하기

Enum클래스에서 정의된 ordinal()이 열거형 상수가 정의된 순서를 반환하지만, 이 값을 열거형 상수의 값으로 사용하지 않는 것이 좋다.

열거형 상수의 값이 불연속적인 경우에는

enum Direciton {EAST(1), SOUTH(5), WEST(-1), NORTH(10)}

위와 같이 열거형 상수 이름 옆 괄호에 그 값을 함께 적어준다.
① 그 후에 지정된 값을 저장할 수 있는 인스턴스 변수
② 생성자를 추가 (하지만 외부에서 생성 불가능-> 묵시적으로 private)
③ 또한 열거형 상수 값에는 여러개의 값을 저장할 수도 있다.

enum Direction {
EAST(1,">"), SOUTH(2,"V"), WEST(3,"<"),NORTH(4,"^");
...}

정리하면 아래 예시와 같다.

enum Direction {
	EAST(1,">"), SOUTH(2,"V"), WEST(3,"<"),NORTH(4,"^");
	
	private static final Direction[] DIR_ARR = Direction.values();
	private final int value;
	private final String symbol;
	
	Direction(int value, String symbol) {//생성자
		this.value=value;
		this.symbol=symbol;
	}
	
	public int getValue() {return value;}
	public String getSymbol() {return symbol;}
	
	public static Direction of(int dir) {//dir에 있는 값 실제로는 dir-1
		if(dir<1||dir>4) {
			throw new IllegalArgumentException("Invalid value : "+dir);
		}
		return DIR_ARR[dir-1];
		
	}
	public Direction rotate(int num) {
		num=num%4;//4번 돌면 제자리이므로 4로 나눈 나머지만
		if(num<0) num+=4;
     //시계 반대로 90도씩 n번 도는 것 = 시계 방향으로 90도씩 n+4번 도는 것 
		
		return DIR_ARR[(value-1+num)%4];
        //DIR_ARR의 index는 0부터 시작하므로
	}
	public String toString() {
		return name()+getSymbol();
	}
}
class EnumEx2 { 
	public static void main(String[] args) {
	
	  for(Direction d : Direction.values())
		  System.out.printf("%s=%d%n",d.name(),d.getValue());
	  
	  Direction d1 = Direction.EAST;
	  Direction d2 = Direction.of(1);
	  
	  System.out.printf("d1=%s, %d%n",d1.name(),d1.getValue());
	  System.out.printf("d2=%s, %d%n",d2.name(),d2.getValue());
	  System.out.println(Direction.EAST.rotate(1));
	  System.out.println(Direction.EAST.rotate(2));
	  System.out.println(Direction.EAST.rotate(-1));
	  System.out.println(Direction.EAST.rotate(-2));
	}
	
}


👆 나는 여기서 Direction.EAST.rotate(1);이 표현이 정말 신기한거 같다.
열거형 Direction의 상수 EAST가 메서드 rotate에 접근한다는 표현법을 기억하자.

열거형에 추상 메서드 추가하기

✔️ 열거형 Transportation은 운송수단의 종류별로 상수를 정의
✔️ 각 운송수단에는 기본 요금이 책정되어 있다.

enum Transportation {
  BUS(100), TRAIN(150), SHIP(100), AIRPLANE(300);//종류별 상수
  
  private final int BASIC_FARE;//기본요금
  private Transportation(int basicFare){
      BASIC_FARE=basicFare;
   }
 int fare() {
     return BASIC_FARE;
  }


}

그러나 우리는 여기에 각 운송수단 별로 거리에 따라 요금을 계산하는 방식이 있을것이며 이것이 각각 수단마다 다르다는 것을 안다. 이럴 때 열거형 추상메서드 fare를 선언해보자.

enum Transportation {
  BUS(100) { int fare(int distance) {return distance*BASIC_FARE;}},
   TRAIN(150){ int fare(int distance) {return distance*BASIC_FARE;}},
   SHIP(100){ int fare(int distance) {return distance*BASIC_FARE;}}, 
   AIRPLANE(300){ int fare(int distance) {return distance*BASIC_FARE;}};
  
  abstract int fare(int distance);//거리에 따라 요금을 측정하는 추상 메서드
  //private final int BASIC_FARE;
  protected final int BASIC_FARE; //각 상수에서 접근하기 위해

  Transportation(int basicFare){
      BASIC_FARE=basicFare;
   }
 public int getBasicFare() {return BASIC_FARE;}


}

예시를 통해 자세히 알아보자

enum Transportation {
	BUS(100) {int fare(int distance){ return BASIC_FARE*distance;}},
	TRAIN(150){int fare(int distance){ return BASIC_FARE*distance;}},
	SHIP(100){int fare(int distance){ return BASIC_FARE*distance;}},
	AIRPLANE(300){int fare(int distance){ return BASIC_FARE*distance;}};
	
	protected final int BASIC_FARE;
	
	Transportation (int basicFare) {
		BASIC_FARE=basicFare;
	}
	
	public int getBasicFare() {return BASIC_FARE;}
	
	abstract int fare(int distance);
	
	
}
public class EnumEx3 {
	public static void main(String[] args) {
		System.out.println("bus fare="+Transportation.BUS.fare(100));
		System.out.println("train fare="+Transportation.TRAIN.fare(100));
		System.out.println("ship fare="+Transportation.SHIP.fare(100));
		System.out.println("airplane fare="+Transportation.AIRPLANE.fare(100));
	}

}

2.4 열거형의 이해

profile
꾸준하게 Ready, Set, Go!

0개의 댓글

관련 채용 정보