[자바 스터디 11주차] Enum

목표

자바의 열거형에 대해 학습하세요.

학습할 것

  • enum 정의하는 방법
  • enum이 제공하는 메소드 (values()와 valueOf())
  • java.lang.Enum
  • EnumSet

enum을 정의하는 방법

 

enum이란?

enum(열거형)은 서로 관련된 상수를 편리하게 선언하기 위한 것으로 상수를 여러개 정의할 때 사용한다. enum은 여러 상수를 정의한 후, 정의된 것 이외의 값은 허용하지 않는다.

 

가장 간단하게 선언하는 법은 다음과 같다.

enum 열거형이름 { 상수명1, 상수명2, 상수명3, ...}

※ 추가적인 특징

  - 열거형 필드의 이름은 상수이기 때문에 대문자로 표기한다.

  - 기본적으로 0부터 시작하는 정수값이 연속적으로 부여된다.

public class EnumDemo {
	enum Season {
    // 0부터 연속적인 정수값 부여
    SPRING, SUMMER, FALL, WINTER }
    
    public static void printSeason(Season season) {
    	switch(season){
        	case SPRING -> System.out.println("봅입니다.");
            case SUMMER -> System.out.println("봅입니다.");
            ...
            default -> throw new IllegalArgumentException("계절의 이름이 아닙니다.");
        }
    }
    
    public static void main(String[] args) {
    	printSeason(Season.SPRING);
    }
}

 

※ 열거형의 장점

개발을 진행할 때 Enum을 통해 얻는 기본적인 장점들은 아래와 같다.

  • 문자열과 비교해, IDE의 적극적인 지원을 받을 수 있다.
    • 자동완성, 오타검증, 텍스트 리팩토링 등
  • 허용 가능한 값들을 제한할 수 있다.
  • 리팩토링시 변경 범위가 최소화 된다.
    • 내용 추가가 필요하더라도, Enum 코드외에 수정할 필요가 없다.

아래는 Java에서 얻을 수 있는 Enum의 장점

  • 확실한 부분과 불확실한 부분을 분리할 수 있다.
    • A값과 B값이 실제로 동일한 것인지, 전혀 다른 의미인지..
    • 코드를 사용하기 위해 추가로 필요한 메서드들은 무엇인지..
    • 변경되면 어디까지 변경해야 하는 것인지..
  • 문맥을 담을 수 있다.
    • A라는 상황에서 "a"와 B라는 상황에서 "a"는 똑같은 문자열 "a"지만 전혀 다른 의미입니다.
    • 문자열은 이를 표현할 수 없지만, Enum은 이를 표현할 수 있다.
    • 이로인해 실행되는 코드를 이해하기 위해 추가로 무언가를 찾아보는 행위를 최소화 할 수 있다

Enum은 왜 만들어졌는가?

Enum을 잘 사용하면 코드의 가독성을 높이고 논리적인 오류를 줄일 수 있다. Enum을 잘 사용하기 위해 우선 Enum이 왜 탄생했는지 먼저 알아보자.

 

예시로 과일의 이름을 입력받으면  해당 과일의 칼로리를 출력하는 프로그램이 있고, 과일의 이름을 다음과 같이 상수로 관리한다고 생각해보자.

public class EnumDemo {
    public static final int APPLE = 1;
    public static final int PEACH = 2;
    public static final int BANANA = 3;

    public static void main(String[] args) {
        int type = APPLE;
        switch (type) {
            case APPLE:
                System.out.println("32 kcal");
                break;
            case PEACH:
                System.out.println("52 kcal");
                break;
            case BANANA:
                System.out.println("16 kcal");
                break;
        }
    }
}

위 코드의 문제점은 각가의 상수에 부여된 1, 2, 3이라는 리터럴은 단순히 상수들을 구분하고 이용하기 위해 부여된 것이지 논리적으로 아무런 의미가 없다. 다시 말해 APPLE은 정수 1과 아무련 관련도 없고 굳이 1이어야 할 이유도 없다.

 

두번째 문제점은 이름의 충돌이 발생할 수 있다는 것이다. 만약 이 프로그램이 커져서, IT 회사의 정보가 추가되었고 회사 이름을 상수로 관리하려한다 해보자.

