Java 为什么我不能在字符串上使用 switch 语句?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/338206/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 13:36:55  来源:igfitidea点击:

Why can't I use switch statement on a String?

javastringswitch-statement

提问by Alex Beardsley

Is this functionality going to be put into a later Java version?

此功能是否会被放入更高版本的 Java 中?

Can someone explain why I can't do this, as in, the technical way Java's switchstatement works?

有人可以解释为什么我不能这样做,例如 Javaswitch语句的工作方式?

采纳答案by erickson

Switch statements with Stringcases have been implemented in Java SE 7, at least 16 years after they were first requested.A clear reason for the delay was not provided, but it likely had to do with performance.

带有Stringcase 的Switch 语句已经在Java SE 7 中实现,至少在它们第一次被请求后16 年没有提供延迟的明确原因,但这可能与性能有关。

Implementation in JDK 7

在 JDK 7 中的实现

The feature has now been implemented in javacwith a "de-sugaring" process;a clean, high-level syntax using Stringconstants in casedeclarations is expanded at compile-time into more complex code following a pattern. The resulting code uses JVM instructions that have always existed.

该功能现已通过javac“脱糖”过程实现;Stringcase声明中使用常量的干净的高级语法在编译时扩展为遵循模式的更复杂的代码。生成的代码使用一直存在的 JVM 指令。

A switchwith Stringcases is translated into two switches during compilation. The first maps each string to a unique integer—its position in the original switch. This is done by first switching on the hash code of the label. The corresponding case is an ifstatement that tests string equality; if there are collisions on the hash, the test is a cascading if-else-if. The second switch mirrors that in the original source code, but substitutes the case labels with their corresponding positions. This two-step process makes it easy to preserve the flow control of the original switch.

一个switchwith Stringcase 在编译过程中被翻译成两个开关。第一个将每个字符串映射到一个唯一的整数——它在原始开关中的位置。这是通过首先打开标签的哈希码来完成的。对应的 case 是if测试字符串相等性的语句;如果散列上存在冲突,则测试是级联if-else-if. 第二个开关反映了原始源代码中的那个,但是用它们对应的位置替换了案例标签。这个两步过程可以很容易地保留原始交换机的流量控制。

Switches in the JVM

JVM 中的开关

For more technical depth on switch, you can refer to the JVM Specification, where the compilation of switch statementsis described. In a nutshell, there are two different JVM instructions that can be used for a switch, depending on the sparsity of the constants used by the cases. Both depend on using integer constants for each case to execute efficiently.

关于 的更多技术深度switch,可以参考JVM规范,其中描述了switch语句编译。简而言之,有两种不同的 JVM 指令可用于切换,具体取决于案例使用的常量的稀疏性。两者都依赖于对每种情况使用整数常量来有效执行。

If the constants are dense, they are used as an index (after subtracting the lowest value) into a table of instruction pointers—the tableswitchinstruction.

如果常量是密集的,它们将用作指令指针表(即指令)中的索引(在减去最小值之后)tableswitch

If the constants are sparse, a binary search for the correct case is performed—the lookupswitchinstruction.

如果常量是稀疏的,则执行对正确情况的二分搜索 -lookupswitch指令。

In de-sugaring a switchon Stringobjects, both instructions are likely to be used. The lookupswitchis suitable for the first switch on hash codes to find the original position of the case. The resulting ordinal is a natural fit for a tableswitch.

switchString对象进行脱糖时,可能会使用这两条指令。在lookupswitch对散列码的第一开关以找到的情况下的原始位置是合适的。生成的序数自然适合 a tableswitch

Both instructions require the integer constants assigned to each case to be sorted at compile time. At runtime, while the O(1)performance of tableswitchgenerally appears better than the O(log(n))performance of lookupswitch, it requires some analysis to determine whether the table is dense enough to justify the space–time tradeoff. Bill Venners wrote a great articlethat covers this in more detail, along with an under-the-hood look at other Java flow control instructions.

