프로그래밍 언어/Java

Java Switch문 정리하기.

코딩하는흑구 2022. 9. 29. 10:09

switch 문을 사용하는 여러 예제들을 정리해보자!

 

 

public enum RoomType {
    BILLA, OFFICETEL, APT;
}

위 enum이 있을 때, switch문이 없는 경우 아래처럼 코드를 짤 수 있을 것입니다.

public void exampleOfIf(RoomType roomType) {
    if (roomType == RoomType.APT) {
        System.out.println("아파트");
    } else if (roomType == RoomType.BILLA) {
        System.out.println("일반주택");
    } else if (roomType == RoomType.OFFICETEL) {
        System.out.println("오피스텔");
    }
}

위 처럼 코드를 짜는 경우는 보기도 매우 어렵고 각 if 조건을 만족하지 않게 된다면(else if 의 마지막 조건 혹은 else절까지 가게 된다면) 모든 조건을 검사하게 됩니다.

 

public void exampleOfSwitch(RoomType roomType) {
    switch(roomType) {
        case APT:
            System.out.println("아파트");
            break;
        case BILLA:
            System.out.println("일반주택");
            break;
        case OFFICETEL:
            System.out.println("오피스텔");
            break;
        default:
            throw new RuntimeException("해당 타입 없음.");
    }
}

이런식으로 간단하게(사실 아직은 간단하지 않습니다.) switch문으로 처리하게 되는 경우 if문처럼 각 조건을 모두 검사하지 않고 해당하는 타입으로 바로 실행로직이 점프하게 됩니다.

break문은 해당 case문을 탈출할 때 사용합니다.

 

break문을 case절마다 사용하지 않고 서로다른 case간에 같은 로직을 실행하고 싶은 경우. 아래와 같이 제어할 수 있습니다.

public void exampleOfSwitch(RoomType roomType) {
    switch(roomType) {
        case APT:
        case BILLA:
            System.out.println("아파트와 일반주택");
            break;
        case OFFICETEL:
            System.out.println("오피스텔");
            break;
        default:
            throw new RuntimeException("해당 타입 없음.");
    }
}

switch / case 데이터 타입

 

 

데이터 타입

switch 문의 인자로 전달 가능한 타입은 제한되어 있습니다.

  • byte, short, int, char
  • Byte, Short, Integer, Character (jdk 5 이후)
  • enum (jdk 5 이후)
  • String  (jdk 7 이후)

 

물론 case에 선언되는 상수 또한 switch문에 넘어가는 타입과 동일해야합니다.

 

Null 허용 안함

swtich 문의 인자로 null 값 혹은 null 값으로 변수가 넘어가게 되는 경우에는 NullPointerException을 반환하게 됩니다.

RoomType roomType = null;
exampleOfSwitch(null); 		// NullPointerException 발생
exampleOfSwitch(roomType); 	// NullPointerException 발생

물론 case 절에 null 값을 기입하게 될수 없습니다. 컴파일이 되지 않습니다.

 

case 은 항상 상수

public static void main(String[] args) {
    String roomType = "APT";
    final String apt = "APT";
    String officetel = "OFFICETEL";

    switch (roomType) {
        case apt: 
            System.out.println("아파트");
        case officetel: // 컴파일 불가.
            System.out.println("오피스텔");
    }
}

위 코드에서 officetel 변수는 final, 즉 상수 취급 키워드가 붙지 않았습니다. 해당 값이 불변이어야 case 절에 사용할 수 있습니다.

 

문자열 비교

switch문이 문자열 비교를 할때는 equals 비교를 진행하기 때문에 동일성 이슈는 발생하지 않습니다. 

public static void main(String[] args) {
    String roomType = "APT";
    
    switch (roomType) {
        case "APT":
            System.out.println("아파트");
        case "OFFICETEL": 
            System.out.println("오피스텔");
    }
}

실제로 switch문의 java bytecode를 살펴보면

   L2
   FRAME APPEND [java/lang/String java/lang/String I]
    ALOAD 2
    LDC "APT"
    INVOKEVIRTUAL java/lang/String.equals (Ljava/lang/Object;)Z
    IFEQ L4
    ICONST_0
    ISTORE 3
    GOTO L4

"APT" 문자열을 비교할 때 String.equals 메소드가 사용되는 모습을 볼 수 있습니다.

 


Switch 표현식

 

새로운 switch 표현식

jdk 12부터 새롭게 추가된 switch 표현식은 아래와 같다.

