문자열에 switch 문을 사용할 수없는 이유는 무엇입니까?


1004

이 기능이 이후의 Java 버전에 적용됩니까?

누군가 Java의 switch명령문이 작동 하는 기술적 방식과 같이 왜 내가 이것을 할 수 없는지 설명 할 수 있습니까 ?


195
요청 후 16 년 후에 SE 7에 있습니다. download.oracle.com/javase/tutorial/java/nutsandbolts/…
angryITguy

81
썬은 그들의 평가에 정직했다 : "Don't hold your breath."lol, bugs.sun.com/bugdatabase/view_bug.do?bug_id=1223179
raffianian

3
@raffian 나는 그것이 그녀가 두 번 한숨을 쉬었 기 때문이라고 생각합니다. 그들은 거의 10 년이 지난 후에도 대답하기에 약간 늦었습니다. 그녀는 그때 손자에게 도시락을 포장했을 것입니다.
WeirdElfB0y

답변:


1003

String케이스가있는 스위치 명령문 은 처음 요청한 후 16 년 이상 Java SE 7 에서 구현되었습니다 . 지연의 명확한 이유는 제공되지 않았지만 성능과 관련이있을 수 있습니다.

JDK 7 구현

이 기능은 이제 javac "설탕 제거"프로세스 로 구현되었습니다 . 선언 String에서 상수를 사용하는 깨끗하고 높은 수준의 구문 case은 컴파일 타임에 패턴에 따라 더 복잡한 코드로 확장됩니다. 결과 코드는 항상 존재하는 JVM 명령어를 사용합니다.

switch을 가진 String경우는 컴파일하는 동안 두 개의 스위치로 변환됩니다. 첫 번째는 각 문자열을 고유 한 정수 (원래 스위치의 위치)에 매핑합니다. 레이블의 해시 코드를 먼저 켜면됩니다. 해당하는 경우는 if문자열 동등성을 테스트 하는 명령문입니다. 해시에 충돌이 있으면 테스트는 계단식 if-else-if입니다. 두 번째 스위치는 원본 소스 코드에서이를 미러링하지만 케이스 레이블을 해당 위치로 대체합니다. 이 2 단계 프로세스를 통해 원래 스위치의 흐름 제어를 쉽게 유지할 수 있습니다.

JVM에서 스위치

보다 자세한 기술 내용스위치switch 사양의 컴파일 이 설명 되어있는 JVM 사양을 참조하십시오 . 간단히 말해, 사례에 사용되는 상수의 희소성에 따라 스위치에 사용할 수있는 두 가지 다른 JVM 명령어가 있습니다. 둘 다 각 경우에 정수 상수를 사용하여 효율적으로 실행합니다.

상수가 밀도가 높으면 명령 포인터 테이블 (명령)에 인덱스 (가장 낮은 값을 뺀 후)로 tableswitch사용됩니다.

상수가 드문 경우 올바른 사례에 대한 이진 검색 ( lookupswitch명령) 이 수행 됩니다.

switchon String객체의 설탕 을 제거 할 때 두 가지 지침이 모두 사용됩니다. 는 lookupswitch해시 코드에 제 1 스위치 케이스의 원래 위치를 찾기 위해 적합하다. 결과 서수는 자연스럽게 적합합니다 tableswitch.

두 명령어 모두 각 경우에 할당 된 정수 상수를 컴파일 타임에 정렬해야합니다. 그동안 런타임시 O(1)의 성능 tableswitch일반적으로이보다 더 나은 표시 O(log(n))의 성능 lookupswitch, 그것은 테이블이 공간 - 시간의 균형을 정당화하기 조밀 충분히인지 여부를 결정하기 위해 몇 가지 분석이 필요합니다. Bill Venners는 다른 Java 흐름 제어 명령에 대한 간단한 설명과 함께이 내용을 자세히 다루는 훌륭한 기사 를 작성 했습니다 .

JDK 7 이전

