web-dev-qa-db-ja.com

文字列に対してswitch文を使用できないのはなぜですか?

この機能は今後のJavaバージョンに組み込まれるのでしょうか。

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

963
Alex Beardsley

Stringケースを含むswitchステートメントは、 Java SE 7 の少なくとも16年間 最初に要求された後 に実装されました。 - 遅延の明確な理由は明らかにされていませんが、パフォーマンスと関係があります。

JDK 7での実装

この機能は現在javacで実装されています 「砂糖除去」プロセスを含むString宣言でcase定数を使用したきれいで高水準の構文は、コンパイル時にパターンに従ってより複雑なコードに展開されます。結果のコードは、常に存在していたJVM命令を使用します。

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

JVM内のスイッチ

switchの技術的な詳細については、 switch文のコンパイル が記述されているJVM仕様を参照できます。一言で言えば、ケースで使用される定数の希薄さに応じて、切り替えに使用できる2つの異なるJVM命令があります。どちらも、効率的に実行するために、各ケースで整数定数を使用することに依存しています。

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

定数がまばらである場合は、正しい大文字と小文字のバイナリ検索(lookupswitch命令)が実行されます。

switchオブジェクトのStringを推奨しない場合は、両方の命令が使用される可能性があります。 lookupswitchは、ケースの元の位置を見つけるためのハッシュコードの最初の切り替えに適しています。結果の序数はtableswitchに自然にフィットします。

どちらの命令も、コンパイル時にソートするために各ケースに割り当てられた整数定数を必要とします。実行時には、tableswitchO(1)のパフォーマンスは一般にlookupswitchO(log(n))のパフォーマンスより優れているように見えますが、テーブルが時空間のトレードオフを正当化するのに十分な密度であるかどうかを判断するための分析が必要です。 Bill Vennersが書いた 素晴らしい記事 他のJavaフロー制御命令の詳細な説明とともに、これをより詳細に説明しています。

JDK 7より前

JDK 7より前のバージョンでは、enumStringベースのスイッチに近いものでした。これは static valueOf すべてのenum型についてコンパイラによって生成されたメソッドを使用します。例えば:

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

コード内で文字列を有効にできる場所がある場合は、文字列をリファクタリングして有効な値の列挙にすることをお勧めします。これを有効にすることができます。もちろん、列挙型に含まれる可能性のあるStringの潜在的な値を制限します。

もちろん、あなたの列挙は '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;
}
120
JeeBee

以下はJeeBeeの投稿に基づく完全な例で、カスタムメソッドを使う代わりにJava enumを使っています。

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
  }
}
89

整数に基づくスイッチは非常に効率的なコードに最適化することができます。他のデータ型に基づくスイッチは、一連のif()ステートメントにしかコンパイルできません。

そのため、CとC++では整数型のスイッチしかできません。他の型では意味がありません。

C#の設計者は、利点がなくてもスタイルが重要であると判断しました。

Javaのデザイナーは明らかにCのデザイナーのように考えていました。

25
James Curran

James Curran氏は、「整数に基づくスイッチは非常に効率的なコードに最適化できる。他のデータ型に基づくスイッチは一連のif()文にしかコンパイルできない。そのため、C&C++では整数型のスイッチしか使えない他のタイプでは無意味だったので。」

私の意見は、それだけですが、あなたがノンプリミティブのスイッチを入れるとすぐに、「等しい」対「==」について考え始める必要があるということです。最初に2つの文字列を比較することはかなり長い手順になるかもしれません、上で述べられたパフォーマンス上の問題を追加します。次に、文字列の切り替えがある場合、大文字と小文字の区別なしの文字列の切り替え、ロケールを考慮/無視する文字列の切り替え、regexに基づく文字列の切り替えなどの要求があります。プログラマーのためのわずかな時間の費用で言語開発者。

18
DJClayworth

1.7以降のStringの直接使用法の例も示されるかもしれません:

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;
    }

}

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

私はこの意見を完全には共有していません、少なくともその速度のために、switchはいくつかのケースでその有用性を持っていると思います、とにかくそれはいくつかの一連のカスケード数値else ifいくつかのコード...

しかし、実際には、スイッチが必要なケースを調べて、それをオブジェクト指向以外のものに置き換えられないかどうかを調べる価値があります。たとえば、Java 1.5+の列挙型、おそらくHashTableまたは他のコレクション(スイッチを持たないLuaのように、ファーストクラスの市民として(匿名)関数を持たないことを後悔する場合があります)またはJavaScript)またはポリモーフィズム。

12
PhiLho

JDK 7以上を使用していない場合は、hashCode()を使用してそれをシミュレートできます。 String.hashCode()は通常異なる文字列に対しては異なる値を返し、等しい文字列に対しては常に等しい値を返すので、かなり信頼できます(異なる文字列 できる "FB""Ea"など、コメントに記載されている@Liiと同じハッシュコードを生成します。 ドキュメント を参照してください。

そのため、コードは次のようになります。

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;
            }
        }
    }
}
8
HyperNeutrino

長年にわたり、私たちはこれに(nオープンソース)プリプロセッサを使用してきました。

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

前処理されたファイルはFoo.jppという名前で、antスクリプトを使ってFoo.Javaに処理されます。

利点は、1.0で動作するJavaに処理されることです(ただし、通常は1.4までサポートされています)。また、enumや他の回避策で欺くのに比べて、これを行う方がはるかに簡単でした(たくさんの文字列スイッチ)。コードは読みやすく、保守し、理解しやすいものでした。 IIRC(現時点では統計や技術的推論を提供することはできません)は、それが自然なJavaの同等物よりも早かったです。

欠点は、あなたがJavaを編集していないので、もう少しワークフロー(編集、処理、コンパイル/テスト)とIDEがJavaにリンクバックすることです(スイッチは一連のものになります)。 if/elseロジックステップ)およびケースの入れ替え順序は維持されません。

私は1.7以降にはお勧めしませんが、以前のJVMをターゲットとするJavaをプログラムしたい場合には便利です(Joe一般が最新のものをインストールすることはめったにないので)。

あなたはそれを SVNから入手することができます またはオンラインで コードを閲覧することができます 。そのままビルドするには EBuild が必要です。

4
Charles Goodwin

他の回答では、これはJava 7で追加されたものであり、以前のバージョンの回避策が示されています。この答えは「理由」に答えることを試みています

Javaは、C++の複雑すぎる問題に対する反応でした。それは単純できれいな言語になるように設計されました。

Stringは、この言語で特殊な場合の処理​​を少し行っていますが、設計者が特殊な大文字小文字と構文糖の量を最小限に抑えようとしていたことは明らかです。

文字列は単純なプリミティブ型ではないので、文字列をオンにすることは内部的にはかなり複雑です。これは、Javaが設計された時点では一般的な機能ではなく、ミニマリスト設計にはあまり適していません。特に、文字列に対して==を特別に使用しないことにしたので、==ではない場合に動作するのは、少々不思議です。

1.0から1.4の間、言語自体はほとんど同じままでした。 Javaに対する機能強化の大部分はライブラリ側にあります。

すべてがJava 5で変わったので、言語は大幅に拡張されました。バージョン7と8では、さらなる拡張が行われました。この態度の変化は、C#の上昇によってもたらされたと思います。

4
plugwash