weixin_41568183
零零乙
2008-12-03 18:23
采纳率: 50%
浏览 1.0k
已采纳

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

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

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

转载于:https://stackoverflow.com/questions/338206/why-cant-i-use-switch-statement-on-a-string

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 邀请回答

14条回答 默认 最新

  • csdnceshi60
    ℡Wang Yan 2008-12-03 18:30
    已采纳

    Switch statements with String cases 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.

    Implementation in JDK 7

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

    A switch with String cases 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 if statement 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.

    Switches in the JVM

    For more technical depth on switch, you can refer to the JVM Specification, where the compilation of switch statements is 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.

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

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

    In de-sugaring a switch on String objects, both instructions are likely to be used. The lookupswitch is 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.

    Both instructions require the integer constants assigned to each case to be sorted at compile time. At runtime, while the O(1) performance of tableswitch generally 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 article that covers this in more detail, along with an under-the-hood look at other Java flow control instructions.

    Before JDK 7

    Prior to JDK 7, enum could approximate a String-based switch. This uses the static valueOf method generated by the compiler on every enum type. For example:

    Pill p = Pill.valueOf(str);
    switch(p) {
      case RED:  pop();  break;
      case BLUE: push(); break;
    }
    
    点赞 评论
  • weixin_41568196
    撒拉嘿哟木头 2008-12-03 18:32

    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.

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

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

    点赞 评论
  • csdnceshi55
    ~Onlooker 2008-12-03 18:44

    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

    ValueEnum enumval = ValueEnum.fromString(myString);
    switch (enumval) {
       case MILK: lap(); break;
       case WATER: sip(); break;
       case BEER: quaff(); break;
       case OTHER: 
       default: dance(); break;
    }
    
    点赞 评论
  • csdnceshi78
    程序go 2008-12-03 20:49

    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."

    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.

    点赞 评论
  • weixin_41568134
    MAO-EYE 2008-12-03 21:45

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

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

    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.

    点赞 评论
  • weixin_41568127
    ?yb? 2011-09-16 13:11

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

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

    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
      }
    }
    
    点赞 评论
  • csdnceshi67
    bug^君 2012-11-29 04:36

    It's a breeze in Groovy; I embed the groovy jar and create a groovy utility 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.)

    it.'p'.each{
    switch (it.@name.text()){
       case "choclate":
         myholder.myval=(it.text());
         break;
         }}...
    
    点赞 评论
  • csdnceshi69
    YaoRaoLov 2013-11-15 15:43

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

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

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

    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.

    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.

    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).

    You can get it from SVN or browse the code online. You'll need EBuild to build it as-is.

    点赞 评论
  • csdnceshi70
    笑故挽风 2015-04-09 06:30

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

    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;
        }
    
    }
    
    点赞 评论
  • weixin_41568126
    乱世@小熊 2015-05-23 20:16

    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 can produce the same hash code as @Lii mentioned in a comment, such as "FB" and "Ea") See documentation.

    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.

    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;
                }
            }
        }
    }
    
    点赞 评论
  • csdnceshi63
    elliott.david 2015-06-05 12:06
    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; 
             } 
        } 
    
    } 
    
    点赞 评论
  • csdnceshi51
    旧行李 2016-01-06 12:13

    When you use intellij also look at:

    File -> Project Structure -> Project

    File -> Project Structure -> Modules

    When you have multiple modules make sure you set the correct language level in the module tab.

    点赞 评论
  • weixin_41568196
    撒拉嘿哟木头 2017-01-19 06:09

    Other answers have said this was added in Java 7 and given workarounds for earlier versions. This answer tries to answer the "why"

    Java was a reaction to the over-complexities of C++. It was designed to be a simple clean language.

    String got a little bit of special case handling in the language but it seems clear to me that the designers were trying to keep the amount of special casing and syntactic sugar to a minimum.

    switching on strings is fairly complex under the hood since strings are not simple primitive types. It was not a common feature at the time Java was designed and doesn't really fit in well with the minimalist design. Especially as they had decided not to special case == for strings, it would be (and is) a bit strange for case to work where == doesn't.

    Between 1.0 and 1.4 the language itself stayed pretty much the same. Most of the enhancements to Java were on the library side.

    That all changed with Java 5, the language was substantially extended. Further extensions followed in versions 7 and 8. I expect that this change of attitude was driven by the rise of C#

    点赞 评论
  • csdnceshi63
    elliott.david 2017-06-01 06:33

    Not very pretty, but here is another way for Java 6 and bellow:

    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);
    
    点赞 评论

相关推荐