본문 바로가기

STUDY/이펙티브자바

6-1) int 상수 대신 enum을 사용하라

정수 열거 패턴

 

public static final int FULL_TIME_JOB		= 0;
public static final int PART_TIME_JOB		= 1;

public static final int STAFF_RANK		= 0;
public static final int MANGER_RANK		= 1;
public static final int SENIOR_RANK		= 2;

개발을 하다보면 상수 값을 사용해야 할 때가 있고 int에 static final 키워드를 추가해서 표현할 수 있다. 그리고 정의된 이름이 상수의 의미를 나타낸다. 정규직, 비정규직 고용형태를 0, 1의 상수로 정의하였고 사원, 대리, 주임 직급을 0, 1, 2의 상수로 정의하였다. 그리고 상수의 이름을 보면 그 의미에 맞게 정의되었다.

 

// FULL_TIME_JOB, PART_TIME_JOB 고용형태를 받아서 급여를 계산하는 메소드
public int calcPayment(int jobCode) {
    ...
}

// 고용형태가 아닌 직급을 나타내는 상수를 받아도 컴파일 에러가 발생하지 않는다.
calcPayment(STAFF_RANK);

하지만 정수 열거 패턴은 위험할 수 있다. 고용형태 상수와 직급 상수가 _JOB과 _RANK라는 접미사로 구분되어있어서 사람은 이름을 보고 구분할 수 있지만, 컴파일러 입장에서는 똑같은 int형 상수일 뿐이다. 실제로 위의 코드를 보면 고용형태 상수를 파라미터로 받아서 급여를 계산하는 calcPayment 메소드가 있지만 직급 상수를 파라미터로 전달해도 아무런 컴파일 에러가 발생하지 않는다.

 

 


enum(열거 타입) 상수

 

public enum Job {
    FULL_TIME, PART_TIME;
}

public enum Rank {
    STAFF, MANAGER, SENIOR;
}

자바에서는 상수를 정의할 수 있는 대안인 enum이 존재한다. enum은 단순히 상수를 정의하는 것 뿐만 아니라, 클래스의 형태로서 더욱 복잡한 역할도 수행할 수 있다. 그리고 int 타입이 아니라 enum으로 만든 타입이 존재하기 때문에 컴파일러도 이들을 구분할 수 있고 안전한 코드 작성을 도와준다.

 

// Job enum을 받아서 급여를 계산하는 메소드
public int calcPayment(Job job) {
    ...
}

// 고용형태 Job이 아닌 객체를 파라미터로 넘기면 컴파일 에러가 발생한다.
calcPayment(RANK.STAFF);

 

 


enum에 필드와 메소드

 

public enum Planet {
    MERCURY(3.302e+23, 2.439e6),
    VENUS  (4.869e+24, 6.052e6),
    EARTH  (5.975e+24, 6.378e6),
    MARS   (6.419e+23, 3.393e6),
    JUPITER(1.899e+27, 7.149e7),
    SATURN (5.685e+26, 6.027e7),
    URANUS (8.683e+25, 2.556e7),
    NEPTUNE(1.024e+26, 2.477e7);

    private final double mass;           // 질량
    private final double radius;         // 반지름
    private final double surfaceGravity; // 중력

    // 중력상수
    private static final double G = 6.67300E-11;

    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
        surfaceGravity = G * mass / (radius * radius);
    }

    public double mass()           { return mass; }
    public double radius()         { return radius; }
    public double surfaceGravity() { return surfaceGravity; }

    public double surfaceWeight(double mass) {
        return mass * surfaceGravity;
    }
}

enum에는 필드와 메소드도 추가할 수 있다. 태양계에는 여덟개의 행성이 있는데 각각 질량과 반지름을 가지고 있다. 그리고 질량과 반지름을 이용해서 표면중력을 계산할 수 있다. 이를 enum으로 표현한게 위의 코드이다. enum에서 class처럼 필드를 선언하고 생성자를 만들어준 후 각각의 상수에 생성자에 필요한 데이터를 전달해주면 된다. 그리고 메소드 역시 class처럼 선언하여 사용하면 된다.

 

 


enum 상수별 메소드 구현

 

public enum Operation {
    PLUS, MINUS, TIMES, DIVIDE;
	
    public double apply(double x, double y) {
        switch(this) {
        case PLUS: return x + y;
        case MINUS: return x - y;
        case TIMES: return x * y;
        case DIVIDE: return x / y;
        }
        throw new AssertionError("알 수 없는 연산: " + this);
    }
}

위의 코드는 사칙연산을 enum을 사용하여 구현하였다. 각각의 연산은 apply 메소드를 통해서 수행되는데, apply 메소드에서는 각 타입을 switch문으로 분기하여 계산 결과를 반환한다. 위 코드는 결과적으로 문제없이 동작하지만 문제가 생길수 있는 코드이다. 연산이 제거되거나 추가되면 apply의 switch문을 수정해야 한다. 그리고 하나의 연산 로직에 수정이 필요해도 apply 코드를 수정해야한다. 각각 독립적으로 수행되어야할 연산이 하나의 메소드를 사용하여 서로 영향을 미칠 수 있는 가능성이 존재한다. 이는 문제가 생길 가능성을 올릴 수 있다.

 

public enum Operation {
    PLUS {
        public double apply(double x, double y) {
            return x + y;
        }
    }, 
    MINUS {
        public double apply(double x, double y) {
            return x - y;
        }
    }, 
    TIMES {
        public double apply(double x, double y) {
            return x * y;
        }
    }, 
    DIVIDE {
        public double apply(double x, double y) {
            return x / y;
        }
    };
	
    public abstract double apply(double x, double y);
}

위 코드에서는 apply를 추상 메소드로 선언하고 각 상수에서 구현하도록 하였다. 이렇게 하면 각각의 연산들은 독립된 apply 구현을 가지고 있으므로 다른 연산이 변경되더라고 영향을 받을 일이 없어서 안전한 코드 변경이 가능해진다.

728x90