public class EnumDemo {
    public static final int APPLE = 1;
    public static final int PEACH = 2;
    public static final int BANANA = 3;

    ... ...

    public static final int APPLE = 1;
    public static final int GOOGLE = 2;
    public static final int FACEBOOK = 3;

}

과일 '사과'와 회사 '애플'은 이름은 같지만 서로 다른 의미를 가진다. 하지만 위의 예시처럼 사용하려면 이름이 중복되기 때문에 컴파일 에러가 발생한다.

 

이름의 중복은 아래처럼 이름을 다르게 해주거나 

public class EnumDemo {
    public static final int FRUIT_APPLE = 1;
    public static final int FRUIT_PEACH = 2;
    public static final int FRUIT_BANANA = 3;

    ... ...

    public static final int COMPANY_APPLE = 1;
    public static final int COMPANY_GOOGLE = 2;
    public static final int COMPANY_FACEBOOK = 3;

}

인터페이스로 만들면 구분이 가능해진다.

interface Fruit {
    int APPLE = 1, PEACH = 2, BANANA = 3;
}

interface Company {
    int APPLE = 1, GOOGLE = 2, FACEBOOK = 3;
}

하지만 이런 식으로 상수를 인터페이스로 관리하는 것은 안티패턴이다. 인터페이스는 규약을 위해 만든 것이지, 이런 식으로 쓰라고 만든 개념이 아니기 때문이다.

 

하지만 여전히 문제가 남아 있다. fruit와 company 모두 int타입의 자료형이기 때문에 아래와 같은 코드가 가능하다.

if (Fruit.APPLE == Company.APPLE) {
    ......
}

하지만 '과일'과 '회사'는 서로 비교조차 되어서는 안되는 다른 개념이다. 따라서 위와 같은 코드는 애초에 작성할 수 없게 컴파일 과정에서 막아줘야 한다.

※이러한 현상(타입은 다르지만 값이 같기 때문에 조건식의 결과가 true)을 typeSafe하지 못한다고 한다.

 

강의 중 Tip

(1) type-safety

type-safety는 숫자만 해당하는게 아니라 문자열도 마찬가지이다. QueryDSL과 같은 라이브러리가 각광받는 이유가 타입세이프 때문이다.

 

문자열은 타입 세이프가 보장되지 않는다. 따라서 문자열로 sql을 작성하는 것보다 QueryDSL과 같이 클래스에서 추출한 정보를 이용해 작성하면 훨씬 수월하고, 컴파일 타임에 오타가 날 일도 없고, 특정한 타입 기반으로 컴파일을 하기 때문에 다 처리된다. 런타임에 문자열 오타로 발생하는 sql 에러를 미연에 방지할 수 있다.

 

예제를 보자. 우리가 반드시 hello를 출력해야 한다고 할 때 다음과 같은 코드를 작성했다.

public clasas TypeSafetyEx{
	public static void main(String[] args){
    	System.out.println("hello");
    }
}

위 코드는 type-safety하지 않은 코드이다. 오타가 발생해서 hellp가 출력될 수도 있고 hell0가 출력될 수도 있다. 그럼 어떻게 해야할까?

public class TypeSafetyEx{
	
    enum Greet {
    	Hello("hello");
        
        Greet(String message){
        	this.message = message;
        }
        
        String message;
        
        public String getMessage(){
        	return message;
        }
    }
    
    public static void main(String[] args){
    	System.out.println(Greet.Hello.getMessage());
    }
    
}

이렇게 enum을 통해서 hello라고 정의해두면 type-safety가 되는 것이다. 코드는 길어졌지만 출력할 때 편하고 오타가 나더라도 컴파일러가 알려주기 때문에 오타 방지가 된다.

 

 

둘이 애초에 비교를 하지 못하도록 하려면 서로 다른 객체로 만들어주면 된다.

class Fruit {
    public static final Fruit APPLE = new Fruit();
    public static final Fruit PEACH = new Fruit();
    public static final Fruit BANANA = new Fruit();
}

class Company {
    public static final Company APPLE = new Company();
    public static final Company GOOGLE = new Company();
    public static final Company FACEBOOK = new Company();
}
public class EnumDemo {

    public static void main(String[] args) {
        if (Fruit.APPLE == Company.APPLE) {}   // 컴파일 에러 발생
    }
}

