12

JLS 8 states that each enum class has implicitly declared method:

public static E[] values();

So, it is public by specification.

At the same time Class.getEnumConstantsShared() method forcibly makes it accessible:

            final Method values = getMethod("values");
            java.security.AccessController.doPrivileged(
                new java.security.PrivilegedAction<Void>() {
                    public Void run() {
                            values.setAccessible(true);
                            return null;
                        }
                    });
            @SuppressWarnings("unchecked")
            T[] temporaryConstants = (T[])values.invoke(null);

I'm wondering: what's the sense?

Andremoniy
  • 34,031
  • 20
  • 135
  • 241
  • 2
    Probably related to this comment: "These can happen when users concoct enum-like classes that don't comply with the enum spec." http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/Class.java#2961 – rom1v Jan 27 '17 at 13:14
  • 2
    Ha, nice. And how is that possible to "concot" such class? O_o – Andremoniy Jan 27 '17 at 13:15
  • Before enums were introduced in Java 5, it was a common practice (and a good practice) to make your own equivalent of an enum: A class with a non-public constructor, and public static constants containing instances of that class. Examples are [TextAttribute](http://docs.oracle.com/javase/8/docs/api/java/awt/font/TextAttribute.html), [FileChannel.MapMode](http://docs.oracle.com/javase/8/docs/api/java/nio/channels/FileChannel.MapMode.html), and [DateFormat.Field](http://docs.oracle.com/javase/8/docs/api/java/text/DateFormat.Field.html). – VGR Jan 27 '17 at 14:47
  • @VGR that's interesting point,but those classes **doesn't contain** `values()` methods. – Andremoniy Jan 27 '17 at 14:55
  • I'm guessing you can concoct such a class by extending Enum instead of using the `enum` keyword. Worst case, you can always generate an enum-looking class using direct bytecode generation. – kaqqao Jan 30 '17 at 09:56
  • 2
    @kaqqao classes can not directly extend `java.lang.Enum`. It is prohibited by compiler. Furthermore, `Class.getEnumConstantsShared` has protection against non-enum classes via invoking method `Class.isEnum`. – Andremoniy Jan 30 '17 at 09:58
  • Both of those can easily be bypassed by a bytecode generator... Look at ByteBuddy for example. You can generate a class of any structure, including those Java compiler would never normally let you. – kaqqao Jan 30 '17 at 10:00
  • It is perverted case, I don't believe that discussed code was created to handle such cases. Furthermore, using this approach of bytecode generator, I can easily generate enum class w/o `values()` method. Where is protection from this case? – Andremoniy Jan 30 '17 at 10:03

1 Answers1

4

Short answer: otherwise it would not work with anything but public Enums

To access the method MyEnum.values(), the method needs to be public (it always is) and MyEnum needs to be public (or it must be in the same package).

The method is accessed from the Class::getEnumConstantsShared() in the package java.lang. So as long as the none public Enum is not in java.lang getEnumConstantsShared() will not have access to it. Access is determined by the direct caller not by somebody higher up the call stack. The direct caller of values.invoke(null) is the code in java.lang.Class so the accessibility rules apply to exactly that context

That can be easily verified (following code will not work) if you create a static method in some package a

public class ValueGetter {
    public static Object[] getValues(Class<? extends Enum> enu) throws Exception {
        Method method = enu.getMethod("values");
        return (Object[]) method.invoke(null);
    }
}

And try to access it like this snippet in a different package b

public class EnumTest {
    enum MyEnum {
        LITERAL;
    }

    public static void main(String[] args) throws Exception {
        System.out.println(Arrays.toString(ValueGetter.getValues(MyEnum.class)));
    }
}
k5_
  • 5,450
  • 2
  • 19
  • 27
  • It is a good point, I even firstly accepted your answer. But then I begin thinking: access to this method is performed only inside same context where this enum is accessible. Furthermore, it is accessed inside class itself. – Andremoniy Feb 02 '17 at 17:10
  • In other words, you can invoke `valueOf(...)` on particular enum only when this enum is accessible and visible. – Andremoniy Feb 02 '17 at 17:11
  • It is actually the same as write `Object values = MyEnum.class.getMethod("values").invoke(null);` - it will work without forcibly making `method.setAccessible(true)`. – Andremoniy Feb 02 '17 at 17:15
  • `ValueGetter` needs to be in a different package for this to actually cause an exception. The `Class` object is mainly independent from the class source, especially (for most cases) it is not in the same package. Enum.valueOf( Class, String ) uses `getEnumContantsShared()`. To call MyEnum.valueOf(String) normal visibilty rules apply. – k5_ Feb 02 '17 at 17:18
  • Yep, I understand that `ValueGetter` needs to be in different package. And I totally agree that it doesn't work without settings method accessible to true. But the problem is that you practically can not invoke `getMethod("value")` on `MyEnum` class from different package: you always will do it from same package, or generally speaking: from scope of enum accessibility. – Andremoniy Feb 02 '17 at 17:29
  • Just try understand my concern: invocation of `MyEnum.getEnumConstantsShared()` and `ValueGetter.getValues(MyEnum.class)` is totally different things (from scope point of view). – Andremoniy Feb 02 '17 at 17:31
  • P.S. As I'm currently little bit tired, I suppose that I can be wrong, that is why I'm not downvoting your answer, waiting for insights and opinion of other users. – Andremoniy Feb 02 '17 at 17:32
  • In other words: there is no practical case, when you can invoke `valueOf` on particular enum, while `values()` method will not be accessible. – Andremoniy Feb 02 '17 at 17:34
  • 1
    For accessibilty only the **direct caller** is relevant not the caller of the caller. The direct caller of `values.invoke(null)` is the code in `java.lang.Class` so the accessibility rules apply to exactly that context. That the method is called through `MyEnum.class.getEnumConstantsShared()` and the method was visible at that context is not longer relevant for accessiblity. – k5_ Feb 02 '17 at 17:56
  • Fine, at least you convinced me :) – Andremoniy Feb 02 '17 at 18:00
  • @FlorentBayle please award your bounty to this perfect answer! – Andremoniy Feb 02 '17 at 18:00
  • 1
    @Andremoniy Done :-). – Florent Bayle Feb 03 '17 at 11:39