这两条指令都要求在编译时对分配给每个 case 的整数常量进行排序。在运行时,虽然 的O(1)性能tableswitch通常看起来比 的O(log(n))性能好lookupswitch,但它需要一些分析来确定表是否足够密集以证明时空权衡是合理的。Bill Venners 写了一篇很棒的文章,更详细地介绍了这一点,并深入了解了其他 Java 流控制指令。

Before JDK 7

JDK 7 之前

Prior to JDK 7, enumcould approximate a String-based switch. This uses the static valueOfmethod generated by the compiler on every enumtype. For example:

在 JDK 7 之前,enum可以近似String基于 -based 的开关。这使用编译器在每种类型上生成的静态valueOf方法enum。例如:

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

回答by James Curran

Switches based on integers can be optimized to very efficent code. Switches based on other data type can only be compiled to a series of if() statements.

基于整数的开关可以优化为非常有效的代码。基于其他数据类型的开关只能编译为一系列 if() 语句。

For that reason C & C++ only allow switches on integer types, since it was pointless with other types.

出于这个原因,C 和 C++ 只允许在整数类型上进行切换,因为它对其他类型毫无意义。

The designers of C# decided that the style was important, even if there was no advantage.

C# 的设计者认为样式很重要,即使没有优势。

The designers of Java apparently thought like the designers of C.

Java 的设计者显然和 C 的设计者一样思考。

回答by JeeBee

If you have a place in your code where you can switch on a String, then it may be better to refactor the String to be an enumeration of the possible values, which you can switch on. Of course, you limit the potential values of Strings you can have to those in the enumeration, which may or may not be desired.

如果您的代码中有一个地方可以打开字符串,那么最好将字符串重构为可能值的枚举,您可以打开它。当然,您可以将字符串的潜在值限制为枚举中的值,这可能是需要的,也可能不是。

Of course your enumeration could have an entry for 'other', and a fromString(String) method, then you could have

当然,您的枚举可以有一个“其他”条目和一个 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;
}

回答by DJClayworth

James Curran succinctly says: "Switches based on integers can be optimized to very efficent code. Switches based on other data type can only be compiled to a series of if() statements. For that reason C & C++ only allow switches on integer types, since it was pointless with other types."

James Curran 简洁地说:“基于整数的开关可以优化为非常高效的代码。基于其他数据类型的开关只能编译为一系列 if() 语句。因此,C 和 C++ 只允许整数类型的开关,因为它对其他类型毫无意义。”

My opinion, and it's only that, is that as soon as you start switching on non-primitives you need to start thinking about "equals" versus "==". Firstly comparing two strings can be a fairly lengthy procedure, adding to the performance problems that are mentioned above. Secondly if there is switching on strings there will be demand for switching on strings ignoring case, switching on strings considering/ignoring locale,switching on strings based on regex.... I would approve of a decision that saved a lot of time for the language developers at the cost of a small amount of time for programmers.

我的观点是,并且仅此而已,一旦您开始打开非原始类型,您就需要开始考虑“等于”与“==”。首先比较两个字符串可能是一个相当冗长的过程,增加了上面提到的性能问题。其次,如果打开字符串,将需要打开忽略大小写的字符串、考虑/忽略语言环境的字符串、基于正则表达式的字符串……我会赞成一个为语言开发人员以程序员的少量时间为代价。

回答by PhiLho

Beside the above good arguments, I will add that lot of people today see switchas an obsolete remainder of procedural past of Java (back to C times).

除了上述好的论点,我switch还要补充一点,今天很多人认为Java 的过程是过时的剩余部分(回到 C 时代)。

I don't fully share this opinion, I think switchcan have its usefulness in some cases, at least because of its speed, and anyway it is better than some series of cascading numerical else ifI saw in some code...

我不完全同意这个观点,我认为switch在某些情况下可以有它的用处,至少因为它的速度,无论如何它比else if我在某些代码中看到的一些级联数值要好......

But indeed, it is worth looking at the case where you need a switch, and see if it cannot be replaced by something more OO. For example enums in Java 1.5+, perhaps HashTable or some other collection (sometime I regret we don't have (anonymous) functions as first class citizen, as in Lua — which doesn't have switch — or JavaScript) or even polymorphism.