이렇게 하면 위에서 언급했던 문제들

1) 상수와 리터럴이 논리적인 연관이 없음.

2) 서로 다른 개념끼리 이름이 충돌할 수 있음

3) 서로 다른 개념임에도 비교하는 코드가 가능함

이 3가지의 문제가 모두 해결된다.

 

하지만 다른 문제가 발생한다. 사용자 정의 타입은 swich문의 조건에 들어갈 수 없다.

※ switch문의 조건으로 들어갈 수 있는 데이터 타입은 byte(Byte),short(Short),int(Integer) / char(Character), String / enum 이다.

public class EnumDemo {

    public static void main(String[] args) {
        Fruit type = Fruit.APPLE;
        switch (type) {   // 컴파일 에러
            case Fruit.APPLE:
                System.out.println("32 kcal");
                break;
            case Fruit.PEACH:
                System.out.println("52 kcal");
                break;
            case Fruit.BANANA:
                System.out.println("16 kcal");
                break;
        }

    }
}

Enum은 위처럼 상수를클래스로 정의해서 관리할 때 얻을 수 있는 이점들을 모두 취하면서 상수들을 더욱 간단히 선언할 수 있도록 하기 위해 만들어졌다.

 

Java Enum의 특징

1. enum에 정의된 상수들은 해당 enum type의 객체이다.

C 등의 다른 언어들도 열거형이 존재한다. 하지만 다른 언어들과 달리 Java의 enum은 단순한 정수값이 아닌 enum type의 객체다.

 

enum Fruit { APPLE, PEACH, BANANA }

 

위와 같은 열거형이 정의되어 있을 때, 이를 클래스로 정의한다면 다음처럼 표현할 수 있다.

class Fruit {
    public static final Fruit APPLE = new Fruit();
    public static final Fruit PEACH = new Fruit();
    public static final Fruit BANANA = new Fruit();
   
}

enum을컴파일까지 한 후 바이트 코드를 분석해보면 위와 같이 바뀐다.

 

※ 주의 사항

  • 열거형 상수의 비교에는 '=='와 compareTo() 사용 가능(등가 비교연산자 가능)
  • =, >, >=, <, <=, <> 같은 비교연산자는 사용할 수 없음(컴파일 에러)
if (NORMAR.BIGMAC > MACALL.BIGMAC) { //열거형 상수에는 비교연산자 사용불가
	System.out.println("이렇게 사용하면 컴파일에러가 난다.");
}

 

비교연산자를 왜 쓸 수 없을까?

바이트 코드를 분석해보면 알겠지만 enum은 클래스이다. 클래스에서 비교연산자를 쓸 수 없다. 생각해보면 객체와 객체는 서로 비교연션자를 쓸 수 없기 때문에 enum에서 지원하지 않는 것이다.

 

 

2. 생성자와 메서드를 추가할 수 있다.

java에서 enum은 엄연한 클래스이다. enum을 정의하는 법과 거의 비슷한데 몇 가지 차이가 있다. 우선 class 대신에 enum이라고 적는다. 첫 줄에는 열거형 상수의 이름을 선언한다. 이름은 대문자로 선언하는 것이 관례이며 각 상수는 콤마로 구분한다. 제일 마지막 상수의 끝에는 세미콜론을 붙여야 한다. 앞부분에서 살펴본 것처럼, 간단히 상수의 이름만 선언할 때는 세미콜론을 붙이지 않아도 된다.

enum Fruit{
	APPLE, PEACH, BANANA; //열거할 상수의 이름 선언, 마지막에 ;을 꼭 붙여야 한다.
    
    Fruit(){
    	System.out.println("생성자 호출 " + this.name());
    }
    
}

public class EnumDemo{
	public static void main(String[] args){
    	Fruit apple = Fruit.APPLE;
      //Fruit grape = new Fruit();
      //에러 발생. 열거형의 생성자의 접근제어자는 항상 private이다.
    }	
}

생성자를 정의할 수 있는데, enum의 생성자의 접근제어자는 private이기 때문에 외부에서 상수를 추가할 수 없다. 열거형의 멤버 중 하나를 호출하면, 열거된 모든 상수의 객체가 생성된다. 위 예시를 보면 APPLE 하나를 호출했는데 열거된 모든 상수의 생성자가 호출되었음을 확인할 수 있다. 상수 하나당 각각의 인스턴스가 만들어지며 모두 public static final 이다.

 

