- jdk pattern matching for switch2023년 08월 26일
- 알쓸개잡
- 작성자
- 2023.08.26.:24
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 다음글이전글이전 글이 없습니다.댓글