열거형(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,
SILVER,
BRONZE
}
enum Tiers{
MASTER,
DIAMOND,
PLATINUM,
GOLD,
SILVER,
BRONZE
}
- 상수는 대소문자 모두 작성 가능하지만 관례적으로 대문자로 작성
- 각 열거 상수들은 객체이므로, 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에 정의되어 있다
리턴 타입 | 메소드(매개 변수) | 설명 |
---|
String | name() | 열거 객체가 가지고 있는 문자열을 반환하며, 반환되는 문자열은 열거타입을 정의할 때 사용한 상수 이름과 동일하다 |
int | ordinal() | 열거 객체의 순번을 반환(0부터 시작) |
int | compareTo(비교값) | 주어진 매개값과 비교하여 순번 차이를 반환 |
열거 타입 | valueOf(String name) | 주어진 문자열의 열거 객체를 반환 |
열거 배열 | values() | 모든 열거 객체들을 배열로 반환 |
enum Medals{
GOLD,
SILVER,
BRONZE
}
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라는 타입으로 변수를 정의했다
- 위 클래스의 인스턴스화는 아래와 같이 진행한다
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> {
public <T> void genericMethod(T element) {
System.out.println(element);
}
}
- 제너릭 메서드의 타입 매개변수는 제너릭 클래스의 타입 매개변수와는 별개이다!
- 만약 위와 같이 T라는 타입 매개변수명을 제너릭 메서드와 제너릭 클래스 모두에서 사용한다고 하더라도, 같은 문자를 이름으로 사용하는 것일 뿐, 서로 다른 타입 매개변수로 간주된다!
- 어떻게 다르게 간주되는가?
- 타입이 지정되는 시점이 서로 다르다!
- 제너릭 클래스의 타입 매개변수는 클래스가 인스턴스화 될 때 지정된다
- 제너릭 메서드의 타입 매개변수는 메서드가 호출될 때 지정된다
ClassExample<String> classExample = new ClassExample();
classExample<Integer>.genericMethod(15);
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<Car>(new Car()));
VehicleFunction.powerUp(new Customer<SportsCar>(new SportsCar()));
VehicleFunction.powerUp(new Customer<SUV>(new SUV()));
System.out.println("\n######################################\n");
VehicleFunction.rollPedal(new Customer<Bycicle>(new Bycicle()));
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()));
}
}