📌열거형(Enum)

여러 상수들을 보다 편리하게 선언할 수 있는 문법요소이다.

예전의 방식

public static final int SPRING = 1;
public static final int SUMMER = 2;
public static final int FALL = 3;
public static final int WINTER = 4;

enum을 사용하면 개선 가능

enum Seasons { SPRING, SUMMER, FALL, WINTER }

사용법

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

enum으로 정의한 상수는 switch문에서도 사용이 가능하다.

class Seasons {
    public static final Seasons SPRING = new Seasons();
    public static final Seasons SUMMER = new Seasons();
    public static final Seasons FALL   = new Seasons();
    public static final Seasons WINTER = new Seasons();
}

public class Main {
    public static void main(String[] args) {
        Seasons seasons = Seasons.SPRING;
        switch (seasons) {
            case Seasons.SPRING:
                System.out.println("봄");
                break;
            case Seasons.SUMMER:
                System.out.println("여름");
                break;
            case Seasons.FALL:
                System.out.println("가을");
                break;
            case Seasons.WINTER:
                System.out.println("겨울");
                break;
        }
    }
}

/*
출력값 

java: incompatible types: Seasons cannot be converted to int
*/

이 코드에서 seansons 사용자 정의 타입이기 때문에 에러가 발생한다
switch문은 char, byte, short, int, Character, Byte, Integer, String, enum 타입만 가능하다.

enum Seasons {SPRING, SUMMER, FALL, WINTER}

public class Main {
    public static void main(String[] args) {
        Seasons seasons = Seasons.SPRING;
        switch (seasons) {
            case SPRING:
                System.out.println("봄");
                break;
            case SUMMER:
                System.out.println("여름");
                break;
            case FALL:
                System.out.println("가을");
                break;
            case WINTER:
                System.out.println("겨울");
                break;
        }
    }
}

//출력값 

이처럼 enum을 사용하면 정상적으로 작동

정리하자면, 자바에서 열거형은 여러 상수들을 보다 편리하게 선안하고 관리할 수 있게하며, 상수명의 중복을 피하고, 타입에 대한 안정성을 보장

또한 더 간결하고 가독성이 좋은 코드를 작상할 수 있고, switch 문에서도 작동이 가능


열거형에서의 메서드

리턴 타입메소드(매개변수)설명
Stringname()열거 객체가 가지고 있는 문자열을 리턴하며, 리턴되는 문자열은 열거타입을 정의할 때 사용한 상수 이름과 동일하다.
intordinal()열거 객체의 순번( 0부터 시작 )을 리턴한다.
intcompareTo(비교값)주어진 매개값과 비교해서 순번 차이를 리턴한다.
열거 타입valueOf(String name)주어진 문자열의 열거 객체를 리턴한다.
열거 배열values()모든 열거 객체들을 배열로 리턴한다.
//예제

enum Level {
  LOW, // 0
  MEDIUM, // 1
  HIGH // 2
}

public class EnumTest {
    public static void main(String[] args) {
        Level level = Level.MEDIUM;

        Level[] allLevels = Level.values();
        for(Level x : allLevels) {
            System.out.printf("%s=%d%n", x.name(), x.ordinal());
        }

        Level findLevel = Level.valueOf("LOW");
        System.out.println(findLevel);
        System.out.println(Level.LOW == Level.valueOf("LOW"));

        switch(level) {
            case LOW:
                System.out.println("낮은 레벨");
                break;
            case MEDIUM:
                System.out.println("중간 레벨");
                break;
            case HIGH:
                System.out.println("높은 레벨");
                break;
        }
    }
}

//출력값
LOW=0
MEDIUM=1
HIGH=2
LOW
true
중간 레벨

📌제네릭(Generic)

타입을 구체적으로 지정하는 것이 아니라, 추후에 지정할 수 있도록 일반화해두는 것을 의미한다.
-> 작성한 클래스 또는 메서드의 코드가 특정 데이터 타입에 얽매이지 않게 해둔 것을 의미한다.

제네릭 클래스의 정의

제네릭이 사용된 클래스를 제네릭 클래스라고 한다.

class Basket<T> {
    private T item;

    public Basket(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }

    public void setItem(T item) {
        this.item = item;
    }
}

여기서 T타입 매개변수라고 한다.

제네릭 클래스를 정의할 때 주의점

class Basket<T> {
	private T item1; // O 
	static  T item2; // X 
}

클래스 변수에는 타입 매개변수를 사용할 수 없다.

클래스 변수에 타입 매개변수를 사용할 수 있다면 클래스 변수의 타입이 인스턴스 별로 달라지게 된다.

즉, Basket<String>으로 만든 인스턴스와, Basket<Integer>로 만든 인스턴스가 공유하는 클래스 변수의 타입이 서로 달라지게 되어, 클래스 변수를 통해 같은 변수를 공유하는 것이 아니게 된다.


제네릭 클래스 사용

타입 매개변수에 치환될 타입으로 기본 타입을 지정할 수 없다.

