Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why cant I make an enum's inner class public?

Tags:

java

enums

java-8

Testing some things out I tried o make an enum in which every one element in a enum have a different class inside.

Take for example:

public enum MyEnum {

    first{
        class First{}
    },
    second {
        class Second{}
    };
}

If i try to put a public modifier before any class, the Modifier not allowed here shows up. I am not quite sure why this would be. I cannot instantiate those classes outside the enum nor see them. However i can manage to get an Instance doing so:

public enum MyEnum {

    first{
        class First{}

        public Object getObject(){
            return new First();
        }
    },
    second {
        class Second{}

        public Object getObject(){
            return new Second();
        }
    };

    public abstract Object getObject();
}


public class Main {
    public static void main(String[] args) {
        System.out.println(MyEnum.first.getObject().getClass());
        System.out.println(MyEnum.second.getObject().getClass());
    }
}

With the output:

class MyEnum$1$First

class MyEnum$2$Second

I can clearly have a reference to this class, why can't i access it then at compilation time?

like image 632
Juan F. Caracciolo Avatar asked May 04 '17 02:05

Juan F. Caracciolo


2 Answers

TL;DR This has been fixed with JDK 16

JDK 16 changed the rules in that an inner class is now allowed to have static members. It seems that the issue of this question has been fixed as a side effect. Since this should have been the behavior in earlier versions as well, public is accepted by the JDK 16+ compiler even with --release 8.

This is a very interesting question. You will not be able to access these classes at compile-time, even if the public modifier was allowed, as they are contained within implicitly anonymous classes, so they can’t be accessed by name anyway (besides within the anonymous class). You can’t access a type through a variable, i.e. accessing MyEnum.first.First is not possible in Java at all.

Still, not being useful is not necessarily determining what can be declared, i.e. declaring a public inner class within a private outer class is possible as well. The formal rules are relevant and while it looks like the expected behavior at the first glance, it is surprisingly not backed by the specification in that way.

JLS §8.9.1, Enum Constants states:

The optional class body of an enum constant implicitly defines an anonymous class declaration (§15.9.5) that extends the immediately enclosing enum type. The class body is governed by the usual rules of anonymous classes…

which gives us an interesting hint, i.e.

class Outer {
    static Object o = new Object() {
        public class Inner {
        }
    };
}

is rejected by the compiler either, prior to JDK 16.

Considering JLS, §8.1.1. Class Modifiers:

The access modifier public (§6.6) pertains only to top level classes (§7.6) and member classes (§8.5), not to local classes (§14.3) or anonymous classes (§15.9.5).

we have to decide in which category Inner or your First class fall. It’s not about the fact that their surrounding class is an anonymous class. Obviously, they are neither, top level nor anonymous classes, as they are nested and have a name. So they must be either, a member class (public allowed) or a local class (public not allowed).

JLS, §8.5. Member Type Declarations:

A member class is a class whose declaration is directly enclosed in the body of another class or interface declaration (§8.1.6, §9.1.4).

The “body of another class … declaration” is defined by pointing to §8.1.6 which indeed defines the ClassBody language grammar, which is commonly used by named declarations, anonymous classes and enum constant bodies; all of them point to the “class body” of §8.1.6. Considering this, our classes are “member classes” as they are contained in a class body.

Now we could try to interpret that as a wrong cross reference, assuming that “body of another class … declaration” was meant to refer to Class Declarations, i.e. named class declarations using the class keyword, however, there is the definition of local classes which disproves this interpretation.

JLS §14.3, Local Classes:

A local class is a nested class (§8 (Classes)) that is not a member of any class and that has a name (§6.2, §6.7).

Every local class declaration statement is immediately contained by a block (§14.2). Local class declaration statements may be intermixed freely with other kinds of statements in the block.

“Block” really means the block like the definition of a non-abstract method, constructor or initializer (§14.2). This does not apply to the Inner class above or your First and Second class. They are not placed in a block and may not be intermixed freely with statements in that context, as statement aren’t allowed at this point.

In other words, they are definitely not local classes and assuming that there is not another category of classes not described by the specification, we have to consider them member classes, as the current writing and linkage of the definition of member classes also suggest, in other words, according to the cited rules, the public modifier should be allowed at this place.

For completeness, here is the definition of anonymous classes, just to show that there is no exceptional rule saying that their member classes aren’t allowed to be public:

15.9.5. Anonymous Class Declarations

An anonymous class declaration is automatically derived from a class instance creation expression by the Java compiler.

An anonymous class is never abstract (§8.1.1.1).

An anonymous class is always implicitly final (§8.1.1.2).

An anonymous class is always an inner class (§8.1.3); it is never static (§8.1.1, §8.5.1).

The last point implies that in turn, their member classes can’t be static either, however, there is no rule forbidding them to be public.

like image 78
Holger Avatar answered Oct 13 '22 01:10

Holger


You are subclassing MyEnum with your current approach (the {} syntax creates an anonymous subclass), which works but seems overly complicated. What you have is equivalent to something like,

public enum MyEnum {
    first, second; // <-- convention would be FIRST, SECOND
    public static class First { // <-- can be public.
    }
    public static class Second {
    }
    public Object getObject() {
        if (this == first) {
            return new First();
        }
        return new Second();
    }
}
like image 25
Elliott Frisch Avatar answered Oct 13 '22 00:10

Elliott Frisch