[코드스테이츠 백엔드 44기 SEB BE] 15일차-1

오태호·2023년 3월 6일
0

코드스테이츠

목록 보기
13/22
post-thumbnail

열거형(Enum)

  • 여러 상수들을 보다 편리하게 선언할 수 있도록 만들어진 자바의 문법
  • 서로 연관된 상수들의 집합
    • 상수
      • 변하지 않는 값
      • final 키워드를 통해 선언
    • 서로 관련있는 내용들을 모아 한 번에 간편하게 관리할 때 사용
    • 몇 가지로 한정된 변하지 않는 데이터를 다룰 때 사용

상수명의 중복을 피할 수 있다

  • 열거형을 사용하지 않았을 때, 여러 상수들을 정의하려면 아래와 같이 전역변수로 상수를 설정하여 사용
private static final int GOLD = 1;
private static final int SILVER = 2;
private static final int BRONZE = 3;
  • 상수에 부여된 1, 2, 3 값은 각 상수를 구분하기 위해서 사용한다
  • 정수값을 통해 상수를 할당하면, 상수명이 중복되는 경우가 발생할 수 있다
public static final int GOLD = 1;
public static final int SILVER = 2;
public static final int BRONZE = 3;

public static final int MASTER = 1;
public static final int DIMOND = 2;
public static final int PLATINUM = 3;
public static final int GOLD = 4;
public static final int SILVER = 5;
public static final int BRONZE = 6;
  • GOLD, SILVER, BRONZE 세 개의 상수 이름이 중복되어 컴파일 에러가 발생한다
  • 이를 인터페이스를 사용해 인터페이스에 상수를 선언하여 구분함으로써 해결할 수 있다

타입에 대한 안정성을 보장한다

  • 인터페이스를 사용해 문제를 해결하였지만 이 때 타입 안정성 문제가 발생한다
interface Medal {
    int GOLD = 1, SILVER = 2, BRONZE = 3;
}

interface Tier {
    int MASTER = 1, DIMOND = 2, PLATINUM = 3, GOLD = 4, SILVER = 5, BRONZE = 6;
}
  • Medal과 Tier는 서로 관련이 없고, Medal.GOLD의 정수값 1과 Tier.GOLD의 정수값 4는 상수를 열거하기 위해 임의로 주어진 값이며 이외의 의미가 없는 값임에도 둘을 비교하는 코드를 작성할 수 있다
if(Medal.GOLD == Tier.GOLD) {}
  • 즉, 의미가 다른 개념임에도 둘을 비교할 수 있고, 비교 시에 에러가 발생하지 않으므로 타입 안정성이 떨어진다
  • 이 문제를 해결해주기 위해 서로 다른 객체로 만들어준다
class Medal {
    public static final int GOLD = 1;
    public static final int SILVER = 2;
    public static final int BRONZE = 3;
}

class Tier {
    public static final int MASTER = 1;
    public static final int DIMOND = 2;
    public static final int PLATINUM = 3;
    public static final int GOLD = 4;
    public static final int SILVER = 5;
    public static final int BRONZE = 6;
}
  • 객체를 생성해줌으로써 상수명 중복 및 타입 안정성 문제를 해결할 수 있다
  • 그러나 사용자 정의 타입이므로 switch문에 활용할 수 없고 코드가 길어진다
    • 열거형을 이용하여 해결할 수 있다
    • 열거형을 사용함으로써 상수명 중복 및 타입 안정성 문제를 해결할 수 있을 뿐만 아니라 코드를 단순하고 가독성이 좋게 만들 수 있으며 switch문에 사용할 수 있다
      • switch문의 조건은 char, byte, short, int, Character, Byte, Short, Integer, String, enum 타입만 가능하고 사용자 정의 타입은 불가능하여 사용자 정의 객체를 switch문에 사용할 수 없다

열거형 정의

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

enum Medals{
    GOLD, // 0
    SILVER, // 1
    BRONZE // 2
}
enum Tiers{
    MASTER, // 0
    DIAMOND, // 1
    PLATINUM, // 2
    GOLD, // 3
    SILVER, // 4
    BRONZE // 5
}
  • 상수는 대소문자 모두 작성 가능하지만 관례적으로 대문자로 작성
  • 각 열거 상수들은 객체이므로, Medals라는 이름의 열거형은 GOLD, SILVER, BRONZE라는 총 3개의 열거 객체를 포함한다고 한다
    • 각 상수들에는 자동으로 0부터 시작하는 정수값이 할당되어 각각의 상수를 가리킨다
  • 열거형에 선언된 상수 접근
    • 열거형이름.상수명
    • Ex. Medals.GOLD
