목표 : 자바의 열거형에 대해 학습하세요.
Enum 이란?
Enum 정의하는 방법
Enum이 제공하는 메소드
java.lang.Enum
EnumSet
EnumMap
Enum 싱글톤
Enumerated type : 열거형 타입
enum Operation {ADDITION, SUBTRACTION, MULTIPLICATION, DIVIDE}
public class Main {
public static void main(String[] args) {
System.out.println(Operation.ADDITION);
System.out.println(Operation.SUBTRACTION);
System.out.println(Operation.MULTIPLICATION);
System.out.println(Operation.DIVIDE);
}
}
/* 실행 결과
* ADDITION
* SUBTRACTION
* MULTIPLICATION
* DIVIDE
*/
enum
이란 enumerated type
의 줄임말로
멤버라 불리는 명명된 값의 집합을 이루는 자료형이다.
즉, 열거형에 사용될 수 있는 특정한 값들을 정의해서 해당 값들만 사용할 수 있게 한다.
참고로, 열거자 이름들은 일반적으로 해당 언어의 상수 역할을 하는 식별자이다.
enum 열거형이름 {상수명1, 상수명2, ...}
enum Operation {ADDITION, SUBTRACTION, MULTIPLICATION, DIVIDE}
열거형을 정의하는 방법은 간단하다.
괄호{}
안에 상수의 이름을 나열하기만 하면 된다.
enum 열거형이름 {
상수명(), // 생성자 호출
상수명(), // 생성자 호출
상수명() // 생성자 호출
private final [데이터타입] [변수이름]; // [접근지정자] [final] 는 설정할 수 있지만,
// private final 을 사용하는 것이 일반적이다.
public get변수이름() {
return 변수이름;
}
private 생성자(매개변수..) {
변수 = 값;
}
}
열거형 상수에 ()
를 붙여, 값을 부여할 수 있다.
()
안에는 프리미티브 타입 데이터는 물론, 레퍼런스 타입 객체도 들어올 수 있다.
단, 열거형 상수에 값을 부여할 경우, 이에 해당하는 생성자도 함께 정의해줘야 한다.
// enum Operation {ADDITION, SUBTRACTION, MULTIPLICATION, DIVIDE}
enum Operation {
ADDITION(), SUBTRACTION(), MULTIPLICATION, DIVIDE; // 디폴트 생성자 사용
}
사실, 앞서 배웠던 방법에서 생성자를 정의하지 않았던 이유는
자동으로 생성되는 디폴트 생성자(매개변수 없는 생성자 의미)를 사용했기 때문이다.
실제로 상수에 ()
를 섞어서 정의해도 에러가 발생하지 않는다.
enum 열거형이름 {
상수명(), // 생성자 호출
상수명(), // 생성자 호출
상수명() // 생성자 호출
[접근지정자] [final] [데이터타입] [변수이름];
private 생성자(매개변수..) {
변수 = 값;
}
}
그리고 추가로 알 수 있는 점은
enum
에서 상수를 선언하는 것은 생성자를 호출하는 것임을 알 수 있다.
그렇기 때문에, 상수 선언시 ()
에 값을 부여하는 것은
()
에 해당하는 매개변수를 가진 생성자를 호출하는 것이며
경우에 따라서 매개변수로 들어온 값을 저장하는 변수도 선언해줄 수 있다.
private final [데이터타입] [변수이름];
public get변수이름() {
return 변수이름;
}
상수의 값을 저장할 데이터는 접근 지정자를 선택할 수 있다.
하지만, public
으로 선언시에는 외부에서 자유롭게 호출할 수 있어
캡슐화가 깨진다는 문제가 있다.
그렇기 때문에, private
접근 지정자를 주고,
getter
메서드를 생성해주는 방식을 주로 사용하고 있다.
생성자는 왜 private로 선언할까?
enum 열거형이름 {
상수명(), // 생성자 호출
상수명(), // 생성자 호출
상수명() // 생성자 호출
[접근지정자] [final] [데이터타입] [변수이름];
private 생성자(매개변수..) {
변수 = 값;
}
}
enum 에서 생성자를 정의한다면 private
로 선언해야 한다.
public
, default
, protected
로 선언시에 컴파일 에러가 발생한다.
enum 타입은 고정된 상수들의 집합으로
enum에 대한 모든 정보들은 런타임이 아닌 컴파일 타임에서 고정 되어야 한다.
그렇기 때문에 컴파일시 안정성을 보장하기 위해 private 생성자
만 사용이 가능하고
이로인해 다른 패키지나 클래스에서 enum 타입
에 접근해서 동적으로 값을 지정할 수 없다.
예제
enum DisplayType {
CRT("Cathode Ray Tube"),
FPD("Flat Panel Display"),
LCD("Liquid Crystal Display"),
PDP("Plasma Display Panel"),
LED("Light emitting diode"),
OLED("Organic Light Emitting Diode");
private final String key;
public String getKey() {
return key;
}
DisplayType(String key) {
this.key = key;
}
}
public class EnumTest {
public static void main(String[] args) {
System.out.println(DisplayType.OLED);
}
}
controll + space
자동완성 기능을 사용해보면
나열된 상수값과 메서드만 사용할 수 있다는 것을 알 수 있다.
- 나열된 상수값이 아닌 새로운 값을 넣으면 컴파일 에러가 발생한다.
- 나열된 상수값을 통해 올바른 값을 넣어주자
import static study.eenum.DisplayType.*;
enum DisplayType {
CRT("Cathode Ray Tube"),
FPD("Flat Panel Display"),
LCD("Liquid Crystal Display"),
PDP("Plasma Display Panel"),
LED("Light emitting diode"),
OLED("Organic Light Emitting Diode");
private final String key;
public String getKey() {
return key;
}
DisplayType(String key) {
this.key = key;
}
}
public class EnumTest {
public static void main(String[] args) {
System.out.println(OLED);
}
}
import static
을 이용하면 '열거형 상수'를 보다 간략하게 나타낼 수 있다.
package study.eenum;
import static study.eenum.DisplayType.*;
enum DisplayType {
CRT("Cathode Ray Tube", "CRT is Good"),
FPD("Flat Panel Display", "FPD is Good"),
LCD("Liquid Crystal Display", "LCD is Good"),
PDP("Plasma Display Panel", "PDP is Good"),
LED("Light emitting diode", "LED is Good"),
OLED("Organic Light Emitting Diode", "OLED is Good");
private final String key;
private final String value;
public String getKey() {
return key;
}
public String getValue() {
return value;
}
DisplayType(String key, String value) {
this.key = key;
this.value = value;
}
}
public class EnumTest {
public static void main(String[] args) {
System.out.println(CRT.getKey());
}
}
열거형 상수의 값을 2개 이상 줄 수도 있다.
앞선 내용과 마찬가지로 상수, 생성자, 변수, getter를 갯수에 맞게끔 정의해주면 된다.
enum 에서는 사용자가 직접 메서드를 정의할 수 있도록 기능을 제공한다.
enum 상수 메서드 정의 방법
enum SampleEnum {
상수 {
[접근제어자] [제어자] [반환형] [메서드이름] (매개변수){
...
}
}
}
enum
에서 정의한 상수에{}
를 이용해서 사용자가 직접 메서드를 정의할 수 있다.
enum 상수 메서드 호출 방법
pubilc class Main {
public static void main(String[] args) {
SampleEnum.상수.메서드이름(인자);
}
}
- enum 상수에 정의된 메서드를 사용하고자 한다면
열거형.상수.메서드(매개변수)
와 같은 방식으로 메서드를 호출하면 된다.
enum 상수 메서드 정의 예시
enum Direction {
EAST {
public void getOppositeDirection() {
System.out.println("EAST")
}
},
WEST {
public void getOppositeDirection() {
System.out.println("WEST")
}
},
SOUTH {
public void getOppositeDirection() {
System.out.println("SOUTH")
}
},
NORTH {
public void getOppositeDirection() {
System.out.println("NORTH")
}
};
}
pubilc class Main {
public static void main(String[] args) {
Direction.EAST.getOppositeDirection();
}
}
- enum 상수에
{}
를 이용해서 해당 상수에 메서드를 정의했다. - 외부에서
Direction.EAST.getOppositeDirection();
를 통해 메서드를 호출한다.
enum에 abstract 메서드 정의하고 오버라이딩하기
enum Direction {
EAST {
@Override
public void getOppositeDirection() {
System.out.println("EAST")
}
},
WEST {
@Override
public void getOppositeDirection() {
System.out.println("WEST")
}
},
SOUTH {
@Override
public void getOppositeDirection() {
System.out.println("SOUTH")
}
},
NORTH {
@Override
public void getOppositeDirection() {
System.out.println("NORTH")
}
};
public abstract void getOppositeDirection(); // 추상메서드 선언
}
- enum 안에 추상 메서드를 선언하고 상수내에서 이를 오버라이딩하여 구현할 수 있다.
- 여기서 드는 의문점 : 어떻게 오버라이딩이 가능하지?
- 한가지 가설은 상수에
{}
를 사용하면 익명클래스가 생성되는 것은 아닌가 싶다. - 그렇기에 현재 enum 자료형을 상속받고 이를 오버라이딩해서 구현하는 것이다.
- 실제로, 추상 메서드를 정의하지 않으면 컴파일 에러가 발생한다.
The optional class body of an enum constant implicitly defines an anonymous class declaration (§15.9.5) that extends the immediately enclosing enum type.
The class body is governed by the usual rules of anonymous classes; in particular it cannot contain any constructors.
Instance methods declared in these class bodies may be invoked outside the enclosing enum type only if they override accessible methods in the enclosing enum type (§8.4.8).
오라클 공식 명세서(설명서)를 보면 위와 같은 글이 있다.
필자의 영어실력, 번역실력이 부족하지 않다면 해석은 이렇다.
- 열거형 상수의 선택적 클래스 본문은
둘러싸인 열거형 형식을 상속하는 익명 클래스 선언을 암시적으로 정의합니다. - 단, 익명 클래스 안에는 생성자를 선언할 수 없습니다.
- 외부 메서드를 호출하고자 한다면, 외부 메서드를 오버라이딩 해야만 합니다.
enum Direction {
EAST {
@Override
public void getOppositeDirection() {
System.out.println("EAST")
}
},
WEST {
@Override
public void getOppositeDirection() {
System.out.println("WEST")
}
},
SOUTH {
@Override
public void getOppositeDirection() {
System.out.println("SOUTH")
}
},
NORTH {
@Override
public void getOppositeDirection() {
System.out.println("NORTH")
}
@Override // 호출하려면 오버라이딩 해야함, 상속 때문인듯
public void print() {
super.print();
}
};
public void print() {
System.out.println("print");
}
public abstract void getOppositeDirection(); // 추상메서드 선언
}
oracle
에서 명시한 대로 enum을 정의해보니 위와 같은 코드가 나왔다.
결과적으로 enum 에 {}
를 하는 것은
enum을 상속받는 익명 클래스를 선언하는 것으로 정리하면 좋을 것 같다.
기본형
enum Operation {
ADDITION, SUBTRACTION, MULTIPLICATION, DIVIDE;
public double apply(double x, double y) {
switch(this) {
case ADDITION: return x + y;
case SUBTRACTION: return x - y;
case MULTIPLICATION: return x * y;
case DIVIDE: return x / y; // divide by zero exception 생략
default:
throw new IllegalArgumentException("Unknown Operation");
}
}
}
public class Main {
public static void main(String[] args) {
System.out.println(Operation.ADDITION.apply(1,2));
}
}
/* 실행 결과
* 3.0
*/
- 해당 코드는 mongzza 님의 Repo를 참조했습니다.
switch 문
을 이용해서 호출된 상수에 따라 메서드를 값을 다르게 리턴합니다.
하지만, switch 문
과 enum
을 같이 사용할 경우 발생하는 문제점은 많다.
- 새로운 열거형 상수가 추가된다면 switch 문을 수정해야한다.
- 모든 CASE에 공통된 로직이 추가되면, 갯수의 배수만큼 코드 길이가 커진다.
- 결국 유지보수성은 낮으며 가독성은 높은 코드가 된다.
그렇기에 이를 메서드로 한번 표현해보겠습니다.
enum Operation {
ADDITION {
@Override
double apply(double x, double y) {
return x + y;
}
},
SUBTRACTION {
@Override
double apply(double x, double y) {
return x - y;
}
},
MULTIPLICATION {
@Override
double apply(double x, double y) {
return x * y;
}
},
DIVIDE {
@Override
double apply(double x, double y) {
return x / y;
}
};
abstract double apply(double x, double y);
}
public class Main {
public static void main(String[] args) {
System.out.println(Operation.ADDITION.apply(1, 2));
}
}
하지만, 위 코드도 자세히 보면 중복이 발생하는 것을 알 수 있고,
추상 메서드가 enum 안에서만 사용이 가능하다는 문제점이 있습니다.
interface Operator {
double apply(double x, double y);
}
enum Operation {
ADDITION ((x, y) -> x + y),
SUBTRACTION ((x, y) -> x - y),
MULTIPLICATION ((x, y) -> x * y),
DIVIDE ((x, y) -> x / y);
private final Operator operator;
Operation(Operator operator) {
this.operator = operator;
}
public double apply(double x, double y) {
return operator.apply(x, y);
}
}
public class Main {
public static void main(String[] args) {
System.out.println(Operation.ADDITION.apply(1, 2));
}
}
람다를 사용하여 깔끔한 코드를 작성했다.
설명을 하자면 아래와 같다.
- 추상 메서드를 함수형 인터페이스로 분리한다.
- 생성자로 분리한 함수형 인터페이스 자료형을 주입받도록 한다.
apply()
메서드가 실행되면 주입된 객체의apply()
를 실행한다.
만약 새로운 열거형을 추가해야한다면,
단순히, 열거형 상수와 로직으로 처리할 람다식을 기술해주면 된다.
Java의 enum
은 기본적으로 제공되는 메소드가 몇 가지가 있다.
package com;
enum Direction { EAST, WEST, SOUTH, NORTH}
// 0 1 2 3
class EnumMain {
public static void main(String[] args) {
Direction east = Direction.EAST;
Direction west = Direction.WEST;
Direction south = Direction.SOUTH;
Direction north = Direction.NORTH;
System.out.println(east.toString());
System.out.println(east.name());
System.out.println("--------------");
System.out.println(east.ordinal());
System.out.println(west.ordinal());
System.out.println(south.ordinal());
System.out.println(north.ordinal());
System.out.println("--------------");
System.out.println(east.compareTo(Direction.EAST));
System.out.println(east.compareTo(Direction.WEST));
System.out.println("--------------");
System.out.println(east.getDeclaringClass());
System.out.println("--------------");
System.out.println(east.valueOf("EAST"));
System.out.println("--------------");
Direction[] directions = east.values();
for(Direction direction : directions){
System.out.println(direction.toString());
}
}
}
/* 실행 결과
* EAST
* EAST
* --------------
* 0
* 1
* 2
* 3
* --------------
* 0
* -1
* --------------
* class com.Direction
* --------------
* EAST
* --------------
* EAST
* WEST
* SOUTH
* NORTH
*/
메서드 이름 | 설명 |
---|---|
toString() | 해당 상수의 이름을 문자열로 반환한다. |
name() | 해당 상수의 이름을 문자열로 반환한다. |
compareTo() | 정렬의 기준을 위한 메서드이다. 비교 대상보다 순서가 빠르면 -1, 같으면 0, 느리면 1을 반환한다. 정렬 순서는 상수가 선언된 순서가 디폴트로 지정되어 있다. |
ordinal() | 상수의 선언 순서에 따른 인덱스(Zero based)값을 반환한다. Enum 안에는 private final int ordinal; 가 정의되어있고 이를 사용한다. |
valueOf() | 인자로 받은 이름과 같은 Enum값으로 반환한다. |
values() | 선언된 모든 Enum값을 순서대로 배열에 담아서 반환한다. |
이 메소드들은 overriding도 가능하므로 참고하도록 하자.
toString() 과 name()의 차이
public final String name() {
return name;
}
public String toString() {
return name;
}
toString()
과name()
은 같은 값을 반환한다.- 그렇다면 차이는 무엇일까?
name()
은final
로 선언된 메서드로 오버라이딩이 불가능하다.toString()
은 일반적인 Object 클래스의 메서드로 우리가 오버라이딩할 수 있다.- 참고
Enum
클래스는 모든 열거형이 공통으로 상속받는 추상 클래스다.
package java.lang;
import java.io.Serializable;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
private final String name;
public final String name() { return name; }
private final int ordinal;
public final int ordinal() { return ordinal; }
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
public String toString() { return name; }
public final boolean equals(Object other) {return this==other;}
public final int hashCode() { return super.hashCode();}
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
public final int compareTo(E o) {
Enum<?> other = (Enum<?>)o;
Enum<E> self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}
@SuppressWarnings("unchecked")
public final Class<E> getDeclaringClass() {
Class<?> clazz = getClass();
Class<?> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
}
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
protected final void finalize() { }
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("can't deserialize enum");
}
}
abstract 클래스
로 인스턴스를 생성할 수 없다.- 하지만, 재미있는 점은 abstract 메서드가 존재하지 않는다는 것이다.
- 즉, 인스턴스의 생성을 막고자 추상클래스로 선언한 것을 알 수 있다.
우리가 선언하는 enum 타입은 이 Enum 클래스를 자동으로 상속하여,
Enum에서 제공하는 ordinal 과 같은 변수, 메서드를 사용할 수 있게 된다.
실제로 final
처리된 name()
를 호출하면 오버라이드 불가능하다는 메세지가 나온다.
EnumSet이란, JDK 5
에서 등장한 java.util
패키지 클래스로
Set 인터페이스
를 기반으로 열거형 타입으로 지정해놓은 요소들을
가장 쉽고 빠르게 배열처럼 요소들을 다룰수 있는 기능을 제공한다.
EnumSet
은 비트연산을 이용해 메모리 공간도 적게 차지하고 속도도 빠르다.
또한, Set
기반이지만 enum
과 static
타입의 메소드들로 구성되어있어
안정성을 최대한 추구하면서도 편리한 사용이 가능하다.
또한, HashSet
과 달리 올바른 버킷을 찾기 위해 해시 코드를 계산할 필요가 없다.
- 사진 출처 : baeldung.com
위 그림을 보면, Set 인터페이스
를 구현하고 AbstractSet 인터페이스
를 상속한다.
즉, 열거형을 이용해 Collection 인터페이스의
및 Set의 기능
을 사용할 수 있다.
EnumSet
을 사용하려면 몇 가지 중요한 사항을 고려해야합니다.
- 생성자 오버라이딩이 불가능하고 동일한 타입의 열거 상수만 선언 가능
null
값을 넣거나NullPointerException
을 던질 수 없다.- 스레드로부터 안전하지 않으므로 필요한 경우 외부에서 동기화해야한다.
- 상수는 열거 형에 선언 된 순서에 따라 저장됩니다. (ordinal 변수)
- 복사본에서는
Fail-Safe
방식의Iterator
를 사용한다.
동작중 컬렉션이 수정되어도 작업을 중단하지 않고 진행한다.
즉,ConcurrentModificationException
이 발생하지 않는다.
참고
import java.util.EnumSet;
enum Day {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
}
public class Main {
public static void main(String[] args) {
EnumSet<Day> enumSet = EnumSet.allOf(Day.class);
// 전체 복사
EnumSet<Day> enumSet2 = EnumSet.copyOf(enumSet); // clone
System.out.println(enumSet2);
// EnumSet 의 내용을을 비워라
enumSet = EnumSet.noneOf(Day.class);
System.out.println(enumSet);
// EnumSet 에서 매개변수로 들어온 2개의 열거형을 새로운 EnumSet 으로 반환해라
enumSet = EnumSet.of(Day.FRIDAY, Day.WEDNESDAY);
System.out.println(enumSet);
// 매개변수로 들어온 EnumSet을 제외한 열거형으로 새로운 EnumSet 반환해라
enumSet = EnumSet.complementOf(enumSet);
System.out.println(enumSet);
// 인덱스 범위만큼 새로운 EnumSet 생성해서 반환해라
enumSet = EnumSet.range(Day.TUESDAY, Day.FRIDAY);
System.out.println(enumSet);
// 인덱스 순서에 맞게 구현하지 않으면, 에러가 발생한다.
// enumSet = EnumSet.range(Day.FRIDAY, Day.TUESDAY);
// System.out.println(enumSet);
}
}
메서드 이름 | 설명 |
---|---|
copyOf(EnumSet s) | 매개변수로 들어온 EnumSet을 복사한다. |
noneOf(Class elementType) | 빈, EnumSet을 반환한다. |
of(E e1, E e2) | 열거형 상수 2개를 입력받아 새로운 EnumSet에 넣어 반환한다. |
complementOf(EnumSet s) | 매개변수에 들어온 EnumSet의 열거형 상수들을 제외한 열거형 상수들을 새로운 EnumSet에 넣어 반환한다. |
range(E from, E to) | 인자로 받은 열거형 상수 사이의 범위를 인덱스의 순서대로 새로운 EnumSet에 넣어 반환하다. 단, 앞선 매개변수의 인덱스가 빠르면 런타임에 에러가 난다. |
동기식으로 사용할 필요가 있다면 Collections.synchronizedSet
을 사용한다.
Set<MyEnum> s = Collections.synchronizedSet(EnumSet.noneOf(MyEnum.class));
EnumMap
은 HashMap
과 비슷하게 Enum을 기준으로 Key
와 Value
를 가진 자료구조이다.
기존, HashMap
은 key
의 고유성을 위해 해싱 처리를 해줬던 것과 달리,
Enum의 상수는 이미 그 자체로 고유한 싱글턴 객체이므로 해싱처리를 해줄 필요가 없다.
그렇기 때문에 HashMap보다 빠르다고 알려진 Map의 형태 중 하나이다.
또 하나의 장점으로는 순서를 기억한다는 특징이 있다.
Enum 자체도 정의된 순서. 즉, ordinal을 통해 순서가 나뉘어져있어
HashMap
에서는 TreeMap
처럼 정렬된다 하더라도 입력시 성능이 우수하다는 특징이 있다.
선언 방법
EnumMap<K, V> em = new EnumMap<K, V>(Class keyType);
Map<LifeCycle, Set<Plant>> plantsByLifeCycle = new EnumMap<>(LifeCycle.class);
선언으로 new EnumMap<>(...)
을 사용하지만 key의 Class 타입으로 넣어줘야한다.
public class EnumMapExample {
public enum GFG {
CODE, CONTRIBUTE, QUIZ, MCQ;
}
public static void main(String args[]) {
EnumMap<GFG, String> testMap = new
EnumMap<GFG, String>(GFG.class);
testMap.put(GFG.CODE, "Start Coding with gfg");
testMap.put(GFG.CODE, "Coding with gfg"); // 똑같은 key가 들어올 수 있고 후자가 앞에 것을 덮어쓴다.
testMap.put(GFG.CONTRIBUTE, "Contribute for others");
testMap.put(GFG.QUIZ, "Practice Quizes");
testMap.put(GFG.MCQ, "Test Speed with Mcqs");
// enumMap의 사이즈 출력
System.out.println("Size of EnumMap in java: " +
testMap.size());
// enumMap의 내용물 출력, toString 오버라이딩 되어있다.
System.out.println("EnumMap: " + testMap);
// get(enum상수)를 통해 value를 얻어온다.
System.out.println("Key : " + GFG.CODE + " Value: "
+ testMap.get(GFG.CODE));
// EnumMap안에 특정 Key가 포함되었는지 파악한다.
System.out.println("Does testMap has " + GFG.CONTRIBUTE + ": "
+ testMap.containsKey(GFG.CONTRIBUTE));
// EnumMap안에 특정 Value가 포함되었는지 파악한다.
System.out.println("Does testMap has :" + GFG.QUIZ + " : "
+ testMap.containsValue("Practice Quizes"));
System.out.println("Does testMap has :" + GFG.QUIZ + " : "
+ testMap.containsValue(null));
EnumMap<GFG, String> testMap2 = new
EnumMap<GFG, String>(GFG.class);
// 이미 정의된 EnumMap을 받는다.
testMap2.putAll(testMap);
System.out.println(testMap2);
// 특정 키의 값을 바꾼다.
testMap2.replace(GFG.MCQ, "replace");
System.out.println(testMap2);
// 특정 키의 값이 주어진 값과 맞다면 다른 값으로 바꾼다.
testMap2.replace(GFG.MCQ, "replace", "oneMoreTime");
System.out.println(testMap2);
// 특정 키의 값이 같지 않기 때문에 바뀌지 않는다.
testMap2.replace(GFG.MCQ, "onenoeMoreTime", "end");
System.out.println(testMap2);
}
}
/*실행결과
Size of EnumMap in java: 4
EnumMap: {CODE=Coding with gfg, CONTRIBUTE=Contribute for others, QUIZ=Practice Quizes, MCQ=Test Speed with Mcqs}
Key : CODE Value: Coding with gfg
Does testMap has CONTRIBUTE: true
Does testMap has :QUIZ : true
Does testMap has :QUIZ : false
{CODE=Coding with gfg, CONTRIBUTE=Contribute for others, QUIZ=Practice Quizes, MCQ=Test Speed with Mcqs}
{CODE=Coding with gfg, CONTRIBUTE=Contribute for others, QUIZ=Practice Quizes, MCQ=replace}
{CODE=Coding with gfg, CONTRIBUTE=Contribute for others, QUIZ=Practice Quizes, MCQ=oneMoreTime}
{CODE=Coding with gfg, CONTRIBUTE=Contribute for others, QUIZ=Practice Quizes, MCQ=oneMoreTime}
Process finished with exit code 0
*/
메서드 이름 | 설명 |
---|---|
put(K key, V value) |
Key 값과 Value 값을 받아 내부 배열에 저장한다. |
putAll(Map<? extends K, ? extends V> m) |
이미 생성된 적있는 Map 객체를 내부 배열에 저장한다. |
size() |
EnumMap의 key와 value 쌍의 갯수를 반환한다. |
get(Object key) |
key를 통해서 value의 값을 반환한다. |
containsKey(Object key) |
EnumMap에 특정 key 가 존재하는지 확인 후 boolean을 반환한다. |
containsValue(Object value) |
EnumMap에 특정 value 값이 존재하는지 확인 후 boolean을 반환한다. |
replace(K key, V value) |
기존 key 에 있던 value값을 바꾼다. |
replace(K key, V oldValue, V newValue) |
안정성을 보장해주는 방법으로 key의 이전 value 값이 맞으면 현재값으로 변경해준다. |
동기식으로 사용할 필요가 있다면 Collections.synchronizedMap
을 사용한다.
Map<EnumKey, V> m
= Collections.synchronizedMap(new EnumMap<EnumKey, V>(...));
enum의 문법적 특성을 이용한 싱글톤 객체 생성
package SingleTone;
public enum EnumSettings {
INSTANCE; // 생성자이자 식별자를 의미 -> 밑에 정의된 생성자에 파라미터가 있다면 여기에도 인수 넣어줘야한다.
// 식별자라고 말을 한 것은 해당 문구를 기준으로 객체를 참조하기에 싱글톤 기준이 된다.
private boolean darkMode = false; // 디폴트 값
private int fontSize = 13; // 디폴트 값
private EnumSettings() {} // 생성자
public EnumSettings getInstance() {
return INSTANCE;
}
public boolean getDarkMode(){
return darkMode;
}
public int getFontSize(){
return fontSize;
}
public void setDarkMode(boolean darkMode){
this.darkMode = darkMode;
}
public void setFontSize(int fontSize){
this.fontSize = fontSize;
}
}
장점 :
- 싱글톤의 특징(단 한 번의 인스턴스 호출, Thread간 동기화) 을 가지며
비교적 간편하게 사용할 수 있는 방법이다. - 단 한번의 인스턴스 생성을 보장하며 사용이 간편하고 직렬화가 자동으로 처리되고
직렬화가 아무리 복잡하게 이루어져도 여러 객체가 생길 일이 없다. - 리플렉션을 통해 싱글톤을 깨트릴 수도 없다.