Java: using switch statement with enum under subclass
Asked Answered
N

8

402

First I'll state that I'm much more familiar with enums in C# and it seems like enums in java is a quite mess.

As you can see, I'm trying to use a switch statement @ enums in my next example but I always get an error no matter what I'm doing.

The error I receive is:

The qualified case label SomeClass.AnotherClass.MyEnum.VALUE_A must be replaced with the unqualified enum constant VALUE_A

The thing is I quite understand the error but I can't just write the VALUE_A since the enum is located in another sub-class. Is there a way to solve this problem? And why is it happening in Java?

//Main Class
public class SomeClass {

    //Sub-Class
    public static class AnotherClass {
        public enum MyEnum {
            VALUE_A, VALUE_B
        }    
        public MyEnum myEnum;
    }

    public void someMethod() { 
        MyEnum enumExample //...

        switch (enumExample) {
            case AnotherClass.MyEnum.VALUE_A: { <-- error on this line
                //..
                break;
            }
        }
    }
}
Netty answered 15/4, 2012 at 10:59 Comment(3)
As darrengorman commented, Java Enum are extremely handy once you get the hang of them – not at all a mess. They are much more flexible and practical than simple enums (merely a labeled integer value) as seen on other platforms. See the Oracle Tutorial. Discover the optimized Set/Map implementations: EnumSet & EnumMap.Cobaltous
When you try to qualify the case statement; in a way, you are trying to say that I can mix different types of enums (not just same enum type) within a single switch statement. Java has stopped it with this approach as discussed here digizol.com/2010/10/enum-case-label-switch-java-qualified.htmlActin
This happened to me while refactoring (moving) a class in IntelliJ 2018.2Duodenal
M
792

Change it to this:

switch (enumExample) {
    case VALUE_A: {
        //..
        break;
    }
}

The clue is in the error. You don't need to qualify case labels with the enum type, just its value.

Moores answered 15/4, 2012 at 11:5 Comment(10)
Ok i feel so stupid :-( You are totally right, i was convinced i tried this exact line and got an error with that so i moved to qualify case, but your suggestion DOES work.Netty
By the way I think you'll find that enums in Java are incredibly useful once you start to use them more, I wouldn't say they're a mess at all :)Moores
I believe i will find you are right in the close future :) Anyway, this is definitely the best programming forum around thanks to people like you.Netty
@milkplusvellocet, I know this post is already old, but I'm curious why Java don't allow the qualified case label in the switch statement?Galah
@cRane01 don't know for sure, but it makes for a cleaner syntax. Specifying the type on each case would be totally redundantMoores
What about if you have another constant called the same thing in another class? Wouldn't VALUE_A be ambiguous in that case?Protease
@Protease No. The switch statement's variable defines the type of the case statement so it can only be one enum.Ysabel
Question: Let's say I have: enum AnimalType { Fish, Horse }. I have a parameter AnimalType a in a function with the statement: switch(myAnimal.getType()) and then case a in that. I get the error, what to do?Ryter
Upvoted this answer already back in 2015 .. here we go again :D Still don't understand why a qualified name is a compiler error. Like, if it's ugly and redundant, so be it, that's on my shoulders. My IDE can tell me that it's unnecessary, but crashing the compiling? I don't know ... Seems odd to me at least, and therefore unintuitive.Vocable
This is slightly counter-intuitive but solved my issue too. When you have a slow build for a large system (made worse by gradle) and have to upgrade a library due to a CVE security flaw urgently, this compile error costs me a lot of valuable time.Abadan
L
64

Wrong:

case AnotherClass.MyEnum.VALUE_A

Right:

case VALUE_A:
Lidialidice answered 7/3, 2019 at 22:15 Comment(2)
I upvoted yours because it is clearer on what the issue is.Devilish
Why is it a problem to qualify it?Dispatch
J
44

Java infers automatically the type of the elements in case, so the labels must be unqualified.

