코드몬스터 2024. 10. 1. 15:23
728x90

 

열거형을 볼 때마다 어떻게 만들어진 클래스인지 궁금해서 정리를 해보았다.

해당 궁금증을 이해하기 위해서는 '2.4 열거형의 이해' 를 바로 읽으면 된다.

 

2. 열거형(Enum)

2.1. 열거형이란?

열거형은 서로 관련된 상수를 편리하게 선언하기 위한 것으로 여러 상수를 정의할 때 사용하면 유용하다.

 

※ 열거형이 없을 때, 작성한 코드

public 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;
}

 

 

※ 열거형으로 작성한 코드

public class Card {
    enum Kind { CLOVER, HEART, DIAMOND, SPADE}
    enum Value { TWO, THREE, FOUR }
    
    final Kind kind;
    final Value value;
}

 

C언어는 타입이 달라도 값이 같으면 조건식 결과가 참으로 나온다.

자바는 실제 값이 같아도 타입이 다르면 컴파일 에러가 발생한다.

if (Card.CLOVER == Card.TWO)            // C언어는 True
if (Card.Kind.CLOVER == Card.Value.TWO) // Java는 컴파일 에러

 

중요한 부분은 상수의 값이 바뀌면, 해당 상수를 참조하는 모든 소스를 다시 컴파일 해야한다.

하지만 열거형 상수를 사용하면, 기존의 소스를 다시 컴파일 하지 않아도 된다.

 

2.2 열거형의 정의와 사용

※ 열거형 정의 방법

public enum Direction {
    EAST, SOUTH, WEST, NORTH
}

 

※ 열거형 사용

class Unit {
    int x, y;
    Direction dir;
    
    void init() {
    	dir = Direction.EAST;
    }
}

 

※ 열거형 비교

  • 열거형 상수간의 비교에는 '=='를 사용할 수 있다.
  • 비교연산자('<', '>')는 사용할 수 없고 compareTo()를 사용해야 한다.
if (dir == Direction.EAST) {
    x++;
} else if (dir > Direction.WEST) {
    // 열거형 상수에서 비교연산자 사용 불가능
} else if (dir.compareTo(Direction.WEST) > 0) {
    // compareTo()는 사용 가능
}

 

  • switch 문에서도 열거형 사용 가능
    • 대신, case에서 Direction.EAST 라고 사용할 수 없다.
void move() {
    switch(dir) {
        case EAST: x++;
            break;
        case WEST: x--;
            break;
        case SOUTH: y++;
            break;
        case NORTH: y--;
            break;
    }	
}

 

 

※ 열거형의 조상 - java.lang.Enum

메서드 설명
Class<E> getDeclaringClass() 열거형의 Class객체를 반환한다.
String name() 열거형 상수의 이름을 문자열로 반환한다.
int ordinal() 열거형 상수가 정의된 순서를 반환한다.(0부터 시작)
T valueOf(Class<T> enumType, String name) 지정된 열거형에서 name과 일치하는 열거형 상수를 반환한다. 

 

2.4 열거형에 멤버 추가하기

  • ordinal()이 열거형 상수가 정의된 순서를 반환하지만, 해당 값을 열거형의 상수 값으로 사용하는 것은 좋지 않다.
public enum Direction {
    EAST(1), SOUTH(5), WEST(-1), NORTH(10)
}

 

public enum Direction {
    EAST(1), SOUTH(5), WEST(-1), NORTH(10);
    
    private final int value;  // 인스턴스 변수 추가
    
    Direction (int value) {   // 생성자
    	this.value = value;
    }
    
    public int getValue() {
    	return value;
    }
}

 

public enum Direction {
    EAST(1, ">"), SOUTH(5, "V"), WEST(-1, "<"), NORTH(10, "^");
    
    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;
    }
}

 

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

 

예시1. 열거형 Trasportation은 운송 수단의 종류 별로 상수를 정의하고 있으며, 각 운송 수단에는 기본요금(BASIC_FARE)이 책정되어 있다.

public enum transportation {
    BUS(100), TRAIN(150), SHIP(100), AIRPLANE(300);
    
    private final int BASIC_FARE;
    
    transportation(int basicFare) {
    	this.BASIC_FARE = basicFare;
    }
    
    public int fare() {
    	return BASIC_FARE;
    }
    
}

 

 

