Spring Framework/Reflection / / 2024. 8. 22. 17:17

Java의 배열과 열거형: 리플렉션을 통한 심층 탐구

[튜토리얼]

 

Lesson: Arrays and Enumerated Types

Java 가상 머신 관점에서 배열과 열거형(enums)은 단순히 클래스일 뿐입니다. Class의 많은 메서드를 이들에 사용할 수 있습니다. 리플렉션(Reflection)은 배열과 열거형을 위한 특정 API를 제공합니다. 이 강의에서는 일련의 코드 샘플을 통해 이러한 객체들을 다른 클래스와 구분하고, 이들에 대해 작업하는 방법을 설명합니다. 또한 다양한 오류도 다루게 됩니다.

 

배열(Arrays)

배열은 컴포넌트 타입과 길이를 가지고 있으며, 길이는 타입의 일부가 아닙니다. 배열은 전체적으로 또는 컴포넌트별로 조작할 수 있습니다. 리플렉션은 후자의 목적을 위해 java.lang.reflect.Array 클래스를 제공합니다.

  • 배열 타입 식별하기(Identifying Array Types): 클래스 멤버가 배열 타입의 필드인지 여부를 확인하는 방법을 설명합니다.
  • 새로운 배열 생성하기(Creating New Arrays): 단순(primitive) 및 복합(java 클래스) 컴포넌트 타입으로 배열 인스턴스를 새로 생성하는 방법을 보여줍니다.
  • 배열 및 배열 컴포넌트 접근하기(Getting and Setting Arrays and Their Components): 배열 타입 필드에 접근하고 개별 배열 엘리먼트를 접근하는 방법을 설명합니다.
  • 문제 해결(Troubleshooting): 일반적인 오류와 프로그래밍 오해를 다룹니다.

열거형(Enumerated Types)

열거형은 리플렉션 코드에서 일반 클래스처럼 다루어집니다. Class.isEnum()은 클래스가 열거형을 나타내는지 여부를 판단합니다. Class.getEnumConstants()는 열거형에 정의된 상수들을 가져옵니다. java.lang.reflect.Field.isEnumConstant()는 필드가 열거형 타입인지 여부를 나타냅니다.

  • 열거형 검사하기(Examining Enums): 열거형의 상수와 기타 필드, 생성자, 메서드를 가져오는 방법을 설명합니다.
  • 열거형 타입 필드 접근 및 설정하기(Getting and Setting Fields with Enum Types): 열거형 상수 값으로 필드를 설정하고 가져오는 방법을 보여줍니다.
  • 문제 해결(Troubleshooting): 열거형과 관련된 일반적인 오류를 설명합니다.

Arrays

배열은 동일한 타입의 고정된 수의 컴포넌트를 포함하는 참조 타입의 객체입니다. 배열의 사이즈는 불변입니다. 배열 인스턴스를 생성하려면 사이즈와 컴포넌트 타입에 대한 정보가 필요합니다. 각 컴포넌트는 기본 타입(예: byte, int, double), 참조 타입(예: String, Object, java.nio.CharBuffer), 또는 배열일 수 있습니다. 다차원 배열은 사실 배열 타입의 컴포넌트를 포함하는 배열에 불과합니다.

배열은 Java 가상 머신에서 구현됩니다. 배열에서 사용할 수 있는 메서드는 Object 클래스로부터 상속된 메서드뿐입니다. 배열의 길이는 그 타입의 일부가 아니며, 배열은 java.lang.reflect.Array.getLength()를 통해 접근할 수 있는 길이 필드를 가지고 있습니다.

리플렉션(Reflection)은 배열 타입 및 배열 컴포넌트 타입에 접근하고, 새로운 배열을 생성하며, 배열 컴포넌트 값을 가져오고 설정하는 메서드를 제공합니다. 다음 섹션에서는 배열에 대한 일반적인 작업의 예를 포함하고 있습니다:

  • 배열 타입 식별하기(Identifying Array Types): 클래스 멤버가 배열 타입의 필드인지 여부를 확인하는 방법을 설명합니다.
  • 새로운 배열 생성하기(Creating New Arrays): 단순 및 복합 컴포넌트 타입으로 배열 인스턴스를 새로 생성하는 방법을 보여줍니다.
  • 배열 및 배열 컴포넌트 접근하기(Getting and Setting Arrays and Their Components): 배열 타입 필드에 접근하고 개별 배열 엘리먼트를 접근하는 방법을 설명합니다.
  • 문제 해결(Troubleshooting): 일반적인 오류와 프로그래밍 오해를 다룹니다.

