1

I currently have this very long if statement:

if (somesTring != null && !"".equals(somestring)
  SomeClass.doSomething();
else if (somesTring != null && !"".equals(somestring)
  SomeClass.doAnother();
...
else
  SomeClass.giveUp();

I think this would execute poorly when the item in question is at the bottom of the decision tree. I'm considering the switch statement, which, according to my coworker, is faster. The sad thing is that the comparisons are performed with strings, which is not supported by the switch statement. Maybe I can get the hashcodes of the strings involved and use that for the switch statement.

I'm also considering a hashmap that contains each of those strings and make decisions with the help of the containsKey method. This, however, involves having to slice up a class that contains a lot of static methods and make each of those methods as a somewhat executable object. By doing this, I can dispose of the very large "if" statement.

Which of the said approaches make more sense with regards to execution time, good practice, and maintainability? Other suggestions are welcome :)

avendael
  • 2,459
  • 5
  • 26
  • 30
  • Where are the strings coming from? Is it possible to use Enums instead? – Rob Mar 24 '11 at 02:35
  • 2
    This smells like premature optimization. Just how big is this set of `if` statements, and how frequently are they executed? – Matt Ball Mar 24 '11 at 02:36
  • 1
    Hashcodes might not work. It's possible, though unlikely, that you'll have two strings that hash to the same value. – tvanfosson Mar 24 '11 at 02:51
  • @Rob: the strings are constants declared within the class. Enums are ok, but I think it's not worth it since I already have a lot of strings to convert. – avendael Mar 24 '11 at 03:09
  • @Matt: Let's just say that there are about 10 other applications that rely on this class to do stuff for them. The number of "stuff" (if statemens) increases as we discover common needs from the different applications. Sorry, but I can't really discuss the specifics here. – avendael Mar 24 '11 at 03:14
  • @tvanfosson- that statement makes no sense. maps handle collisions just fine. – MeBigFatGuy Mar 24 '11 at 03:51
  • @MeBigFatGuy: maps handle collisions with chaining or open addressing. A `switch` would not have any collision resolution – Matt Ball Mar 24 '11 at 04:26

2 Answers2

5

Use the command pattern. You can get rid of the if statements completely

Here is similar question

Long list of if statements in Java

By using command,

execution time - You can verify. Don't think its going to be a big difference.

good practice - Yes

maintainability - Yes, there is a pattern established.

Community
  • 1
  • 1
isobar
  • 1,165
  • 8
  • 10
4

"Use switch instead of if-else, its more readable and has better performance." I have to admit that this was one of my favorite code review comment. Until one fine day, while hacking Apache Sanselan's image format decoding function, I tried optimizing the code based on the same comments and while benchmark there was hardly any difference. I thought about investigating it further. Though found some interesting mail chains, though about posting my finding.

To begin with decided to run some samples on switch and if-else constructs and analyze further.

Wrote three function 1. For if-else - a if-else ladder based on int comparisons 2. For Switch - switch with 21 cases, from 1 to 20 3. For Switch - switch with sparse random values

The reason for choosing two functions for switch was this statement from VM spec "Compilation of switch statements uses the tableswitch and lookupswitch instructions"

Lets see the function codes

if-else view sourceprint

public static void testIfElse(int jumpLabel) { 

    if(1 == jumpLabel) { 
        System.out.println("1"); 
    } else if(2 == jumpLabel) { 
        System.out.println("2"); 
    } else if(3 == jumpLabel) { 
        System.out.println("3"); 
    } else if(4 == jumpLabel) { 
        System.out.println("4"); 
    } 
    // Removed for simplicity 
    else { 
        System.out.println("default"); 
    } 
}

Lets see the switch functions

Finite switch version view sourceprint?

public static void testSwitchFinite(int jumpLable) { 

    switch (jumpLable) { 
        case 1:        
            System.out.println("1");  
            break; 

         case 2:    
            System.out.println("2"); 
            break; 

         case 3:    
            System.out.println("3");     
            break; 

         case 4:    
            System.out.println("4");      
            break;  

         case 5: 
            System.out.println("5");       
            break;  

         // Removed other cases for simplicity 
         default: 
            System.out.println("default");     
         break; 
    } 
}

Sparse switch version view sourceprint?

public static void testSwitchSparse(int jumpLable) { 

    switch (jumpLable) { 
        case 100: 
            System.out.println("1"); 
            break; 

        case -1: 
            System.out.println("2"); 
            break; 

        case 5000: 
            System.out.println("3"); 
            break; 

        case -8: 
            System.out.println("4"); 
            break; 

        case 1600: 
            System.out.println("5"); 
            break; 

        case 250: 
            System.out.println("250"); 
            break; 

        // Removed other cases for simplicity 
        default: 
            System.out.println("default"); 
            break; 
    } 
}

With the groundwork done, its the benchmarking time. The benchmarking strategy was simple. Run these functions in loop and see the result, with iteration ranging from 100 to 1000.

Lets look at one of the functions

view sourceprint?

 public static void testSwitchPerf(int iteration) { 

    long t1 = System.nanoTime(); 
    for (int i = 0; i < iteration; i++) { 
        testSwitchFinite(i); 
    } 

    long t2 = System.nanoTime(); 

    System.out.println("Time Taken (switch) = "+(t2 - t1)/1000000); 

}

Well this was the ground work, after executing the conditions, here is the data

Iteration -> 100 1000 
if-else 8 ms 69 ms 
switch finite 3 ms 34 ms 
switch sparse 7 ms 21 ms 

NOTE: There is some difference due to the way data is provided and the sample space doesn't provide precise results. However, since the sample space is same for both, it would serve its purpose

Conclusion 1. There is no significant execution difference between if-else and switch. The difference observed is due to the sample space choosen. 2. If using if-else, its always recommended to put frequently used if condition at the top of if-else ladder 3. The finite switch statement was converted to tableswitch and sparse switch was converted to lookupswitch

would be interested to hear from folks about their experience in relation to this. I would say, i still prefer switch for readability. Henceforth, my review comment shall be modified as "Use switch instead of if-else, its more readable and has better performance

tvanfosson
  • 524,688
  • 99
  • 697
  • 795
developer
  • 9,116
  • 29
  • 91
  • 150
  • 3
    Read http://wikis.sun.com/display/HotSpotInternals/MicroBenchmarks and http://www.ibm.com/developerworks/java/library/j-jtp02225.html?ca=drs-j0805 – BalusC Mar 24 '11 at 02:50
  • Thank you for this insightful answer. I can now prove to my coworker that the speed increase is not significant ;) – avendael Mar 24 '11 at 03:07
  • This helps me, but I think the command pattern will help me in the long run. I upvoted this question for everyone's reference. Thanks! – avendael Mar 24 '11 at 03:58
  • @Damodar, you need to significantly change your tests here. They are *dominated* by the IO in the `System.out` calls. I just ran the same tests with no IO and couldn't get meaningful results after *billions* of iterations. – Gray Sep 07 '11 at 19:26