int i;
switch(i) {
   case 5: // <- integer is expected
}
MyEnum e;
switch (e) {
   case VALUE_A: // <- an element of the enumeration is expected
}
Julienne answered 15/4, 2012 at 11:3 Comment(25)
Why must it be unqualified?Mcroberts
If you could qualify, then you might use something else than MyEnum which would not make sense.Julienne
@Kru, but I can use something grammatically-else for non-enum-typed case expressions. E.g. static final int MY_CONST = 7; …; switch(intVariable) {case MY_CONST: …;} instead of case 7. So this restriction for enums makes no sense (I can use not only primary literals, but also manually-defined constants for integer switch expression, but I can't use manually-defined constants, but only primary names for enums).Hierodule
@Hierodule enums in Java don't have integers assigned to them like in C++. They are abstract just like java boolean.Guideline
@user13947194, nobody said they have. I just pointed out the logical fallacy: for one type we can use both the primary literals and the manually-defined constants while for the other type we can use only primary names but not manually-defined constants.Hierodule
@Hierodule I don't understand what you are talking about. Java enums are not associated with an integer and can't be associated with an integer. They can only be referenced by their name.Guideline
@user13947194, “They [enum values] can only be referenced by their name” — nope. We can create aliases for enum values — static final MyEnumType aliasToEnumValue = MyEnumType.VALUE_A; as well as we can create aliases for integer values — static final int aliasToIntValue = 12. But the design flaw is that only the latter (case aliasToIntValue) can be used a case label but not the former (case aliasToEnumValue). (As for “Java enums are not associated with an integer and can't be associated with an integer” — please don't argue with the statements that I have never expressed.)Hierodule
@Hierodule now I understand you. I think enums in Java are Objects, and when you apply final to Objects you only have a const pointer and not a const Object. int on the other hand is a primitive. Applying final to int makes the value itself const. Another thing is, you probably dont need to do this, and Java is the type of parent to push you in the "right" direction.Guideline
@user13947194, "when you apply final to Objects you only have a const pointer and not a const Object" — that doesn't matter whether the object itself is const or not, because: (1) const final myAlias = MyEnumType.VALUE_E copies only the pointer; (2) switch (myEnumValue) {…} compares only the pointer. So fields in MyEnumType (if there are any) are irrelevant here. So, handling of enums and ints/chars is identical here (but syntax is different and that's weird). "you probably dont need". Of course, I need.Hierodule
My philosophy is: every logical inconsistency (including syntactic ones) may bite, sooner or later. Even if a specific one haven't bitten somebody yet, that doesn't mean it will never do that.Hierodule
@Hierodule I don't know how you reckon that switch(myEnumValue) compares only the pointer. If that were the case, two enum objects of the same value would not compare equal.Guideline
@user13947194, can you please provide an example of what you mean by "two enum objects of the same value"?Hierodule
@Hierodule I did a little research and found that you can't create multiple instances of an enum using new keyword. Which means only one instance of an enum is created. One might then think we can switch on pointers. But sadly you cant do that because pointers are only known at runtime. To emphasize the point, switch statements only work on values known at compile time. To emphasize further, since you only and can only make the pointer const; Java has no way of knowing what value is stored in your enum variable. You can change that value at anytime, during runtime.Guideline
@user13947194… ([0] “you can't create multiple instances of an enum using new keyword” — of course.) So, I've checked the generated bytecode. [1] You were right that, in case of enums, Java's switch doesn't simply compare pointers (although I disagree that pointer is always unknown at compile time; it's possible to know in some cases; but Java doesn't use that). [2] You were wrong that it checks the values of the fields; instead, it checks the result of the call to the enum's predefined virtual method; that's the thing a user can't influence (regardless to whether fields are const or no).Hierodule
@user13947194, so, in general, I oppose to your assumption that inability to mark an object value as const somehow relates to the restriction of using enum aliases as switch case labels.Hierodule
@Hierodule You mind telling me how pointers can be known at compile time? Whether it checks a value or a value obtained from a method is not the point. The point is you can change the value of your enum variable at anytime via assigning it a different value. MyEnum y = MyEnum.a; y = MyEnum.z; If you further 'oppose' my 'assumption' I can't make a better case.Guideline
@user13947194, "you can change the value of your enum variable at anytime via assigning it a different value". You can't. In MyEnum y = MyEnum.a; y = MyEnum.z, exactly the "pointer" gets changed (not the "object" itself). And when MyEnum y is marked as final, changing the "pointer" is forbidden. (As for the pointers at compile time, it's off-topic, because it's not used here anyway; and I was partially wrong — not the pointers themselves but their offsets to a certain base can be theoretically known at compile time (which should be still enough); but that's not used anyway here.)Hierodule
@Hierodule you make a tough point. I must say I never ever create Java enums and always used static final ints. Yes you can't modify a final enum pointer; essentially making it truly immutable. Now all we need to do is compare the offsets; and we know them at compile time! The only thing I can think of is, are these offsets relative to the entire app?Guideline
@user13947194, sorry for not replying, there was a rush at work. I suppose, these offsets are per module (i.e. per *.dll- or *.so-file in the native world and per *.jar file in JRE world). Because every module may expect that all of its “static” data is put into contiguous area by the “linker” but has no clue about other modules' “static” data. But that would be enough, as all values for a specific enum are always declared in the same module (Java doesn't support extending enums).Hierodule
Nevertheless, I think this is offtopic, as (1) Java uses virtual method calls (not pointer offsets) when comparing enums in switch and (2) as well as the pointer offsets strategy, the current strategy (based on virtual method calls) doesn't in any way prevent from using enum aliases in switches. So, to be short, the only thing that prevents us from using enum aliases in switches in Java is the syntax (technical reasons don't prevent it). Do you agree now with me that such syntax is a design flaw?Hierodule
I am happy that you are busy at work. I am busy coding too, but self study. Now your first logic/ point is that since the entire enum is stored in one JAR file; Java would be able to deduce via offset. There is a problem sadly. A Java object can exist in multiple class/JAR files. Infact a java object can be received over a network. The short story, you can't rely on offsets to deduce a Java Object. Other possible problem is the assumption that Java can manipulate offsets to deduce an object. Once an object is loaded into memory it is now an OS specific environment.Guideline
Your second logic is that your logic one is off topic aka irrelevant to our discussion; as Java uses virtual method calls to get enum value. This shows me you didn't learn from my previous elaborations. You cannot switch on method calls as methods calls are not integer values. You cannot switch on pointers because pointers are only known at runtime.Guideline
@user13947194, yes, let's really stop discussion of pointers, because they are really offtopic (despite I believe that you're not 100% correct about them). Method calls aren't integer values but they return integer values. To be precise, Java uses virtual method call to determine the integer value associated with the result of an enum-typed expression in parentheses (after switch) while the integer values associated with the enum values used as case labels are known at compile-time. (That's how it works de-facto, you can check that with javap.)Hierodule
(BTW, I need to say sorry, because some time ago I said “I never stated that enums have associated ints”, but now I do state that. Because at that point of time I didn't care about actual implementation and didn't look into javap and then I did.) And, to say truth, now I don't understand your point at all: IIUC, you're saying that switch for enums in Java can be implemented neither by comparing pointers (or offsets) nor by calling a virtual method; but the fact is that switch for enums is impemented in Java in some way; does that mean that you assume some third way?Hierodule
I am happy you finally get that you can't switch on a method; you must switch on the integer it returns. Question: what do you know about polymorphic/ virtual methods. If nothing else you must know that virtual methods are runtime features. Cannot be deduced at compile time. So even if Java doesn't use virtual pointer tables to implement virtual methods; it makes no difference as they are still runtime information. And switch statements require that they be known at compile time.Guideline
C
7

