61

I need to change the following if's to a switch-case while checking for a String, to improve the cyclomatic complexity.

String value = some methodx;
if ("apple".equals(value)) {
    method1;
}

if ("carrot".equals(value)) {
    method2;
}

if ("mango".equals(value)) {
    method3;
}

if ("orange".equals(value)) {
    method4;
}

But I am not sure what value I'm going to get.

ManoDestra
  • 6,325
  • 6
  • 26
  • 50
randeepsp
  • 3,572
  • 9
  • 33
  • 40
  • 1
    'if' is not a loop, its a statement to test some condition – Habib Apr 20 '12 at 05:04
  • 3
    if loop? really? In order to use switch statement, you have to install jdk7. http://stackoverflow.com/questions/338206/switch-statement-with-strings-in-java – KV Prajapati Apr 20 '12 at 05:04
  • http://docs.oracle.com/javase/tutorial/java/nutsandbolts/switch.html just read from this link and try to do it your self, there is an example for switch statement with strings – Habib Apr 20 '12 at 05:06
  • Check [this][1] might be helpful [1]: http://stackoverflow.com/questions/8555421/string-as-switch-statement – Ketan Bhavsar Apr 20 '12 at 05:09
  • 2
    PS.. changing to a switch statement will do nothing for your complexity.. It is still the same number of paths. Just implemented differently. – baash05 Apr 20 '12 at 05:25
  • 1
    since efficiency came into question, afk **else if** would be better instead from the second "if" onwards – ılǝ May 16 '13 at 01:29
  • 1
    better to use **else if ladder** than multiple if.... – SweetWisher ツ May 21 '14 at 05:38
  • 1
    I would actually prefer using value.equals("xx") code - it looks pretty (= more readable = faster navigation in code = time = money) and it doesnt cost you anything – Srneczek Nov 15 '15 at 18:47
  • Possible duplicate of [Why can't I switch on a String?](https://stackoverflow.com/questions/338206/why-cant-i-switch-on-a-string) – default locale Jun 01 '17 at 07:03

13 Answers13

175

Java (before version 7) does not support String in switch/case. But you can achieve the desired result by using an enum.

private enum Fruit {
    apple, carrot, mango, orange;
}

String value; // assume input
Fruit fruit = Fruit.valueOf(value); // surround with try/catch

switch(fruit) {
    case apple:
        method1;
        break;
    case carrot:
        method2;
        break;
    // etc...
}
cdeszaq
  • 30,869
  • 25
  • 117
  • 173
nickdos
  • 8,348
  • 5
  • 30
  • 47
  • 1
    I voted this up, but.. it would be slower than your if "loop".. It is AFK the best approach. – baash05 Apr 20 '12 at 05:14
  • i have a problem, my "value" is not a constant and this solution is giving me problems. Is there any solution??? – guelo Jun 18 '14 at 07:51
  • "value" is not a constant in this example - it varies depending on the user input. It should correspond to a known list of possible values (defined by the enum) but doesn't have to, as any "unknown" value can be caught by the try/catch statement and processed there. – nickdos Jun 18 '14 at 23:59
  • A switch does not reduce cyclomatic complexity (at least as PMD computes it). You have to use an enum and put "command" style class references in a map or the method in the enum itself. I just dealt with a similar case yesterday. – Καrτhικ Sep 05 '14 at 16:34
23

Everybody is using at least Java 7 now, right? Here is the answer to the original problem:

String myString = getFruitString();

switch (myString) {

    case "apple":
        method1();
        break;

    case "carrot":
        method2();
        break;

    case "mango":
        method3();
        break;

    case "orange":
        method4();
        break;
}

Notes

  • The case statements are equivalent to using String.equals.
  • As usual, String matching is case sensitive.
  • According to the docs, this is generally faster than using chained if-else statements (as in cHao's answer).
Community
  • 1
  • 1
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
20

Learn to use else.

Since value will never be equal to two unequal strings at once, there are only 5 possible outcomes -- one for each value you care about, plus one for "none of the above". But because your code doesn't eliminate the tests that can't pass, it has 16 "possible" paths (2 ^ the number of tests), of which most will never be followed.

With else, the only paths that exist are the 5 that can actually happen.

String value = some methodx;
if ("apple".equals(value )) {
    method1;
}
else if ("carrot".equals(value )) {
    method2;
}
else if ("mango".equals(value )) {
    method3;
}
else if ("orance".equals(value )) {
    method4;
}

Or start using JDK 7, which includes the ability to use strings in a switch statement. Course, Java will just compile the switch into an if/else like construct anyway...

cHao
  • 84,970
  • 20
  • 145
  • 172
  • 3
    It actually performs a switch on the hashcode and then performs if/else to resolve hashcode collisions. – Paul Jackson Nov 27 '13 at 14:35
  • 1
    **if else** is slow :( – Maveňツ Aug 11 '14 at 11:55
  • 2
    @Mann: `if` *without* `else` is even slower, on average. (It'd try every comparison every time. The `else` is what allows you to finish early once you've found a match.) The cleanest way would indeed be to use a `switch`, which allows for optimizations like the one mentioned in the comment before yours. But that's not an option in versions of Java before 1.7. – cHao Aug 11 '14 at 13:43
  • 2
    Here it is recommended to use a switch... http://docs.oracle.com/javase/7/docs/technotes/guides/language/strings-switch.html – user1156544 Nov 27 '15 at 11:49
7

To reduce cyclomatic complexity use a map:

Map<String,Callable<Object>> map = new HashMap < > ( ) ;
map . put ( "apple" , new Callable<Object> () { public Object call ( method1 ( ) ; return null ; } ) ;
...
map . get ( x ) . call ( ) ;

or polymorphism

emory
  • 10,725
  • 2
  • 30
  • 58
2

Just to make concrete emory's answer, the executable code is the following :

  Map<String,Callable<USer>> map = new HashMap<String,Callable<User>>();
  map.put( "test" , new Callable<User> () { public User call (){ return fillUser("test" ); }} ) ;
  map.put( "admin" , new Callable<Utente> () { public Utente call (){  return fillUser("admin" ); }} ) ;

where user is a POJO, and then

  User user = map.get(USERNAME).call();

finally the called method is somewhere :

 private User fillUser(String x){        
        User user = new User();
        // set something in User
        return user;
}
m.piunti
  • 340
  • 2
  • 8
0

Java does not support Switch-case with String. I guess this link can help you. :)

Community
  • 1
  • 1
Sunmit Girme
  • 559
  • 4
  • 13
  • 30
0

Here is a possible pre-1.7 way, which I can't recommend:

public class PoorSwitch
{
    final static public int poorHash (String s) {
        long l = 0L;
        for (char c: s.toCharArray ()) {
            l = 97*l + c;
        }
        return (int) l;
    }

    public static void main (String args[])
    {
        String param = "foo";
        if (args.length == 1)
        {
            param = args[0];
        }
        // uncomment these lines, to evaluate your hash
        // test ("foo");
        // test ("bar");
        switch (poorHash (param)) {
            // this doesn't work, since you need a literal constant
            // so we have to evaluate our hash beforehand:
            // case poorHash ("foo"): {
            case 970596: {
                System.out.println ("Foo!");
                break;
            }
            // case poorHash ("bar"): {
            case 931605: {
                System.out.println ("Bar!");
                break;
            }
            default: {
                System.out.println ("unknown\t" + param);
                break;
            }
        }
    }

    public static void test (String s)
    {
        System.out.println ("Hash:\t " + s + " =\t" + poorHash (s));
    }
}

Maybe you could work with such a trick in a generated code. Else I can't recommend it. Not so much that the possibility of a hash collision makes me worry, but if something is mixed up (cut and paste), it is hard to find the error. 931605 is not a good documentation.

Take it just as proof of concept, as curiosity.

user unknown
  • 35,537
  • 11
  • 75
  • 121
  • This is similar to what a HashMap does internally. But a HashMap has code to deal with the (remote) possibility of a collision. A HashMap hides the complexity and reduces cyclomatic complexity. – emory Apr 20 '12 at 16:18
  • A HashMap has, but the hashcode hasn't. You could be right if suggesting to use the well established, tested Java "foo".hashCode (), but is there a guarantee it will never change? Is the value/the algorithm guaranteed like a public API? If you provide your own hashCode, you have at least control over it. – user unknown Apr 20 '12 at 16:48
  • There *is* a guaranty that `"foo".hashCode()` will always return the same value. Simply because Java7+ code using `switch` over string compiles to code assuming exactly that invariant property. So it can’t change in future versions and it’s even simpler to see that the algorithm hasn’t changed from Java 1.0 to Java 7 either. But your code should use `equals` within the `case` statements to protect against hash collisions. – Holger Jul 26 '18 at 18:01
0

We can apply Switch just on data type compatible int :short,Shor,byte,Byte,int,Integer,char,Character or enum type.

omar
  • 1
0

Evaluating String variables with a switch statement have been implemented in Java SE 7, and hence it only works in java 7. You can also have a look at how this new feature is implemented in JDK 7.

Desta Haileselassie Hagos
  • 23,140
  • 7
  • 48
  • 53
0

Java 8 supports string switchcase.

String type = "apple";

switch(type){
    case "apple":
       //statements
    break;
    default:
       //statements
    break; }
Yash
  • 482
  • 4
  • 12
0
    String name,lname;
 name= JOptionPane.showInputDialog(null,"Enter your name");
   lname= JOptionPane.showInputDialog(null,"Enter your father name");
    if(name.equals("Ahmad")){
       JOptionPane.showMessageDialog(null,"welcome "+name);
    }
    if(lname.equals("Khan"))
   JOptionPane.showMessageDialog(null,"Name : "+name +"\nLast name :"+lname ); 

    else {
       JOptionPane.showMessageDialog(null,"try again " );
    } 
  }}
latifa
  • 1
-1

Not very pretty but here is another way:

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);
-11
String value = someMethod();
switch(0) {
default:
    if ("apple".equals(value)) {
        method1();
        break;
    }
    if ("carrot".equals(value)) {
        method2();
        break;
    }
    if ("mango".equals(value)) {
        method3();
        break;
    }
    if ("orance".equals(value)) {
        method4();
        break;
    }
}
Triqui
  • 261
  • 3
  • 4