JDK 7 이전에는 기반 스위치와 enum비슷했습니다 String. 이것은 모든 유형 에서 컴파일러가 생성 한 정적valueOf 메소드를 사용 합니다enum . 예를 들면 다음과 같습니다.

Pill p = Pill.valueOf(str);
switch(p) {
  case RED:  pop();  break;
  case BLUE: push(); break;
}

26
문자열 기반 스위치의 해시 대신 If-Else-If를 사용하는 것이 더 빠를 수 있습니다. 몇 가지 항목 만 저장할 때 사전이 꽤 비싸다는 것을 알았습니다.
Jonathan Allen

84
if-elseif-elseif-elseif-else는 더 빠를 수 있지만 100보다 99 번 더 깨끗한 코드를 가져옵니다. 불변의 문자열은 해시 코드를 캐시하므로 해시를 "계산"하는 것이 빠릅니다. 어떤 이점이 있는지 확인하려면 코드를 프로파일 링해야합니다.
erickson

21
switch (String)를 추가하지 않는 이유는 switch () 문에서 기대하는 성능 보장을 충족하지 않기 때문입니다. 그들은 개발자들을 "도둑질"하고 싶지 않았다. 솔직히 나는 그들이 switch ()의 성능을 보장해야한다고 생각하지 않습니다.
길리

2
Pill에 따라 일부 작업을 수행하는 데 사용 하는 str경우 str예외를 잡거나 valueOf수동으로 불필요한 오버 헤드를 추가하는 각 열거 유형 필자의 경험 valueOf에 따르면 나중에 문자열 값의 형식 안전 표현이 필요한 경우 열거 형으로 변환하는 데 사용 하는 것이 합리적 입니다.
MilesHampson

컴파일러가 숫자 쌍 (x, y)이 있는지 여부를 테스트하려고 노력하는지 궁금합니다. 값 세트 가 다르고 문자열 수의 두 배보다 작은 (hash >> x) & ((1<<y)-1)모든 문자열 (또는 적어도 그보다 크지 않습니다). hashCode(1<<y)
supercat

125

코드에서 문자열을 켤 수있는 장소가 있다면 가능한 값의 열거로 문자열을 리팩터링하는 것이 좋습니다. 물론, 열거 할 수있는 문자열의 잠재적 인 값을 제한 할 수 있습니다.

물론 열거에는 'other'에 대한 항목과 fromString (String) 메소드가있을 수 있습니다.

ValueEnum enumval = ValueEnum.fromString(myString);
switch (enumval) {
   case MILK: lap(); break;
   case WATER: sip(); break;
   case BEER: quaff(); break;
   case OTHER: 
   default: dance(); break;
}

4
이 기술을 사용하면 대소 문자 구분, 별명 등의 문제를 결정할 수 있습니다. 언어 디자이너가 "모든 사람에게 맞는"솔루션을 제안하는 대신.
Darron

2
문자열을 켜는 경우 열거 형이 필요할 경우 JeeBee에 동의하십시오. 이 문자열은 일반적으로 미래에 변경되거나 변경되지 않을 수있는 인터페이스 (사용자 또는 기타)로가는 무언가를 나타내므로 열거 형으로 대체하는 것이 좋습니다.
hhafez

18
이 방법을 잘 작성 하려면 xefer.com/2006/12/switchonstring 을 참조하십시오 .
David Schmitt

@DavidSchmitt 쓰기에는 한 가지 큰 결함이 있습니다. 실제로 메소드에 의해 발생되는 예외 대신 모든 예외를 포착 합니다 .
M. Mimpen

91

다음은 사용자 정의 메소드를 사용하는 대신 java enum을 사용하여 JeeBee의 게시물을 기반으로 한 완전한 예제입니다.

Java SE 7 이상에서는 대신 switch 문의 표현식에 String 객체를 사용할 수 있습니다.

public class Main {