enum Medals{GOLD, SILVER, BRONZE}

public class Test {
    public static void main(String[] args) {
        Medals medal = Medals.GOLD;
        System.out.println(medal);
    }
}
  • medal이라는 Medals 타입 참조 변수에 Medals.GOLD를 할당하는 예시

열거형에서 사용할 수 있는 메서드

  • 메서드들은 모든 열거형의 조상인 java.lang.Enum에 정의되어 있다
리턴 타입메소드(매개 변수)설명
Stringname()열거 객체가 가지고 있는 문자열을 반환하며, 반환되는 문자열은 열거타입을 정의할 때 사용한 상수 이름과 동일하다
intordinal()열거 객체의 순번을 반환(0부터 시작)
intcompareTo(비교값)주어진 매개값과 비교하여 순번 차이를 반환
열거 타입valueOf(String name)주어진 문자열의 열거 객체를 반환
열거 배열values()모든 열거 객체들을 배열로 반환
enum Medals{
    GOLD, // 0
    SILVER, // 1
    BRONZE // 2
}

public class Test {
    public static void main(String[] args) {
        Medals medal = Medals.GOLD;

        Medals[] medals = medal.values();
        
        for(Medal m : medals) {
            System.out.printf("%s=%d\n", m.name(), m.ordinal());
        }

        Medals target = Medals.valueOf("BRONZE");
        System.out.println(target);
        System.out.println(Medals.BRONZE == Medals.valueOf("BRONZE"));

        switch(medal) {
            case GOLD:
                System.out.println("금메달");
                break;
            case SILVER:
                System.out.println("은메달");
                break;
            case HIGH:
                System.out.println("동메달");
                break;
        }
    }
}

// 출력값
GOLD=0
SILVER=1
BRONZE=2
BRONZE
true
금메달

제너릭(Generic)

제너릭의 필요성

  • 한 클래스와 같은 기능을 하면서 인스턴스 변수에 다른 타입의 데이터도 저장할 수 있게 할 때 제너릭을 이용할 수 있다
    • 제너릭을 통해 단 하나의 클래스만으로 모든 타입의 데이터를 저장할 수 있는 인스턴스를 만들 수 있다
class ClassExample<T> {
    private T variable;

    public ClassExample(T variable) {
        this.variable = variable;
    }

    public T getVariable() {
        return variable;
    }

    public void setVariable(T variable) {
        this.variable = variable;
    }
}
  • <T>가 클래스 이름 옆에 추가되었으며, 클래스 바디 내에 T라는 타입으로 변수를 정의했다
    • <T>와 T가 제너릭 문법에 해당한다
  • 위 클래스의 인스턴스화는 아래와 같이 진행한다
ClassExample<String> classExample = new ClassExample("Hello JAVA");
  • ClassExample
    • 'ClassExample 클래스 내의 T를 String으로 바꿔라'라는 뜻으로 볼 수 있다
    • ClassExample 클래스 내부의 T가 모두 String으로 치환되는 것처럼 동작한다

제너릭이란?

  • 클래스나 메서드의 코드를 작성할 때, 타입을 구체적으로 지정하는 것이 아닌, 추후에 지정할 수 있도록 일반화해두는 것
  • 작성한 클래스나 메서드의 코드가 특정 데이터 타입에 얽매이지 않게 해둔 것

제너릭 클래스

제너릭 클래스 정의

  • 제너릭 클래스 -> 제너릭이 사용된 클래스
class ClassExample<T> {
    private T variable;

    public ClassExample(T variable) {
        this.variable = variable;
    }

    public T getVaraible() {
        return variable;
    }

    public void setVariable(T variable) {
        this.variable = variable;
    }
}
  • T : 타입 매개변수
  • <T>와 같이 꺽쇠 안에 넣어 클래스 이름 옆에 작성하여 클래스 내부에서 사용할 타입 매개변수를 선언한다
  • 타입 매개변수는 임의의 문자로 지정할 수 있다
    • T : Type, K : Key, V : Value, E : Element, N : Number, R : Result
    • 위와 같은 것들이 자주 사용된다

제너릭 클래스 정의 시 주의점

  • 클래스 변수에는 타입 매개변수를 사용할 수 없다!