public static void main(String[] args) {
    String roomType = "APT";

    var roomTypeString = switch (roomType) {
        case "APT" ->"아파트";
        case "OFFICETEL" -> "오피스텔";
        default -> "일반주택";
    };
    System.out.println(roomTypeString);
}

switch는 이제 더이상 제어문의 역할을 하는 것이 아닌 표현식으로써 메소드에 감싸져서 특정한 값을 리턴하지 않고도 스스로 값을 리턴하여 변수에 담을 수 있게 된다.

 

 

yield 키워드 추가

switch 문이 새롭게 표현식으로써 동작이 가능하게 변경됨에 따라 기존 c/c++을 토대로 저레벨 제어문처럼 동작가능하던 switch문에 변화가 생겼습니다.

 

예를 들어,

public static void main(String[] args) {
    String roomType = "OFFICETEL";

    var roomTypeString = getRoomTypeString(roomType);
    System.out.println(roomTypeString);
}

private static String getRoomTypeString(String roomType) {
    return switch (roomType) {
        case "APT" ->"아파트";
        case "OFFICETEL" -> {
            String temp = "오피스텔";
            return temp;
        }
        default -> "일반주택";
    };
}

다음과 같은 코드가 있을 때,

return문은 과연 함수를 종료하는 return문일지 switch문에서 값을 리턴하는 return일지에 대한 기준이 애매모호해집니다.(이건 개발자가 코드를 보는 관점에서 애매해졌다는 것이지 실제로 동작하는 과정에서는 반드시 해당 return 문을 포함하는 메소드의 종료를 나타내는 것은 확실합니다.)

 

따라서 switch 표현식에서 어떤 값을 리턴할때의 리턴문이 별도로 필요해지게 됩니다. 그에 따라 "생산하다, 산출하다" 라는 뜻의 yield 라는 키워드가 생긴걸로 보여집니다.

 

위 코드는 실제 컴파일 에러가 발생하겠지만 return 문을 yield 문으로 변경해보겠습니다.

private static String getRoomTypeString(String roomType) {
    return switch (roomType) {
        case "APT" ->"아파트";
        case "OFFICETEL" -> {
            String temp = "오피스텔";
            yield temp;
        }
        default -> "일반주택";
    };
}

 

 

사실 아래처럼 간단한 표현식의 경우는 위의 yield 키워드를 고민할 필요는 없겠지만 상황에 따라 여러 로직이 필요할 수 있음을 대비해야 합니다.

 

var roomTypeString = switch (roomType) {
        case "APT" ->"아파트";
        case "OFFICETEL" -> "오피스텔";
        default -> "일반주택";
    };

 

Exhaustiveness(철저함)

public static void main(String[] args) {
    RoomType roomType = RoomType.APT;

    String roomTypeStr = switch (roomType) {
        case APT -> "아파트";
        case OFFICETEL -> "오피스텔";
        case BILLA -> "일반주택";
    };
    System.out.println(roomTypeStr);
}

위의 코드는 컴파일 됩니다.

 

public static void main(String[] args) {
        RoomType roomType = RoomType.APT;

        String roomTypeStr = switch (roomType) {
            case APT -> "아파트";
            case OFFICETEL -> "오피스텔";
//            case BILLA -> "일반주택";
        };
        System.out.println(roomTypeStr);
    }

하지만 위의 코드는 컴파일이 되지 않습니다. (error: the switch expression does not cover all possible input values)

위와 같은 에러메시지를 내보낸 후 switch 표현식이 모든 경우를 커버할 수 없다는 이야기를 합니다. 이게 무슨이야기인지 확인해보겠습니다.

 

위에서 선언했던 enum 클래스인 RoomType 클래스를 다시 확인해보겠습니다.

public enum RoomType {
    BILLA, OFFICETEL, APT;
}

세가지 타입의 상수값이 존재하고 있는데요.

세번째 case문에서 BILLA case를 주석처리해버렸습니다. 따라서 변수로 넘어온 roomType이라는 값에 BILLA 타입의 RoomType 인스턴스가 넘어오게 된다면 해당 switch 표현식은 별도의 default 문이 없기 때문에 반환할 값을 산정하지 못하게 됩니다.

 

자바 컴파일러는 이를 캐치하여 컴파일 오류를 뱉어주게 됩니다. (필자는 여기서 if 문이 아닌 switch문을 사용해야될 이유를 하나 더 생각하게 되는 것 같습니다.)