생성자를 이용해서 상수에 데이터를 추가할 수 있다.

(보통, 불연속적인 열거형 상수 사용시 원하는 값을 ()안에 넣을 수 있다.)

enum Currency {

    PENNY(1), NICKLE(5), DIME(10), QUARTER(25);

    private int value;

    Currency(int value) {
        this.value = value;
    }

    public int value() {
        return value;
    }
}

public class EnumDemo {

    public static void main(String[] args) {
        System.out.println(Currency.DIME.value());    // 10
    }
}

 

다음과 같이 switch문을 이용해 각 상수별로 다른 로직을 실행하는 메서드를 정의할 수도 있다.

enum Transport {
    BUS(1200), TAXI(3900), SUBWAY(1200);

    private final int BASIC_FARE;   // 기본요금

    Transport(int basicFare) {
        BASIC_FARE = basicFare;
    }

    public double fare() {     // 운송 수단별로 다르게 책정되는 요금
       	switch(this) {
        	case BUS -> {
            	return BASIC_FARE * 1.5
            }
            case TAXI -> {
                return BASIC_FARE * 2.0;
            }
            case SUBWAY -> {
                return BASIC_FARE * 0.5;
            }
            default -> throw new IllegalArgumentException(); // 실행될 일 없는 코드이지만 없으면 컴파일 에러
        }
    }

}

 

아래와 같이 추상 메서드를 선언해서 각 상수 별로 다르게 동작하는 코드를 구현할 수도 있다.

enum Transport {
    BUS(1200) {
        @Override
        double fare(int distance) {
            return distance * BASIC_FARE * 1.5;
        }
    },

    TAXI(3900) {
        @Override
        double fare(int distance) {
            return distance * BASIC_FARE * 2.0;
        }
    },

    SUBWAY(1200) {
        @Override
        double fare(int distance) {
            return distance * BASIC_FARE * 0.5;
        }
    };

    protected final int BASIC_FARE;  // 기본요금, protected로 선언해야 상수에서 접근 가능

    Transport(int basicFare) {
        BASIC_FARE = basicFare;
    }

    abstract double fare(int distance);   // 거리에 따른 요금 계산

 

3. 동일성 비교시 equals보단 '==을 사요앻라

enum은 하나의 인스턴스를 보장하기 때문에 '==' 사용을 추천

equals를 사용하여 NPE 발생 위험을 부담할 필요가 없다.

 

Enum은 언제 사용하는가

필요한 원소를 컴파일 타임에 다 알 수 있는 상수 집합이라면 항상 열거 타입을 사용하자. 태양계 행성, 한 주의 요일, 체스 말 처럼 본질적으로 열거 타입인 타입은 당연히 포함된다. 그리고 메뉴 아이템, 연산 코드, 명령줄 플래그 등 허용하는 값 모두를 컴파일타임에 이미 알고 있을 때도 쓸 수 있다. 열거 타입에 정의된 상수 개수가 영원히 고정 불변일 필요는 없다. 열거 타입은 나중에 상수가 추가되도 바이너리 수준에서 호환되도록 설계되었다.

- 이펙티브 자바 3/E. Item 34, 219쪽

 

Java Enum 활용기 - 우아한형제들(이동욱)

https://woowabros.github.io/tools/2017/07/10/java-enum-uses.html

 

Java Enum 활용기 - 우아한형제들 기술 블로그

안녕하세요? 우아한 형제들에서 결제/정산 시스템을 개발하고 있는 이동욱입니다.이번 사내 블로그 포스팅 주제로 저는 Java Enum 활용 경험을 선택하였습니다. 이전에 개인 블로그에 Enum에 관해

woowabros.github.io

java.lang.Enum

모든 enum은 내부적으로 java.lang.Enum 클래스를 부모 클래스로 가진다.

그 밖에 Object클래스로부터 상속받은 메소드들이 있다.

 

values(), valueOf()

java.lang.Enum 에선 values(), valueOf() 메소드에 대한 내용을 자세히 찾아볼 수 없다. 그 이유는 컴파일러가 자동으로 추가해주는 메소드이기 때문이다.

 

컴파일까지 실행한 후 바이트코드를 분석해보면 static으로 선언되어 있는 메소드 2개를 발견할 수 있다. 이로써 values(), valueOf() 메소드는 컴파일러가 자동으로 추가해준다는 사실을 직접 확인할 수 있다.

 

임시로 추가

enum Hamburger {
    BIGMAC, SHANGHI, MACCHICKEN;
}

public class EnumTest {
    public static void main(String[] args) {
        Hamburger hamburger = Hamburger.valueOf("MACCHICKEN");
        System.out.println(hamburger + " 버거 입니다.");
    }
}

enum Hamburger {
    BIGMAC, SHANGHI, MACCHICKEN;
}

public class EnumTest {