    /**
    * @param args the command line arguments
    */
    public static void main(String[] args) {

      String current = args[0];
      Days currentDay = Days.valueOf(current.toUpperCase());

      switch (currentDay) {
          case MONDAY:
          case TUESDAY:
          case WEDNESDAY:
              System.out.println("boring");
              break;
          case THURSDAY:
              System.out.println("getting better");
          case FRIDAY:
          case SATURDAY:
          case SUNDAY:
              System.out.println("much better");
              break;

      }
  }

  public enum Days {

    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
  }
}

26

정수 기반 스위치는 매우 효율적인 코드로 최적화 될 수 있습니다. 다른 데이터 유형을 기반으로하는 스위치는 일련의 if () 문으로 만 컴파일 할 수 있습니다.

이러한 이유로 C & C ++는 정수 유형에서만 스위치를 허용합니다. 다른 유형에서는 의미가 없기 때문입니다.

C #의 디자이너는 이점이 없더라도 스타일이 중요하다고 결정했습니다.

Java의 디자이너는 분명히 C의 디자이너처럼 생각했습니다.


26
해시 가능한 개체를 기반으로하는 스위치는 해시 테이블을 사용하여 매우 효율적으로 구현 될 수 있습니다. .NET을 참조하십시오. 따라서 당신의 이유는 완전히 정확하지 않습니다.
Konrad Rudolph

그래, 그리고 이것은 내가 이해하지 못하는 것입니다. 그것들은 장기적으로 해싱 객체가 너무 비싸 질까 두려워합니까?
Alex Beardsley

3
@Nalandial : 실제로 컴파일러 부분에 약간의 노력을 기울이면 문자열 집합이 알려지면 완벽한 해시를 생성하기가 쉽기 때문에 전혀 비싸지 않습니다. (이것은 .NET에서는 수행되지 않습니다. 노력의 가치가 없을 수도 있습니다).
Konrad Rudolph

3
@Nalandial & @Konrad Rudolph-문자열을 해싱하는 것은 (불변의 성질로 인해)이 문제에 대한 해결책처럼 보이지만 최종 객체가 아닌 모든 객체는 해싱 기능을 재정의 할 수 있음을 기억해야합니다. 이로 인해 컴파일시 일관성을 유지하기가 어려워집니다.
martinatime

2
정규식 엔진처럼 문자열과 일치하도록 DFA를 구성 할 수도 있습니다. 해싱보다 훨씬 더 효율적일 수 있습니다.
네이트 CK

19

String1.7 이후 의 직접 사용 예도 표시 될 수 있습니다.

public static void main(String[] args) {

    switch (args[0]) {
        case "Monday":
        case "Tuesday":
        case "Wednesday":
            System.out.println("boring");
            break;
        case "Thursday":
            System.out.println("getting better");
        case "Friday":
        case "Saturday":
        case "Sunday":
            System.out.println("much better");
            break;
    }

}

18

James Curran은 간결하게 말합니다. "정수를 기반으로하는 스위치는 매우 효율적인 코드로 최적화 할 수 있습니다. 다른 데이터 유형을 기반으로하는 스위치는 일련의 if () 문으로 만 컴파일 할 수 있습니다. 따라서 C & C ++에서는 정수 유형에 대한 스위치 만 허용합니다. 다른 유형과는 무의미했기 때문입니다. "

저의 의견은 단지 프리미티브가 아닌 스위치를 시작하자마자 "같음"과 "=="에 대해 생각하기 시작해야한다는 것입니다. 먼저 두 문자열을 비교하는 것은 상당히 긴 절차 일 수 있으며 위에서 언급 한 성능 문제를 더합니다. 둘째로 문자열을 전환하는 경우 대소 문자를 무시하고 로케일을 고려하거나 무시하는 문자열을 전환하고 정규 표현식을 기반으로 문자열을 전환하는 문자열을 전환해야합니다 .... 나는 많은 시간을 절약 한 결정을 승인 할 것입니다. 프로그래머에게는 적은 시간이 소요되는 언어 개발자.


