19

I would like to use a java switch statement, which uses class names as case constants. Is it possible somehow? Or do I have to duplicate the class names?

Following code does not work because of compiler error:

case expressions must be constant expressions

String tableName = "MyClass1";

...

switch (tableName) {
case MyClass1.class.getSimpleName():
    return 1;
case MyClass2.class.getSimpleName():
    return 2;
default:
    return Integer.MAX_VALUE;
}

Here is a online demonstration of the issue (openjdk 1.8.0_45): http://goo.gl/KvsR6u

dedek
  • 7,981
  • 3
  • 38
  • 68

3 Answers3

12

The compiler error already says it. The case labels must be constant expressions and neither, class literals nor the result of invoking getSimpleName() on them, are constant expressions.

A working solution would be:

String tableName = "MyClass1";
...
switch (tableName) {
    case "MyClass1":
        return 1;
    case "MyClass2":
        return 2;
    default:
        return Integer.MAX_VALUE;
}

The expression MyClass1.class.getSimpleName() is not simpler than "MyClass1", but, of course, there won’t be any compile-time check whether the names match existing classes and refactoring tools or obfuscators don’t notice the relationship between the class MyClass1 and the string literal "MyClass1".

There is no solution to that. The only thing you can do to reduce the problem, is to declare the keys within the associated class to document a relationship, e.g.

class MyClass1 {
    static final String IDENTIFIER = "MyClass1";
    ...
}
class MyClass2 {
    static final String IDENTIFIER = "MyClass2";
    ...
}
...
String tableName = MyClass1.IDENTIFIER;
...
switch (tableName) {
    case MyClass1.IDENTIFIER:
        return 1;
    case MyClass2.IDENTIFIER:
        return 2;
    default:
        return Integer.MAX_VALUE;
}

This documents the relationship to the reader, but tools still won’t ensure that the actual string contents matches the class name. However, depending on what you want to achieve, it might become irrelevant now, whether the string contents matches the class name…

Holger
  • 285,553
  • 42
  • 434
  • 765
  • This the correct answer for me: *The case labels must be constant expressions and neither, class literals nor the result of invoking `getSimpleName()` on them, are constant expressions.* – dedek Jan 07 '16 at 08:09
  • Nice explanation of **compile-time constant expressions** can be found here: http://stackoverflow.com/a/3827424/1857897 – dedek Jan 07 '16 at 08:13
  • I thought that *class literals* are constant expressions, but then `""+MyClass1.class` should work, but it doesn't... – dedek Jan 07 '16 at 08:20
  • 2
    What is even more counter-intuitive is that `enum` constant references are not compile-time constants. They can be used in `case` labels and annotations though, but that's the exception to the usage rules (you can also use class literals in annotations despite not being compile-time constants). So when you say `final EnumType X = EnumType.FOO;`, then `X` will not be a compile-time constant and can't be used as `case` label (unlike primitive types and `String`s). The bottom line is that only primitive types and `String`s can be compile-time constants (because the specification says so). – Holger Jan 07 '16 at 10:28
  • @Holger - The limitation of this solution is that you cannot easily see which code uses a particular class. You have to use a "find text" option in your IDE instead of "find code which uses that class" option. This can be a problem when you want to refactor all the users of that class. – MasterJoe Oct 21 '19 at 20:33
  • 3
    @MasterJoe2 in case of Eclipse, it will identify, e.g. `MyClass2.IDENTIFIER` as a usage of `MyClass2`. It doesn't work if you don't have the source code, but still isn't a naive text search. If you're referring to the first variant, the limitations have been described in the answer already. – Holger Oct 22 '19 at 06:51
8

Instead of using a switch, why not store the mappings in a map?

Create a map of String to Integer, and map all the class names to their return value.

On a request, if the entry does not exist, return the default value. Otherwise, return the value in the map.

samczsun
  • 1,014
  • 6
  • 16
  • I was thinking about this solution, but wouldn't be `switch` more efficient? – dedek Jan 05 '16 at 13:57
  • 2
    Perhaps, but your code will also be less readable as you need a line for every class name and then a few more for every case. The few nanoseconds of optimization you get will likely never be worth the cleaness of the code you can have using a `Map` – samczsun Jan 05 '16 at 14:00
  • 1
    Oh, I forgot to add. If I remember correctly, `switch`es on Strings just [switch on their hashcode](http://www.benf.org/other/cfr/java7switchonstring.html) unless something has changed recently – samczsun Jan 05 '16 at 14:02
  • Thanks for your comments +1, but I'm still curious if the switch could work for me.. – dedek Jan 05 '16 at 14:04
3

Instead of Switch..case why don't you use If..Else. Should work in all versions of java till i know.

if (tableName.equals(MyClass1.class.getSimpleName())) {
     return 1;
} else if (tableName.equals(MyClass2.class.getSimpleName())) {
     return 2;
} else {
     return Integer.MAX_VALUE;
}
yglodt
  • 13,807
  • 14
  • 91
  • 127
Rajen Raiyarela
  • 5,526
  • 4
  • 21
  • 41
  • 1
    I really do not like `else if` statements, see the `Map` based solution by @Sam-Sun – dedek Jan 05 '16 at 14:11
  • is up to the choice, but till i know this is supported in all versions and you will not require to handle any version dependencies. – Rajen Raiyarela Jan 05 '16 at 14:16
  • 2
    the problem of this solution is that in the worst case, it has to go over every single possibility, that's why people want "switch". – Leonmax Jul 03 '17 at 19:43
  • 1
    As far as I can remember the switch case in java is just sugar for if/then/else and the compiled code is just that. – slott Dec 18 '18 at 10:19
  • 1
    @slott That only applies for `switch`es with few cases. With many cases, a jump table is used. – Kröw Aug 13 '22 at 22:43