• 티스토리 홈
  • 프로필사진
    알쓸개잡
  • 태그
  • 블로그 관리
  • 글 작성
알쓸개잡
  • 프로필사진
    알쓸개잡
    • 분류 전체보기 (98)
      • 스프링부트 (57)
      • AWS (5)
      • 쿠버네티스 (7)
      • 자바 (19)
      • 인프라 (1)
      • ETC (9)
  • 리얼포스 Realforce R3S 키보드 R3SD13 토프레 무점접키보드, 기본, 기본, 텐키리스
    반응형
# Home
# 공지사항
#
# 태그
# 검색결과
# 방명록
  • jdk pattern matching for switch
    2023년 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
    다음글
    다음 글이 없습니다.
    이전글
    이전 글이 없습니다.
    댓글
조회된 결과가 없습니다.
스킨 업데이트 안내
현재 이용하고 계신 스킨의 버전보다 더 높은 최신 버전이 감지 되었습니다. 최신버전 스킨 파일을 다운로드 받을 수 있는 페이지로 이동하시겠습니까?
("아니오" 를 선택할 시 30일 동안 최신 버전이 감지되어도 모달 창이 표시되지 않습니다.)
목차
표시할 목차가 없습니다.
    • 안녕하세요
    • 감사해요
    • 잘있어요

    티스토리툴바