Basket<String>  basket1 = new Basket<String>("Hello");
Basket<Integer> basket2 = new Basket<Integer>(10);
Basket<Double>  basket3 = new Basket<Double>(3.14);

new Basket<...>은 생략 가능

Basket<String>  basket1 = new Basket<>("Hello");
Basket<Integer> basket2 = new Basket<>(10);
Basket<Double>  basket2 = new Basket<>(3.14);

다형성도 적용 가능

class Flower { ... }
class Rose extends Flower { ... }
class RosePasta { ... }

class Basket<T> {
    private T item;

    public T getItem() {
        return item;
    }

    public void setItem(T item) {
        this.item = item;
    }
}

class Main {
    public static void main(String[] args) {
        Basket<Flower> flowerBasket = new Basket<>();
        flowerBasket.setItem(new Rose());      // 다형성 적용
        flowerBasket.setItem(new RosePasta()); // 에러
    }
}

제한된 제네릭 클래스

Basket클래스는 타입을 지정하는 데에 있어 제한이 없다.

class Flower { ... }
class Rose extends Flower { ... }
class RosePasta { ... }

// 제네릭 클래스 정의 
class Basket<T> {
    private T item;

    public T getItem() {
        return item;
    }

    public void setItem(T item) {
        this.item = item;
    }
}

class Main {
    public static void main(String[] args) {

        // 인스턴스화 
        Basket<Rose> roseBasket = new Basket<>();
        Basket<RosePasta> rosePastaBasket = new Basket<>();
    }
}

단, 아래 코드처럼 매개변수를 선언하면, Basket클래스를 인스턴스화할 때 타입으로 Flower클래스의 하위 클래스만 지정하도록 제한된다.

class Flower { ... }
class Rose extends Flower { ... }
class RosePasta { ... }

class Basket<T extends Flower> {
    private T item;
	
		...
}

class Main {
    public static void main(String[] args) {
    
        // 인스턴스화 
        Basket<Rose> roseBasket = new Basket<>();
        Basket<RosePasta> rosePastaBasket = new Basket<>(); // 에러
    }
}

특정 클래스를 상속받으면서 동시에 특정 인터페이스를 구ㅕㄴ한 클래스만 타입으로 지정할 수 있도록 제한하려면 아래와 같이&를 사용하여 코드를 작성해준다.

interface Plant { ... }
class Flower implements Plant { ... }
class Rose extends Flower implements Plant { ... }

class Basket<T extends Flower & Plant> { // (1)
    private T item;
	
		...
}

class Main {
    public static void main(String[] args) {

        // 인스턴스화 
        Basket<Flower> flowerBasket = new Basket<>();
        Basket<Rose> roseBasket = new Basket<>();
    }
}

제네릭 메서드

클래스 내부의 특정 메서드만 제네릭으로 선언한 것

class Basket {
		...
		public <T> void add(T element) {
				...
		}
}

제네릭 메서드의 타입 매개변수는 제네릭 클래스의 타입 매개변수와 별개의 것이다.

class Basket<T> {                        // 1 : 여기에서 선언한 타입 매개변수 T와 
		...
		public <T> void add(T element) { // 2 : 여기에서 선언한 타입 매개변수 T는 서로 다른 것이다.
				...
		}
}

즉, 클래스명 옆에서 선언한 타입 매개변수는 클래스가 인스턴스화될 때 타입이 지정된다.

Basket<String> basket = new Bakset<>(); // 위 예제의 1의 T가 String으로 지정된다. 
basket.<Integer>add(10);                // 위 예제의 2의 T가 Integer로 지정된다. 
basket.add(10);                         // 타입 지정을 생략할 수도 있다. 

또한 클래스 타입 매개변수와 달리 매개변수는 static메서드도 선언하여 사용 가능

class Basket {
		...
		static <T> int setPrice(T element) {
				...
		}
}

제네릭 메서드는 메서드가 호출되는 시점에서 제네릭 타입이 결정되므로, 제네릭 메서드를 정의하는 시점에서는 실제 어떤 타입이 입력되는지 알 수 없다.

class Basket {
    public <T> void print(T item) {
        System.out.println(item.length()); // 불가
    }
}

자바의 최상위 클래스인 Object클래스의 메서드는 사용 가능하다.
ex) equals(), toString()

class Basket {
    public <T> void getPrint(T item) {
        System.out.println(item.equals("Kim coding")); // 가능
    }
}

와일드 카드

어떠한 타입으로든 대체될 수 있는 타입 파라미터를 의미한다.

일반적으로는 extendssuper키워드를 조합하여 사용한다.

<? extends T>
<? super T>

<? extends T>는 와일드카드에 상한 제한을 두는 것으로서, TT를 상속받는 하위 클래스 타입만 타입 파라미터로 받을 수 있도록 지정

<? super T>는 와일드카드에 하한 제한을 두는 것으로, TT의 상위 클래스만 타입 파라미터로 받도록 한다.

extendssuper 키워드와 조합하지 않은 와일드(<?>)는 <? extends Object>와 같다.

//예제
class Phone {}