기술적으로 정규 표현식은 기본적으로 상태 머신이기 때문에 이미 "전환"됩니다. 그들은 단지 두 "경우"를 가지고 matchednot matched. ([named] groups / etc와 같은 것을 고려하지 않음.)
JAB

1
docs.oracle.com/javase/7/docs/technotes/guides/language/… states : Java 컴파일러는 일반적으로 연결된 if-then-else 문보다 String 객체를 사용하는 스위치 문에서보다 효율적인 바이트 코드를 생성합니다.
Wim Deblauwe

12

위의 좋은 주장 외에도 오늘 많은 사람들이 switch 이 Java 과거의 절차 적 과거 (C 번으로 돌아가는)의 남은 부분으로 간주 .

나는이 견해를 완전히 공유하지 않으며 switch, 적어도 속도 때문에 어떤 경우에는 유용 ​​할 수 있다고 생각 합니다. 어쨌든 else if일부 코드에서 본 일련의 계단식 숫자보다 낫습니다 ...

그러나 실제로 스위치가 필요한 경우를 살펴보고 더 많은 OO로 바꿀 수 없는지 살펴볼 가치가 있습니다. 예를 들어 Java 1.5 이상의 열거 형, 아마도 HashTable 또는 다른 컬렉션 (때로는 스위치 또는 JavaScript가없는 Lua에서와 같이 일급 시민으로서 (익명) 기능이 없거나 유감스럽게 생각합니다) 또는 다형성이 있습니다.


"때로는 우리가 일류 시민으로서 (익명) 기능을 가지고 있지 않은 것을 후회합니다" 더 이상 사실이 아닙니다.
user8397947

물론입니다. 그러나 지난 10 년 동안의 모든 답변에 의견을 추가하여 최신 버전의 Java로 업데이트 할 경우 전 세계에 의견을 제시하고 싶습니까? :-D
PhiLho

8

JDK7 이상을 사용하지 않는 경우 hashCode()이를 사용 하여 시뮬레이션 할 수 있습니다 . String.hashCode()일반적으로 서로 다른 문자열에 대해 다른 값을 반환하고 항상 같은 문자열에 대해 동일한 값을 반환 하기 때문에 상당히 안정적입니다 (다른 문자열 "FB" and 등의 주석에 언급 된 @Lii와 동일한 해시 코드를 생성 할 수 있음"Ea" ) 설명서를 참조하십시오 .

따라서 코드는 다음과 같습니다.

String s = "<Your String>";

switch(s.hashCode()) {
case "Hello".hashCode(): break;
case "Goodbye".hashCode(): break;
}

그렇게하면 기술적으로 전원을 켤 수 있습니다 int.

또는 다음 코드를 사용할 수 있습니다.

public final class Switch<T> {
    private final HashMap<T, Runnable> cases = new HashMap<T, Runnable>(0);

    public void addCase(T object, Runnable action) {
        this.cases.put(object, action);
    }

    public void SWITCH(T object) {
        for (T t : this.cases.keySet()) {
            if (object.equals(t)) { // This means that the class works with any object!
                this.cases.get(t).run();
                break;
            }
        }
    }
}

5
두 개의 다른 문자열이 동일한 해시 코드를 가질 수 있으므로 해시 코드를 켜면 잘못된 대소 문자가 사용될 수 있습니다.
Lii

@Lii 이것을 지적 해 주셔서 감사합니다! 그러나 가능성은 낮지 만 작동한다고 믿지 않습니다. "FB"와 "Ea"는 동일한 해시 코드를 가지므로 충돌을 찾는 것이 불가능하지 않습니다. 두 번째 코드는 아마도 더 안정적 일 것입니다.
HyperNeutrino

case명령문이 항상 일정한 값이어야한다고 생각한 것처럼 컴파일이 놀랍습니다 String.hashCode()(실제로 계산이 JVM간에 변경되지 않은 경우에도).
StaxMan

@StaxMan Hm 흥미 롭습니다. 그러나 예, case명령문 값은 컴파일 타임에 결정할 수 없으므로 잘 작동합니다.
HyperNeutrino

