CS

If와 Switch

dlxortmd123 2022. 11. 2. 01:59

의문점

If와 Swith가 내부적으로 어떤 차이가 있고, 어떤 상황에 사용하면 좋을까?

내부적으로 어떻게 동작할까?

If-else

  • 원래 코드
public static void IfTest(int num) {
        if (num == 1) {
            System.out.println("숫자 1입니다.");
        } else if (num == 2) {
            System.out.println("숫자 2입니다.");
        } else {
            System.out.println("알 수 없는 숫자입니다.");
        }
}
  • 바이트 코드
public static IfTest(I)V
   L0
    LINENUMBER 21 L0
    ILOAD 0
    ICONST_1
    IF_ICMPNE L1
   L2
    LINENUMBER 22 L2
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "\uc22b\uc790 1\uc785\ub2c8\ub2e4."
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
    GOTO L3
   L1
    LINENUMBER 23 L1
   FRAME SAME
    ILOAD 0
    ICONST_2
    IF_ICMPNE L4
   L5
    LINENUMBER 24 L5
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "\uc22b\uc790 2\uc785\ub2c8\ub2e4."
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
    GOTO L3
   L4
    LINENUMBER 26 L4
   FRAME SAME
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "\uc54c \uc218 \uc5c6\ub294 \uc22b\uc790\uc785\ub2c8\ub2e4."
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L3
    LINENUMBER 28 L3
   FRAME SAME
    RETURN
   L6
    LOCALVARIABLE num I L0 L6 0
    MAXSTACK = 2
    MAXLOCALS = 1
  • if 문은 조건을 검사하고 다음으로 넘기고 또 다음 조건을 검사하는 식의 반복으로 이루어진다. 따라서 조건이 많아지게 되고, 만약 해당하는 조건이 뒤에 있다면 거쳐야 하는 명령어가 많아지게 된다.
  • IF_ICMPNE 명령어를 통해 인자를 비교한다.

switch

  • 원래 코드
public static void switchTest(int num) {
        switch (num) {
            case 1:
                System.out.println("숫자 1입니다.");
                break;
            case 2:
                System.out.println("숫자 2입니다.");
                break;
            default:
                System.out.println("알 수 없는 숫자입니다.");
        }
}
  • 바이트 코드
public static switchTest(I)V
   L0
    LINENUMBER 8 L0
    ILOAD 0
    LOOKUPSWITCH
      1: L1
      2: L2
      default: L3
   L1
    LINENUMBER 10 L1
   FRAME SAME
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "\uc22b\uc790 1\uc785\ub2c8\ub2e4."
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L4
    LINENUMBER 11 L4
    GOTO L5
   L2
    LINENUMBER 13 L2
   FRAME SAME
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "\uc22b\uc790 2\uc785\ub2c8\ub2e4."
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L6
    LINENUMBER 14 L6
    GOTO L5
   L3
    LINENUMBER 16 L3
   FRAME SAME
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "\uc54c \uc218 \uc5c6\ub294 \uc22b\uc790\uc785\ub2c8\ub2e4."
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L5
    LINENUMBER 18 L5
   FRAME SAME
    RETURN
   L7
    LOCALVARIABLE num I L0 L7 0
    MAXSTACK = 2
    MAXLOCALS = 1
  • switch는 컴파일 시 tableswitch 또는 lookupswitch 를 이용한다.
  • tableswitch는 switch 문의 케이스들이 만들어질 테이블의 target offset에 인덱스로 효율적으로 표현될 때 사용한다. 만약 switch로 들어온 표현식이 인덱스의 범위를 벗어난 경우, default target이 사용된다.
    • 위의 예로 들면 1부터 시작한 offset 테이블을 생성할 수 있고, 3과 같이 범위를 벗어난 경우 default target 으로 넘어가게 된다.
  • 만약 케이스들이 산개해(흩어져) 있다면, lookupswitch 를 이용한다. 이 경우는 케이스의 값을 인덱스로 활용하여 산개한 케이스에 대해 더 효율적으로 대처할 수 있다.
  • JVM은 선형 검색(O(n)) 보다 효율적으로 검색하기 위해, lookupswitch를 case의 키로 정렬한다.
  • lookupswitch는 들어온 expression과 키 값을 대조해야 하는 과정이 존재해서, 단순히 범위를 체크하고 테이블에 인덱스하는 tableswitch에 비해 비효율적일 수 있습니다.
    • 공간이 충분하면 tableswitch, 공간의 여유가 없다면 lookupswitch를 사용하는 것이 좋아보인다.
  • 예시
Method int chooseFar(int)
0   iload_1
1   lookupswitch 3:
         -100: 36
            0: 38
          100: 40
      default: 42
36  iconst_m1
37  ireturn
38  iconst_0
39  ireturn
40  iconst_1
41  ireturn
42  iconst_m1
43  ireturn
Method int chooseFar(int)
0   iload_1
1   lookupswitch 3:
         -100: 36
            0: 38
          100: 40
      default: 42
36  iconst_m1
37  ireturn
38  iconst_0
39  ireturn
40  iconst_1
41  ireturn
42  iconst_m1
43  ireturn
  • switch 문의 또 다른 특징으로는 인자로 받는 expression은 int 데이터만 받는 점이다. 예를 들어 byte, char, short 은 int로 승격되어 컴파일된다. 따라서 그 외에 double, float 등은 int 형으로 변환 후 expression에 넣어주어야 한다.
    • 그렇다면, JAVA 7부터 지원하는 switch 문에 String을 넣는 것은 어떻게 동작할까?
    • 바로 hashcode를 사용한다. hashcode를 통해 int 값으로 변환 후 lookupswitch에 넣어 사용한다.
      • tableswitch 대신 lookupswitch를 사용하는 이유는 아무래도 hashcode 값은 연속될 가능성이 거의 없기 때문인가보다…

어떤 상황에 사용하면 좋을까?


  • 일단 기본적으로 조건이 적으면 별 차이가 없을 것 같다. 하지만 조건이 많아지면 case 문을 쓰는 것이 좋아보인다.
  • 만약 if 문을 쓰더라도, 자주 쓰는 조건을 앞에 두면 실행하는 명령어가 적어 더 효율적일 것이다.

Chapter 3. Compiling for the Java Virtual Machine

[Java] switch 문이 if-else 보다 효율적인 이유