class ClassExample<T> {
    private T variable1; // 가능
    static T variable2; // 불가능
}
  • 클래스 변수에 타입 매개변수를 사용할 수 없는 이유는?
    • 클래스 변수는 모든 인스턴스가 공유하는 변수이다
    • 그런데 만약 클래스 변수에 타입 매개변수를 사용하면 변수의 타입이 인스턴스마다 달라진다
      • 결국, 클래스 변수를 통해 같은 변수를 공유할 수 없게 된다
    • 따라서, static이 붙은 변수 또는 메서드에는 타입 매개변수를 사용할 수 없다!

제너릭 클래스 사용

  • 제너릭 클래스
    • 멤버를 구성하는 코드에 특정한 타입이 지정되지 않은 클래스
    • 제너릭 클래스를 인스턴스화 할 때 의도하고자 하는 타입을 지정해줘야 한다
      • 매개변수에 치환될 타입으로 기본 타입을 지정할 수 없다!
      • 원시 타입을 지정할 때에는 래퍼 클래스를 활용하여 지정한다
ClassExample<String> classExample1 = new ClassExample<>("Hello JAVA");
ClassExample<Integer> classExample2 = new ClassExample<>(10);
ClassExample<Character> classExample3 = new ClassExample<>('c');
  • new ClassExample<>은 구체적 타입을 생략할 수 있다
    • 참조 변수의 타입을 통해 유추할 수 있기 때문
  • 제너릭 클래스를 사용할 때에도 다형성을 적용할 수 있다
class Car {}
class SportsCar extends Car {}
class Computer {}

class ClassExample<T> {
    private T variable;

    public T getVariable() {
        return variable;
    }

    public void setVariable(T variable) {
        this.variable = variable;
    }
}

class Main {
    public static void main(String[] args) {
        ClassExample<Car> carClassExample = new ClassExample<>();
        carClassExample.setItem(new SportsCar()); // 다형성 적용
        carClassExample.setItem(new Computer()); // 에러 발생
    }
}

제한된 제너릭 클래스

class Car {}
class SportsCar extends Car {}
class Computer {}

class ClassExample<T> {
    private T variable;

    public T getVariable() {
        return variable;
    }

    public void setVariable(T variable) {
        this.variable = variable;
    }
}

class Main {
    public static void main(String[] args) {
        ClassExample<Car> carClassExample = new ClassExample<>();
        ClassExample<Computer> computerClassExample = new ClassExample<>();
    }
}
  • 위와 같이 제너릭 클래스는 인스턴스화를 할 때 어떠한 타입도 지정해줄 수 있다(타입을 지정하는 데에 있어 제한이 없다)
class Car {}
class SportsCar extends Car {}
class Computer {}

class ClassExample<T extends Car> {
    private T variable;

    public T getVariable() {
        return variable;
    }

    public void setVariable(T variable) {
        this.variable = variable;
    }
}

class Main {
    public static void main(String[] args) {
        ClassExample<SportsCar> carClassExample = new ClassExample<>();
        ClassExample<Computer> computerClassExample = new ClassExample<>(); // 에러 발생
    }
}
  • 타입 매개변수를 선언할 때, 위와 같이 작성을 해준다면 제너릭 클래스를 인스턴스화할 때 타입으로 Car 클래스 및 Car 클래스의 하위 클래스만 지정하도록 제한된다
interface Vehicle {}
class Car implements Vehicle {}
class SportsCar extends Car implements Vehicle {}

class ClassExample<T extends Vehicle> {
    private T variable;

    public T getVariable() {
        return variable;
    }

    public void setVariable(T variable) {
        this.variable = variable;
    }
}

class Main {
    public static void main(String[] args) {
        ClassExample<Car> carClassExample = new ClassExample<>();
        ClassExample<SportsCar> sportsCarClassExample = new ClassExample<>();
    }
}
  • 특정 인터페이스를 구현한 클래스만 타입으로 지정할 수 있도록 제한할 수 있다
    • 이 경우에도 동일하게 extends 키워드를 사용한다
interface Vehicle {}
class Car implements Vehicle {}
class SportsCar extends Car implements Vehicle {}

class ClassExample<T extends Car & Vehicle> {
    private T variable;

    public T getVariable() {
        return variable;
    }

    public void setVariable(T variable) {
        this.variable = variable;
    }
}

