우주먼지
article thumbnail
Published 2022. 9. 13. 10:00
Enum & Generic & Wrapper Languages/Java

💡 Enum (열거형)

 

서로 연관된 상수들의 집합 (상수 = final 키워드를 통한 변하지 않는 값 선언)
상수명은 대문자로 하는것이 관례
상수 하나하나는 객체로 간주함
따로 값을 지정해주지 않으면 0부터 시작하는 int형 값 자동할당

 

기본 형식

  • enum 열거형이름 {상수명1, 상수명2 ...}
  • enum Seasons {SPRING, SUMMER, FALL, WINTER}
  • JDK1.5 이전버전에서는 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;

public static final int DJANGO  = 1;
public static final int SPRING  = 2; // 계절의 SPRING과 중복 발생!
public static final int NEST    = 3;
public static final int EXPRESS = 4;

위와 같이 상수를 선언하고, 상수명이 중복이 되면 컴파일 에러가 발생함

interface Seasons {
	int SPRING = 1, SUMMER = 2, FALL = 3, WINTER = 4;
}

interface Frameworks {
	int DJANGO = 1, SPRING = 2, NEST = 3, EXPRESS = 4;
}

1차적인 해결법으로 위와 같이 인터페이스를 사용하여 상수구분을 함으로써 해결이 가능하지만,

타입 안정성이라는 새로운 문제가 생김

이외에도, 객체 생성을 통한 방법 외 여러가지가 있지만 switch문에 활용할 수 없는 등 여러 문제가 있다.

(switch 문은 사용자정의 타입이 호환이 안됨)


Enum을 활용한 상수 정의

enum Seasons { SPRING, SUMMER, FALL, WINTER }
enum Frameworks { DJANGO, SPRING, NEST, EXPRESS }

위와 같이 enum을 활용해 코드작성을 하면 앞선 문제들의 해결과 코드의 간결을 동시에 챙길수 있다

또한, switch문에서도 활용이 가능하다

 

switch문을 활용한 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에서 사용할 수 있는 Method

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
중간 레벨

위의 코드에서

 

values() = level에 정의된 모든 상수를 배열로 반환함

name() , ordinal() = values로 받은 배열의 각각 이름과 순서를 출력값으로 반환

valueOf() = 지정된 열거형에서 이름과 일치하는 열거형의 상수를 반환

ordinal = 객체의 순번(인덱스 번호) 리턴

 

Java에서 열거형은 상수명의 중복,타입의 안정성, 보다 편리한 상수선언을 보장하며 switch문에서 동작가능 


💡 Generic

타입을 추후에 지정할 수 있도록 일반화 해두는 것

 

예시

제네릭을 사용하지 않았을때 작성한 비효율적인 코드

class Basket {
    private String item;

    Basket(String item) {
        this.item = item;
    }

    public String getItem() {
        return item;
    }

    public void setItem(String item) {
        this.item = item;
    }
}
class BasketString { private String item; ... }
class BasketInteger { private int item; ... }
class BasketChar { private char item; ... }
class BasketDouble { private double item; ... }

 

하지만 아래 예시로 단 하나의 Basket 클래스 만으로
모든 타입의 데이터를 저장할 수 있는 인스턴스 생성가능

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;
    }
}

위의 Basket 클래스는 다음과 같이 인스턴스화도 가능하다

Basket<String> basket1 = new Basket<String>("기타줄");

위의 코드는 "Basket 클래스 내의 T를 String으로 바꿔라" 라는 의미이기도 하며, 실행하면 타입<T>가 전부 String이 됨


Generic Class 정의

  • Generic이 사용된 클래스를 칭함
  • 클래스 변수에는 타입 매개변수를 사용못함 ex) static T item1; // X

 

기본 형식

  • class Basket<T> // 임의의 타입 매개변수 T 선언
  • class Basket<T, V> // 임의의 타입매개변수 여러개 선언

 

Generic Class 사용

  • 멤버를 구성하는 코드에 특정한 타입지정이 되지않은 클래스이므로, 제네릭을 인스턴스화를 할때 타입지정 해줘야함
  • 단, 타입 매개변수에 치환될 타입으로 기본타입 지정 X , Wrapper Class O
Basket<String>  basket1 = new Basket<String>("Hello");
Basket<Integer> basket2 = new Basket<Integer>(10);
Basket<Double>  basket3 = new Basket<Double>(3.14);

 

위의 코드에서 new Bastet<> 의내용은 생략 가능(참조변수의 타입으로 유추 가능하기 때문)


Generic Class 다형성 적용

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;
    }
}

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

 

제한된 Generic Class

  • 타입 매개변수를 선언할때 extends로 상속이 되면 상위클래스로 지정된 클래스의 하위 클래스만 지정하도록 제한
    • ex) class Basket<T extends Flower>
  • 특정 클래스와 특정 인터페이스를 구현한 클래스만 타입으로 지정할 수 있도록 & 를 사용하여 제한
    • ex) class Basket<T extends Flower & Plant> // 무조건 클래스명을 먼저 써야함
class Flower { ... }
class Rose extends Flower { ... }
class RosePasta { ... }

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

public static void main(String[] args) {

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

 

Generic Method

  • 클래스 전체가 아닌 클래스 내부의 특정 메소드만 제네릭 선언
  • 제네릭 클래스/메소드 는 서로 다른 타입의 매개변수로 간주
  • static 메소드에서도 선언/사용 가능
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);                         // 타입 지정을 생략할 수도 있습니다. 

💡 와일드카드

  • 일반적으로 extends 나 super를 조합하여 사용

 

와일드카드 ex)

<? extends T>
<? super T>

<? extends T>는 와일드카드에 상한 제한을 두는 것으로서,

T와 T를 상속받는 하위 클래스 타입만 타입 파라미터로 받을 수 있도록 지정

 

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

 

참고로, extends 및 super 키워드와 조합하지 않은 와일드카드(<?>)는 <? extends Object>와 같다

즉, 모든 클래스 타입은 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 : 휴대폰의 기본적인 통화 기능으로, 모든 휴대폰에서 사용할 수 있는 기능입니다.
    • → ? extends Phone으로 타입을 제한할 수 있습니다.
  • faceI : 애플의 안면 인식 보안 기능으로, 아이폰만 사용 가능합니다.
    • → ? 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("안드로이드 폰에서만 통화 녹음이 가능합니다. ");
    }
}

💡 Wrapper 클래스

  • 프로그래밍을 하다보면 기본타입의 데이터를 객체로 표현해야 할 경우가 있는데 이때 사용함
  • 자바의 모든 기본타입은 값은 갖는 객체생성이 가능하며, 이를 포장객체라고 부르며, 기본타입의 값을 내부에 두고 포장
  • 래퍼 클래스로 감싸고 있는 기본타입 값은 외부에서 변경X, 값을 변경하려면 새로운 포장객체를 생성해야함

기본 타입에 대응하는 Wrapper 클래스의 종류

 

'Languages > Java' 카테고리의 다른 글

Annotation & Lambda & Stream & I/O  (0) 2022.09.15
Exception & Collection Framework  (2) 2022.09.14
객체지향 프로그래밍 2 (다형성 & 추상화)  (0) 2022.09.07
심화 학습 Reference  (0) 2022.09.07
공부해야 할 Class & Method  (0) 2022.09.06
profile

우주먼지

@o귤o

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

검색 태그