1036

这个功能会被放入更高版本的 Java 中吗?

有人可以解释为什么我不能这样做,就像 Javaswitch声明的技术方式一样吗?

4

14 回答 14

1020

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

JDK 7 中的实现

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

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

JVM 中的开关

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

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

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

switch在对对象进行脱糖时String,可能会使用两个指令。适用于第lookupswitch一次打开哈希码找到机箱的原始位置。生成的序数自然适合 a tableswitch

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

在 JDK 7 之前

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

Pill p = Pill.valueOf(str);
switch(p) {
  case RED:  pop();  break;
  case BLUE: push(); break;
}
于 2008-12-03T18:30:45.677 回答
129

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

当然你的枚举可以有一个“其他”的条目和一个 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;
}
于 2008-12-03T18:44:19.753 回答
91

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

请注意,在 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
  }
}
于 2011-09-16T13:11:33.573 回答
26

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

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

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

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

于 2008-12-03T18:32:10.383 回答
20

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

}
于 2015-04-09T06:30:08.267 回答
19

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

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

于 2008-12-03T20:49:42.213 回答
12

除了上述好的论点之外,我还要补充一点,今天很多人认为switchJava 的程序过去已经过时(回到 C 时代)。

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

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

于 2008-12-03T21:45:38.480 回答
8

如果你没有使用JDK7或更高版本,你可以使用hashCode()它来模拟它。因为String.hashCode()通常对不同的字符串返回不同的值,并且总是对相等的字符串返回相同的值,所以它是相当可靠的(不同的字符串可以产生与注释中提到的@Lii 相同的哈希码,例如"FB"and "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;
            }
        }
    }
}
于 2015-05-23T20:16:28.527 回答
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来按原样构建它。

于 2013-11-15T15:43:38.777 回答
4

其他答案说这是在 Java 7 中添加的,并为早期版本提供了解决方法。这个答案试图回答“为什么”

Java 是对 C++ 过于复杂的反应。它被设计成一种简单干净的语言。

字符串在语言中得到了一些特殊情况处理,但我似乎很清楚,设计者试图将特殊大小写和语法糖的数量保持在最低限度。

由于字符串不是简单的原始类型,因此打开字符串在后台相当复杂。在设计 Java 时,这不是一个常见的功能,并且与极简设计并不完全吻合。尤其是当他们决定不对字符串使用特殊情况 == 时,如果 == 不使用 case,那么 case 会(并且是)有点奇怪。

在 1.0 和 1.4 之间,语言本身几乎保持不变。大多数对 Java 的增强都在库方面。

Java 5 改变了这一切,语言得到了极大的扩展。在版本 7 和 8 中进行了进一步的扩展。我预计这种态度的改变是由 C# 的兴起推动的

于 2017-01-19T06:09:48.553 回答
1

这个答案很好地解释了技术细节。我只是想使用 Java 12 switch 表达式添加它,您可以使用以下语法来完成它:

String translation(String cat_language) {
    return switch (cat_language) {
        case "miau miau" -> "I am to run";
        case "miauuuh" -> "I am to sleep";
        case "mi...au?" ->  "leave me alone";
        default ->  "eat";
    };
} 
于 2021-03-16T08:29:27.793 回答
0

JEP 354: Switch Expressions (Preview) in JDK-13 和JEP 361: Switch Expressions (Standard) in JDK-14 将扩展switch 语句,因此它可以用作表达式

现在你可以:

  • 直接从switch 表达式分配变量,
  • 使用新形式的开关标签 ( case L ->):

    “case L ->”开关标签右侧的代码被限制为表达式、块或(为方便起见)throw 语句。

  • 每个案例使用多个常量,用逗号分隔,
  • 并且没有更多的价值中断

    为了从 switch 表达式中产生一个值,breakwith value 语句被删除以支持yield语句。

因此,答案 ( 1 , 2 ) 中的演示可能如下所示:

  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");
    }
于 2020-02-09T10:39:17.953 回答
0

在 Java 11+ 中也可以使用变量。唯一的条件是它必须是一个常数。

例如:

final String LEFT = "left";
final String RIGHT = "right";
final String UP = "up";
final String DOWN = "down";

String var = ...;

switch (var) {
    case LEFT:
    case RIGHT:
    case DOWN:
    default:
        return 0;
}

PS。我没有在早期的 jdks 上尝试过这个。因此,如果那里也支持,请更新答案。

于 2020-06-30T15:35:58.783 回答
-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);
于 2017-06-01T06:33:28.257 回答