この機能は新しいJavaバージョンに組み込まれる予定ですか?
Javaのswitch
ステートメントの技術的な方法のように、なぜこれを実行できないのか誰かが説明できますか?
"Don't hold your breath."
笑、bugs.sun.com/bugdatabase/view_bug.do?bug_id=1223179
この機能は新しいJavaバージョンに組み込まれる予定ですか?
Javaのswitch
ステートメントの技術的な方法のように、なぜこれを実行できないのか誰かが説明できますか?
"Don't hold your breath."
笑、bugs.sun.com/bugdatabase/view_bug.do?bug_id=1223179
回答:
String
ケース付きのswitchステートメントは、最初にリクエストされてから少なくとも16年後にJava SE 7に実装されています。遅延の明確な理由は示されていませんが、パフォーマンスに関係している可能性があります。
この機能はjavac
、「砂糖を取り除く」プロセスで実装されました。宣言でString
定数を使用するクリーンでハイレベルな構文case
は、コンパイル時にパターンに従うより複雑なコードに展開されます。結果のコードは、常に存在していたJVM命令を使用します。
例は、コンパイル時に二つのスイッチに翻訳されています。1つ目は、各文字列を一意の整数(元のスイッチ内の位置)にマップします。これは、最初にラベルのハッシュコードをオンにすることによって行われます。対応するケースは、文字列の等価性をテストするステートメントです。ハッシュに衝突がある場合、テストはカスケードです。2番目のスイッチは、元のソースコードと同じですが、ケースラベルを対応する位置に置き換えます。この2段階のプロセスにより、元のスイッチのフロー制御を簡単に保持できます。switch
String
if
if-else-if
のより技術的な詳細についてswitch
は、JVM仕様を参照してください。ここでは、switchステートメントのコンパイルについて説明しています。簡単に言えば、ケースで使用される定数の希薄さに応じて、スイッチに使用できる2つの異なるJVM命令があります。どちらも効率的に実行するために、それぞれの場合に整数定数を使用することに依存しています。
定数が密である場合、それらは(最小値を差し引いた後の)インデックスとして、命令ポインターのテーブル(命令)への使用されtableswitch
ます。
定数がスパースである場合、正しい大文字小文字の二分探索が実行されます— lookupswitch
命令。
デsugaringではswitch
上String
のオブジェクトは、両方の命令が使用される可能性があります。lookupswitch
ケースの元の位置を見つけるためにハッシュコードの最初のスイッチに適しています。結果の序数はに自然に適合しますtableswitch
。
どちらの命令でも、各ケースに割り当てられた整数定数をコンパイル時にソートする必要があります。一方、実行時には、O(1)
性能tableswitch
、一般的には、より良い見えるO(log(n))
のパフォーマンスlookupswitch
、それは表には時間と空間のトレードオフを正当化するために、緻密に十分であるかどうかを判断するために、いくつかの分析が必要です。Bill Vennersが他のJavaフロー制御命令の裏側を見て、これをより詳細にカバーする素晴らしい記事を書きました。
JDK 7より前のバージョンでenum
は、- String
ベースのスイッチを概算できました。これは、valueOf
すべてのenum
型でコンパイラによって生成された静的メソッドを使用します。例えば:
Pill p = Pill.valueOf(str);
switch(p) {
case RED: pop(); break;
case BLUE: push(); break;
}
Pill
基づいて何らかのアクションを実行するだけの場合は、例外をキャッチしたり、名前との一致を手動で確認したりする必要なく、RED、BLUEの範囲外の値str
を処理できるため、if-elseが望ましいと主張します。各列挙型は、不必要なオーバーヘッドを追加するだけです。私の経験では、文字列値のタイプセーフな表現が後で必要になった場合にのみ、列挙に変換するのに使用することが理にかなっています。str
valueOf
valueOf
(hash >> x) & ((1<<y)-1)
すべての文字列に対して異なる値を生成し、文字列の数の2倍未満(または少なくともそれ以上ではありません)。hashCode
(1<<y)
コード内に文字列をオンにできる場所がある場合は、文字列をリファクタリングして、有効な値の列挙にして、スイッチをオンにすることをお勧めします。もちろん、必要な文字列の潜在的な値を、列挙の値に制限することもできますが、これは望ましくない場合もあります。
もちろん、列挙には「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;
}
以下は、カスタムメソッドを使用する代わりに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
}
}
整数に基づくスイッチは、非常に効率的なコードに最適化できます。他のデータ型に基づくスイッチは、一連のif()ステートメントにのみコンパイルできます。
そのため、CおよびC ++は整数型のスイッチのみを許可します。これは、他の型では意味がないためです。
C#の設計者は、利点がないとしても、スタイルが重要であると判断しました。
Javaの設計者は、Cの設計者のように思われたようです。
String
1.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;
}
}
James Curran氏は簡潔に次のように述べています。「整数に基づくスイッチは非常に効率的なコードに最適化できます。他のデータ型に基づくスイッチは、一連のif()ステートメントにのみコンパイルできます。そのため、CおよびC ++は整数型のスイッチのみを許可します。他のタイプでは無意味だったからです。」
私の意見は、それだけですが、非プリミティブに切り替え始めるとすぐに、「等しい」と「==」について考える必要があるということです。まず、2つの文字列を比較するのはかなり時間がかかる手順であり、上記のパフォーマンスの問題に追加されます。次に、文字列をオンにする場合は、大文字と小文字を無視して文字列をオンにする、ロケールを考慮/無視する文字列をオンにする、正規表現に基づいて文字列をオンにするなどの要求があります。多くの時間を節約した決定を承認します。プログラマーにとって少しの時間を犠牲にして言語開発者。
matched
そしてnot matched
。(ただし、[名前付き]グループなどのことは考慮に入れていません。)
上記の良い議論に加えて、私は今日多くの人々が見ることを付け加えます switch
、Javaの手続き型の過去(C回に戻る)の時代遅れの残りとしててます。
私はこの意見を完全に共有していませんswitch
が、少なくともその速度が原因で、場合によってはその有用性を発揮できると思います。とにかく、一連のカスケード数値よりも優れていますelse if
、いくつかのコードで見たています...
しかし、実際には、スイッチが必要な場合を検討し、それを他のオブジェクト指向オブジェクトに置き換えることができないかどうかを確認する価値があります。たとえば、Java 1.5以降の列挙型、おそらくHashTableやその他のコレクション(Luaのように、ファーストクラスシチズンとして(匿名)関数がないことを後悔していることがあります。
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;
}
}
}
}
case
ステートメントが常に定数値でString.hashCode()
なければならず、実際にはそうではない(実際に計算がJVM間で変更されていなかったとしても)ので、これがコンパイルされることに驚いています。
case
ステートメントの値はコンパイル時に決定可能である必要はないので、うまく機能します。
何年もの間、(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が必要です。
他の回答では、これはJava 7で追加されたものであり、以前のバージョンの回避策が示されています。この答えは「なぜ」に答えようとします
Javaは、C ++の過度の複雑さに対する反応でした。シンプルでクリーンな言語になるように設計されています。
文字列は、言語で少し特殊なケース処理を行いましたが、デザイナーが特殊なケースと構文糖の量を最小限に抑えようとしていたことは明らかです。
文字列は単純なプリミティブ型ではないため、内部で文字列をオンにするのはかなり複雑です。これは、Javaが設計された時点では一般的な機能ではなく、ミニマリストの設計にはあまり適していません。特に、文字列に対して==を特別なケースにしないことを決定したので、==が機能しない場合にケースが機能するのは少し奇妙です(そして実際です)。
1.0と1.4の間で、言語自体はほとんど同じままでした。Javaの拡張機能のほとんどはライブラリ側にありました。
そのすべてがJava 5で変更され、言語は大幅に拡張されました。バージョン7および8でさらに拡張が行われました。この態度の変化はC#の台頭によって引き起こされたと思います
JEP 354: JDK-13のスイッチ式(プレビュー)および JEP 361: JDK-14のスイッチ式(標準)は、 switchステートメントを拡張して式として使用できるようにします。
今することができます:
case L ->
)を使用:
"case L->"スイッチラベルの右側のコードは、式、ブロック、または(便宜上)throwステートメントに制限されています。
スイッチ式から値を生成するには、
break
with valueステートメントを削除してyield
ステートメントを優先します。
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");
}
それはGroovyのそよ風です。私はgroovy jarを埋め込み、groovy
これらすべてを実行するためのユーティリティクラスを作成します。さらに、Javaで実行するのに苛立たしいことがわかります(企業でJava 6を使用しているためです)。
it.'p'.each{
switch (it.@name.text()){
case "choclate":
myholder.myval=(it.text());
break;
}}...
intellijを使用する場合は、以下も参照してください。
ファイル->プロジェクト構造->プロジェクト
ファイル->プロジェクト構造->モジュール
複数のモジュールがある場合は、モジュールタブで正しい言語レベルを設定していることを確認してください。
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;
}
}
}