<참고자료>
자바의 정석 3판 - 열거형(enums)
열거형이란?
서로 관련된 상수를 편리하게 선언하기 위한 것으로, 여러 상수를 정의할 때 사용하면 유용하다.
열거형이 가지는 값 뿐만아니라 타입까지 관리하기 때문에 논리적 오류를 줄일 수 있다.
포커 카드를 클래스로 선언하는 경우의 예제를 알아보자.
포커카드는 그림과 같이 2가지(값, 모양)로 구분된다
카드 그림의 모양 : 클로버 / 하트 / 스페이드 / 다이아몬드
카드의 값 : A , 2 , ...., J, Q, K
enums을 사용하지 않을 경우
class Card{
static final int CLOVER = 0;
static final int HEART = 1;
static final int DIAMOND = 2;
static final int SPADE = 3;
static final int TWO = 0;
static final int THREE = 1;
static final int FOUR = 2;
final int kind;
final int num;
}
enums를 사용할 경우
class Card{
enum Kind { CLOVER, HEART, DIAMOND, SPADE } //열거형 Kind 정의
enum Value { TWO, THREE, FOUR } //열거형 Value 정의
final Kind kind;
final Vlaue value;
}
Java Enums 특징
타입에 안전한 열거형이다(typesafe enum)
타입이 달라도 값이 같으면 조건식 결과가 참(true)인 언어들이 있다. 하지만, 자바에서는 실제 값이 같아도 타입이 다르면, 컴파일 에러가 발생한다.
즉, 단순한 값 뿐 아니라 타입까지 체크한다.
타입에 안전하지 않은 경우
if(Card.CLOVER == Card.TWO) //true
위의 예시코드를 보면, 둘다 int로 선언되었으며, 값이 0 이기 때문에,
int 0 == int 0 이므로, true가 반환되지만,
프로그래머 입장에서는 둘을 같다고 판단하려고 짠 코드가 아닐 것이다.
자바의 열거형은 이를 지원한다.
타입에 안전(Java)
if(Card.Kind.CLOVER == Card.Value.TWO) //컴파일 에러
자바에선 이 코드에 대해 컴파일 에러를 띄운다.
서로 다른 Type을 비교했기 때문이다.
이처럼 Java의 enum을 사용하면, 값 뿐 아니라 Type까지 체크할 수 있다.
또한, 상수의 값이 바뀌어도, 별도의 변경이 필요하지 않다.(재 컴파일 X)
전자의 코드의 경우 값이 변경될 경우, 해당 상수를 참조하는 모든 소스코드를 다시 컴파일 해야한다.
하지만, 자바 enum을 사용한 경우 소스를 다시 컴파일 할 필요가 없다.
열거형(enums)의 사용
열거형의 정의
enum 열거형이름 { 상수명1, 상수명2, ... }
열거형의 사용
열거형을 사용하려는 클래스에서 열거형 클래스를 정의 한 후, 열거형이름.상수명 으로 접근한다 (클래스의 static 변수 참조와 동일)
enum Direction { EAST, SOUTH, WEST, NORTH }
class Unit{
int x, y; // 해당 유닛의 좌표
Driection dir; // 열거형을 인스턴스 변수로 선언
void init(){
dir = Direction.EAST; // 열거형이름.상수명 으로 접근
}
}
열거형 상수의 비교
1. '=='사용가능
equals()와 ==를 제공한다. (후자가 매우 빠른 성능)
2. '>' , '<' 사용불가
위와 같은 비교연산자는 사용할 수 없으며, compareTo()를 사용 가능하다.
* compareTo함수는 같으면 0, 왼쪽이 크면 양수, 오른쪽이 크면 음수 를 반환한다.
if (dir == Direction.EAST) {
...
} else if(dir.compareTo(Direction.WEST) > 0 {
...
}
참고로 switch문의 조건식에도 열거형을 사용할 수 있다.
주의 할 사항은 case문의 열거형은 "열거형이름.상수명" 이 아닌 단순하게 "상수명"만을 적는다.
switch(dir) {
case EAST:
x++;
break;
case WEST:
...
}
열거형의 출력
Direction[] dArr = Direction.values();
for(Direction d : dArr)
System.out.printf("%s = %d\n", d.name(), d.ordinal());
/* 정의한 순서대로
* EAST = 0
* SOUTH = 1
* WEST = 2
* NORTH = 3
*/
열거형 Direction에 정의된 모든 상수를 출력하는 방법이다.
- values() : 열거형의 모든 상수를 배열에 담아 반환한다.
- name() : 열거형 상수의 이름을 문자열로 반환한다.
- ordinal() : 열거형 상수가 정의된 순서를 반환한다. (0부터 시작하며, int형으로 반환됨)
valueOf()
열거형 상수의 이름으로 문자열 상수에 대한 참조를 얻을 수 있게 해준다.
지정된 열거형에서 name과 일치하는 열거형 상수를 반환한다.
2가지 방식으로 사용가능하다.
Direction d1 = Direction.valueOf("WEST"); //d1 = WEST
Direction d2 = Enum.valueOf(Direction.class, "WEST"); // d2 = WEST
후자와 같은 방법으로 사용하는 경우에는 Class<T> enumType을 함께 써주어야 한다.
열거형에 멤버 추가
열거형에서 ordinal()에서 반환된 상수는 정의된 순서를 반환하지만,
이를 실제 상수값으로 사용하지 않는 것이 좋다. 내부적인 용도로 사용되기 위해 존재하는 것이기 때문
Direction[] dArr = Direction.values();
for(Direction d : dArr)
System.out.printf("%s = %d\n", d.name(), d.ordinal());
/* 정의한 순서대로
* EAST = 0
* SOUTH = 1
* WEST = 2
* NORTH = 3
*/
열거형 상수 정의
1. 열거형 상수 값 정의
열거형 상수 값이 불연속적인 경우에는 , 열거형 상수 이름 옆에 원하는 값을 괄호와 함께 적어준다.
enum Direction { EAST(1), SOUTH(5), WEST(-1), NORTH(10) }
2. 변수 / 생성자 / (getter) 선언
또한, 지정된 값을 저장할 수 있는 인스턴스 변수와 생성자, getter 를 추가해야한다.
enum Direction {
EAST(1), SOUTH(5), WEST(-1), NORTH(10); // 이런 경우 끝에 ; 이 추가된다
private final int value; // 값들을 저장할 필드(인스턴스 변수)
//생성자 -> 묵시적으로 private형 외부에서 호출 불가
Direction(int value) { this.value = value; }
//private형을 받기 위해 getter
public int getValue() {return value;}
}
인스턴수 변수가 final이어야 한다는 제약이 있지는 않다
3. 값 여러개 추가 가능
필요하다면, 하나의 열거형 상수에 여러가지 값을 지정할 수 있지만, 그에 맞게 인스턴스 변수와 생성자등을 추가해야한다.
enum Direction {
EAST(1,">"), SOUTH(5,"V"), WEST(-1,"<"), NORTH(10,"^"); // 이런 경우 끝에 ; 이 추가된다
private final int value; // 값들을 저장할 필드(인스턴스 변수)
private final String symbol; // 값을 저장할 필드
//생성자 -> 묵시적으로 private형 외부에서 호출 불가
Direction(int value, String symbol) {
this.value = value;
this.symbol = symbol;
}
//private형을 받기 위해 getter
public int getValue() {return value;}
public String getSymbol() {return symbol;}
}
열거형에 추상 메서드 추가
사실 열거형에 추상 메소드를 선언할 일이 크게 없다.
이는 예를 들어, 각 상수값마다 다른 함수가 적용되어야 할 때 사용된다.
예를 들어, 운송수단을 정의한 열거형을 선언하고, 값으로 기본요금과 거리에 따른 요금값을 가진다고 생각해보자.
운송수단의 이름 / 기본요금 은 변하지 않으니 상관없지만,
거리에 따른 요금은 거리에 의존하기 때문에, 함수로 계산되어야 한다. fair함수
하지만 거리에 따른 요금이 운송수단마다 계산하는 방법이 다를 것이다. 이러한 경우에 추상 메소드를 사용한다.
enum Transporation {
BUS(100) {int fair(int distance) {return distance*BASIC_FAIR;}}, //운송 수단 별로 내부가 다르기 때문에 추상메서드로 선언한 후 따로 구현
TRAIN(150) {int fair(int distance) {return distance*2*BASIC_FAIR;}},
SHIP(100) {int fair(int distance) {return distance*3*BASIC_FAIR;}},
AIRPLANE(300) {int fair(int distance) {return distance*5*BASIC_FAIR;}};
abstract int fair(int distance); // 거리에 따른 요금을 계산하는 추상 메서드
//값을 저장할 변수선언
protected final int BASIC_FAIR; // 각 상수에서 접근해야하기 때문에 protected사용
//생성자
Transporation(int basicFair){
BASIC_FAIR = basicFair;
}
//getter(외부에서 값을 사용해야 하므로)
public int getBasicFare() {return BASIC_FAIR;}
}
*참고 :
추상 메소드를 가지는 클래스는 추상 클래스여야한다.
하지만 enum앞에 abstract가 붙지 않는데, 그 이유는 enum자체(모든 열거형)가 추상 클래스 Enum의 자손이기 때문이다.
열거형의 이해
열거형의 이해를 돕기 위해 내부적인 설명
1. 열거형 상수는 하나하나 클래스의 객체이다
다음과 같은 열거형은
enum Direction { EAST, SOUTH, WEST, NORTH }
다음과 같다.
즉, Direction이라는 클래스를 객체로 구현한 것이 바로, 열거형의 상수이다.
class Direction {
static final Direction EAST = new Direction("EAST");
static final Direction SOUTH = new Direction("SOUTH");
static final Direction WEST = new Direction("WEST");
static final Direction NORTH = new Direction("NORTH");
private String name;
private Direction(String name){
this.name = name;
}
}
그렇기 때문에, static상수인 EAST, SOUTH, WEST, NORTH의 값은 객체의 주소이고,
이 값은 바뀌지 않기 때문에 ==로 비교가 가능하다.
2. 모든 열거형은 추상클래스 Enum의 자손이다.
Enum을 흉내내어 작성한 클래스 MyEnum이다.
/*
* 1. <T extends MyEnum<T>> : 타입 T가 MyEnum의 자손 이어야 한다.
* 2. 추상 메서드를 추가할 경우 추상 클래스여야 하므로 abstarct를 붙여주어야 한다.
*/
abstract class MyEnum<T extends MyEnum<T>> implements Comparable<T>{
static int id = 0;
int ordinal;
String name = "";
public int ordinal() {return ordinal;}
MyEnum(String name){ //새로 enum상수를 name으로 생성하면,
this.name = name;//입력 받은 name을 이름으로 가지고
ordinal = id++; //ordinal은 자동으로 1씩 증가시킨다 (선언 순서대로 0,1,2,...의 값을 가진다)
}
public int compareTo(T t){
return ordinal - t.ordinal(); //<T extends..>가 없다면 타입 T에 ordinal이 있는지 확인 불가능 하므로 이렇게 짤 수 없음
}
}
enum 으로 정의한 값들은 다양한 메소드를 지원한다.
그중에 생성자와 다음 두가지 메소드를 구현해 본 것이다.
- ordinal() : 열거형 상수가 정의된 순서를 반환한다. (0부터 시작하며, int형으로 반환됨)
- compareTo(T t) : 열거형 객체끼리의 ordinal을 비교해준다. → Comparable 인터페이스를 구현한 것이다.
'LANGUAGE > JAVA' 카테고리의 다른 글
[JAVA] 배열 Array (0) | 2021.10.28 |
---|---|
[Java] 제어자(modifier) (0) | 2021.09.30 |