文字列でswitchステートメントを使用できないのはなぜですか?


1004

この機能は新しいJavaバージョンに組み込まれる予定ですか?

Javaのswitchステートメントの技術的な方法のように、なぜこれを実行できないのか誰かが説明できますか?


195
それはSE 7です。要求から16年。download.oracle.com/javase/tutorial/java/nutsandbolts/...
angryITguy

81
Sunはその評価に正直だった:"Don't hold your breath."笑、bugs.sun.com/bugdatabase/view_bug.do?bug_id=1223179
raffian

3
@raffian彼女は「ため息」を2回出したからだと思います。ほぼ10年後、彼らも返信するのが少し遅れました。その時、彼女は孫たちに弁当箱を詰めていたかもしれません。
WeirdElfB0y

回答:


1004

Stringケース付きのswitchステートメントは、最初にリクエストされてから少なくとも16年Java SE 7に実装されています遅延の明確な理由は示されていませんが、パフォーマンスに関係している可能性があります。

JDK 7での実装

この機能はjavac 、「砂糖を取り除く」プロセスで実装されました。宣言でString定数を使用するクリーンでハイレベルな構文caseは、コンパイル時にパターンに従うより複雑なコードに展開されます。結果のコードは、常に存在していたJVM命令を使用します。

例は、コンパイル時に二つのスイッチに翻訳されています。1つ目は、各文字列を一意の整数(元のスイッチ内の位置)にマップします。これは、最初にラベルのハッシュコードをオンにすることによって行われます。対応するケースは、文字列の等価性をテストするステートメントです。ハッシュに衝突がある場合、テストはカスケードです。2番目のスイッチは、元のソースコードと同じですが、ケースラベルを対応する位置に置き換えます。この2段階のプロセスにより、元のスイッチのフロー制御を簡単に保持できます。switchStringifif-else-if

JVMのスイッチ

のより技術的な詳細についてswitchは、JVM仕様を参照してください。ここでは、switchステートメントコンパイルについて説明しています。簡単に言えば、ケースで使用される定数の希薄さに応じて、スイッチに使用できる2つの異なるJVM命令があります。どちらも効率的に実行するために、それぞれの場合に整数定数を使用することに依存しています。

定数が密である場合、それらは(最小値を差し引いた後の)インデックスとして、命令ポインターのテーブル(命令)への使用されtableswitchます。

定数がスパースである場合、正しい大文字小文字の二分探索が実行されます— lookupswitch命令。

デsugaringではswitchStringのオブジェクトは、両方の命令が使用される可能性があります。lookupswitchケースの元の位置を見つけるためにハッシュコードの最初のスイッチに適しています。結果の序数はに自然に適合します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を使用する方が速い場合があります。数個のアイテムしか保存しないと、辞書が非常に高くつくことがわかりました。
ジョナサンアレン

84
if-elseif-elseif-elseif-elseの方が速いかもしれませんが、クリーンなコードは100の99倍です。文字列は不変であり、ハッシュコードをキャッシュするため、ハッシュの「計算」は高速です。コードのプロファイルを作成して、どのようなメリットがあるかを判断する必要があります。
エリクソン2008

21
switch(String)を追加しない理由は、switch()ステートメントで期待されるパフォーマンス保証が満たされないためです。彼らは開発者を「誤解させる」ことを望んでいませんでした。率直に言って、そもそもswitch()のパフォーマンスを保証すべきではないと思います。
ギリ

2
Pill基づいて何らかのアクションを実行するだけの場合は、例外をキャッチしたり、名前との一致を手動で確認したりする必要なく、RED、BLUEの範囲外の値strを処理できるため、if-elseが望ましいと主張します。各列挙型は、不必要なオーバーヘッドを追加するだけです。私の経験では、文字列値のタイプセーフな表現が後で必要になった場合にのみ、列挙に変換するのに使用することが理にかなっています。strvalueOfvalueOf
MilesHampson 2013

