[스터디]Java의 정석 22일차

Kristopher·2022년 1월 24일
0

Java 스터디

목록 보기
22/31

(Ch12) 1. 지네릭스 ~ 2.4 열거형의 이해

지네릭스

지네릭스는 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입체크를 해주는 기능이다. 컴파일시에 객체타입을 체크하기 때문에 타입 안정성을 높이고 형변환의 번거로움을 줄일 수 있다.

지네릭스의 장점
1. 타입 안정성을 제공한다.
2. 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다.

지네릭 클래스의 선언은 다음과 같이 이루어진다.

//클래스 옆에 <T>를 붙이고 자료형 입력값을 T로 바꾼다.
class Box<T> {
    T item;
    
    void setItem(T item) {this.item = item;}
    T getItem() {return item;}
}

이 때 사용된 T는 Type variable의 첫 글자에서 따온 것이고 상황에 따라 적절한 기호를 사용하면 된다. ArrayList의 경우 요소(Element)라고 부르므로 ArrayList를 사용하고, Map의 경우 키(Key), 값(Value)를 사용하므로 Map<K,V>와 같이 표기한다. 이들은 기호만 다를 뿐 참조형 타입을 의미하므로 본질적으로는 같은 것이다.

지네릭 클래스를 선언한 이후, 객체를 생성할 때는 T대신에 실제 사용될 타입을 지정해주어야 한다.

Box<String> b = new Box<String>();
b.setItem("ABC");
String item = b.getItem(); // 형변환 불필요

<>안에 String타입을 지정했으므로, 지네릭 클래스가 다음과 같이 정의된 것이다.

class Box {
    String item;
  
    void setItem(String item) {this.item = item;}
    String getItem() {return item;}
}

지네릭스와 관련된 용어를 정리해보자.

class Box<T> {}
//Box<T> : 지네릭 클래스
//T : 타입 변수 혹은 타입 매개변수
//Box : 원시 타입(raw type)
Box<String> b = new Box<String>();
//String : 매개변수화된 타입(parameterized type), 대입된 타입

지네릭 클래스의 객체 생성과 사용

지네릭 클래스를 사용해 객체 생성시, 참조변수와 생성자에 대입된 타입이 일치해야 한다. 이는 두 타입이 상속관계에 있어모 마찬가지이다. 그렇기에 JDK1.7부터는 추정가능한 경우 타입을 생략할 수 있다.

Box<Apple> appleBox = new Box<Apple>();
Box<Apple> appleBox = new Box<>(); // 생성자에 대입된 타입 역시 Apple일 것이므로 생략 가능

제한된 지네릭 클래스

타입 매개변수 T에 지정할 수 있는 타입의 종류를 제한하기 위해서는 어떻게 해야할까? 이 경우 지네릭 타입에 extends를 사용하면, 특정 타입의 자손들만 대입할 수 있게 제한할 수 있다.

class FruitBox<T extends Fruit>{//Fruit의 자손 타입만 지정 가능
    ArrayList<T> list = new ArrayList<T>();  
}

이는 클래스 뿐만 아니라 인터페이스를 구현해야하는 상황에서도 implements대신 extends를 사용해야 한다는 점에 주의해야 한다.

interface Eatable {}
class FruitBox<T extends Eatable> {...}

와일드 카드

타입 매개변수로 사용하는 T도 일종의 지역변수이기 때문에 static멤버에는 사용할 수 없다. 그렇기에 static 메소드에는 지네릭스를 적용하지 않던가, 매개변수 대신 특정 타입을 지정해줘야 한다. 이 경우 지네릭 타입을 고정하게 되므로 각각의 타입별로 static 메소드를 구현할 수 밖에 없지만 이 경우 컴파일 에러가 발생한다. 지네릭 타입은 컴파일시에만 사용하고 제거되므로 메소드 중복 정의로 인한 에러가 발생하는 것이다. 이러한 상황에 사용 가능한 것이 와일드 카드이고, 기호로는 '?'를 사용한다.

< ? extends T> : 와일드카드의 상한제한. T와 그 자손들만 가능
< ? super T> : 와일드 카드의 하한제한. T와 그 조상들만 가능
< ? > : 제한 없음. 모든 타입이 가능

지네릭 메소드

