为什么我不能在String上使用switch语句?

时间:2008-12-03 18:23:57

标签: java string switch-statement

此功能是否会被放入以后的Java版本中?

有人可以解释为什么我不能这样做,比如Java的switch语句的技术方式有效吗?

17 个答案:

答案 0 :(得分:977)

Stringjavac案例已经在Java SE 7中实施,至少16年after they were first requested.没有提供延迟的明确原因,但可能与性能有关。

在JDK 7中实施

该功能现已在String with a "de-sugaring" process;中实现了一个干净的高级语法,使用case中的switch常量声明在编译时扩展为更复杂代码遵循模式。生成的代码使用始终存在的JVM指令。

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

在JVM中切换

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

如果常量是密集的,则将它们用作索引(在减去最低值之后)到指令表的表中 - lookupswitch指令。

如果常量是稀疏的,则执行正确情况的二进制搜索 - switch指令。

在对Stringlookupswitch对象进行去糖处理时,可能会使用这两个指令。 tableswitch适用于第一次切换哈希码以查找案例的原始位置。生成的序数非常适合O(1)

两个指令都要求在编译时对分配给每个案例的整数常量进行排序。在运行时,虽然tableswitch的{​​{1}}性能通常比O(log(n))的{​​{1}}性能更好,但需要进行一些分析以确定表格是否足够密集以证明其合理性时空权衡。 Bill Venners写了a great article,更详细地介绍了这一点,以及对其他Java流程控制指令的深入研究。

在JDK 7之前

在JDK 7之前,lookupswitch可以近似基于enum的切换。这使用编译器在每个String类型上生成的the static valueOf方法。例如:

enum

答案 1 :(得分:120)

如果您的代码中有一个可以打开字符串的位置,那么最好将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;
}

答案 2 :(得分:89)

以下是基于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
  }
}

答案 3 :(得分:25)

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

因此,C& C ++只允许在整数类型上进行切换,因为它对其他类型没有意义。

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

Java的设计者显然认为是C的设计者。

答案 4 :(得分:18)

也可以显示自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;
    }

}

答案 5 :(得分:18)

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

我认为,只有这样,一旦你开始转换非原语,你需要开始考虑“等于”与“==”。首先比较两个字符串可能是一个相当冗长的过程,增加了上面提到的性能问题。其次,如果有字符串切换,将需要切换字符串忽略大小写,切换字符串考虑/忽略语言环境,根据正则表达式切换字符串....我会赞成一个为节省大量时间的决定语言开发人员花费了少量时间给程序员。

答案 6 :(得分:12)

除了上述好的论点之外,我还要补充一点,今天很多人都认为switch是Java过程性过时的过时(回到C次)。

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

但实际上,值得研究一下你需要一个开关的情况,看看它是否不能被更多的OO取代。例如Java 1.5+中的枚举,也许是HashTable或其他一些集合(有时我很遗憾,我们没有(匿名)函数作为一等公民,如Lua - 没有switch - 或JavaScript)甚至多态。

答案 7 :(得分:8)

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

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

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 :(得分: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很少安装最新版本),它会很有用。

您可以获取from SVN或浏览code online。您需要EBuild按原样构建它。

答案 9 :(得分:4)

其他答案表示,这是在Java 7中添加的,并为早期版本提供了解决方法。这个答案试图回答&#34;为什么&#34;

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

字符串在语言中得到了一些特殊的案例处理,但我似乎很清楚设计师试图将特殊套管和语法糖的量保持在最低限度。

由于字符串不是简单的原始类型,因此开启字符串相当复杂。这在Java设计时并不是一个常见的功能,并且与极简主义设计并不完全吻合。特别是因为他们决定不对字符串特殊情况==,对于在==不做的情况下工作的情况会有点奇怪。

在1.0到1.4之间,语言本身几乎保持不变。 Java的大多数增强功能都在库的一侧。

随着Java 5的改变,语言得到了大幅扩展。在版本7和版本8中进一步扩展。我希望这种态度的改变是由C#的兴起推动的。

答案 10 :(得分:0)

JDK-13中的

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

现在您可以:

  • 直接从开关表达式
  • 分配变量
  • 使用新形式的开关标签(//your Resource Route Route::resource('chapter', 'ChapterController'); //Custom `route` for your `index` method it will replace your `resource index route` Route::get('chapter/{id}', ['as'=>'chapter.index','uses'=>'ChapterController@index']); ):

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

  • 每种情况下均使用多个常量,以逗号分隔,
  • ,也没有更多的休息时间

    要从switch表达式中产生一个值,请删除带有值语句的case L ->,而推荐使用break语句。

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

yield

答案 11 :(得分: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尝试过。因此,如果那里也支持,请更新答案。

答案 12 :(得分:0)

在此 answer 中很好地解释了技术细节。我只是想在 Java 12 switch expressions 中添加它,您可以使用以下语法:

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

答案 13 :(得分:-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);

答案 14 :(得分:-3)

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

it.'p'.each{
switch (it.@name.text()){
   case "choclate":
     myholder.myval=(it.text());
     break;
     }}...

答案 15 :(得分:-4)

当你使用intellij时,请看:

档案 - &gt;项目结构 - &gt;项目

档案 - &gt;项目结构 - &gt;模块

如果您有多个模块,请确保在模块选项卡中设置正确的语言级别。

答案 16 :(得分:-7)

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

}