コンパイラーは、値のセットが異なる(hash >> x) & ((1<<y)-1)すべての文字列に対して異なる値を生成し、文字列の数の2倍未満(または少なくともそれ以上ではありません)。hashCode(1<<y)
スーパーキャット2013

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
この手法では、大文字と小文字を区別しない、エイリアスなどの問題を決定することもできます。言語設計者に依存する代わりに、「1つのサイズですべてに対応する」ソリューションを考え出す必要があります。
ダロン

2
JeeBeeに同意します。文字列をオンにする場合は、おそらくenumが必要です。文字列は通常、将来変更される可能性があるかまたは変更されない可能性があるインターフェイス(ユーザーまたはその他)に行くものを表すので、列挙型に置き換えるほうがよい
hhafez

18
このメソッドのわかりやすい説明については、xefer.com / 2006/12 / switchonstringを参照してください。
デビッドシュミット

@DavidSchmittこの記事には1つの大きな欠陥があります。メソッドによって実際にスローされる例外ではなく、すべての例外をキャッチます。
M.ミンペン、2014

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-文字列のハッシュ(不変の性質による)はこの問題の解決策のように思われますが、すべての非finalオブジェクトはハッシュ関数をオーバーライドできることを覚えておく必要があります。これにより、コンパイル時にスイッチの一貫性を確保することが困難になります。
martinatime 2008

2
文字列に一致するDFAを作成することもできます(正規表現エンジンが行うように)。おそらくハッシュよりもさらに効率的です。
Nate 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 ++は整数型のスイッチのみを許可します。他のタイプでは無意味だったからです。」

私の意見は、それだけですが、非プリミティブに切り替え始めるとすぐに、「等しい」と「==」について考える必要があるということです。まず、2つの文字列を比較するのはかなり時間がかかる手順であり、上記のパフォーマンスの問題に追加されます。次に、文字列をオンにする場合は、大文字と小文字を無視して文字列をオンにする、ロケールを考慮/無視する文字列をオンにする、正規表現に基づいて文字列をオンにするなどの要求があります。多くの時間を節約した決定を承認します。プログラマーにとって少しの時間を犠牲にして言語開発者。


技術的には、正規表現は基本的に単なる状態マシンであるため、すでに「切り替え」です。彼らは単に2つの「ケース」を持っている、matchedそしてnot matched。(ただし、[名前付き]グループなどのことは考慮に入れていません。)
JAB '30

1
docs.oracle.com/javase/7/docs/technotes/guides/language/…状態:Javaコンパイラーは、連鎖したif-then-elseステートメントからよりも、Stringオブジェクトを使用するswitchステートメントからより効率的なバイトコードを生成します。
Wim Deblauwe 2016

12

上記の良い議論に加えて、私は今日多くの人々が見ることを付け加えます switch、Javaの手続き型の過去(C回に戻る)の時代遅れの残りとしててます。

私はこの意見を完全に共有していませんswitchが、少なくともその速度が原因で、場合によってはその有用性を発揮できると思います。とにかく、一連のカスケード数値よりも優れていますelse if、いくつかのコードで見たています...

