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

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

这个功能会被放到稍后的Java版本中吗?

有人能解释为什么我不能这样做,就像Java的EDCOX1 0语句的技术方法一样吗?


使用EDCOX1,0个实例的开关语句在Java SE 7中实现,至少在16年后被请求。没有提供延迟的明确原因,但可能与性能有关。

在JDK 7中实现

该功能现在已经在javac中通过"去糖"过程实现;在case声明中使用String常量的干净、高级语法在编译时按照模式扩展为更复杂的代码。生成的代码使用一直存在的JVM指令。

在编译过程中,带有String案例的switch被转换为两个开关。第一个将每个字符串映射到一个唯一的整数,它在原始开关中的位置。这是通过首先打开标签的哈希代码来完成的。相应的情况是测试字符串相等性的if语句;如果哈希上存在冲突,则测试是级联if-else-if。第二个开关反映了原始源代码中的情况,但将大小写标签替换为相应的位置。这两个步骤的过程使它很容易保持原开关的流量控制。

JVM中的交换机

有关switch的更多技术深度,可以参考JVM规范,其中描述了switch语句的编译。简而言之,有两种不同的JVM指令可用于交换机,这取决于实例所用常量的稀疏程度。两者都依赖于为每种情况使用整型常量来高效地执行。

如果常量很密集,则将它们用作索引(减去最小值后)到指令指针表中,即tableswitch指令。

如果常量是稀疏的,则执行二进制搜索以查找正确的大小写—lookupswitch指令。

在对String对象进行switch去糖处理时,两种指令都可能被使用。lookupswitch适用于第一个打开哈希码的哈希码,以找到案件的原始位置。由此产生的序数自然适合于tableswitch

这两条指令都要求在编译时对分配给每个事例的整型常量进行排序。在运行时,虽然tableswitchO(1)性能通常比lookupswitchO(log(n))性能要好,但需要进行一些分析,以确定该表是否足够密集,以证明时空权衡的合理性。Bill Venners写了一篇伟大的文章,更详细地介绍了这一点,以及引擎盖下的其他Java流控制指令。

JDK 7之前

在JDK7之前,enum可以近似于基于String的交换机。它使用编译器在每个enum类型上生成的静态valueOf方法。例如:

1
2
3
4
5
Pill p = Pill.valueOf(str);
switch(p) {
  case RED:  pop();  break;
  case BLUE: push(); break;
}


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

当然,枚举可以有一个"other"的条目和一个fromstring(string)方法,然后可以有

1
2
3
4
5
6
7
8
ValueEnum enumval = ValueEnum.fromString(myString);
switch (enumval) {
   case MILK: lap(); break;
   case WATER: sip(); break;
   case BEER: quaff(); break;
   case OTHER:
   default: dance(); break;
}


下面是基于JeeBee的POST的完整示例,使用JavaEnm而不是使用自定义方法。

请注意,在JavaSE 7中,稍后可以在Switter语句的表达式中使用String对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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&AMP+C++只允许对整数类型进行切换,因为它与其他类型无关。

C的设计师认为,即使没有优势,风格也很重要。

JAVA的设计者显然像C设计师一样思考。


还可以显示自1.7起直接使用String的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
    }

}

JamesCurran简明扼要地说:"基于整数的开关可以优化为非常有效的代码。基于其他数据类型的开关只能编译为一系列if()语句。出于这个原因,C&AMP+C++只允许对整数类型进行切换,因为它与其他类型毫无意义。

我的观点是,只要你开始打开非原语,你就需要开始思考"等于"和"=="。首先,比较两个字符串是一个相当长的过程,这增加了上面提到的性能问题。其次,如果要打开字符串,则需要打开字符串忽略大小写、打开字符串考虑/忽略区域设置、打开基于regex的字符串….我赞成这样一个决定,即以程序员的少量时间为代价,为语言开发人员节省大量时间。


除了上述好的论点外,我还要补充的是,今天很多人把EDOCX1 0作为Java程序过时的剩余部分(回到C次)。

我不完全同意这一观点,我认为switch在某些情况下,至少因为它的速度,而且无论如何,它比我在一些代码中看到的一系列级联数字else if要好。

但事实上,这是值得一看的情况下,你需要一个开关,看看它是否可以取代更多的OO。例如Java 1.5 +中的枚举,也许是哈希表或一些其他集合(有时我很遗憾我们没有(匿名)函数作为第一类公民,如在Lua中没有交换或JavaScript)甚至多态性。


如果不使用JDK7或更高版本,可以使用hashCode()来模拟它。由于String.hashCode()通常为不同的字符串返回不同的值,并始终为相同的字符串返回相同的值,因此它相当可靠(不同的字符串可以产生与注释中提到的@lii相同的哈希代码,如"FB""Ea",请参见文档。

所以,代码应该是这样的:

1
2
3
4
5
6
String s ="<Your String>";

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

这样,从技术上讲,您可以打开一个int

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
            }
        }
    }
}


Other answers have said this was added in Java 7 and given workarounds for earlier versions.这个答案试图回答"为什么"

Java was a reaction to the over-complexities of C++.它是一种简单的语言。

严格地说,在语言上有一点特殊的案例处理比特,但对我来说似乎很清楚的是,设计师试图将更多的特殊套管和句法糖保持在最低限度。

自从弦乐不是简单的原始类型以来,弦乐上的开关是公平的复合物。这不是Java时代的一个共同特征,也不是以最低限度的设计来实现的。特别是当他们决定不去特别案件====为了弦乐,它将是(并且是)一个非常陌生的比特,以便在工作地点====doesn't。

在1.0和1.4之间,语言本身也很漂亮。对爪哇的增强大部分在图书馆的一侧。

所有的语言都随着爪哇5号的变化而发生了很大的变化。在第7和第8版中进一步扩展。我希望这一态度的改变是由C 35的崛起驱动的。


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

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

预处理的文件名为foo.jpp,并使用Ant脚本处理为foo.java。

优点是它被处理成Java,运行在1(虽然通常我们只支持返回到1.4)。此外,与使用枚举或其他解决方法来缓冲代码相比,执行此操作(许多字符串开关)要容易得多——代码更易于阅读、维护和理解。IIRC(在这一点上不能提供统计或技术推理)它也比自然的Java等价物快。

缺点是你不编辑Java,所以它有更多的工作流程(编辑,处理,编译/测试)加上IDE会链接回Java,这有点复杂(交换机变成一系列的I/EL逻辑步骤),并且交换机的情况不被维护。

我不建议它用1.7 +,但是如果你想把Java瞄准早期JVM(因为乔公众很少有最新安装的),那么它是有用的。

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


不是很漂亮,但这里是Java 6和Bellow的另一条路:

1
2
3
4
5
6
7
8
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);


在Groovy中是微风;我嵌入了Groovy jar并创建了一个EDCOX1×3的实用工具类来完成所有这些事情,而且更多的是我在Java中感到恼火(因为我在Java中被困在企业中使用Java 6)。

1
2
3
4
5
6
it.'p'.each{
switch (it.@name.text()){
   case"choclate":
     myholder.myval=(it.text());
     break;
     }}...


当你使用情报时

文件>工程结构>工程

文件>工程结构>模块

当你有多个模块时,确保你在Tab模块中设置正确的语言水平。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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;
         }
    }

}