1024

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?

Mahozad
  • 6,430
  • 9
  • 43
  • 70
Alex Beardsley
  • 19,982
  • 13
  • 48
  • 67
  • 199
    It's in SE 7. 16 yrs after it's request. http://download.oracle.com/javase/tutorial/java/nutsandbolts/switch.html – angryITguy Jul 21 '11 at 06:54
  • 85
    Sun was honest in their evaluation: `"Don't hold your breath."` lol, http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=1223179 – raffian Aug 11 '13 at 19:49
  • 3
    @raffian I think it's because she 'sigh'ed twice. They were a little late to reply too, after almost 10 years. She might have been packing lunch boxes to her grandchildren then. – WeirdElfB0y Aug 26 '16 at 11:17

17 Answers17

1015

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;
}
erickson
  • 249,448
  • 50
  • 371
  • 469
  • 26
    It might be faster to just use If-Else-If instead of a hash for a string based switch. I have found dictionaries to be quite expensive when only storing a few items. – Jonathan Allen Dec 05 '08 at 07:17
  • 86
    An if-elseif-elseif-elseif-else might be faster, but I'd take the cleaner code 99 times times out of 100. Strings, being immutable, cache their hash code, so "computing" the hash is fast. One would have to profile code to determine what benefit there is. – erickson Dec 05 '08 at 17:06
  • 22
    The reason given against adding switch(String) is that it wouldn't meet the performance guarantees expects from switch() statements. They didn't want to "mislead" developers. Frankly I don't think they should guarantee the performance of switch() to begin with. – Gili Dec 22 '08 at 22:15
  • 2
    If you are just using `Pill` to take some action based on `str` I would argue if-else is preferable as it allows you to handle `str` values outside of the range RED,BLUE without needing to catch an exception from `valueOf` or manually check for a match against the name of each enumeration type which just adds unnecessary overhead. In my experience it has only made sense to use `valueOf` to transform into an enumeration if a typesafe representation of the String value was needed later on. – MilesHampson Jul 23 '13 at 10:07
  • I wonder if compilers make any effort to test whether there is any pair of numbers (x,y) for which the set of values `(hash >> x) & ((1< – supercat Dec 18 '13 at 18:39
  • Is if-else-if faster for Strings? – fernal73 Dec 20 '20 at 18:22
  • 1
    @fernal73 It depends on how many ifs you have cascaded, and whether the switch string's hash code has already been computed. For two or three, it could probably be faster. At some point though, the switch statement will probably perform better. More importantly, for many cases, the switch statement is probably more readable. – erickson Dec 21 '20 at 03:22
  • I think the difference is negligible. – fernal73 Dec 21 '20 at 04:10
  • https://raw.githubusercontent.com/Fernal73/LearnJava/master/JMH/jmh/SwitchString.java. Benchmark Mode Cnt Score Error Units SwitchString.ifElseRandom thrpt 25 11245993.080 ± 13505.859 ops/s SwitchString.ifElseRandomHash thrpt 25 14610552.723 ± 9316.162 ops/s SwitchString.switchCaseRandom thrpt 25 11360947.624 ± 13233.443 ops/s SwitchString.switchCaseRandomHash thrpt 25 13036989.963 ± 25903.511 ops/s – fernal73 Dec 21 '20 at 07:03
128

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;
}
JeeBee
  • 17,059
  • 4
  • 47
  • 60
  • 4
    This technique also lets you decide on issues such a case insensitivity, aliases, etc. Instead of depending on a language designer to come up with the "one size fits all" solution. – Darron Dec 03 '08 at 21:12
  • 3
    Agree with JeeBee, if you are switching on strings probably need an enum . The string usually represents something going to an interface (user or otherwise) that may or not change in the future so better replace it with enums – hhafez Dec 03 '08 at 23:24
  • 18
    See http://www.xefer.com/2006/12/switchonstring for a nice write-up of this method. – David Schmitt Apr 14 '10 at 09:04
  • @DavidSchmitt The write-up has one major flaw. It catches **all** exceptions instead of the ones that are actually thrown by the methode. – M. Mimpen Feb 04 '14 at 12:48
91

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
  }
}
Patrick M
  • 9,455
  • 9
  • 56
  • 97
Thulani Chivandikwa
  • 2,680
  • 23
  • 25
26

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.

James Curran
  • 95,648
  • 35
  • 171
  • 253
  • 26
    Switches based on any hashable object may be implemented very efficiently using a hash table – see .NET. So your reason isn't completely correct. – Konrad Rudolph Dec 03 '08 at 18:36
  • Yeah, and this is the thing I don't understand. Are they afraid hashing objects will, in the long run, become too expensive? – Alex Beardsley Dec 03 '08 at 18:41
  • 3
    @Nalandial: actually, with a little effort on the part of the compiler, it's not expensive at all because when the set of strings is known, it's pretty easy to generate a perfect hash (this isn't done by .NET, though; probably not worth the effort, either). – Konrad Rudolph Dec 03 '08 at 20:53
  • 3
    @Nalandial & @Konrad Rudolph - While hashing a String (due to it's immutable nature) seems like a solution to this problem you have to remember that all non-final Objects can have their hashing functions overridden. This makes it difficult at compile time to ensure consistency in a switch. – martinatime Dec 03 '08 at 22:01
  • 2
    You can also construct a DFA to match the string (like regular expression engines do). Possibly even more efficient than hashing. – Nate C-K Aug 22 '11 at 17:30
  • @Konrad & Nate: While a hashtable or a finite state machine would work in a switch, it would take a freakin' lot of case items for the intrinsic overhead of either to be overcome. My "wild-ass guess" is that 80% of all switches have less than 10 cases, and 99.9% have less then 20. At those sizes, it would be very hard to beat chained if()s for speed. – James Curran Aug 24 '11 at 19:54
  • One more thing to mention is that Java Bytecode has direct support for switching on int values; so there is "native" implementation for efficient switching on int constants (including Enums, where ordinal() can be used). – StaxMan Oct 04 '11 at 23:48
  • How would the intrinsic overhead of an FSM (which could itself be implemented as a series of cascaded switches with each successive character in the string as the value being switched on) be greater than that of chained if()s in terms of speed? Worst-case, it seems to me that they would be about equal, as the chained ifs will be performing the string comparison each time unless some sort of optimization is performed, and best-case the FSM-as-cascaded-switches would in fact be more efficient in terms of the number of operations performed. – JAB Jul 30 '12 at 17:30
  • Of course, that's not taking into account memory overhead and how the speed would be affected by issues with caching and various other aspects of processor pipelining/etc., as well as the fact that the worst-case and best-case scenarios for the two algorithms may or may not differ depending on what strings are being checked and how they are ordered... (I guess amortized best-/worst-case would be best?) – JAB Jul 30 '12 at 17:33
  • Just because you think there isn't any advantage - doesn't actually mean it isn't useful. I dare you to ask any Python developer about multi-line comments, they will all probably tell you that's useless. They will tell you that if you really want them to use docstrings - even though docstrings are loaded into memory (by default). – Natalie Adams Dec 09 '12 at 21:55
  • Loading docstrings into memory benefits introspection. Not only do you know the function names and signatures, you also have their documentation. Great for interactive or for building an IDE. – Jonathan Baldwin Oct 12 '13 at 03:40
19

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

}
sponge
  • 7,923
  • 13
  • 43
  • 77
19

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.

DJClayworth
  • 24,627
  • 8
  • 50
  • 71
  • Technically, regexes already "switch", as they are basically just state machines; they merely have only two "cases", `matched` and `not matched`. (Not taking into account things like [named] groups/etc., though.) – JAB Jul 30 '12 at 17:35
  • 2
    http://docs.oracle.com/javase/7/docs/technotes/guides/language/strings-switch.html states: _The Java compiler generates generally more efficient bytecode from switch statements that use String objects than from chained if-then-else statements._ – Wim Deblauwe Jul 14 '16 at 19:00
12

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.

PhiLho
  • 38,673
  • 6
  • 89
  • 128
  • "sometime I regret we don't have (anonymous) functions as first class citizen" [That's no longer true.](https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html) – dorukayhan Jan 18 '17 at 20:40
  • @dorukayhan Yes, of course. But do you want to add a comment at all answers from the last ten years to tell the world we can have them if we update to newer versions of Java? :-D – PhiLho Jan 24 '17 at 14:12
8

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;
            }
        }
    }
}
hyper-neutrino
  • 4,300
  • 1
  • 20
  • 40
  • 5
    Two different string can have the same hashcode, so if you switch on hashcodes the wrong case-branch might be taken. – Lii Jan 23 '16 at 20:45
  • @Lii Thanks for pointing this out! It's unlikely, though, but I wouldn't trust it working. "FB" and "Ea" have the same hashcode, so it's not impossible to find a collision. The second code is probably more reliable. – hyper-neutrino Jan 26 '16 at 03:13
  • I am surprised this compiles, as `case` statements had to, I thought, always be constant values, and `String.hashCode()` is not such (even if in practice calculation has never changed between JVMs). – StaxMan Jan 26 '18 at 03:28
  • @StaxMan Hm interesting, I never stopped to observe that. But yeah, `case` statement values don't have to be determinable at compile-time so it works finely. – hyper-neutrino Jan 26 '18 at 03:47
4

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.

Charles Goodwin
  • 6,019
  • 2
  • 31
  • 61
  • 6
    You don't need the 1.7 JVM to run code with a String switch. The 1.7 compiler turns the String switch into something that uses previously existing byte code. – Dawood ibn Kareem Dec 04 '13 at 01:18
4

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#

plugwash
  • 7,223
  • 1
  • 24
  • 40
  • Narrative about switch(String) fits with history, timeline, context cpp/cs. – Espresso Apr 04 '18 at 15:48
  • It was a big mistake not to implement this feature, everything else is a cheap excuse Java lost many users over the years because of the lack of progress and the stubbornness of designers not to evolve the language. Fortunately they completely changed direction and attitude after JDK7 – firephil Mar 09 '19 at 22:28
0

JEP 354: Switch Expressions (Preview) in JDK-13 and JEP 361: Switch Expressions (Standard) in JDK-14 will extend the switch statement so it can be used as an expression.

Now you can:

  • directly assign variable from switch expression,
  • use new form of switch label (case L ->):

    The code to the right of a "case L ->" switch label is restricted to be an expression, a block, or (for convenience) a throw statement.

  • use multiple constants per case, separated by commas,
  • and also there are no more value breaks:

    To yield a value from a switch expression, the break with value statement is dropped in favor of a yield statement.

So the demo from the answers (1, 2) might look like this:

  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");
    }
Iskuskov Alexander
  • 2,805
  • 3
  • 15
  • 28
0

In Java 11+ it's possible with variables too. The only condition is it must be a constant.

For Example:

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. I've not tried this with earlier jdks. So please update the answer if it's supported there too.

  • info: labels must be "constant expressions" since version 7: [JLS 14.11](https://docs.oracle.com/javase/specs/jls/se7/html/jls-14.html#jls-14.11) –  Mar 16 '21 at 11:35
0

The technicalities were nicely explained in this answer. I just wanted to add that with Java 12 switch expressions you can do it with the following syntax:

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";
    };
} 
dreamcrash
  • 36,542
  • 23
  • 64
  • 87
-2

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);
-4

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;
     }}...
Octavia Togami
  • 3,638
  • 4
  • 29
  • 42
Alex Punnen
  • 3,420
  • 36
  • 52
  • 9
    @SSpoke Because this is a java question and a Groovy answer is off-topic and a useless plug. – Martin Apr 11 '14 at 13:19
  • 12
    Even in conservative large SW houses Groovy is used along with Java. JVM gives a language agnostic environment more than a language now, to mix and use the most relevant programming paradigm for the solution. So maybe now I should add a snippet in Clojure to gather more downvotes :) ... – Alex Punnen May 22 '14 at 04:02
  • 1
    Also, how does the syntax work? I'm guessing Groovy is a different programming language...? Sorry. I don't know anything about Groovy. – hyper-neutrino Jan 26 '16 at 03:09
-4

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.

botenvouwer
  • 3,658
  • 7
  • 38
  • 67
  • 1
    Not sure how your answer is relevant to the question. He asked why a string switch statements like following is not available: String mystring = "something"; switch (mystring) { case "something" sysout("got here"); . . } – Deepak Agarwal Nov 14 '16 at 22:43
-9
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; 
         } 
    } 

} 
CubeJockey
  • 2,191
  • 8
  • 22
  • 31
Issac Balaji
  • 1,405
  • 1
  • 13
  • 25
  • 9
    The OP isn't asking how to switch on a string. He/She is asking why he/she isn't able to, because of restrictions on syntax before JDK7. – hyper-neutrino Jul 02 '15 at 13:29