しかし、実際には、スイッチが必要な場合を検討し、それを他のオブジェクト指向オブジェクトに置き換えることができないかどうかを確認する価値があります。たとえば、Java 1.5以降の列挙型、おそらくHashTableやその他のコレクション(Luaのように、ファーストクラスシチズンとして(匿名)関数がないことを後悔していることがあります。


「ファーストクラスの市民として(匿名の)機能がないことを後悔している」 それはもはや真実ではありません。
user8397947 2017年

@dorukayhanはい、もちろんです。しかし、過去10年間のすべての回答にコメントを追加して、Javaの新しいバージョンに更新した場合にコメントを追加できるようにしたいと思いますか?:-D
PhiLho 2017年

8

JDK7以降を使用していない場合は、を使用hashCode()してシミュレーションできます。そのためString.hashCode()、通常は別の文字列に異なる値を返し、常に同じ文字列の同じ値を返し、かなり信頼性がある(別の文字列ができる @Liiコメントで述べたように同じハッシュコードを生成、など"FB""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
2つの異なる文字列が同じハッシュコードを持つ可能性があるため、ハッシュコードをオンにすると、誤った大文字と小文字の分岐が行われる可能性があります。
Lii

@Liiこれを指摘してくれてありがとう!それはありそうもないが、私はそれが機能することを信用しません。「FB」と「Ea」は同じハッシュコードを持っているので、衝突を見つけることは不可能ではありません。2番目のコードはおそらくより信頼性があります。
HyperNeutrino 2016年

caseステートメントが常に定数値でString.hashCode()なければならず、実際にはそうではない(実際に計算がJVM間で変更されていなかったとしても)ので、これがコンパイルされることに驚いています。
StaxMan 2018年

@StaxManうーん、面白いよ。しかし、そうです、caseステートメントの値はコンパイル時に決定可能である必要はないので、うまく機能します。
HyperNeutrino 2018年

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コンパイラは、文字列スイッチを、既存のバイトコードを使用するものに変換します。
Dawood ibnカリーム

4

他の回答では、これはJava 7で追加されたものであり、以前のバージョンの回避策が示されています。この答えは「なぜ」に答えようとします

Javaは、C ++の過度の複雑さに対する反応でした。シンプルでクリーンな言語になるように設計されています。

文字列は、言語で少し特殊なケース処理を行いましたが、デザイナーが特殊なケースと構文糖の量を最小限に抑えようとしていたことは明らかです。

文字列は単純なプリミティブ型ではないため、内部で文字列をオンにするのはかなり複雑です。これは、Javaが設計された時点では一般的な機能ではなく、ミニマリストの設計にはあまり適していません。特に、文字列に対して==を特別なケースにしないことを決定したので、==が機能しない場合にケースが機能するのは少し奇妙です(そして実際です)。

1.0と1.4の間で、言語自体はほとんど同じままでした。Javaの拡張機能のほとんどはライブラリ側にありました。

そのすべてがJava 5で変更され、言語は大幅に拡張されました。バージョン7および8でさらに拡張が行われました。この態度の変化はC#の台頭によって引き起こされたと思います


switch(String)に関する説明は、履歴、タイムライン、コンテキストcpp / csに適合します。
エスプレッソ2018

この機能を実装しないことは大きな間違いでした。他のすべては安価な言い訳であり、Javaは言語の進化に対する進歩の欠如とデザイナーの頑固さのために、長年にわたって多くのユーザーを失いました。幸いなことに、彼らは完全にJDK7後方向と態度を変えた
firephil

0

JEP 354: JDK-13のスイッチ式(プレビュー)および JEP 361: JDK-14のスイッチ式(標準)は、 switchステートメントを拡張してとして使用できるようにします

今することができます:

  • スイッチ式から変数を直接割り当てる
  • 新しい形式のスイッチラベル(case L ->)を使用:

    "case L->"スイッチラベルの右側のコードは、式、ブロック、または(便宜上)throwステートメントに制限されています。

  • カンマで区切って、ケースごとに複数の定数を使用し、
  • また、これ以上の値の区切りはありません

    スイッチ式から値を生成するには、 break with valueステートメントを削除してyieldステートメントを優先します。

答え(からデモだから、12のようになります):

  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の回答はトピック外であり、役に立たないプラグインであるためです。
マーティン

12
保守的な大規模なSWの家でも、JavaとともにGroovyが使用されます。JVMは、現在の言語よりも言語にとらわれない環境を提供し、ソリューションに最も関連するプログラミングパラダイムを組み合わせて使用​​します。だから今、私はより多くの反対票を集めるためにClojureにスニペットを追加する必要があります:) ...
Alex Punnen '22

1
また、構文はどのように機能しますか?Groovyは別のプログラミング言語だと思います...?ごめんなさい。Groovyについては何も知りません。
HyperNeutrino 2016年

-4

intellijを使用する場合は、以下も参照してください。

ファイル->プロジェクト構造->プロジェクト

ファイル->プロジェクト構造->モジュール

複数のモジュールがある場合は、モジュールタブで正しい言語レベルを設定していることを確認してください。


1
あなたの答えが質問にどのように関連しているかはわかりません。彼は、次のような文字列switchステートメントが使用できない理由を尋ねました。String mystring = "something"; スイッチ(mystring){case "something" sysout( "got here"); 。。}
Deepak Agarwal 2016年

-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、2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.