Usually, in problems that I encounter in school, there are ones that the expects us to use switch cases. Though I always use arrays as alternative for this kind of problem for the sake of shorter codes.
So a solution with Switch-Statement will look like this:
String day;
int dayNum = n%7; //n is an input
switch(dayNum){
case 0:
day = "Sunday";
break;
case 1:
day = "Monday";
break;
case 2:
day = "Tuesday";
break;
case 3:
day = "Wednesday";
break;
case 4:
day = "Thursday";
break;
case 5:
day = "Friday";
break;
case 6:
day = "Saturday";
break;
}
System.out.println("Today is " + day);
And a solution with array as alternative for this looks like this:
int dayNum = n%7; //n is an input
String[] day = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
System.out.println("Today is " + day[dayNum]);
The second example is the kind of code I usually prefer to use.
What I'd like to ask is how does the 2 solutions compare when it comes to memory and time complexity?
The code you wrote is just bad use of switches.
switches are great when one, or preferably both, of these things holds:
The values you are triggering on aren't consecutive (i.e. case 384:, then case 30491:, etcetera. Trivial example is when switching on a string for a certain command.
The thing you want to do is a bunch of code (vs. returning a single result, as here).
For what its worth, you've also written the case block in a rather convoluted fashion. If you have a separate method for this, you could write:
switch (foo) {
case 0: return "Sunday";
case 1: return "Monday";
}
Also (new in.. java 14?), you can write this too:
String dayName = switch(foo) {
case 0 -> "Sunday";
case 1 -> "Monday";
default -> throw new IllegalArgumentException("Unknown day");
};
Lastly, this code should not exist. Because java.time.DayOfWeek already exists. You've badly reinvented the wheel.
Performance
When talking about 7 elements it just doesn't matter. Other factors dominate, simple as that.
As a general principle (which doesn't kick in until many many hundreds, maybe thousands, see footnote!), the reason your array-based code is linear as you wrote it is solely because java would not (currently; future java changes are afoot that may change this) realize that the line that 'makes' your string array with day names can be executed once for the lifetime of a JVM boot and never again; in other words, this code, under the hood, compiles down to making a new string array with 7 slots and then assigning 7 constants to the slots (those constants are the references to the strings, which are calculated "once per VM lifetime").
If you move that array into a static field, that goes away and they're both O(1) performance (the same 'speed' whether you have 7 days or 7 million days), because they both just do a single lookup. No looping.
IMPORTANT PERFORMANCE FOOTNOTE: Computers are incredibly complex and act nothing like a von neumann model. Predicting the performance of code based on common sense therefore usually steers you extremely wrong. There are therefore 3 important rules to keep in mind:
Write code the way java programmers the world over tend to write it, and keep things flexible. IF performance issues occur, you will probably need to change the relevant part of your app. This is easier if your code is flexible. Most rules of thumb about performance you read about make your code less flexible and are therefore bad! - also, the JVM optimizes code, and does this essentially by being a giant pattern matching machine, detecting patterns it knows how to run well. The programmers who make this pattern matching optimizing machine tend to write patterns that are commonly used in java code. Thus, idiomatic java code tends to be fast in practice.
Nobody can look at code and just know how it ends up performing in real life, because it's too complex. Don't take my word for it; the very engineers that write the VM say this all the time. If they can't figure it out, you stand no chance whatsoever. Therefore, a profiler report or JMH timing result is required before even thinking about letting code performance ideas determine how you write your code. See the rule above: Without this, the best, fastest code is the cleanest, most flexible, most easy to read code.
The one exception is algorithmic complexity (big O notation). This is a complex topic (generally, university-level informatics and very math heavy), well beyond an SO answer, but plenty of online resources can be found if you want to dive in. But do note that the big-O factor often doesn't start controlling until you get a ton of data. For example, in this case technically your array-based code is O(n) (because you create the array inside the code, every time you run it), whereas the switch is O(1), but given that n is 7 here, that's irrelevant. It probably won't get relevant until you get to hundreds of items.
Related
This question already has answers here:
What are switch expressions and how are they different from switch statements?
(4 answers)
Closed 1 year ago.
I've been working with switch statements in my latest project and wanted to have two possible values execute the same code. So I looked up methods on how do that. Quite the only answer I found looks like this:
switch (element) {
case hi:
case hello:
//do something here
break;
}
But I experimented a bit more and found that my IDE doesn't complain when I did:
switch (element) {
case hi, hello:
//do something here
break;
}
So I tried it out and found that it works. So I was wondering if there is something wrong with using this method as I found nothing on it online. I would just love to be able to use it as it looks so much cleaner.
Recent versions of java (12 and up) have been putting in a ton of work on switch. There are now quite a few forms. Some of these forms have an underlying principle that strongly suggests some new syntactical feature is important, and for consistency's sake, if that feature can also be allowed for the other forms, the other forms are updated too.
History
The original java 1.0 switch is a straight copy from C (which explains their utterly bizarre and silly syntax; it's what C had, and java was designed to be comfortable to C coders. We can whinge about the fact that this means the syntax is daft, but it's hard to deny that the C style syntax, no matter how crazy it seems, has taken over the planet, with C, C#, Java, and Javascript added together taking up a huge chunk of the 'market' so to speak).
It:
Required that the thing you switch is on an int and only an int
That each case is a specific and constant number (no case 10-100, and definitely no case <5 or case x:).
It's a statement (The switch itself doesn't have a value, it's a way to conditionally jump to a statement).
Unlike the C version, treating the switch's nature as a weird GOTO-esque construct and thus allowing you to interleave a control structure such as a do/while loop through it is not actually allowed (fortunately, I guess)? - So, Duff's Device is not and has never been legal in java.
Recent updates:
Together with enums: Enums in switches
Java1.5 brought enums, and as enums are intended to replace int constants, they needed to be usable in switches. Given that enum values have an 'ordinal' which is an int, the implementation is trivial: switch (someEnum) { case ENUMVAL: } is light sugar around switch(someEnum.ordinal()) { case 1: }, where 1 is the ordinal value of ENUMVAL.
Only semi-recent: Strings
In JDK7, strings-in-switch support was added. The same principles apply: Only constants, null is not okay - and it's 'fast', in that its implemented as a jump table (contrast to a sequence of if/elseif statements, which requires a comparison for each one. A jump table just jumps, using a single comparison, straight to the right line or to the end if none match.
Now the new stuff (12+): As an expression
These days switch can be an expression. As in, the switch returns a thing. However, in order to make that work, each and every 'path' throughout your switch must return something, and it cannot possibly be the case (heh) that there is a missing case statement; after all, what should the expression resolve to then? Thus, in this 'mode', you must have exhausted all options. Interesting subcase of this is enums, where you can be 'exhaustive', and yet at runtime this can never be guaranteed; what if someone adds a value to an enum and recompiles JUST the enum and drops that in there? Now an erstwhile exhaustive (covers every option) switch no longer is:
int y = switch(textyNumber) {
case "one": yield 1;
case "two": yield 2;
default: yield 0;
};
yield is a new approach here. It's a lot like 'return' - it 'returns' from the switch, returning that value. It is a compiler error if that default clause is missing.
The weirdness with enums means that if you're exhaustive (cover every enum value), it compiles, but if you then add an enum value, do not recompile the code with the switch, and attempt to toss a newly created enum value through, the switch throws a java.lang.IncompatibleClassChangeError. The more you know.
arrow syntax
Because 'yield' is a tad unwieldy and it can be useful to just one-liner the desired expression, similar to how you can write e.g.:
Comparator<String> byLength = (a, b) -> a.length() - b.length();
that same arrow syntax is now legal in switches, and for consistency, it's even legal in statement-form switches:
int x = 1;
int y = switch(x) {
case 1 -> 2;
default -> 0;
};
switch (y) {
case 0 -> System.out.println("Interesting");
}
The arrow prevents fallthrough (it implies break), and takes only one statement. But a whole block also counts as one statement:
switch (y) {
case 0 -> {
System.out.println("one");
System.out.println("two");
}
case 1 -> {
System.out.println("three");
}
}
would print just 'one', 'two'.
multi-case
Commas can be used to separate values; this is similar to how you can use bars to separate exception types (catch (IOException | SQLException e) { ... }. Applies to all forms.
pattern matching
Now we get to the real intriguing stuff:
record Point(int x, int y) {}
...
Object p = new Point(10, 20);
switch (p) {
case Point(x, y) -> System.out.printf("Point [%d, %d]", x, y);
}
will soon be legal java! Now the thing that follows the 'case' is a 'template'. Can you smash whatever p resolves to into this template? If yes, the case matches, and the variables are filled in for you. This works with 'deconstructable' types (currently: only records; in future any type can provide a deconstructor), as well as an instanceof kinda deal:
Object o = someExpression;
switch (o) {
case Number n -> System.out.println("is a number. intvalue: " + n.intValue());
case String s -> System.out.println("is a string: " + s.toLowerCase());
}
I think that covers all the updates that are available for switch up to java v 17 and a bit beyond it, even.
NB: You find this info in extreme detail and as early as you want it by following the appropriate openjdk dev mailing lists. This stuff would be found mostly on amber-dev, with some of the pattern matching stuff found on valhalla-dev due to being closely associated with records, and records overlaps with value types (valhalla = value types for java, amber = general language updates). At java conferences somebody will usually give a breakdown of where this stands, reddit's /r/java tends to post it when someone from the OpenJDK team (for this stuff, you're looking at Brian Goetz generally) posts a major update on how the feature is envisioned / has been implemented. Follow Brian on twitter, that helps too. The release notes, and the umbrella JEP page associated with any new java release should also mention this stuff with links you can then follow.
Your original code segment uses fall-through to give the same response for both cases; some people find this difficult to read and follow. Java 12 gave us two related features that people find help clean up their switch statements, one of which you've discovered here:
cases can be comma separated rather than relying on fall-through
the arrow label can be used instead of the colon and break; commands.
Therefore, if you find yourself not liking fall-through, and not wanting to worry about whether you remembered your break commands, you can write the switch as follows:
switch (element) {
case hi, hello -> // do something here
case goodbye, bye -> // do something else here
}
Notice that in this case, we don't have any break statements, but there is no fall-through between the hi case and the bye case; they are separate due to the arrow label.
This is not available in Java, However You can do this thing with Kotlin, which is 100% Compatible with Java
I'm reluctant to use a switch, but I saw switch will be improved in Java 12
Java 12 added the switch expression as an experimental feature. A Java switch expression is a switch statement which can return a value.
The only use case I found (before Java 12) where switch may be useful is returning different values from a small closed set of cases, e.g.:
switch (input) {
case "A":
return "1";
case "B":
return "2";
default:
return "0";
}
Or in Java 12 example:
return
switch(digitInDecimal){
case 0 -> '0';
case 1 -> '1';
case 2 -> '2';
default -> '?';
But I found an old but high-ranked answer that says to avoid multiple return statements:
Assigning a value to a local variable and then returning that at the end is considered a good practice. Methods having multiple exits are harder to debug and can be difficult to read.
So I wonder, is that answer still relevant due to switch changes?
Must I wait for Java 12 where switch can be used without temporary variables and breaks?
Assigning a value to a local variable and then returning that at the end is considered a good practice.
I have no idea when it was considered a good practice. To me, switch is usually * an indicator that a design error was made. I would rather put my effort into thinking how to avoid a switch than into wondering how to return a value from a switch.
A few examples
Long list of if statements in Java
How to avoid switch-case statements in Java
Converting many 'if else' statements to a cleaner approach
Methods having multiple exits are harder to debug and can be difficult to read.
The same goes for a method that has a lot of breaks - that's what you are going to do if you choose the "local-variable approach".
In my opinion, none of these
// 1
switch (input) {
case "A":
return "1";
case "B":
return "2";
default:
return "0";
}
// 2
String varibleToReturn = null;
switch (input) {
case "A":
varibleToReturn = "1";
break;
case "B":
varibleToReturn = "2";
break;
default:
varibleToReturn = "0";
}
return varibleToReturn;
// 3
return switch(digitInDecimal) {
case 0 -> '0';
case 1 -> '1';
case 2 -> '2';
default -> '?';
}
makes a significant difference, or a slight improvement. Yes, Java-12's switch would give more conciseness and expressiveness, but the fundamental idea remains the same.
Must I wait for Java 12 where switch can be used without temporary variables and breaks?
What does it mean? :) No, the deadline is tomorrow, you have to work with what you've got at hand now.
*I am not underestimating the usefulness of switch. It may come in handy, for instance, when you programme at low-level, or you write an optimization.
I am just saying that in the real world, with Springs, and Hibernates, in a world of patterns, switch is obsolescent.
But I found an old but high-ranked answer that says to avoid multiple
return statements:
Assigning a value to a local variable and then returning that at the
end is considered a good practice. Methods having multiple exits are
harder to debug and can be difficult to read.
So I wonder, is that answer still relevant due to switch changes?
This is a common misconception, it originates form the phrase: "Single entry, single exit." (Page 24) All this originates from an other era, one that lead to structured programming languages and eventually to object oriented programming languages (like Java).
Don't worry about multiple return statements, there is nothing wrong with it.
Many code analysis tools like sonar throw errors for switch case if used instead of multiple is-else for cyclomatic complexity . Is there a number which should be a threshold of when one should use if else and when one should use switch case ?
Say if its a case of 50 , to reduce cyclomatic complexity one should use switch case ? Would be good if you could support your answer with an example ?
To answer the question with another question.
Would you rather read:
if(cond1)..
else if(cond2)...
else if(cond50)...
else ...
or
value = map.get(key)
If there are so many possible cases, than neither the switch or if seem appropriate and should therefore be replaced with key-value structure or state machine pattern - if branching is really complicated.
You can find an example implementation here:
fsm
As to when to use if or switch, keep in mind that switch cannot accept the following types:
long, double, float, boolean so when you have such an input, switch is obviously not a choice.
I wouldn't use number as a metric to use switch or if statement. There is one exception however, if an input can have only 2 possible values it is better to use if. Even Sonar should suggest you this.
That said, switch is usually more readable than the series of if statements. As an important decision factor, other than readability, within switch statement there can be groups of values which might require same execution path. It is basically safer version of the goto. Unlike if, where each outcome is usually bound to specific execution path.
I want to know the difference between packed switch and sparse switch opcodes in dalvik. Please if you can provide examples. The explanation provided by google is unclear to me.
packed-switch
sparse switch
Thanks.
It sounds as if packed-switch is equivalent to Java's tableswitch, and sparse-switch to lookupswitch.
A packed-switch uses a simple jump table, indexed by the form low + n, where low is the lowest test value among the case labels, and n is the input to the switch. The values at each index represent the bytecode offsets for each case. Finding the correct jump address is a constant-time operation.
A sparse-switch uses a sorted list of key-value pairs, where each key is a test value from a case label, and the values are jump offsets. Finding the correct jump target for a lookupswitch requires a binary search on the key, so it is a logarithmic-time operation.
The compiler will choose which to use. If the keys tend to be clustered or packed closely together, then a packed-switch (or, in Java terms, a tableswitch) can be emitted efficiently. But if the keys are sparse, and the range of values (high - low + 1) is large, then using a jump table would require a large block of bytecode, as all values in that range must exist in the jump table regardless of whether there is a corresponding case label. In these scenarios, the compiler will emit a sparse-switch (lookupswitch).
Interestingly, the Dalvik engineers chose to name these opcodes in a way that describes the key distributions for which they should be used, whereas the Java engineers chose names which describe the conceptual data structures that the bytecode operands resemble.
Let's look at some examples. Consider the following Java code, which will produce a tableswitch (and, when converted to Dalvik, a packed-switch):
static String packedSwitch(final int n) {
switch (n) {
case 5:
return "Five";
case 3:
return "Three";
case 1:
return "One";
default:
return "Other";
}
}
Conceptually, the payload for the packed-switch opcode would look something like this:
As you can see, it's fairly compact. Three out of the five slots point to actual case targets, with the remaining two jumping to the default target. But what if our test values were more spread out?
static String sparseSwitch(final int n) {
switch (n) {
case 500:
return "Five Hundred";
case 300:
return "Three Hundred";
case 100:
return "One Hundred";
default:
return "Other";
}
}
If the compiler tried to emit this as a packed-switch, the payload would look something like this:
Notice how only three out of a few hundred slots actually point to case labels from the original code. The rest are there simply to fill up the jump table. Not very space efficient, is it? That's why the compiler would emit a sparse-switch, which has a far more compact bytecode footprint for this particular example:
Now, that's much more reasonable, don't you think? The downside, however, is that instead of knowing exactly which index to jump to based on the input, we have to perform a binary search on the table until we find a matching test value. The larger the switch, the more significant the impact on performance, though the effect has a logarithmic curve.
This question already has answers here:
Closed 12 years ago.
Possible Duplicates:
Is "else if" faster than "switch() case"?
What is the relative performance difference of if/else versus switch statement in Java?
I know that case statements can be implemented with jump tables. Does this make them more efficient than if statements?
Is this just micro-optimization that should be avoided?
I think the main thing is to write the code as clearly as possible. Micro-optimizations like this shouldn't be the focus.
For example, if you have something like this:
if (age == 10) {
// ...
} else if (age == 20) {
// ...
} else if (age == 30) {
// ...
} else if (age == 40) {
// ...
}
Then it's clearer to use a switch statement:
switch (age) {
case 10:
// ...
break;
case 20:
// ...
break;
case 30:
// ...
break;
case 40:
// ...
break;
}
Again, I would focus on making the code easiest to read and maintain rather than nano-second level efficiency gains.
Any compiler will make the jump table if it can verify that the values are reasonably compact. (I doubt if they are in this case, being multiples of 10.)
This is a micro-optimization. Micro-optimization makes sense only if you know that it does. Typically, there are larger "fish to fry" elsewhere, in the form of function calls that could be done without. However, if you have already tuned the daylights out of this code, and your profiling shows that a good fraction (like 10% or more) of time is going into these IF statements (and not to their contents) then it helps. This can happen, for example, in a byte-code interpreter.
Added: Another reason I like to use switch is, even if it doesn't make a jump table - when stepping through the code in a debugger, it goes directly to the proper case, rather than making me step through a lot of false if statements. Makes it easier to debug.
If you had a very large chain of if else statements, then, yes, you might feel the difference. But it is very unrealistic that you'll ever write such a long ifelse chain. And if even you did, it's still very unlikely that that is where your performance bottleneck would be.
Write your code to be readable first, and let yourself be guided by a profiler when the need for performance optimization arises.
Probably is doen't matter. Bytecode is just a "transport format" to the JVM. What happens insite the JVM is very different from the bytecode representation. (Example: Bytecode doesn't offer float operations, so float +-*/% float is done as double operations and then the result is casted back to float. Same is true for byte/short, they are converted to int and then back.) But for switch they are two bytecode formats, one already with a jump table. But honestly: I would choose a format which is best for you and the reader of your program. The JVM will do the rest. If you are too smart you JVM maybe don't get your point and in the end the program is slower.
"We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil" D. Knuth
Yes
No, it is part of your program design. But you should consider whether an over-ridable method mightn't be an even better solution, with a family of types.