class IPhone extends Phone {}
class Galaxy extends Phone {}

class IPhone12Pro extends IPhone {}
class IPhoneXS extends IPhone {}

class S22 extends Galaxy {}
class ZFlip3 extends Galaxy {}

class User<T> {
		public T phone;

		public User(T phone) {
				this.phone = phone;
		}
}

휴대폰별로 사용할 수 있는 기능을 분류해보자.

  • call: 모든 휴대폰에서 사용 가능
    • ->? extedns Phone으로 타입에 제한
  • faceId: 아이폰만 사용 가능
    • ->? extends IPhone으로 타입에 제한
  • samsungPay: 삼성 휴대폰에서만 사용 가능
    • ->? extends Galaxy으로 타입에 제한
  • recordVoice: 아이폰을 제외한 안드로이드 휴대폰에서만 사용 가능
    • ->? super Galaxy으로 타입에 제한
class PhoneFunction {
    public static void call(User<? extends Phone> user) {
        System.out.println("-----------------------------");
        System.out.println("user.phone = " + user.phone.getClass().getSimpleName());
        System.out.println("모든 Phone은 통화를 할 수 있습니다.");
    }

    public static void faceId(User<? extends IPhone> user) {
        System.out.println("-----------------------------");
        System.out.println("user.phone = " + user.phone.getClass().getSimpleName());
        System.out.println("IPhone만 Face ID를 사용할 수 있습니다. ");
    }

    public static void samsungPay(User<? extends Galaxy> user) {
        System.out.println("-----------------------------");
        System.out.println("user.phone = " + user.phone.getClass().getSimpleName());
        System.out.println("Galaxy만 삼성 페이를 사용할 수 있습니다. ");
    }

    public static void recordVoice(User<? super Galaxy> user) {
        System.out.println("-----------------------------");
        System.out.println("user.phone = " + user.phone.getClass().getSimpleName());
        System.out.println("안드로이드 폰에서만 통화 녹음이 가능합니다. ");
    }
}

여기서 호출을 해보면

public class Example {
    public static void main(String[] args) {
        PhoneFunction.call(new User<Phone>(new Phone()));
        PhoneFunction.call(new User<IPhone>(new IPhone()));
        PhoneFunction.call(new User<Galaxy>(new Galaxy()));
        PhoneFunction.call(new User<IPhone12Pro>(new IPhone12Pro()));
        PhoneFunction.call(new User<IPhoneXS>(new IPhoneXS()));
        PhoneFunction.call(new User<S22>(new S22()));
        PhoneFunction.call(new User<ZFlip3>(new ZFlip3()));

        System.out.println("\n######################################\n");

//        PhoneFunction.faceId(new User<Phone>(new Phone())); // X
        PhoneFunction.faceId(new User<IPhone>(new IPhone()));
        PhoneFunction.faceId(new User<IPhone12Pro>(new IPhone12Pro()));
        PhoneFunction.faceId(new User<IPhoneXS>(new IPhoneXS()));
//        PhoneFunction.faceId(new User<Galaxy>(new Galaxy())); // X
//        PhoneFunction.faceId(new User<S22>(new S22())); // X
//        PhoneFunction.faceId(new User<ZFlip3>(new ZFlip3())); // X

        System.out.println("\n######################################\n");

//        PhoneFunction.samsungPay(new User<Phone>(new Phone())); // X
//        PhoneFunction.samsungPay(new User<IPhone>(new IPhone())); // X
//        PhoneFunction.samsungPay(new User<IPhone12Pro>(new IPhone12Pro())); // X
//        PhoneFunction.samsungPay(new User<IPhoneXS>(new IPhoneXS())); // X
        PhoneFunction.samsungPay(new User<Galaxy>(new Galaxy()));
        PhoneFunction.samsungPay(new User<S22>(new S22()));
        PhoneFunction.samsungPay(new User<ZFlip3>(new ZFlip3()));

        System.out.println("\n######################################\n");

        PhoneFunction.recordVoice(new User<Phone>(new Phone()));
//        PhoneFunction.recordVoice(new User<IPhone>(new IPhone())); // X
//        PhoneFunction.recordVoice(new User<IPhone12Pro>(new IPhone12Pro())); // X
//        PhoneFunction.recordVoice(new User<IPhoneXS>(new IPhoneXS())); // X
        PhoneFunction.recordVoice(new User<Galaxy>(new Galaxy()));
//        PhoneFunction.recordVoice(new User<S22>(new S22())); // X
//        PhoneFunction.recordVoice(new User<ZFlip3>(new ZFlip3())); // X
    }
}

주석으로 처리된 부분은 에러로 감지되는 부분이다.

recordVoiceS22ZFlip3를 타입으로 지정하면서 호출할 때 왜 에러가 발생할까?
<? super Galaxy>는 상속 계층도 상에서 GalaxyGalaxy보다 위에 있는 상위 클래스만 타입으로 지정할 수 있게 제한

profile
프로젝트, 오류, CS 공부, 코테 등을 꾸준히 기록하는 저만의 기술 블로그입니다!

0개의 댓글