但确实,值得看看你需要一个 switch 的情况,看看它是否不能被更多的 OO 替代。例如 Java 1.5+ 中的枚举,可能是 HashTable 或其他一些集合(有时我很遗憾我们没有(匿名)函数作为一等公民,就像在 Lua 中一样——它没有 switch——或 JavaScript)甚至多态。

回答by Thulani Chivandikwa

The following is a complete example based on JeeBee's post, using java enum's instead of using a custom method.

以下是基于 JeeBee 帖子的完整示例,使用 java 枚举而不是使用自定义方法。

Note that in Java SE 7 and later you can use a String object in the switch statement's expression instead.

请注意,在 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
  }
}

回答by Alex Punnen

It's a breeze in Groovy; I embed the groovy jar and create a groovyutility class to do all these things and more which I find exasperating to do in Java (since I am stuck using Java 6 in the enterprise.)

在 Groovy 中轻而易举;我嵌入了 groovy jar 并创建了一个groovy实用程序类来完成所有这些和更多的事情,我发现在 Java 中做这些事情很令人恼火(因为我在企业中使用 Java 6。)

it.'p'.each{
switch ([email protected]()){
   case "choclate":
     myholder.myval=(it.text());
     break;
     }}...

回答by Charles Goodwin

For years we've been using a(n open source) preprocessor for this.

多年来,我们一直为此使用(n 开源)预处理器。

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

Preprocessed files are named Foo.jpp and get processed into Foo.java with an ant script.

预处理过的文件被命名为 Foo.jpp 并使用 ant 脚本处理为 Foo.java。

Advantage is it is processed into Java that runs on 1.0 (although typically we only supported back to 1.4). Also it was far easier to do this (lots of string switches) compared to fudging it with enums or other workarounds - code was a lot easier to read, maintain, and understand. IIRC (can't provide statistics or technical reasoning at this point) it was also faster than the natural Java equivalents.

优点是它被处理成运行在 1.0 上的 Java(虽然通常我们只支持回到 1.4)。此外,与使用枚举或其他变通方法来捏造它相比,执行此操作(大量字符串切换)要容易得多 - 代码更易于阅读、维护和理解。IIRC(此时无法提供统计数据或技术推理)它也比自然的 Java 等价物更快。

Disadvantages are you aren't editing Java so it's a bit more workflow (edit, process, compile/test) plus an IDE will link back to the Java which is a little convoluted (the switch becomes a series of if/else logic steps) and the switch case order is not maintained.

缺点是您没有编辑 Java,因此它的工作流程(编辑、处理、编译/测试)多一点,而且 IDE 将链接回 Java,这有点复杂(切换变成了一系列 if/else 逻辑步骤)并且不维护开关盒顺序。

I wouldn't recommend it for 1.7+ but it's useful if you want to program Java that targets earlier JVMs (since Joe public rarely has the latest installed).

我不会为 1.7+ 推荐它,但如果您想编写面向早期 JVM 的 Java(因为 Joe public 很少安装最新版本),它会很有用。

You can get it from SVNor browse the code online. You'll need EBuildto build it as-is.

您可以从 SVN获取它或在线浏览代码。您将需要EBuild来按原样构建它。

回答by Gunnar Forsgren - Mobimation

An example of direct Stringusage since 1.7 may be shown as well:

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

}

回答by HyperNeutrino

If you are not using JDK7 or higher, you can use hashCode()to simulate it. Because String.hashCode()usually returns different values for different strings and always returns equal values for equal strings, it is fairly reliable (Different strings canproduce the same hash code as @Lii mentioned in a comment, such as "FB"and "Ea") See documentation.

如果你没有使用JDK7或更高版本,你可以使用hashCode()它来模拟它。因为String.hashCode()通常为不同的字符串返回不同的值,并且总是为相等的字符串返回相等的值,所以它是相当可靠的(不同的字符串可以产生与注释中提到的@Lii 相同的哈希码,例如"FB"and "Ea")参见文档

So, the code would look like this:

所以,代码看起来像这样:

String s = "<Your String>";

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

That way, you are technically switching on an int.

这样,您在技术上是在打开int.

Alternatively, you could use the following code:

或者,您可以使用以下代码:

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