class Main {
    public static void main(String[] args) {
        ClassExample<Car> carClassExample = new ClassExample<>();
        ClassExample<SportsCar> sportsCarClassExample = new ClassExample<>();
    }
}
  • 특정 클래스를 상속받으면서 동시에 특정 인터페이스를 구현한 클래스만 타입으로 지정하도록 제한하고 싶다면 &를 이용하여 제한한다
    • 이 때는 클래스를 인터페이스보다 앞에 위치시켜야 한다

제너릭 메서드

  • 클래스 내부의 특정 메서드만 제너릭으로 선언할 수 있다
  • 제너릭 메서드의 타입 매개변수 선언은 반환타입 앞에서 이루어지고, 해당 메서드 내에서만 선언한 타입 매개변수를 사용할 수 있다
class ClassExample {
    public <T> void genericMethod(T element) {
        System.out.println(element);
    }
}
class ClassExample<T> { // 제너릭 클래스의 타입 매개변수 T와
    public <T> void genericMethod(T element) { // 제너릭 메서드의 타입 매개변수 T는 서로 다른 것!
        System.out.println(element);
    }
}
  • 제너릭 메서드의 타입 매개변수는 제너릭 클래스의 타입 매개변수와는 별개이다!
    • 만약 위와 같이 T라는 타입 매개변수명을 제너릭 메서드와 제너릭 클래스 모두에서 사용한다고 하더라도, 같은 문자를 이름으로 사용하는 것일 뿐, 서로 다른 타입 매개변수로 간주된다!
  • 어떻게 다르게 간주되는가?
    • 타입이 지정되는 시점이 서로 다르다!
      • 제너릭 클래스의 타입 매개변수는 클래스가 인스턴스화 될 때 지정된다
      • 제너릭 메서드의 타입 매개변수는 메서드가 호출될 때 지정된다
ClassExample<String> classExample = new ClassExample(); // 제너릭 클래스의 T가 String으로 지정됨
classExample<Integer>.genericMethod(15); // 제너릭 메서드의 T가 Integer로 지정됨
classExample.genericMethod(15); // 타입 지정을 생략할 수 있음
  • 제너릭 메서드를 호출할 때 제너릭 메서드에서 선언한 타입 매개변수의 구체적인 타입이 지정된다
class ClassExample<T> {
    static <T> void classMethod(T element) {
        System.out.println(element);
    }
}
  • 클래스 타입 매개변수와 달리 타입 매개변수는 static 메서드에도 선언하여 사용할 수 있다!
class ClassExample<T> {
    public <T> void classMethod(T element) {
        System.out.println(element.length()); // 불가
    }
}
  • 제너릭 메서드는 메서드가 호출되는 시점에 제너릭 타입이 결정된다
    • 즉, 제너릭 메서드를 정의하는 시점에는 어떤 타입이 입력될지 알 수 없다!
    • 그러므로, length()와 같은 String 클래스의 메서드는 제너릭 메서드를 정의하는 시점에 사용할 수 없다!
    • 그러나 자바의 모든 클래스는 모든 자바 클래스의 최상위 클래스인 Object 클래스를 상속받으므로 equals(), toString()과 같은 Object 클래스의 메서드는 사용 가능하다!

와일드카드

  • 어떠한 타입으로든 대체될 수 있는 타입 파라미터를 의미한다
  • ? 기호로 와일드카드를 사용할 수 있다
  • 일반적으로 와일드카드는 extends와 super 키워드를 조합하여 사용한다
<? extends T>
<? super T>
  • <? extends T>
    • 와일드카드에 상한 제한을 두는 것
    • T 및 T를 상속받은 하위 클래스 타입만 타입 파라미터로 받을 수 있음을 지정한다
  • <? super T>
    • 와일드카드에 하한 제한을 두는 것
    • T 및 T의 상위 클래스만 타입 파라미터로 받을 수 있음을 지정한다
  • extends 및 super 키워드와 조합하지 않은 와일드카드는 <? extends Object>와 같다
    • 즉, 모든 클래스 타입은 Object 클래스를 상속받기 때문에, 모든 클래스 타입을 타입 파라미터로 받을 수 있다!
class Vehicle {}

class Car extends Vehicle {}
class Bicycle extends Vehicle {}

class SportsCar extends Car {}
class SUV extends Car {}

class MTB extends Bicycle {}
class Fixie extends Bicycle {}

class Customer<T> {
    public T vehicle;

