열거형은 자바 5에서 추가된 자료형으로, 의미가 부여된 이름을 갖는 상수의 선언에 그 목적이 있습니다.
열거형 학습에 앞서, 자바 5 이전에는 의미가 부여된 이름을 갖는 상수를 어떻게 선언했는지 다음 코드로 확인해보겠습니다.
publicinterfaceScale{
int DO = 0;
int RE = 1;
int MI = 2;
int FA = 3;
int SO = 4;
int RA = 5;
int TI = 6;
}
publicclassInterfaceBaseConst{
publicstaticvoidmain(String[] args){
int sc = Scale.DO;
switch (sc) {
case Scale.DO:
System.out.println("음 이름 : 도");
break;
case Scale.RE:
System.out.println("음 이름 : 레");
break;
case Scale.MI:
System.out.println("음 이름 : 미");
break;
case Scale.FA:
System.out.println("음 이름 : 파");
break;
default:
System.out.println("음 이름 : 솔, 라, 시");
}
}
}
출력 결과값은 다음과 같습니다.
음 이름 : 도
인터페이스 내에 선언된 변수는 public, static, final 이 선언된 것으로 간주합니다.
따라서 인터페이스 Scale 을 통해 7개의 상수가 선언되었습니다.
인터페이스 Scale 은 음계를 표현한 상수들을 담고 있습니다.
이 경우 중요한 것은 상수의 값이 아니라 상수의 이름입니다.
즉 상수의 값이 바뀌어도 이름이 바뀌지 않는다면 코드에 아무런 영향을 주지 않습니다.
그리고 이렇게 연관된 상수들을 하나의 인터페이스로 묶어서 선언하는 것이 자바 5 이전에 사용하던 방법입니다.
그러나 이 방법에는 문제가 하나 있는데, 그 문제점을 다음 코드에서 확인하겠습니다.
publicinterfaceAnimal{
int Dog = 1;
int CAT = 2;
}
publicinterfacePerson{
int MAN = 1;
int WOMAN = 2;
}
publicclassNonSafeConst{
publicstaticvoidmain(String[] args){
who(Person.MAN);
who(Animal.Dog);
}
publicstaticvoidwho(int man){
switch (man) {
case Person.MAN:
System.out.println("남자입니다.");
break;
case Person.WOMAN:
System.out.println("여자입니다.");
break;
}
}
}
출력 결과값은 다음과 같습니다.
남자입니다.
남자입니다.
Person.MAN 도 값이 1이고 Animal.DOG 도 값이 1이기 때문에,
위와 같은 실수를 범해도 컴파일 오류는 물론 실행 오류도 발생하지 않습니다.
그 문제점을 해결하기 위해 자바 5에서 열거형이 소개되었습니다.
열거형의 정의
열거형은 다음과 같이 정의합니다.
그 안에 위치한 이름들을 가리켜 '열거형 값' 이라고 합니다.
publicenumScale{
DO, RE, MI, FA, SOL, RA, THI
}
열거형은 클래스와 성격이 유사합니다.
따라서 다음과 같이 참조변수의 선언도 가능합니다.
단 이렇게 선언된 참조변수는 해당 열거형 내에 선언된 '열거형 값'만 대입이 가능합니다.
Scale scale = Scale.DO;
기본적으로 열거형 값은 Scale.DO 와 같이 표현하지만,
case 문에서는 표현의 간결함을 위해 Do 와 같이 열거형 값의 이름만 명시하기로 약속되어 있습니다.
다음은 열거형 이전의 코드를 열거형 이후의 코드로 수정한 코드입니다.
publicenumScale{
DO, RE, MI, FA, SOL, RA, THI
}
publicclassSimpleEnum{
publicstaticvoidmain(String[] args){
Scale scale = Scale.DO;
System.out.println(scale);
switch (scale) {
case DO:
System.out.println("음 이름 : 도");
break;
case RE:
System.out.println("음 이름 : 레");
break;
case MI:
System.out.println("음 이름 : 미");
break;
case FA:
System.out.println("음 이름 : 파");
break;
default:
System.out.println("음 이름 : 솔, 라, 시");
}
}
}
publicclassPerson{
publicstaticfinal Person MAN = new Person();
publicstaticfinal Person WOMAN = new Person();
@Overridepublic String toString(){
return"I am Person";
}
}
publicclassInClassInst{
publicstaticvoidmain(String[] args){
System.out.println(Person.MAN);
System.out.println(Person.WOMAN);
}
}
출력 결과값은 다음과 같습니다.
I am Person
I am Person
위 코드에서 보이듯이 Person 클래스 내에서 Person 형 참조변수를 선언하는 것도, Perosn 인스턴스를 생성하는 것도 가능합니다.
그럼 다음 코드를 보겠습니다. 이 코드에서는 열거형 값이 해당 자료형의 인스턴스라는 사실을 알려줍니다.
publicclassParentAdder{
publicintadd(int a, int b){
return a + b;
}
}
publicclassChildAdderextendsParentAdder{
publicdoubleadd(double a, double b){
System.out.println("ChildAdder.add");
return a + b;
}
}
publicclassOverrideMistake{
publicstaticvoidmain(String[] args){
ParentAdder parentAdder = new ChildAdder();
System.out.println(parentAdder.add(3, 4));
}
}
이 코드 작성자의 기대값은 이렇습니다.
ChildAdder.add
7
하지만 실제 결과값은 다음과 같습니다.
7
실수를 찾으셨나요?
개발자의 실수는 아래의 코드입니다.
publicdoubleadd(double a, double b){}
개발자의 의도는 상위 클래스의 add 메소드를 Override 하는 것이었습니다.
하지만 매개변수형과 반환형이 달랐기 때문에 Override 되지 않았고, 그 결과값이 달라지게 된 것입니다.
이런 유형의 실수는 매우 흔합니다.
그럼에도 불구하고 발견이 쉽지 않기 때문에 치명적인 실수가 될 수 있습니다.
개발자의 실수들은 컴파일 과정에서 확인되는 것이 가장 좋습니다.
그러나 위의 경우에는 정상적으로 컴파일도 되고 실행도 됩니다.
이러한 상황을 방지하기 위해서 @Override 어노테이션을 사용하는 것입니다.
쉽게 말해 자바 컴파일러에게 '나는 상위 클래스의 메소드를 오버라이딩(Overriding) 할거야!'
라는 메모를 남기는 것이라고 생각하면 됩니다.
이제 개발자의 실수가 들어간 코드에 @Override 어노테이션을 적용해보겠습니다.
publicclassChildAdderextendsParentAdder{
@Overridepublicdoubleadd(double a, double b){
System.out.println("ChildAdder.add");
return a + b;
}
}
결과는 아래와 같습니다.
컴파일 과정에서 오류가 발생하여 프로그램이 실행되지 않고, 컴파일러가 친절하게 오류 원인을 제공합니다.
컴파일러 덕분에 실수를 인지할 수 있었고, 코드를 다음과 같이 수정할 수 있었습니다.
publicclassChildAdderextendsParentAdder{
publicintadd(int a, int b){
System.out.println("ChildAdder.add");
return a + b;
}
}
코드의 결과값은 다음과 같습니다.
드디어 개발자가 원하는 결과를 제공할 수 있게 되었습니다.
ChildAdder.add7
코드를 잘 보시면 @Override 어노테이션이 없다는 것을 알 수 있습니다.
맞습니다. 해당 어노테이션이 존재하지 않아도 정상적으로 Override 를 할 수 있습니다.
하지만, 우리는 조금 더 안정적인 코드 작성 습관을 들여야할 필요가 있습니다.
따라서 다음과 같이 메소드를 수정합니다.
package inheritance;
publicclassChildAdderextendsParentAdder{
@Overridepublicintadd(int a, int b){
System.out.println("ChildAdder.add");
return a + b;
}
}