메소드 선언부에 지네릭 타입이 선언된 메소드를 지네릭 메소드라고 한다.

//지네릭 메소드 예시
static <T> void sort (List<T> list, Comparator<? super T> c)

지네릭 클래스에 정의된 타입 매개변수와 지네릭 메소드에 정의된 타입 매개변수는 전혀 별개의 것이다. 같은 문자 T를 공유하지만 같은 것이 아니라는 사실에 주의해야 한다. 지네릭 메소드를 사용하면 static멤버에 타입 매개변수를 사용할 수 없었던 것을 가능하게 한다.

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

지네릭 메소드 호출 시 타입 변수에 타입을 대입해야 한다. 이 경우 대부분 컴파일러가 타입을 추정할 수 있기에 생략가능하지만, 대입된 타입을 생략할 수 없는 경우에는 참조변수나 클래스 이름을 생략할 수 없다.

System.out.println(Juicer.<Apple>makeJuice(fruitBox));
// 대입된 타입 생략 가능한 경우
System.out.println(Juicer.makeJuice(fruitBox));
// 대입된 타입 생략 불가능한 경우 (Juicer도 꼭 붙여야 함)
System.out.println(Juicer.<Fruit>makeJuice(fruitBox));

지네릭 타입의 형변환

지네릭 타입과 넌지네릭 타입간의 형변환은 경고가 발생하긴 하지만 가능하다. 하지만 대입된 타입이 다른 지네릭 타입간의 형변환은 불가능하다. 만일 대입된 타입끼리 다형성이 적용될 수 있다면 형변환이 예외적으로 가능하다.

지네릭 타입의 제거

지네릭 타입은 컴파일 후에는 제거되는데, JDK1.5부터 지네릭스가 도입되었기에 이전 버전과의 호환성을 위해 지우는 것이다.

지네릭 타입의 제거 과정
1. 지네릭 타입의 경계를 제거한다.
2. 지네릭 타입을 제거한 후에 타입이 일치하지 않으면, 형변환을 추가한다.

열거형(enums)

열거형은 관련된 상수를 편리하기 선언하기 위해 사용한다. 열거형이 갖는 값과 타입을 동시에 관리하기 때문에 논리적인 오류를 줄일 수 있다.

class Card {
    enum Kind {CLOVER, HEART, DIAMONT, SPADE}
    enum Value {TWO, THREE, FOUR}

    final Kind kind;
    final Value value;
}

자바의 열거형은 C언어와 달리 실제 값이 같아도 타입이 다르면 컴파일 에러가 발생한다. 기존에는 상수의 값이 바뀌면, 해당 상수를 참조하는 모든 소스를 다시 컴파일해야 했지만, 열거형 상수를 사용하면 다시 컴파일할 필요가 없다.

열거형의 정의

열거형을 사용할 때는 괄호 안에 상수의 이름을 나열하기만 하면 된다.

enum 열거형이름 {상수명1, 상수명2, ... }

모든 열거형은 java.lang.Enum을 조상으로 갖는다. 따라서 Enum클래스에 정의된 메소드들을 사용하여 열거형의 값들을 출력한다.

String name() : 열거형 상수의 이름을 문자열로 반환
int ordinal() : 열거형 상수가 정의된 순서를 반환한다.(0부터 시작)

열거형에 멤버 추가하기

Enum클래스에서 ordinal 메소드를 통해 정의된 순서를 반환할 수 있디만, 이 값은 내부적으로만 사용하고 열거형 상수의 값을 따로 지정하는 것이 적절하다. 열거형 상수의 값은 열거형 상수 이름 옆에 ()와 함께 적어주면 된다.

enum Direction {EAST(1), SOUTH(3), WEST(5), NORTH(7);}

열거형의 생성자는 암묵적으로 private이기 때문에 외부에서 호출이 불가하다. 또한 열거형 상수에 여러 값을 지정할 수도 있는데, 그에 맞춰 인스턴스 변수와 생성자 등을 새로 추가해주어야 한다.

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

    private final int value;
    private final String symbol;

    Direction(int value, String symbol){
	    this.value = value;
	    this.symbol = symbol;
    }
		...
}

Reference

Java의 정석
남궁성의 정석코딩

profile
개발자 지망생입니다.

0개의 댓글