4

수년간 우리는 이것을 위해 (n 오픈 소스) 프리 프로세서를 사용해 왔습니다.

//#switch(target)
case "foo": code;
//#end

사전 처리 된 파일 이름은 Foo.jpp이며 ant 스크립트를 사용하여 Foo.java로 처리됩니다.

장점은 1.0에서 실행되는 Java로 처리된다는 점입니다 (일반적으로 1.4로만 지원됨). 또한 열거 형이나 다른 해결 방법으로 처리하는 것과 비교 하여이 작업을 수행하는 것이 훨씬 쉽습니다 (코드를 읽고 유지 관리하고 이해하는 것이 훨씬 쉬웠습니다. IIRC (현재 통계 또는 기술적 추론을 제공 할 수 없음)는 자연 Java에 비해 빠릅니다.

단점은 Java를 편집하지 않으므로 약간 더 많은 워크 플로우 (편집, 프로세스, 컴파일 / 테스트)와 IDE가 약간 복잡한 Java로 다시 링크됩니다 (스위치는 일련의 if / else 논리 단계가 됨) 스위치 케이스 순서는 유지되지 않습니다.

1.7 이상에서는 권장하지 않지만 이전 JVM을 대상으로하는 Java를 프로그래밍하려는 경우 유용합니다 (Joe public은 최신 버전을 거의 설치하지 않기 때문에).

SVN에서 가져 오거나 온라인으로 코드를 찾아 볼 수 있습니다 . 그대로 빌드 하려면 EBuild 가 필요합니다 .


6
String 스위치로 코드를 실행하기 위해 1.7 JVM이 필요하지 않습니다. 1.7 컴파일러는 String 스위치를 기존의 바이트 코드를 사용하는 것으로 바꿉니다.
Dawood ibn Kareem

4

다른 답변에 따르면 Java 7에 추가되었으며 이전 버전에 대한 해결 방법이 제공되었습니다. 이 답변은 "왜"

Java는 C ++의 복잡한 문제에 대한 반응이었습니다. 단순하고 깨끗한 언어로 설계되었습니다.

String은 언어에서 약간의 특수한 경우를 처리했지만 디자이너가 특별한 케이스와 구문 설탕의 양을 최소한으로 유지하려고 노력하고 있음이 분명합니다.

문자열은 단순한 기본 유형이 아니기 때문에 문자열 전환은 상당히 복잡합니다. Java가 디자인 될 당시에는 일반적인 기능이 아니 었으며 미니멀리스트 디자인에는 적합하지 않습니다. 특히 그들은 문자열에 대해 특별한 경우 ==를 사용하지 않기로 결정했기 때문에 ==가 아닌 경우 작동하는 것은 조금 이상합니다.

1.0과 1.4 사이에서 언어 자체는 거의 동일하게 유지되었습니다. Java에 대한 대부분의 개선 사항은 라이브러리 측에있었습니다.

모든 것이 Java 5로 바뀌면서 언어가 실질적으로 확장되었습니다. 버전 7과 8에서 추가 확장이 이어졌습니다. 이러한 태도 변화는 C #의 상승으로 인한 것 같습니다.


switch (String)에 대한 설명은 히스토리, 타임 라인, 컨텍스트 cpp / cs에 적합합니다.
에스프레소

이 기능을 구현하지 않는 것은 큰 실수였습니다. 다른 모든 것은 Java가 발전하지 않았고 언어를 진화시키지 않는 디자이너의 완고함으로 인해 수년 동안 많은 사용자를 잃은 싼 변명입니다. 다행히도 그들은
JDK7

0

JEP 354 : JDK-13의 스위치 표현식 (미리보기) JEP 361 : JDK-14의 스위치 표현식 (표준) 스위치 문 을 확장하여 표현식 으로 사용할 수 있습니다.

이제 다음을 수행 할 수 있습니다.

  • 스위치 식에서 변수를 직접 할당 ,
  • 새로운 형태의 스위치 레이블 사용 (case L -> )을 사용하십시오.

    "case L->"스위치 레이블의 오른쪽에있는 코드는 표현식, 블록 또는 (편의를 위해) throw 문으로 제한됩니다.

  • 쉼표로 구분하여 사례 당 여러 상수를 사용하십시오.
  • 그리고 더 이상 가치 파괴 가 없습니다 .

    스위치 식에서 값을 산출하기 위해 breakwith value 문이 문을 대신하여 삭제됩니다 yield.

따라서 답변 ( 1 , 2 ) 의 데모 는 다음과 같습니다.

  public static void main(String[] args) {
    switch (args[0]) {
      case "Monday", "Tuesday", "Wednesday" ->  System.out.println("boring");
      case "Thursday" -> System.out.println("getting better");
      case "Friday", "Saturday", "Sunday" -> System.out.println("much better");
    }

-2

별로 예쁘지는 않지만 Java 6의 또 다른 방법은 다음과 같습니다.

String runFct = 
        queryType.equals("eq") ? "method1":
        queryType.equals("L_L")? "method2":
        queryType.equals("L_R")? "method3":
        queryType.equals("L_LR")? "method4":
            "method5";
Method m = this.getClass().getMethod(runFct);
m.invoke(this);

-3

Groovy의 산들 바람입니다. groovy jar을 포함시키고 groovy이러한 모든 작업을 수행하기 위해 유틸리티 클래스를 작성하고 Java에서해야 할 일을 더 많이 찾습니다 (기업에서 Java 6을 사용하고 있기 때문에).

it.'p'.each{
switch (it.@name.text()){
   case "choclate":
     myholder.myval=(it.text());
     break;
     }}...

9
@SSpoke 이것은 Java 질문이므로 Groovy 답변은 주제가 맞지 않으며 쓸모없는 플러그입니다.
Martin

12
보수적 인 대형 SW 주택에서도 Groovy는 Java와 함께 사용됩니다. JVM은 솔루션에 가장 관련성이 높은 프로그래밍 패러다임을 혼합하여 사용할 수 있도록 언어보다 환경에 구애받지 않는 환경을 제공합니다. 그래서 아마 지금은 더 downvotes를 수집 Clojure의 한 조각을 추가해야합니다 :) ...
알렉스 Punnen

1
또한 구문은 어떻게 작동합니까? Groovy가 다른 프로그래밍 언어라고 생각합니다 ...? 죄송합니다. 나는 Groovy에 대해 아무것도 모른다.
HyperNeutrino

-4

intellij를 사용할 때 다음 사항도 확인하십시오.

파일-> 프로젝트 구조-> 프로젝트

파일-> 프로젝트 구조-> 모듈

모듈이 여러 개인 경우 모듈 탭에서 올바른 언어 수준을 설정해야합니다.


1
답변이 질문과 어떤 관련이 있는지 잘 모르겠습니다. 그는 다음과 같은 문자열 스위치 문을 사용할 수없는 이유를 물었습니다. String mystring = "something"; switch (mystring) {case "something"sysout ( "got here"); . . }
Deepak Agarwal

-8
public class StringSwitchCase { 

    public static void main(String args[]) {

        visitIsland("Santorini"); 
        visitIsland("Crete"); 
        visitIsland("Paros"); 

    } 

    public static void visitIsland(String island) {
         switch(island) {
          case "Corfu": 
               System.out.println("User wants to visit Corfu");
               break; 
          case "Crete": 
               System.out.println("User wants to visit Crete");
               break; 
          case "Santorini": 
               System.out.println("User wants to visit Santorini");
               break; 
          case "Mykonos": 
               System.out.println("User wants to visit Mykonos");
               break; 
         default: 
               System.out.println("Unknown Island");
               break; 
         } 
    } 

} 

8
OP는 문자열을 켜는 방법을 묻지 않습니다. JDK7 이전의 구문 제한으로 인해 왜 할 수 없는지 묻고 있습니다.
HyperNeutrino
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.