From Java 14 onwards, one can use switch expressions.

For this post

public enum MyEnum {
    VALUE_A, VALUE_B;
}
public void someMethod() { 
    MyEnum enumExample //...

    switch (enumExample) {
        case VALUE_A -> {
            // logic
        }
        case VALUE_B -> {
            // logic
        }   
    }
}

Switch expression

Like all expressions, switch expressions evaluate to a single value and can be used in statements. They may contain "case L ->" labels that eliminate the need for break statements to prevent fall through. You can use a yield statement to specify the value of a switch expression.

public enum Month {
    JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC;
}

Example 1: Returns value.

public static int getNoOfDaysInAMonth(Month month, boolean isLeapYear) {
    return switch(month) {
        case APR, JUN, SEP, NOV -> 30;
        case FEB -> (isLeapYear)? 29: 28;
        case JAN, MAR, MAY, JUL, AUG, OCT, DEC -> 31;
    };
}

Example 2: Doesn't returns value.

public static void printNoOfDaysInAMonth(Month month, boolean isLeapYear) {
    switch(month) {
        case APR, JUN, SEP, NOV -> {
            System.out.println("30 days");
        }
        case FEB -> {
            System.out.println(((isLeapYear)? 29: 28) + " days");
        }
        case JAN, MAR, MAY, JUL, AUG, OCT, DEC -> {
            System.out.println("31 days");
        }
    };
}

Reference

Switch Expressions

Crwth answered 25/3, 2021 at 8:59 Comment(0)
W
6

this should do:

//Main Class
public class SomeClass {

    //Sub-Class
    public static class AnotherClass {
        public enum MyEnum {
            VALUE_A, VALUE_B
        }    
        public MyEnum myEnum;
    }

    public void someMethod() { 
        AnotherClass.MyEnum enumExample = AnotherClass.MyEnum.VALUE_A; //...

        switch (enumExample) {
            case VALUE_A: { //<-- error on this line
            //..
            break;
            }
        }
    }
}
Whomsoever answered 15/4, 2012 at 11:9 Comment(1)
You saved the day!Hypercorrection
B
2

This is how I am using it. And it is working fantastically -

public enum Button {
        REPORT_ISSUES(0),
        CANCEL_ORDER(1),
        RETURN_ORDER(2);

        private int value;

        Button(int value) {
            this.value = value;
        }

        public int getValue() {
            return value;
        }
    }

And the switch-case as shown below

@Override
public void onClick(MyOrderDetailDelgate.Button button, int position) {
    switch (button) {
        case REPORT_ISSUES: {
            break;
        }
        case CANCEL_ORDER: {
            break;
        }
        case RETURN_ORDER: {
            break;
        }
    }
}
Bolometer answered 30/3, 2017 at 9:30 Comment(0)
I
1

You dont need put instance of enum class, you have to put only enum constant of enum class, like this:

switch (enumExample) {
   case VALUE_A: { <-- puting only constant, it will works.
       //..
      break;
   }
Idaidae answered 14/2, 2023 at 17:18 Comment(0)
C
0

Write someMethod() in this way:

public void someMethod() {

    SomeClass.AnotherClass.MyEnum enumExample = SomeClass.AnotherClass.MyEnum.VALUE_A;

    switch (enumExample) {
    case VALUE_A:
        break;
    }

}

In switch statement you must use the constant name only.

Clotilde answered 15/4, 2012 at 11:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.