Enum
Enum
컴퓨터 프로그래밍에서 Enumerated Type(열거형 타입)은 요소, 멤버라 불리는 명명된 값의 집합을 이루는 자료형이다. 열거자 이름들은 일반적으로 해당 언어의 상수 역할을 하는 식별자이다. -wikipedia
- enum 은 상수 값 멤버들로 집합을 이루는 열거형 타입이다. 상수는 변하지 않는 값으로, 따라서 enum 에는 정수뿐 아니라, 문자열 등 그외 자료형도 정의할 수 있다.
- JAVA의 enum은 변수, 메소드 및 생성자를 추가할 수도 있다. 주요 목적은 개별적인 데이터 유형 (Enumerate Data Types)을 정의하는 데 있다.
- Enum 선언은 클래스 외부 또는 클래스 내부에서 선언할 수 있지만 메서드 내부에서는 선언할 수 없다.
- JDK5 부터 추가되었으며 그 이전에는 상수를 이용하여 사용하였는데,
- 상수를 이용하는 방법
public class Constants {
public static final String MALE = "MALE";
public static final String FEMALE = "FEMALE";
}
public static void main(String[] args) {
String gender;
gender = Constants.MALE;
gender = Constants.FEMALE;
}
위와 같은 방식으로 사용하는데, 위 방법의 문제점은 gender = "메롱";
이렇게 수행되어도 코드상 전혀 문제가 되지 않아, 실행할때 원했던 값이 MALE 또는 FEMALE이 아닌 전혀 다른 값이 들어오게 되므로 문제를 발생시킬 수 있다.
그러므로, 특정값만 가져야 한다면 열거형을 사용하여 작성하는 것이 좋다. (컴파일 타임에 가능한 모든 값을 알고 있을 때)
또 상수를 사용하였을때 문제점은 TYPE-SAFETY 하지 않는다는 것이다.
public static void main(String[] args) {
System.out.println("hello"); // 오타 발생 여지 있음
}
위와같이 코딩할때는 hello를 helo 로 쓴다든지 하는 오타가 발생할 여지가 있어 type-safety 하지 않는데, enum 을활용하면 type-safety 한 코드를 작성할 수 있다.
enum Hello {
HELLO("HELLO");
String message;
Hello(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
public static void main(String[] args) {
System.out.println(Hello.HELLO.getMessage());
}
마지막으로, 상수로 정의하여 사용하였을때의 문제점은 컴파일러가 보기에는 int를 비교하는것이기때문에 전혀 다른 의미의 상수여도 같은 상수로 볼 수 있다는 점이다.
class Test1 {
public static final int APPLE = 1;
}
class Test2 {
public static final int MONKEY = 1;
}
class App {
public static void main(String[] args) {
if (Test1.APPLE == Test2.MONKEY) {
// ...
}
}
}
사과와 원숭이는 비교조차 안되는 개념인데 컴파일러는 알지 못한다. 컴파일러단에서 저런 코드를 비교 못하게 막게 하기 위해서는 ENUM 을 사용하면 된다.
-
열거형(enum) 정의 방법
enum 열거형_이름 { 상수명(), // 생성자 호출 상수명; private final [데이터타입] [변수이름]; public get변수이름() { return 변수이름; } private 생성자(매개변수 ..) { 변수이름 = 매개변수값; } }
열거형 상수에 생성자에 primitive type 데이터, reference type 객체도 들어 올 수 있으며, 열거형 상수에 값을 부여할 경우 이에 해당하는 생성자도 함게 정의해줘야 하며, 그냥 () 사용시에는 디폴트 생성자를 사용하는 것이다.
또, 생성자를 정의한다면
private
로 정의해야 하는데 (다른 접근제어자 사용시 컴파일 에러 발생) enum 타입은 고정된 상수들의 집합으로 컴파일 타임에 고정되어야 하기 때문에 다른 패키지나 클래스에서 동적으로 값을 지정하는 것을 막기 위해private
로 정의해야 한다.public enum Gender { MALE, FEMALE; }
Gender gender; gender = Gender.MALE; gender = Gender.FEMALE; gender = "테스트"; // 컴파일 에러 발생
Enum 을 정의햇을 때 내부적으로는 아래와 같이 변화된다.
class Gender { public static final Gender MALE = new Gender(); public static final Gender FEMALE = new Gender(); }
- 모든 enum 상수들은 enum 타입의 객체로 표현된다.
final
이므로 자식 enum 을 만들 수 없다.
또, enum 내부에
main()
메서드를 선언할 수 있다. → enum을 cmd prompt 에서 호출 가능enum Gender { MALE, FEMALE; // Driver method public static void main(String[] args) { Gender gender = Gender.MALE; System.out.println(gender); } }
MALE
- 다중값을 가진 enum 상수
enum Fruit { 여름과일("포도", "수박"), 겨울과일("딸기", "귤") private final String f1; private final String f2; public String getF1() { return f1; } public String getF2() { return f2; } Fruit(String f1, String f2) { this.f1 = f1; this.f2 = f2; } }
-
enum 상수와 메서드
enum EnumExam {
상수 {
[접근제어자] [리턴타입][메서드이름](매개변수) {
// ...
}
}
}
EnumExam.상수.메서드이름();
이런 식으로 사용 할 수 있다.
switch 문
에서의 enum 사용
enum Day {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY;
}
public class EnumTest {
Day day;
public EnumTest(Day day) {
this.day = day;
}
public void dayIsLike() {
switch(day) {
case MONDAY:
System.out.println("Mondays are bad.");
break;
case FRIDAY:
System.out.println("FRIDAY are best.");
break;
case SUNDAY:
System.out.println("SUNDAY are depressed");
break;
default:
System.out.println("so-so");
break;
}
}
public static void main(String[] args) {
String str = "FRIDAY";
EnumTest t1 = new EnumTest(Day.valueOf(str));
t1.dayIsLike();
}
}
java.lang.Enum 클래스 상속
모든 enum 은 암묵적으로 java.lang.Enum.class
를 상속받기 때문에 다른 클래스를 상속받을 수 없다. 그러나 다른 인터페이스들은 구현할 수 있다.
Enum 클래스는 모든 자바 열거체의 공통된 조상클래스로, 열거체를 조작하기 위한 다양한 메소드가 포함되어 있다.
바이트코드
// class version 58.0 (58)
// access flags 0x4031
// signature Ljava/lang/Enum<LFruit;>;
// declaration: Fruit extends java.lang.Enum<Fruit>
public final enum Fruit extends java/lang/Enum {
// compiled from: Fruit.java
// access flags 0x4019
public final static enum LFruit; GRAPE
// access flags 0x4019
public final static enum LFruit; APPLE
// access flags 0x4019
public final static enum LFruit; BANANA
// access flags 0x101A
private final static synthetic [LFruit; $VALUES
// access flags 0x9
public static values()[LFruit;
L0
LINENUMBER 1 L0
GETSTATIC Fruit.$VALUES : [LFruit;
INVOKEVIRTUAL [LFruit;.clone ()Ljava/lang/Object;
CHECKCAST [LFruit;
ARETURN
MAXSTACK = 1
MAXLOCALS = 0
// access flags 0x9
public static valueOf(Ljava/lang/String;)LFruit;
L0
LINENUMBER 1 L0
LDC LFruit;.class
ALOAD 0
INVOKESTATIC java/lang/Enum.valueOf (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
CHECKCAST Fruit
ARETURN
L1
LOCALVARIABLE name Ljava/lang/String; L0 L1 0
MAXSTACK = 2
MAXLOCALS = 1
// access flags 0x2
// signature ()V
// declaration: void <init>()
private <init>(Ljava/lang/String;I)V
L0
LINENUMBER 1 L0
ALOAD 0
ALOAD 1
ILOAD 2
INVOKESPECIAL java/lang/Enum.<init> (Ljava/lang/String;I)V
RETURN
L1
LOCALVARIABLE this LFruit; L0 L1 0
MAXSTACK = 3
MAXLOCALS = 3
// access flags 0x8
static <clinit>()V
L0
LINENUMBER 2 L0
NEW Fruit
DUP
LDC "GRAPE"
ICONST_0
INVOKESPECIAL Fruit.<init> (Ljava/lang/String;I)V
PUTSTATIC Fruit.GRAPE : LFruit;
NEW Fruit
DUP
LDC "APPLE"
ICONST_1
INVOKESPECIAL Fruit.<init> (Ljava/lang/String;I)V
PUTSTATIC Fruit.APPLE : LFruit;
NEW Fruit
DUP
LDC "BANANA"
ICONST_2
INVOKESPECIAL Fruit.<init> (Ljava/lang/String;I)V
PUTSTATIC Fruit.BANANA : LFruit;
L1
LINENUMBER 1 L1
ICONST_3
ANEWARRAY Fruit
DUP
ICONST_0
GETSTATIC Fruit.GRAPE : LFruit;
AASTORE
DUP
ICONST_1
GETSTATIC Fruit.APPLE : LFruit;
AASTORE
DUP
ICONST_2
GETSTATIC Fruit.BANANA : LFruit;
AASTORE
PUTSTATIC Fruit.$VALUES : [LFruit;
RETURN
MAXSTACK = 4
MAXLOCALS = 0
}
- values() 와 valueOf()는 자바의 모든 열거형에 컴파일러가 추가해주는 것을 알 수 있다.
Enum 클래스 메소드
values()
: enum에 선언된 모든 값들을 저장한 배열을 생성하여 리턴-
ordinal()
: enum constant 의 배열 인덱스를 리턴 (0부터 시작)Most programmers will have no use for this method. It is designed for use by sophisticated enum-based data structures, such as EnumSet and EnumMap
순서이기때문에 바뀔 수 있는 것을 감안해야 하기 때문에, ordinal 메소드에 의존한 코드를 작성하면 안된다.
valueOf()
: 찾고자 하는 특정 string value 가 enum constant 로 있으면 해당 값 리턴- Throws
IllegalArgumentException
,NullPointerException
- Throws
enum Color {
BLUE, PINK, YELLOW;
}
public class App {
public static void main(String[] args) {
Color colors[] = Color.values();
for (Color col : colors) {
System.out.println(col + " at index " + col.ordinal());
}
System.out.println(Color.valueOf("BLUE"));
}
}
valueOf()
사용할 때 없는 값을 부르게 되면?
public class App {
public static void main(String[] args) {
System.out.println(Color.valueOf("RED"));
}
}
name()
: 해당 enum constant 이름을 리턴compareTo()
: 전달인자와 enum 객체의 순서를 비교한다.- 지정된 객체보다 작은경우 : 음의정수,
- 동일하면 : 0,
- 크면 : 양의 정수를 리턴
enum Tutorials {
TOPIC_1, TOPIC_2, TOPIC_3;
}
public class App {
public static void main(String[] args) {
Tutorials t1, t2, t3;
t1 = Tutorials.TOPIC_1;
t2 = Tutorials.TOPIC_2;
t3 = Tutorials.TOPIC_3;
if (t1.compareTo(t2) > 0) {
System.out.println(t2 + " completed before " + t1);
}
if (t1.compareTo(t2) < 0) {
System.out.println(t1 + " completed before " + t2);
}
if (t1.compareTo(t3) == 0) {
System.out.println(t1 + " completed with " + t3);
}
}
}
getDeclaringClass()
: enum constants 의 enum type에 상응하는 클래스 객체 리턴
enum Tutorials {
TOPIC_1, TOPIC_2, TOPIC_3;
}
public class App {
public static void main(String[] args) {
/*
returns the Class object corresponding to
this enum constant's enum type
*/
System.out.println(Tutorials.TOPIC_1.getDeclaringClass());
}
}
- Object 클래스 메소드 오버라이딩
toString()
: enum constant name을 리턴한다.protected void finalize()
: 해당 enum 클래스가 final 메소드를 가질 수 없게 됨equals()
: 전달인자와 enum constant를 비교해서 동일하면 true 아니면 false 리턴hashCode()
: enum constant의 hash code를 리턴clone()
-
Object 클래스 clone()
복제하는 메소드로, 여러 정보를 담고 있는 객체가 있을때 그 객체의 clone 메소드를 사용하면 같은 정보를 담고있는 또 다른 하나의 객체를 만들 수 있는 메소드
class Student{ String name; Student(String name) { this.name = name; } }
Student 클래스 인스턴스를 하나 생성 후
(Student s1 = new Student("yesol");
)s1.clone()
을 하면 컴파일 에러가 발생한다. clone 메서드를 사용하려면 JVM 에게 복제 가능한 클래스라는것을 알려줘야 하기때문이다. 그럴러면 Cloneable 인터페이스를 구현해주면 된다.class Student implements Cloneable{ String name; Student(String name){ this.name = name; } protected Object clone() throws CloneNotSupportedException{ return super.clone(); } } public class App { public static void main(String[] args) { Student s1 = new Student("yesol"); try { Student s2 = (Student)s1.clone(); System.out.println(s1.name); System.out.println(s2.name); } catch (CloneNotSupportedException e) { e.printStackTrace(); } } }
yesol yesol
- Cloneable 인터페이스를 들여다보면 아무것도 없는것을 볼 수 있는데 단지 JVM에게 복제 가능한 클래스라는것을 알려주기 위한 구분자의 역할을 하는 것이다. (일종의 복제 할 수 있냐 없냐의 약속)
- Object는 java.lang 패키지에 있고, clone 메소드는 protected(같은 패키지내에서만 사용 가능) 이기 때문에 오버라이딩 하여 사용해야 한다.
-
enum은 각 값당 인스턴스는 하나여야 하기 때문에 clone을 제공하지 않는다.
- 동일한 object를 리턴한다면 clone (복제)가 아니다.
enum Mobile { Samsung(400), LG(250); int price; Mobile(int price) { this.price = price; } int showPrice() { return price; } } public class App { public static void main(String[] args) { System.out.println("Enums can never be cloned ..."); App a = new App() { @Override protected final Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } }; System.out.println("CellPhone List: "); for (Mobile m: Mobile.values()) { System.out.println(m + " costs " + m.showPrice() + " won"); } } }
-
EnumSet
- 자료구조 set
- 데이터를 비순차적(unordered)으로 저장하는 순열 자료구조(collection)
- Array, List 와 마찬가지로 Set은 수정이 가능하다.
- 중복을 허용하지 않아 같은 값을 삽입하면 마지막에 삽입한 값 하나만 저장된다.
-
빠른 Lookup (데이터 탐색) 에 주로 쓰임
- 저장할 데이터의 Hash 값을 구한 후
- Hash 값에 해당하는 공간 (Bucket)에 값을 저장한다.
- 해시 테이블을 사용해서 해시값을 기반으로 데이터를 저장하기 때문에 특정 값을 포함하는지 확인하는 작업이 빠르다.
- 사용 목적
- 중복된 값을 골라야 할 때
- 특정 값이 있는지 빠르게 확인해야 할 때
- 순서가 상관없을 때
https://www.geeksforgeeks.org/enumset-class-java/
enum 클래스에서 작업하는 특별한 형태의 set collection 이다. 열거형 타입으로 지정해 놓은 요소들을 쉽고 빠르게 배열처럼 요소를 다룰 수 있는 기능을 제공한다.
- AbstractSet 클래스를 확장하고 Set interface를 구현한다.
- HashSet 보다 빠르다
- 원소갯수가 64개를 넘지 않을 경우에 겉은 set 기반이지만 내부적으로 long 데이터형의 비트필드를 사용하게 된다.
비트필드는 2의 제곱수들로 이루어져 메모리 공간을 적게 차지하고 속도도 빠른데, 이 원리를 기반으로 하는 것이 EnumSet이다.
HashSet의 경우 배열과 해쉬코드를 이용하는데 상황에 따라 다르겠지만 통상 비트연산을 사용하는 EnumSet보다 속도면에서 뒤떨어진다.
- 원소갯수가 64개를 넘지 않을 경우에 겉은 set 기반이지만 내부적으로 long 데이터형의 비트필드를 사용하게 된다.
비트필드는 2의 제곱수들로 이루어져 메모리 공간을 적게 차지하고 속도도 빠른데, 이 원리를 기반으로 하는 것이 EnumSet이다.
- enum값만 포함할 수 있으며 모든 값은 동일한 enum타입에 속해야 한다.
- null값을 추가 할 수 없다.
- thread-safe 하지 않다. → 동기화 하여 사용해야 한다.
- enum안에 선언된 순서에 따라 요소들이 저장된다.
- It uses a fail-safe iterator that works on a copy, so it won’t throw a ConcurrentModificationException if the collection is modified when iterating over it
계층구조
E : 저장된 요소의 타입
예제
1.
enum Food {
SALAD, KIMCHI, RICE, BREAD;
}
public class App {
public static void main(String[] args) {
EnumSet<Food> set1, set2, set3, set4;
set1 = EnumSet.of(Food.RICE, Food.BREAD, Food.SALAD);
set2 = EnumSet.complementOf(set1);
set3 = EnumSet.allOf(Food.class);
set4 = EnumSet.range(Food.SALAD, Food.RICE);
System.out.println("set1 = " + set1);
System.out.println("set2 = " + set2);
System.out.println("set3 = " + set3);
System.out.println("set4 = " + set4);
}
}
of(E e1, E e2 ...)
: Creates an enum set initially containing the specified element.complementOf(EnumSet<E> s)
: Creates an enum set with the same element type as the specified enum set, initially containing all the elements of this type that are not contained in the specified set.allOf(Class<E> elementType)
: Creates an enum set containing all of the elements in the specified element type.range(E from , E to)
: Creates an enum set initially containing all of the elements in the range defined by the two specified endpoints.
2.
enum Food {
SALAD, KIMCHI, RICE, BREAD;
}
public class App {
public static void main(String[] args) {
EnumSet<Food> allOfset, noneOfSet;
allOfset = EnumSet.allOf(Food.class);
noneOfSet = EnumSet.noneOf(Food.class);
noneOfSet.add(Food.KIMCHI);
System.out.println("EnumSet Using add() : " + noneOfSet);
noneOfSet.addAll(allOfset);
System.out.println("EnumSet Using addAll() : " + noneOfSet);
}
}
noneOf(Class<E> elementType)
: Creates an empty enum set with the specified element type
enum Food {
SALAD, KIMCHI, RICE, BREAD;
}
public class App {
public static void main(String[] args) {
EnumSet<Food> foods = EnumSet.allOf(Food.class);
Iterator<Food> iterate = foods.iterator();
System.out.print("EnumSet: ");
while (iterate.hasNext()) {
System.out.print(iterate.next());
System.out.print(", ");
}
}
}
EnumSet 객체 생성
EnumSet은 추상클래스이기 때문에 직접 인스턴스를 만들 수 없다.
- 인스턴스를 만드는데 필요한 많은 static factory 메소드를 갖고 있다.
JDK 가 제공하는 EnumSet을 구현한 클래스가 두개 있는데,
- RegularEnumSet
- JumboEnumSet
RegularEnumSet은 EnumSet의 요소를 single long object로 저장한다. long 타입의 element 의 각 bit값은 Enum 값을 나타낸다. long 의 크기는 64bit 이므로 최대 64개의 다른 요소를 저장할 수 있다.
JumboEnumSet은 긴 요소의 배열을 사용하여 EnumSet의 요소를 저장한다. RegularEnumSet과의 차이점은 64개 이상의 값을 허용한다는 것이다. (그 외는 다른 것 없음)
if (universe.length <= 64)
return new RegularEnumSet<>(elementType, universe);
else
return new JumboEnumSet<>(elementType, universe);
EnumMap
- JAVA MAP
- Map
- 탐색 가능한 key, value로 이루어지는 쌍의 묶음(Collection)
- 똑같은 key 를 가진 entry 가 존재하지 않는다.
- key는 반드시 unique한 값이어야 한다.
- key가 entry를 저장하게 될 공간을 결정하여 associative store 라고 부르기도 함
- List-based map
- unsorted, node list로 구현
- 데이터 탐색에 선형탐색알고리즘(linear search algorithm)이 사용된다.
- 원하는 데이터를 찾기 위해 순차적으로 탐색하는 방법
- get, remove, put
- Big-O : O(n)
- 구현하기 쉽다는 장점이 있으나 데이터 개수가 적을때에만 효율적이다.
-
Hash table
- entry 저장하기 위한 주소로 key를 사용한다.
- Big-O : 대부분 O(1)
- Map
Map 인터페이스에서 키를 특정 enum 타입만을 사용하도록 하는 구현체