이 모든 작업은 java.lang.reflect.Array의 정적 메서드를 통해 지원됩니다.

 

Identifying Array Types

배열 타입은 Class.isArray()를 호출하여 식별할 수 있습니다. Class 객체를 얻으려면 이 학습 자료의 "Retrieving Class Objects" 섹션에서 설명한 방법 중 하나를 사용하면 됩니다.

ArrayFind 예제는 지정된 클래스에서 배열 타입의 필드를 식별하고, 각각의 구성 요소 타입을 보고합니다.

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import static java.lang.System.out;

public class ArrayFind {
    public static void main(String... args) {
    boolean found = false;
     try {
        Class<?> cls = Class.forName(args[0]);
        Field[] flds = cls.getDeclaredFields();
        for (Field f : flds) {
         Class<?> c = f.getType();
        if (c.isArray()) {
            found = true;
            out.format("%s%n"
                               + "           Field: %s%n"
                   + "            Type: %s%n"
                   + "  Component Type: %s%n",
                   f, f.getName(), c, c.getComponentType());
        }
        }
        if (!found) {
        out.format("No array fields%n");
        }

        // 실제 코드에서는 이 예외를 더 우아하게 처리해야 합니다.
     } catch (ClassNotFoundException x) {
        x.printStackTrace();
    }
    }
}

 

Class.get*Type()의 리턴 값에 대한 문법은 Class.getName()에서 설명되어 있습니다. 타입 이름의 시작 부분에 있는 '[' 문자의 수는 배열의 차원 수(즉, 중첩 깊이)를 나타냅니다.

다음은 출력 예시입니다. 사용자 입력은 이탤릭체로 표시됩니다.

기본 타입인 byte의 배열:

