11주차 항해일지 - Enum

김종하·2021년 1월 28일
0

Live Study - WHITESHIP

목록 보기
12/14
post-thumbnail

학습사항

  • enum 정의하는 방법
  • enum이 제공하는 메소드 (values()와 valueOf())
  • java.lang.Enum
  • EnumSet

Enum 이란?

'열거형타입' 으로 해석되는 enum 은 서로 연관된 상수들의 집합을 선언하기 위한 특수한 형태의 클래스이다.

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에서 관리됨으로 '==' 비교를 하는데 있어서 아무런 문제가 없다.

enum이 제공하는 메소드 ( values(), valueOf(), ordinal() )

values()

values() 메소드는 enum 에서 정의한 상수들을 배열형태로 반환해준다.

 	Season[] values = Season.values();
        for(Season v : values){
            System.out.println(v);
        }
-->
SPRING
SUMMER
FALL
WINTER

valueOf()

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()

ordinal() 열거형 상수가 정의된 순서를 반환해준다.

Season[] values = Season.values();
        for(Season v : values){
            System.out.println(v + " " + v.ordinal());
        }
-->
SPRING 0
SUMMER 1
FALL 2
WINTER 3

java.lang.Enum

모든 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

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

0개의 댓글