jdk 17 이전 릴리즈 에서는 switch 문의 선택자 표현식은 숫자, 문자열, Enum 상수로만 평가되었고 case 레이블은 상수만 지원을 하였습니다. jdk 17 릴리즈 부터는 switch 문의 선택자 표현식은 모든 타입이 될 수 있으면 case 레이블에는 패턴이 지원되도록 개선 되었습니다.
따라서 switch statement(문) 혹은 switch expression(표현식) 의 case 레이블에 pattern matching 을 적용할 수 있게 되었습니다. 이번 포스팅에서는 jdk pattern matching 을 swich 문과 표현식에 적용하는 방법을 기술합니다.
jdk pattern matching for switch 이력
- jdk 17 에서 preview 기능으로써 JEP 406 에서 제안됨.
- jdk 18 에서 re-preview 기능으로써 JEP 420 에서 제안됨.
- jdk 19 에서 re-preview 기능으로써 JEP 427 에서 제안됨.
jdk pattern matching for instanceof
jdk pattern matching 은 jdk 14 버전부터 preview 기능으로 처음 도입 되었습니다. 참고로 instanceof 연산자에 pattern matching 을 적용하는 방법은 아래 포스팅을 참고하시면 되겠습니다.
2023.08.26 - [자바] - jdk pattern matching for instanceof
우선 아래 코드는 instanceof 연산자에 pattern matching 을 적용한 코드 입니다.
public interface Shape {
void print();
}
public class Rectangle implements Shape {}
public class Circle implements Shape {}
public class Triangle implements Shape {}
...
public void printShape(Shape shape) {
if (shape instanceof Rectangle r) {
r.print();
} else if (shape instanceof Triangle t) {
t.print();
} else if (shape instanceof Circle c) {
c.print();
} else {
throw new IllegalArgumentException();
}
}
pattern matching for switch
위 코드를 switch 문의 pattern matching 을 적용하면 아래와 같은 코드가 됩니다.
아래 코드는 Intellij IDE 에서 jdk 17 의 language level 은 17(Preview) - Pattern matching for switch 셋팅으로 컴파일 하였습니다.
void printShapeSwitch1(Shape shape) {
switch(shape) {
case Rectangle r: {
r.print();
break;
}
case Triangle t:
t.print();
break;
case Circle c:
c.print();
break;
case null:
System.out.println("shape is null");
break;
default:
throw new IllegalArgumentException();
}
}
void printShapeSwitch2(Shape shape) {
switch (shape) {
case Rectangle r -> r.print();
case Triangle t -> t.print();
case Circle c -> c.print();
case null -> System.out.println("shape is null");
default -> throw new IllegalArgumentException();
}
}
printShapeSwitch1() 과 printShapeSwitch2() 는 동일한 기능을 수행합니다.
case 레이블에 null 도 넣을 수 있습니다. case 레이블에 null 대신 Object 를 넣어도 됩니다.
pattern matching 은 switch expression 에서도 적용할 수 있습니다.
public interface Shape {
void print();
default String shapeName() {
return this.getClass().getSimpleName();
}
}
public class Circle implements Shape{}
public class Rectangle implements Shape{}
public class Triangle implements Shape{}
....
Shape shape = new Rectangle();
String shapeName = shapeName(shape);
System.out.println("shape name: " + shapeName);
....
String shapeName(Shape shape) {
return switch (shape) {
case Rectangle r -> r.shapeName();
case Triangle t -> t.shapeName();
case Circle c -> c.shapeName();
default -> throw new IllegalArgumentException();
};
}
switch selector 표현식 타입에는 어떠한 타입도 가능합니다.
public interface Shape {
void print();
default String shapeName() {
return this.getClass().getSimpleName();
}
}
public class Circle implements Shape{}
public class Rectangle implements Shape{}
public class Triangle implements Shape{}
....
public enum Sports {
BASEBALL("야구"),
BASKETBALL("농구"),
SOCCER("축구")
;
private final String name;
Sports(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
....
@Test
void switch_selector_test() {
Object obj = Sports.BASEBALL;
String switchExpressionValue = objectName(obj);
Assertions.assertThat(switchExpressionValue)
.isEqualTo(Sports.BASEBALL.getName());
Shape shape = new Circle();
switchExpressionValue = objectName(shape);
Assertions.assertThat(switchExpressionValue)
.isEqualTo(shape.shapeName());
String string = "switch selector";
switchExpressionValue = objectName(string);
Assertions.assertThat(switchExpressionValue)
.isEqualTo(string.toUpperCase());
}
....
String objectName(Object object) {
return switch (object) {
case Sports sports -> sports.getName();
case Rectangle r -> r.shapeName();
case Circle c -> c.shapeName();
case Triangle t -> t.shapeName();
case String s -> s.toUpperCase();
default -> "other type object";
};
}
위 switch_selector_test() 테스트 코드의 테스트는 모두 정상적으로 통과 합니다.
pattern label Dominance (지배력)
case 레이블은 switch 블록에 표시되는 순서대로 테스트 되는데 이 때 여러 개의 패턴 레이블이 일치하는 경우 컴파일 오류가 발생합니다.
String objectName(Object object) {
return switch (object) {
case Sports sports -> sports.getName();
//object 가 Shape 타입의 객체라면 무조건 여기서 패턴은 일치합니다.
case Shape s -> s.shapeName();
//상단 case 레이블에서 패턴이 일치 하기 때문에
//this case label is dominated by a preceding case label 오류가 발생합니다!!
case Rectangle r -> r.shapeName();
//상단 case 레이블에서 패턴이 일치 하기 때문에
//this case label is dominated by a preceding case label 오류가 발생합니다!!
case Circle c -> c.shapeName();
//상단 case 레이블에서 패턴이 일치 하기 때문에
//this case label is dominated by a preceding case label 오류가 발생합니다!!
case Triangle t -> t.shapeName();
case String s -> s.toUpperCase();
default -> "other type object";
};
}
========================================================================
//아래 테스트 코드는 정상 통과 됩니다.
@Test
void switch_dominance_test() {
Shape shape = new Circle();
switchExpressionValue = objectName(shape);
Assertions.assertThat(switchExpressionValue)
.isEqualTo(shape.shapeName());
}
String objectName(Object object) {
return switch (object) {
case Sports sports -> sports.getName();
//object 가 Circle 객체인 경우 아래 case 레이블은 패턴이 일치하지 않습니다.
case Rectangle r -> r.shapeName();
//object 가 Circle 객체인 경우 아래 case 레이블은 패턴이 일치하지 않습니다.
case Triangle t -> t.shapeName();
//object 가 Circle 객체인 경우 Shape 의 구현체 이므로
//아래 case 레이블은 패턴이 일치합니다.
case Shape s -> s.shapeName();
case String s -> s.toUpperCase();
default -> "other type object";
};
}
type coverage in switch Expression
switch expression(표현식) 은 모든 case 에 대해서 커버가 되어야 합니다. 아래 switch expression 코드는 모든 case 에 대해서 고려되지 않았기 때문에 컴파일 오류가 발생합니다.
@Test
void switch_expression_coverage_test() {
String string = "switch expression coverage";
Integer length = length(string);
Assertions.assertThat(length).isEqualTo(string.length());
}
Integer length(Object object) {
return switch(object) {
case String s -> s.length();
case Integer i -> i;
//String 타입과 Integer 타입만 case 에서 고려되었기 때문에
//the switch expression does not cover all possible input values
//오류가 발생합니다.!!
};
}
===============================================
Integer length(Object object) {
return switch(object) {
case String s -> s.length();
case Integer i -> i;
//default label 을 추가하여 모든 case에 고려가 되어 정상 동작합니다.
default -> throw new IllegalArgumentException();
};
}
===============================================
Integer length(Object object) {
return switch(object) {
case String s -> s.length();
case Integer i -> i;
//모든 객체는 Object 를 상속하므로 모든 case에 고려되어 정상 동작합니다.
//단, case Object 가 case String, case Integer 보다 상단에 선언되면
//Dominance 컴파일 오류가 발생합니다!!
case Object o -> 0;
};
}
위의 코드에서 알 수 있듯이 switch expression 에는 default 문이 반드시 있어야 합니다. 단, Enum 과 같이 미리 정의된 상수가 있는 타입의 경우에는 컴파일러에서 default 문이 없는 경우 암묵적으로 default 를 추가해 줍니다. 이에 대한 내용은 아래 포스팅의 Exhaustiveness (완전성) 항목을 참고하시면 도움이 됩니다.
2023.08.20 - [자바] - java switch expression - from jdk 14
'자바' 카테고리의 다른 글
Java - 람다 표현식(lambda expression) - 4개의 주요 functional interface (0) | 2023.09.15 |
---|---|
Java - 람다 표현식 (lambda expression) 개요 (0) | 2023.09.10 |
jdk pattern matching for instanceof (0) | 2023.08.26 |
java switch expression - from jdk 14 (0) | 2023.08.20 |
java Array vs ArrayList (0) | 2023.08.19 |
댓글