예시2. 거리에 따라 요금을 계산하는 방식이 각 운송 수단마다 다를 것이다. 열거형에 추상메서드 'fare(int distance)'를 선언하여 추상 메서드를 구현해라

 

=> 각 상수들이 추상 메서드를 구현해야 되는 것을 보면 상수는 어떤 클래스라는 것을 알 수 있지 않을까?

=> private final int BASIC_FARE; (변경 전)   →  protected final int BASIC_FARE; (변경 후)

public enum transportation {
    BUS(100){
        @Override
        public int fare(int distance) {
            return BASIC_FARE * distance;
        }
    } ,
    TRAIN(150) {
        @Override
        public int fare(int distance) {
            return BASIC_FARE * distance;
        }
    },
    SHIP(100) {
        @Override
        public int fare(int distance) {
            return BASIC_FARE * distance;
        }
    },
    AIRPLANE(300) {
        @Override
        public int fare(int distance) {
            return BASIC_FARE * distance;
        }
    };

    protected final int BASIC_FARE;

    transportation(int basicFare) {
        this.BASIC_FARE = basicFare;
    }

    public abstract int fare(int distance);

}

 

2.4 열거형의 이해

앞에서 봤던 Direction과 Transportation으로 열거형이 어떻게 만들어지는지 확인해보자!!!

 

※ 열거형 클래스로 구현

public enum Direction {
    EAST, SOUTH, WEST, NORTH
}

 

※ 일반 클래스로 열거형 클래스를 구현

  • 상수(EAST, WEST 등)가 해당 클래스의 인스턴스(new Direction)라는 것을 알 수 있다.
  • 인스턴스 변수 name 은 상수의 이름을 나타낸다.
class Direction {
    public static final Direction EAST = new Direction("EAST");
    public static final Direction WEST = new Direction("WEST");
    public static final Direction NORTH = new Direction("NORTH");
    public static final Direction SOUTH = new Direction("SOUTH");
    
    private String name;
    
    private Direction(String name) {
        this.name = name;
    }
}

 

※ Enum 클래스를 직접 작성해보자

  • 인스턴스 변수 ordinal 값이 있다.
  • 상수를 나열하면 인덱스 값으로 ordinal이 자동으로 추가 된다.
    • 해당 값을 상수 값으로 사용하면 좋지 않다.
    • 중간에 상수가 삭제하거나 수정하면 ordinal 값이 틀리게 된다.
      (ex. ordinal의 값이 3인 상수를 지우면 => 1, 2, 5, 4, 6 으로 바뀐다)
abstract class MyEnum<T extends MyEnum<T>> implements Comparable<T> {
    static int id = 0;  // 객체에 붙이는 일련번호(0부터 시작)

    int ordinal;    // 상수 일련번호 값
    String name;    // 상수 이름

    public int ordinal() {
        return this.ordinal;
    }

    MyEnum(String name) {
        ordinal = id++; // 객체를 생성(인스턴스 생성)할 때마다 id의 값을 증가
        this.name = name;
    }

    @Override
    public int compareTo(T o) {
        return ordinal - o.ordinal();
    }
}

 

만약,  MyEnum<T>로 작성했다면 o.rodianl()을 사용할 수 없다.

왜? T o의 인스턴스 메서드에서 ordinal()이 있는지 없는지 알 수 없다.

 

★ 즉, 타입 T가 MyEnum<T>의 자손이어야 한다는 것을 MyEnum<T extends<MyEnum<T>>로 선언했다. ★

 

※ 직접 작성한 Enum 클래스 사용

  • 많이 생각해보자 코드가 쉽지는 않다!
abstract public class MyTransportation extends MyEnum {
    static final MyTransportation BUS = new MyTransportation("BUS", 100) {
        @Override
        int fare(int distance) {
            return distance * basicFare;
        }
    };
    static final MyTransportation TRAIN = new MyTransportation("TRAIN", 150) {
        @Override
        int fare(int distance) {
            return distance * basicFare;
        }
    };
    static final MyTransportation SHIP = new MyTransportation("SHIP", 100) {
        @Override
        int fare(int distance) {
            return distance * basicFare;
        }
    };

    protected int basicFare;

    private MyTransportation(String name, int basicFare) {
        super(name);
        this.basicFare = basicFare;
    }

    public int getBasicFare() {
        return basicFare;
    }

    abstract int fare(int distance);
}