$ java ArrayFind java.nio.ByteBuffer
final byte[] java.nio.ByteBuffer.hb
           Field: hb
            Type: class [B
  Component Type: byte

 

참조 타입 StackTraceElement의 배열:

$ java ArrayFind java.lang.Throwable
private java.lang.StackTraceElement[] java.lang.Throwable.stackTrace
           Field: stackTrace
            Type: class [Ljava.lang.StackTraceElement;
  Component Type: class java.lang.StackTraceElement

 

predefined는 참조 타입 java.awt.Cursor의 1차원 배열이고, cursorProperties는 참조 타입 String의 2차원 배열입니다:

$ java ArrayFind java.awt.Cursor
protected static java.awt.Cursor[] java.awt.Cursor.predefined
           Field: predefined
            Type: class [Ljava.awt.Cursor;
  Component Type: class java.awt.Cursor
static final java.lang.String[][] java.awt.Cursor.cursorProperties
           Field: cursorProperties
            Type: class [[Ljava.lang.String;
  Component Type: class [Ljava.lang.String;

 

Creating New Arrays

non-reflective 코드와 마찬가지로, 리플렉션을 통해 java.lang.reflect.Array.newInstance()를 사용하여 임의의 타입과 차원의 배열을 동적으로 생성할 수 있습니다. ArrayCreator 예제는 동적으로 배열을 생성할 수 있는 기본 인터프리터입니다. 구문은 다음과 같습니다:

fully_qualified_class_name variable_name[] = 
     { val1, val2, val3, ... }

 

여기서 fully_qualified_class_name은 단일 문자열 아규먼트를 가진 생성자가 있는 클래스를 나타낸다고 가정합니다. 배열의 차원은 제공된 값의 수에 따라 결정됩니다. 다음 예제는 fully_qualified_class_name의 배열 인스턴스를 생성하고, val1, val2 등으로 주어진 값으로 그 값을 채웁니다. (이 예제는 Class.getConstructor()와 java.lang.reflect.Constructor.newInstance()에 대한 기본적인 이해를 필요로 합니다. 생성자에 대한 리플렉션 API에 대한 논의는 이 강의의 "Creating New Class Instances" 섹션을 참조하십시오.)

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.Arrays;
import static java.lang.System.out;

public class ArrayCreator {
    private static String s = "java.math.BigInteger bi[] = { 123, 234, 345 }";
    private static Pattern p = Pattern.compile("^\\s*(\\S+)\\s*\\w+\\[\\].*\\{\\s*([^}]+)\\s*\\}");

    public static void main(String... args) {
        Matcher m = p.matcher(s);

        if (m.find()) {
            String cName = m.group(1);
            String[] cVals = m.group(2).split("[\\s,]+");
            int n = cVals.length;

            try {
                Class<?> c = Class.forName(cName);
                Object o = Array.newInstance(c, n);
                for (int i = 0; i < n; i++) {
                    String v = cVals[i];
                    Constructor ctor = c.getConstructor(String.class);
                    Object val = ctor.newInstance(v);
                    Array.set(o, i, val);
                }

                Object[] oo = (Object[])o;
                out.format("%s[] = %s%n", cName, Arrays.toString(oo));

            // 실제 코드에서는 이 예외를 더 우아하게 처리해야 합니다.
            } catch (ClassNotFoundException x) {
                x.printStackTrace();
            } catch (NoSuchMethodException x) {
                x.printStackTrace();
            } catch (IllegalAccessException x) {
                x.printStackTrace();
            } catch (InstantiationException x) {
                x.printStackTrace();
            } catch (InvocationTargetException x) {
                x.printStackTrace();
            }
        }
    }
}
$ java ArrayCreator
java.math.BigInteger [] = [123, 234, 345]

 

위 예제는 리플렉션을 통해 배열을 생성하는 것이 바람직할 수 있는 한 가지 경우를 보여줍니다. 즉, 컴포넌트 타입이 런타임까지 알려지지 않은 경우입니다. 이 경우, 코드는 Class.forName()을 사용하여 원하는 컴포넌트 타입의 클래스를 얻은 다음, 특정 생성자를 호출하여 배열의 각 컴포넌트를 초기화한 후 해당 배열 값을 설정합니다.

 

Getting and Setting Arrays and Their Components

non-reflective 코드와 마찬가지로, 배열 필드는 전체 또는 구성 요소별로 설정하거나 가져올 수 있습니다. 배열을 한 번에 설정하려면 java.lang.reflect.Field.set(Object obj, Object value)를 사용하십시오. 전체 배열을 가져오려면 Field.get(Object)를 사용하십시오. 개별 구성 요소는 java.lang.reflect.Array의 메서드를 사용하여 설정하거나 가져올 수 있습니다.

Array 클래스는 모든 기본 타입의 컴포넌트를 설정하고 가져오기 위한 setFoo()  getFoo() 형식의 메서드를 제공합니다. 예를 들어, int 배열의 구성 요소는 Array.setInt(Object array, int index, int value)를 사용하여 설정할 수 있으며, Array.getInt(Object array, int index)를 사용하여 가져올 수 있습니다.

이 메서드들은 데이터 타입의 자동 확장을 지원합니다. 따라서 Array.getShort()를 사용하여 int 배열의 값을 설정할 수 있으며, 16비트 short가 32비트 int로 확장되어 데이터 손실 없이 저장될 수 있습니다. 반면에 Array.setLong()을 int 배열에 호출하면, 64비트 long을 32비트 int로 축소할 수 없기 때문에 IllegalArgumentException이 발생합니다. 이 규칙은 전달된 실제 값이 대상 데이터 타입에 정확히 표현될 수 있는지 여부와 관계없이 적용됩니다. Java 언어 명세서(Java Language Specification), Java SE 7 Edition의 "Widening Primitive Conversion" 및 "Narrowing Primitive Conversion" 섹션에는 확장 및 축소 변환에 대한 완전한 논의가 포함되어 있습니다.

참조 타입(배열의 배열 포함)의 배열 구성 요소는 Array.set(Object array, int index, int value)  Array.get(Object array, int index)를 사용하여 설정하고 가져옵니다.

 

Setting a Field of Type Array

GrowBufferedReader 예제는 배열 타입의 필드 값을 교체하는 방법을 보여줍니다. 이 경우, 코드가 java.io.BufferedReader의 백업 배열을 더 큰 배열로 교체합니다. (이는 원래 BufferedReader의 생성을 수정할 수 없는 코드로 가정합니다. 그렇지 않으면 입력 버퍼 크기를 허용하는 BufferedReader(java.io.Reader in, int size) 생성자를 사용하는 것이 더 간단합니다.)

import java.io.BufferedReader;
import java.io.CharArrayReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import static java.lang.System.out;

public class GrowBufferedReader {
    private static final int srcBufSize = 10 * 1024;
    private static char[] src = new char[srcBufSize];
    static {
    src[srcBufSize - 1] = 'x';
    }
    private static CharArrayReader car = new CharArrayReader(src);

    public static void main(String... args) {
    try {
        BufferedReader br = new BufferedReader(car);

        Class<?> c = br.getClass();
        Field f = c.getDeclaredField("cb");

        // cb는 private 필드입니다.
        f.setAccessible(true);
        char[] cbVal = char[].class.cast(f.get(br));

        char[] newVal = Arrays.copyOf(cbVal, cbVal.length * 2);
        if (args.length > 0 && args[0].equals("grow"))
        f.set(br, newVal);

        for (int i = 0; i < srcBufSize; i++)
        br.read();

        // 새로운 백업 배열이 사용되고 있는지 확인합니다.
        if (newVal[srcBufSize - 1] == src[srcBufSize - 1])
        out.format("Using new backing array, size=%d%n", newVal.length);
        else
        out.format("Using original backing array, size=%d%n", cbVal.length);

        // 실제 코드에서는 이 예외를 더 우아하게 처리해야 합니다.
    } catch (FileNotFoundException x) {
        x.printStackTrace();
    } catch (NoSuchFieldException x) {
        x.printStackTrace();
    } catch (IllegalAccessException x) {
        x.printStackTrace();
    } catch (IOException x) {
        x.printStackTrace();
    }
    }
}
$ java GrowBufferedReader grow
Using new backing array, size=16384
$ java GrowBufferedReader
Using original backing array, size=8192

 

위의 예제는 배열 유틸리티 메서드 java.util.Arrays.copyOf()를 사용합니다. java.util.Arrays에는 배열 작업 시 유용한 많은 메서드가 포함되어 있습니다.

 

Accessing Elements of a Multidimensional Array

다차원 배열은 단순히 중첩된 배열입니다. 2차원 배열은 배열의 배열이며, 3차원 배열은 2차원 배열의 배열입니다. CreateMatrix 예제는 리플렉션을 사용하여 다차원 배열을 생성하고 초기화하는 방법을 보여줍니다.

import java.lang.reflect.Array;
import static java.lang.System.out;

public class CreateMatrix {
    public static void main(String... args) {
        Object matrix = Array.newInstance(int.class, 2, 2);
        Object row0 = Array.get(matrix, 0);
        Object row1 = Array.get(matrix, 1);

        Array.setInt(row0, 0, 1);
        Array.setInt(row0, 1, 2);
        Array.setInt(row1, 0, 3);
        Array.setInt(row1, 1, 4);

        for (int i = 0; i < 2; i++)
            for (int j = 0; j < 2; j++)
                out.format("matrix[%d][%d] = %d%n", i, j, ((int[][])matrix)[i][j]);
    }
}
$ java CreateMatrix
matrix[0][0] = 1
matrix[0][1] = 2
matrix[1][0] = 3
matrix[1][1] = 4

 

동일한 결과는 다음 코드 조각을 사용하여 얻을 수 있습니다:

Object matrix = Array.newInstance(int.class, 2);
Object row0 = Array.newInstance(int.class, 2);
Object row1 = Array.newInstance(int.class, 2);

Array.setInt(row0, 0, 1);
Array.setInt(row0, 1, 2);
Array.setInt(row1, 0, 3);
Array.setInt(row1, 1, 4);

Array.set(matrix, 0, row0);
Array.set(matrix, 1, row1);

 

가변 아규먼트 Array.newInstance(Class<?> componentType, int... dimensions)는 다차원 배열을 생성하는 편리한 방법을 제공하지만, 다차원 배열은 여전히 중첩 배열이라는 원칙에 따라 컴포넌트를 초기화해야 합니다. (리플렉션은 이 목적을 위한 다중 인덱스 get/set 메서드를 제공하지 않습니다.)

 

Enumerated Types

enum은 고정된 이름 값을 사용할 때 타입 안전한 열거를 정의하기 위해 사용되는 언어 구조입니다. 모든 enum은 암묵적으로 java.lang.Enum을 상속합니다. enum은 하나 이상의 enum 상수를 포함할 수 있으며, 이는 해당 enum 타입의 고유한 인스턴스를 정의합니다. enum 선언은 필드, 메서드, 생성자(일부 제한 사항이 있음)와 같은 멤버를 가질 수 있다는 점에서 클래스와 매우 유사한 enum타입을 정의합니다.

enum은 클래스이므로, 리플렉션에서 명시적인 java.lang.reflect.Enum 클래스를 정의할 필요가 없습니다. enum에 특정한 리플렉션 API는 Class.isEnum(), Class.getEnumConstants(), 및 java.lang.reflect.Field.isEnumConstant()뿐입니다. enum과 관련된 대부분의 리플렉션 작업은 다른 클래스나 멤버와 동일합니다. 예를 들어, enum 상수는 enum에서 public static final 필드로 구현됩니다. 다음 섹션에서는 Class와 java.lang.reflect.Field를 사용하여 enum을 다루는 방법을 설명합니다.

  • 열거형 검사하기(Examining Enums): enum의 상수 및 기타 필드, 생성자, 메서드를 가져오는 방법을 설명합니다.
  • 열거형 타입 필드 접근 및 설정하기(Getting and Setting Fields with Enum Types): enum 상수 값을 사용하여 필드를 설정하고 가져오는 방법을 보여줍니다.
  • 문제 해결(Troubleshooting): enum과 관련된 일반적인 오류를 설명합니다.

enum에 대한 소개는 "Enum Types" 강의를 참조하십시오.

 

Examining Enums

리플렉션은 enum에 특화된 세 가지 API를 제공합니다:

  • Class.isEnum()
    • 이 클래스가 enum 타입을 나타내는지 여부를 나타냅니다.
  • Class.getEnumConstants()
    • enum에 정의된 상수 목록을 선언된 순서대로 가져옵니다.
  • java.lang.reflect.Field.isEnumConstant()
    • 이 필드가 열거형 타입의 요소를 나타내는지 여부를 나타냅니다.

때때로 enum 상수 목록을 동적으로 가져와야 할 때가 있습니다. non-reflective 코드에서는 enum의 암묵적으로 선언된 정적 메서드인 values()를 호출하여 이를 수행합니다. 그러나 enum 타입의 인스턴스가 없는 경우 가능한 값 목록을 얻는 유일한 방법은 Class.getEnumConstants()를 호출하는 것입니다. 이는 enum 타입을 인스턴스화하는 것이 불가능하기 때문입니다.

다음은 완전히 지정된 이름이 주어졌을 때 Class.getEnumConstants()를 사용하여 enum에서 상수를 순서대로 가져오는 방법을 보여주는 EnumConstants 예제입니다.

import java.util.Arrays;
import static java.lang.System.out;

enum Eon { HADEAN, ARCHAEAN, PROTEROZOIC, PHANEROZOIC }

public class EnumConstants {
    public static void main(String... args) {
    try {
        Class<?> c = (args.length == 0 ? Eon.class : Class.forName(args[0]));
        out.format("Enum name:  %s%nEnum constants:  %s%n",
               c.getName(), Arrays.asList(c.getEnumConstants()));
        if (c == Eon.class)
        out.format("  Eon.values():  %s%n",
               Arrays.asList(Eon.values()));

        // 실제 코드에서는 이 예외를 더 우아하게 처리해야 합니다.
    } catch (ClassNotFoundException x) {
        x.printStackTrace();
    }
    }
}

 

출력 예시는 다음과 같습니다. 사용자 입력은 이탤릭체로 표시됩니다.

$ java EnumConstants java.lang.annotation.RetentionPolicy
Enum name:  java.lang.annotation.RetentionPolicy
Enum constants:  [SOURCE, CLASS, RUNTIME]
$ java EnumConstants java.util.concurrent.TimeUnit
Enum name:  java.util.concurrent.TimeUnit
Enum constants:  [NANOSECONDS, MICROSECONDS, 
                  MILLISECONDS, SECONDS, 
                  MINUTES, HOURS, DAYS]

 

이 예제는 Class.getEnumConstants()가 리턴하는 값이 enum 타입에서 values()를 호출하여 리턴된 값과 동일하다는 것도 보여줍니다.

$ java EnumConstants
Enum name:  Eon
Enum constants:  [HADEAN, ARCHAEAN, 
                  PROTEROZOIC, PHANEROZOIC]
Eon.values():  [HADEAN, ARCHAEAN, 
                PROTEROZOIC, PHANEROZOIC]

 

 

enum은 클래스이므로, 다른 정보는 이 학습 자료의 "Fields, Methods, and Constructors" 섹션에서 설명한 동일한 리플렉션 API를 사용하여 얻을 수 있습니다. EnumSpy 코드는 이러한 API를 사용하여 enum 선언에 대한 추가 정보를 얻는 방법을 보여줍니다. 이 예제는 Class.isEnum()을 사용하여 검사할 클래스 집합을 제한합니다. 또한, Field.isEnumConstant()을 사용하여 enum 선언에서 다른 필드와 구별되는 enum 상수를 식별합니다(모든 필드가 enum 상수는 아닙니다).

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Member;
import java.util.List;
import java.util.ArrayList;
import static java.lang.System.out;

public class EnumSpy {
    private static final String fmt = "  %11s:  %s %s%n";

    public static void main(String... args) {
    try {
        Class<?> c = Class.forName(args[0]);
        if (!c.isEnum()) {
        out.format("%s is not an enum type%n", c);
        return;
        }
        out.format("Class:  %s%n", c);

        Field[] flds = c.getDeclaredFields();
        List<Field> cst = new ArrayList<Field>();  // enum constants
        List<Field> mbr = new ArrayList<Field>();  // member fields
        for (Field f : flds) {
        if (f.isEnumConstant())
            cst.add(f);
        else
            mbr.add(f);
        }
        if (!cst.isEmpty())
        print(cst, "Constant");
        if (!mbr.isEmpty())
        print(mbr, "Field");

        Constructor[] ctors = c.getDeclaredConstructors();
        for (Constructor ctor : ctors) {
        out.format(fmt, "Constructor", ctor.toGenericString(),
               synthetic(ctor));
        }

        Method[] mths = c.getDeclaredMethods();
        for (Method m : mths) {
        out.format(fmt, "Method", m.toGenericString(),
               synthetic(m));
        }

        // 실제 코드에서는 이 예외를 더 우아하게 처리해야 합니다.
    } catch (ClassNotFoundException x) {
        x.printStackTrace();
    }
    }

    private static void print(List<Field> lst, String s) {
    for (Field f : lst) {
         out.format(fmt, s, f.toGenericString(), synthetic(f));
    }
    }

    private static String synthetic(Member m) {
    return (m.isSynthetic() ? "[ synthetic ]" : "");
    }
}
$ java EnumSpy java.lang.annotation.RetentionPolicy
Class:  class java.lang.annotation.RetentionPolicy
     Constant:  public static final java.lang.annotation.RetentionPolicy
                  java.lang.annotation.RetentionPolicy.SOURCE 
     Constant:  public static final java.lang.annotation.RetentionPolicy
                  java.lang.annotation.RetentionPolicy.CLASS 
     Constant:  public static final java.lang.annotation.RetentionPolicy 
                  java.lang.annotation.RetentionPolicy.RUNTIME 
        Field:  private static final java.lang.annotation.RetentionPolicy[] 
                  java.lang.annotation.RetentionPolicy. [ synthetic ]
  Constructor:  private java.lang.annotation.RetentionPolicy() 
       Method:  public static java.lang.annotation.RetentionPolicy[]
                  java.lang.annotation.RetentionPolicy.values() 
       Method:  public static java.lang.annotation.RetentionPolicy
                  java.lang.annotation.RetentionPolicy.valueOf(java.lang.String) 

 

출력은 java.lang.annotation.RetentionPolicy 선언에 세 가지 enum 상수만 포함되어 있음을 보여줍니다. enum 상수는 public static final 필드로 노출됩니다. 필드, 생성자, 메서드는 컴파일러에 의해 생성됩니다. $VALUES 필드는 values() 메서드의 구현과 관련이 있습니다.

참고: enum 타입의 발전을 지원하는 등의 다양한 이유로 enum 상수의 선언 순서는 중요합니다. Class.getFields()와 Class.getDeclaredFields()는 반환된 값의 순서가 선언된 소스 코드의 순서와 일치한다는 보장을 하지 않습니다. 애플리케이션에서 순서가 필요할 경우 Class.getEnumConstants()를 사용하십시오.

java.util.concurrent.TimeUnit에 대한 출력은 훨씬 더 복잡한 enum이 가능하다는 것을 보여줍니다. 이 클래스에는 몇 가지 메서드와 함께 enum 상수가 아닌 static final로 선언된 추가 필드가 포함되어 있습니다.

$ java EnumSpy java.util.concurrent.TimeUnit
Class:  class java.util.concurrent.TimeUnit
     Constant:  public static final java.util.concurrent.TimeUnit
                  java.util.concurrent.TimeUnit.NANOSECONDS
     Constant:  public static final java.util.concurrent.TimeUnit
                  java.util.concurrent.TimeUnit.MICROSECONDS
     Constant:  public static final java.util.concurrent.TimeUnit
                  java.util.concurrent.TimeUnit.MILLISECONDS
     Constant:  public static final java.util.concurrent.TimeUnit
                  java.util.concurrent.TimeUnit.SECONDS
     Constant:  public static final java.util.concurrent.TimeUnit
                  java.util.concurrent.TimeUnit.MINUTES
     Constant:  public static final java.util.concurrent.TimeUnit
                  java.util.concurrent.TimeUnit.HOURS
     Constant:  public static final java.util.concurrent.TimeUnit
                  java.util.concurrent.TimeUnit.DAYS
        Field:  static final long java.util.concurrent.TimeUnit.C0
        Field:  static final long java.util.concurrent.TimeUnit.C1
        Field:  static final long java.util.concurrent.TimeUnit.C2
        Field:  static final long java.util.concurrent.TimeUnit.C3
        Field:  static final long java.util.concurrent.TimeUnit.C4
        Field:  static final long java.util.concurrent.TimeUnit.C5
        Field:  static final long java.util.concurrent.TimeUnit.C6
        Field:  static final long java.util.concurrent.TimeUnit.MAX
        Field:  private static final java.util.concurrent.TimeUnit[] 
                  java.util.concurrent.TimeUnit. [ synthetic ]
  Constructor:  private java.util.concurrent.TimeUnit()
  Constructor:  java.util.concurrent.TimeUnit
                  (java.lang.String,int,java.util.concurrent.TimeUnit)
                  [ synthetic ]
       Method:  public static java.util.concurrent.TimeUnit
                  java.util.concurrent.TimeUnit.valueOf(java.lang.String)
       Method:  public static java.util.concurrent

 

Getting and Setting Fields with Enum Types

enum을 저장하는 필드는 Field.set()  Field.get()을 사용하여 다른 참조 타입과 동일하게 설정하고 가져올 수 있습니다. 필드 접근에 대한 자세한 내용은 이 강의의 "Fields" 섹션을 참조하십시오.

다음은 서버 애플리케이션에서 실행 중에 일반적으로 허용되지 않는 트레이스 레벨을 동적으로 수정해야 하는 애플리케이션을 고려해 봅시다. 서버 객체의 인스턴스가 사용 가능하다고 가정합니다. SetTrace 예제는 코드가 enum의 문자열 표현을 enum 타입으로 변환하고, enum을 저장하는 필드의 값을 가져와 설정하는 방법을 보여줍니다.

import java.lang.reflect.Field;
import static java.lang.System.out;

enum TraceLevel { OFF, LOW, MEDIUM, HIGH, DEBUG }

class MyServer {
    private TraceLevel level = TraceLevel.OFF;
}

public class SetTrace {
    public static void main(String... args) {
    TraceLevel newLevel = TraceLevel.valueOf(args[0]);

    try {
        MyServer svr = new MyServer();
        Class<?> c = svr.getClass();
        Field f = c.getDeclaredField("level");
        f.setAccessible(true);
        TraceLevel oldLevel = (TraceLevel)f.get(svr);
        out.format("Original trace level:  %s%n", oldLevel);

        if (oldLevel != newLevel) {
         f.set(svr, newLevel);
        out.format("    New  trace level:  %s%n", f.get(svr));
        }

        // 실제 코드에서는 이 예외를 더 우아하게 처리해야 합니다.
    } catch (IllegalArgumentException x) {
        x.printStackTrace();
    } catch (IllegalAccessException x) {
        x.printStackTrace();
    } catch (NoSuchFieldException x) {
        x.printStackTrace();
    }
    }
}

 

enum 상수는 싱글톤이므로 ==  != 연산자를 사용하여 동일한 타입의 enum 상수를 비교할 수 있습니다.

$ java SetTrace OFF
Original trace level:  OFF
$ java SetTrace DEBUG
Original trace level:  OFF
    New  trace level:  DEBUG
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유