학습사항
- enum 정의하는 방법
- enum이 제공하는 메소드 (values()와 valueOf())
- java.lang.Enum
- EnumSet
'열거형타입' 으로 해석되는 enum 은 서로 연관된 상수들의 집합을 선언하기 위한 특수한 형태의 클래스이다.
Enum 을 선언하기 위해선 enum 키워드를 사용해서 선언할 수 있다.
public enum Season {
SPRING, SUMMER, FALL, WINTER
}
위의 enum은 내부적으로 다음과 동일한 형태를 가지게 된다.
public class Season {
private static final Season SPRING = new Season();
private static final Season SUMMER = new Season();
private static final Season FALL = new Season();
private static final Season WINTER = new Season();
}
enum 을 사용하기 이전에는, 클래스 내에 final static 로 변수 선언하는 방식을 사용하기도 하였는데 예를들어 작성하자면 다음과 같다.
public class Season2 {
public static final int SPRING = 0;
public static final int SUMMER = 1;
public static final int FALL = 2;
public static final int WINTER = 3;
}
그렇다면 상수를 표현하기 위해 사용된 enum 과 클래스 내에 final static 로 변수 선언하는 방식을 비교해보도록 하자
우선, 상수를 사용하는 클래스를 하나 만들어보았다.
public class LoLChampionShip {
private int season2; // 안좋은 방식의 상수
private Season season; // enum을 활용한 상수
// getter & setter
}
그리고 상수값이 제대로 동작하고 있는지 확인해보았다.
public static void main(String[] args) {
LoLChampionShip 여름시즌롤챔피언십 = new LoLChampionShip();
여름시즌롤챔피언십.setSeason(Season.SUMMER);
여름시즌롤챔피언십.setSeason2(Season2.SUMMER);
LoLChampionShip 여름시즌롤챔피언십2 = new LoLChampionShip();
여름시즌롤챔피언십2.setSeason(Season.SUMMER);
여름시즌롤챔피언십2.setSeason2(Season2.SUMMER);
System.out.println(여름시즌롤챔피언십.getSeason()==여름시즌롤챔피언십2.getSeason()); // true
System.out.println(여름시즌롤챔피언십.getSeason2()==여름시즌롤챔피언십2.getSeason2()); // true
}
두 방법 모두 잘 비교가 되고 있는 것을 확인할 수 있다. 하지만 또 다른 상수를 추가해보도록 하자
우선, enum 을 활용해 지역 이름에 대한 상수를 추가하였다
public enum Region {
KOREA, NORTHAMERICA, CHINA, EUROPE
}
다음으로, 과거의 방식으로 지역 이름에 대한 상수를 추가하였다.
public class Region2 {
public static final int KOREA = 0;
public static final int NORTHAMERICA = 1;
public static final int CHINA = 2;
public static final int EUROPE = 3;
}
추가한 상수들을 사용하도록 LoLchmpionShip 코드를 수정하였다.
public class LoLChampionShip {
private int season2; // 안좋은 방식의 상수
private Season season; // enum을 활용한 상수
private int region2; // 안좋은 방식의 상수
private Region region; // enum을 활용한 상수
// getter & setter
}
public static void main(String[] args) {
LoLChampionShip 한국오픈여름시즌롤챔피언십 = new LoLChampionShip();
한국오픈여름시즌롤챔피언십.setSeason(Season.SUMMER);
한국오픈여름시즌롤챔피언십.setSeason2(Season2.SUMMER);
한국오픈여름시즌롤챔피언십.setRegion(Region.KOREA);
한국오픈여름시즌롤챔피언십.setRegion2(Region2.KOREA);
LoLChampionShip 북미오픈여름시즌롤챔피언십 = new LoLChampionShip();
북미오픈여름시즌롤챔피언십.setSeason(Season.SUMMER);
북미오픈여름시즌롤챔피언십.setSeason2(Season2.SUMMER);
북미오픈여름시즌롤챔피언십.setRegion(Region.NORTHAMERICA);
북미오픈여름시즌롤챔피언십.setRegion2(Region2.NORTHAMERICA);
System.out.println(북미오픈여름시즌롤챔피언십.getSeason2() == 북미오픈여름시즌롤챔피언십.getRegion2());
}
위 코드를 보면 과거의 방법에 문제점이 바로 보인다. 시즌과 지역을 == 비교했는데 true가 나와버렸다.
그에 반해 클래스의 특별한 형태인 enum 으로 비교를 했다면 == 연산의 피연사자들간의 타입이 다르기 때문에 컴파일 에러가 발생한다.
이를 통해 enum 을 사용하면 타입에 안전한 열거형(typesafe enum)을 만들 수 있다는 사실을 알 수 있었다.
추가적으로 Enum 클래스에서 선언한 상수들은 클래스가 로드될 때 하나의 인스턴스로 생성되어 싱글톤 형태로 JVM에서 관리됨으로 '==' 비교를 하는데 있어서 아무런 문제가 없다.
values() 메소드는 enum 에서 정의한 상수들을 배열형태로 반환해준다.
Season[] values = Season.values();
for(Season v : values){
System.out.println(v);
}
-->
SPRING
SUMMER
FALL
WINTER
valueOf(String name) 는 지정된 열거형에서 name과 일치하는 열거형 상수를 반환해준다.
Season season = Season.valueOf("WINTER");
System.out.println(season); // WINTER
Region region = Region.valueOf("WINTER");
System.out.println(region); // KOREA
ordinal() 열거형 상수가 정의된 순서를 반환해준다.
Season[] values = Season.values();
for(Season v : values){
System.out.println(v + " " + v.ordinal());
}
-->
SPRING 0
SUMMER 1
FALL 2
WINTER 3
모든 enum 타입은 java.lang.enum 을 extends 한 클래스이다.
java.lang.enum 은 추상클래스로써, 상속을 통해서 구현해야만 사용할 수 있고 직접 객체를 생성해 사용할 수는 없다.
모든 enum 타입이 java.lang.enum 타입을 상속하고 있으니, 자바의 단일상속원칙에 따라 enum 은 다른 클래스를 상속할 순 없다. (인터페이스를 implements 하는 것은 당연히 가능하다)
enum 타입도 클래스임으로 다른 클래스들과 마찬가지로 필드와 메서드들을 가질 수 있다.
enum에 추상 메서드(abstract method)를 추가하면 상수에서 재정의해서 사용할 수 있다.
필드와 메서드를 추가해서 활용해보도록하자
package weeks11;
public enum Season {
SPRING("봄"){
@Override
void describeSeaon() {
System.out.println("봄은 따듯합니다.");
}
}, SUMMER("여름"){
@Override
void describeSeaon() {
System.out.println("여름은 뜨겁습니다.");
}
}, FALL("가을"){
@Override
void describeSeaon() {
System.out.println("가을은 서늘합니다.");
}
}, WINTER("겨울"){
@Override
void describeSeaon() {
System.out.println("겨울은 춥습니다.");
}
};
private String description;
Season(String description){
this.description = description;
}
public String getDescription(){
return this.description;
}
abstract void describeSeaon();
}
---------
Season.SUMMER.describeSeaon(); // 여름은 뜨겁습니다.
System.out.println(Season.WINTER.getDescription()); // 겨울
EnumSet 은 열거형 타입과 함께 사용하기 위한 set 구현체이다.
abstract class 로써 직접 객체를 생성해 사용할 순 없고, 정의해둔 static method 들을 활용하여 사용할 수 있다.
사용자에게 구조의 복잡함을 감추고 ( EnumSet은 원소갯수가 64개 이하면 내부적으로 long 데이터형의 비트필드를 사용해서 메모리를 최소한을 쓰고 속도를 높이게 설계되어있다. ) 사용의 편의성을 최대한 높였다.
EnumSet을 활용해 보도록 하자
package weeks11;
import java.util.EnumSet;
public class EnumSetTester {
public static void main(String[] args) {
//allOf(enum type) -> 인자로 받은 열거형의 모든 상수를 받는다.
EnumSet<Season> seasonEnumSet = EnumSet.allOf(Season.class);
System.out.println(seasonEnumSet.size());
seasonEnumSet.forEach(System.out::println);
System.out.println();
//copOf(enumset) -> 인자로 받은 enumset 이 가지고 있는 값들을 모두 받는다.
EnumSet<Season> copySeasonEnumSet = EnumSet.copyOf(seasonEnumSet);
seasonEnumSet.forEach(System.out::println);
System.out.println();
//of(e, e1) -> 인자로 받은 상수들만 받는다.
EnumSet<Region> regionEnumSet = EnumSet.of(Region.KOREA, Region.CHINA);
regionEnumSet.forEach(System.out::println);
System.out.println();
//complementOf(enumset) -> 인자로 받은 enumset 이 포함하지 않는 enum 의 상수들만 받는다.
EnumSet<Region> regionEnumSet1 = EnumSet.complementOf(regionEnumSet);
regionEnumSet1.forEach(System.out::println);
System.out.println();
}
}
--->
4
SPRING
SUMMER
FALL
WINTER
SPRING
SUMMER
FALL
WINTER
KOREA
CHINA
NORTHAMERICA
EUROPE
스터디 깃헙주소 : https://github.com/whiteship/live-study/issues/11
예제코드 깃헙레포 : https://github.com/JadenKim940105/whiteship-study/tree/master/src/main/java/weeks11