    public Customer(T vehicle) {
        this.vehicle = vehicle;
    }
}

  • 위 클래스들의 상속 계층도를 그림
  • 각 탈 것들 별로 사용할 수 있는 기능들을 분류
    • go : 탈 것을 타고 이동하는 기능, 모든 탈 것에서 사용할 수 있는 기능
      • ? extends Vehicle을 통해 타입을 제한할 수 있다
    • powerUp : 자동차의 시동을 거는 기능, 자동차만 가능
      • ? extends Car을 통해 타입을 제한할 수 있다
    • rollPedal : 자전거의 패달을 구르는 기능, 자전거에서만 가능
      • ? extends Bicycle을 통해 타입을 제한할 수 있다
    • tankUp : 연료를 넣는 기능, 자전거를 모든 탈 것에서 가능
      • ? super Car로 타입을 제한할 수 있을 것으로 보인다
class VehicleFunction {
    public static void go(Customer<? extends Vehicle> customer) {
        System.out.println("-----------------------------");
        System.out.println("customer.vehicle = " + customer.vehicle.getClass().getSimpleName());
        System.out.println("모든 Vehicle은 이동할 수 있습니다.");
    }

    public static void powerUp(Customer<? extends Car> customer) {
        System.out.println("-----------------------------");
        System.out.println("customer.vehicle = " + customer.vehicle.getClass().getSimpleName());
        System.out.println("Car만 시동을 걸 수 있습니다.");
    }

    public static void rollPedal(Customer<? extends Bicycle> customer) {
        System.out.println("-----------------------------");
        System.out.println("customer.vehicle = " + customer.vehicle.getClass().getSimpleName());
        System.out.println("Bicycle만 페달을 구를 수 있습니다.");
    }

    public static void tankUp(Customer<? super Car> customer) {
        System.out.println("-----------------------------");
        System.out.println("customer.vehicle = " + customer.vehicle.getClass().getSimpleName());
        System.out.println("Bicycle을 탈 것은 연료를 충전할 수 있습니다.");
    }
}
public class Example {
	public static void main(String[] args) {
		VehicleFunction.go(new Customer<Vehicle>(new Vehicle()));
        VehicleFunction.go(new Customer<Car>(new Car()));
        VehicleFunction.go(new Customer<Bycicle>(new Bycicle()));
        VehicleFunction.go(new Customer<SportsCar>(new SportsCar()));
        VehicleFunction.go(new Customer<SUV>(new SUV()));
        VehicleFunction.go(new Customer<MTB>(new MTB()));
        VehicleFunction.go(new Customer<Fixie>(new Fixie()));

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

        // VehicleFunction.powerUp(new Customer<Vehicle>(new Vehicle())); // X
        VehicleFunction.powerUp(new Customer<Car>(new Car()));
        // VehicleFunction.powerUp(new Customer<Bycicle>(new Bycicle())); // X
        VehicleFunction.powerUp(new Customer<SportsCar>(new SportsCar()));
        VehicleFunction.powerUp(new Customer<SUV>(new SUV()));
        // VehicleFunction.powerUp(new Customer<MTB>(new MTB())); // X
        // VehicleFunction.powerUp(new Customer<Fixie>(new Fixie())); // X

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

        // VehicleFunction.rollPedal(new Customer<Vehicle>(new Vehicle())); // X
        // VehicleFunction.rollPedal(new Customer<Car>(new Car())); // X
        VehicleFunction.rollPedal(new Customer<Bycicle>(new Bycicle()));
        // VehicleFunction.rollPedal(new Customer<SportsCar>(new SportsCar())); // X
        // VehicleFunction.rollPedal(new Customer<SUV>(new SUV())); // X
        VehicleFunction.rollPedal(new Customer<MTB>(new MTB()));
        VehicleFunction.rollPedal(new Customer<Fixie>(new Fixie()));

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

        VehicleFunction.tankUp(new Customer<Vehicle>(new Vehicle()));
        VehicleFunction.tankUp(new Customer<Car>(new Car()));
        // VehicleFunction.tankUp(new Customer<Bycicle>(new Bycicle())); // X
        // VehicleFunction.tankUp(new Customer<SportsCar>(new SportsCar())); // X
        // VehicleFunction.tankUp(new Customer<SUV>(new SUV())); // X
        // VehicleFunction.tankUp(new Customer<MTB>(new MTB())); // X
        // VehicleFunction.tankUp(new Customer<Fixie>(new Fixie())); // X
	}
}
profile
자바, 웹 개발을 열심히 공부하고 있습니다!

0개의 댓글