    public static void main(String[] args) {
        Hamburger[] hamburger = Hamburger.values();
        System.out.println("현재 저희 매장에 있는 버거는");
        for (Hamburger burgers : hamburger) {
            System.out.println(burgers + " 버거가 있습니다.");
        }
    }
}

 

 

ordinal

package me.gracenam.study.week11;

public class EnumExample {

    enum Fruit {
        Apple, Banana
    }

    public static void main(String[] args) {
        System.out.println(Fruit.Apple.ordinal());
        System.out.println(Fruit.Banana.ordinal());
    }
}

 

각 상수의 순서가 출력되었는데, ordinal()의 가장 큰 문제점은 바로 순서가 바뀔 수 있다는 가능성을 간과한 것이다.

package me.gracenam.study.week11;

public class EnumExample {

    enum Fruit {
        Apple, Banana
    }

    public static void main(String[] args) {
        System.out.println(Fruit.Apple.ordinal());
        System.out.println(Fruit.Banana.ordinal());

        if(Fruit.Apple.ordinal() == 0) {
            System.out.println("hello");
        }
    }
}

Apple이 첫 번째 일 경우에 hello를 출력하는데 만일 Apple 앞에 다른 과일이 추가된다고 생각해보자. 이렇게 될 경우 Apple은 0이 아니고 1이 되고 hello는 출력되지 못한다.

 

이처럼 입력이 언제 어떻게 변경될지 모르기 때문에 ordinal()을 이용해서 만드는 것은 매우 위험하다. 실제로 ordinal()을 살펴보면 아래와 같이 나와 있다.

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.

대부분의 프로그래머는 이 메서드를 쓸 일이 없다. 이 메서드는 EnumSet과 EnumMap 같이 열거 타입 기반의 범용 자료구조에 쓸 목적으로 설계되었다.

ordinal()은 Enum 내부에서 사용하기 위해 만든 것이지, 프로그래머가 이 메서드에 의존하는 코드를 작성하는 것은 안티패턴이다.

 

EnumSet

Set 인터페이스를 기반으로 하면서 Enumeration type을 사용하는 방법이다.

 

EnumSet의 상속구조와 제공하는 메소드는 다음과 같다.

 

EnumSet의 특징은 다음과 같다.

  • EnumSet은 AbstractSet 클래스를 상속하고 Set 인터페이스를 구현한다.
  • 오직 열거형 상수만을 값으로 가질 수 있다. 또한 모든 값은 같은 enum type이어야 한다.
  • null value를 추가하는 것을 허용하지 않는다. NPE를 던지는 것도 허용하지 않는다.
  • ordinal값의 순서대로 요소가 저장된다.
  • thread-safe하지 않다. 동기식으로 사용하려면 Collections.synchronizedMap을 사용하거나, 외부에서 동기화를 구현해야 한다.
  • 모든 메서드는 arithmetic bitwise operation을 사용하기 때문에 모든 기본 연산의 시간 복잡도가 O(1)이다
import java.util.EnumSet;

enum Gfg { CODE, LEARN, CONTRIBUTE, QUIZ, MCQ };

public class EnumSetExample {
    public static void main(String[] args) {
		
       	// Creating a set
        EnumSet<Gfg> set1, set2, set3, set4;
        
        // Adding elements
		set1 = EnumSet.of(Gfg.QUIZ, Gfg.CONTRIBUTE, Gfg.LEARN, Gfg.CODE); //일일이 입력
        set2 = EnumSet.complementOf(set1); //인자로 들어온 enumset에서 누락된값만 집어넣음?
        set3 = EnumSet.allOf(Gfg.class); //전체를 다 집어넣음
        set4 = EnumSet.range(Gfg.CODE, Gfg.CONTRIBUTE); //시작범위, 끝범위 입력하면 그 사이에 값 대입


        System.out.println("Set 1: " + set1);
        System.out.println("Set 2: " + set2);
        System.out.println("Set 3: " + set3);
        System.out.println("Set 4: " + set4);
    }
}

 

※ EnumSet에 new 연산자를 사용하지 않는 이유

EnumSet은 다른 컬렉션들과 달리 new 연산자를 사용할 수 없다. 단지 정적 팩토리 메소드만으로 EnumSet의 구현 객체를 반환받을 수 있다. 왜그럴까?

 

EnumSet의 내부를 살펴보면 abstract 클래스, 추상 클래스라는 것을 알 수 있다. 즉, EnumSet은 추상 클래스이기 때문에 객체로 생성 및 사용이 불가능한 것이다.

 

왜 이렇게 만들었을까?

1. 사용자 편의성 - (사용자는 어떤 구현 객체가 적합한지 몰라도 상관없다.)

RegularEnumSet은 원소의 개수가 적을 때 적합하고, JumboEnumSet은 원소의 개수가 많을 때 적합하지만, 이는 EnumSet의 구현체들을 모두 알고 있는 사용자가 아니라면 난해한 선택지가 될 수 있다. 하지만 EnumSet을 가장 잘 알고 있는 EnumSet 개발자가 적절한 구현 객체를 반환해준다면 EnumSet을 사용하는 입장에서는 어떤 구현체가 적합한지 고려하지 않아도 된다.

 

2. 사용자 편의성2 - (사용자는 빈번하게 발생되는 EnumSet 초기화 과정을 간단히 진행할 수 있다.)

EnumSet이 다루는 Enum의 모든 원소들을 Set에 담는 행위는 빈번하게 수행될 것으로 예상되는 일이다. 이러한 경우를 대비하여 EnumSet의 allOf라는 메소드를 사용하면 모든 Enum 원소가 담겨있는 EnumSet을 쉽게 반환받고 사용할 수 있다.

 

3.EnumSet의 확장성과 유지보수의 이점

EnumSet을 유지보수하는 과정에서 RegularEnumSet과 JumboEnumSet 이외에 다른 경우를 대비하는 구현 클래스가 추가 된다고 하여도 내부에 감추어져 있기 때문에, EnumSet을 사용하면 기존의 코드에는 전혀 영향이 업삳. 심지어 RegularEnumSet이 삭제된다 하더라도 사용자에게 영향이 없다 이는 EnumSet의 확장성의 큰 이점으로 작용할 수 있다.

 

 

Enum 싱글톤

kwj1270님이 싱글톤을 활용해 만드신 예제

 

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간 동기화)을 가지며 비교적 간편하게 사용할 수 있는 방법
  • 단 한번의 인스턴스 생성을 보장하며 사용이 간편하고 직렬화가 자동으로 처리되고 직렬화가 아무리 복잡하게 이루어져도 여러 객체가 생길일이 없다.
  • 리플렉션을 통해 싱글톤을 깨트릴 수도 없다.

※ 리플렉션이란?

Class someThing = 클래스.class;
// Class someThing = Class.forName("클래스 이름");

Method m[] someThing.getDeclareMethods();
Field[] f = c.getFields();
Constructor[] cs = c.getConstructors();
Class[] inter = c.getInterfaces();
Class superClass = c.getSuperclass();
하나의 클래스에서 다른 클래스의 정보를 알아낼 수 있는 개념
출처 : https://opentutorials.org/module/987/8927

구체적인 클래스 타입을 알지 못해도, 그 클래스의 메서드/타입/변수들을 접근할 수 있도록 해준다.
출처 : https://brunch.co.kr/@kd4/8

객체를 통해 클래스의 정보를 분석해 내는 프로그램 기법을 말한다.
출처 : https://gyrfalcon.tistory.com/entry/Java-Reflection [Minsub's Blog]

 

 

참조

wisdom-and-record.tistory.com/52

velog.io/@ljs0429777/11%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C-Enum

yadon079.github.io/2021/java%20study%20halle/week-11-feedback

parkadd.tistory.com/50

siyoon210.tistory.com/152