抽象枚举的sun.reflect.Reflection处理中可能存在的错误?
我已经确定了什么至少是不受欢迎的行为,并且最多也是Sun JDK使用抽象方法处理对Java enum
的反射的一个错误。 我已经为这个特定行为搜索了一个错误报告和StackOverflow答案,并且干了起来。 如果您认为自己在如此经过良好使用和经过仔细测试的代码中发现了类似问题,那么您或多或少总是错误,因此请理智地检查我并告诉我在哪里出错。
代码
考虑下面的代码:
A / Greeting.java
package a;
public enum Greeting {
HELLO {
@Override
public void greet() {
System.out.println("Hello!");
}
};
public abstract void greet();
}
B / EnumTest.java
package b;
import java.lang.reflect.Method;
import a.Greeting;
public class EnumTest {
public static void main(String[] args) throws Exception {
Greeting g=Greeting.HELLO;
Method greet=g.getClass().getMethod("greet");
System.out.println("Greeting "+g.getClass()+" ...");
greet.invoke(g);
System.out.println("Greeted!");
}
}
另外,请注意Greeting
和EnumTest
有不同的包。 (这最终成问题。)
错误
当你运行这段代码时,你希望得到如下输出:
Greeting class a.Greeting ...
Hello!
Greeted!
相反,您会得到以下输出:
Greeting class a.Greeting$1 ...
Exception in thread "main" java.lang.IllegalAccessException: Class b.EnumTest can not access a member of class a.Greeting$1 with modifiers "public"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:95)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:261)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:253)
at java.lang.reflect.Method.invoke(Method.java:594)
at b.EnumTest.main(EnumTest.java:13)
了解行为
首先,请注意Greeting
是public
, Greeting$greet
是public
。 (即使错误消息表明public
访问!)所以发生了什么?
什么在这里?
如果你通过代码,你会发现最终的“问题”是sun.reflect.Reflection$verifyMemberAccess()
返回false
。 (所以,Reflection API声称我们无法访问此方法。)失败的特定代码位于以下位置:
public static boolean verifyMemberAccess(Class currentClass,
// Declaring class of field
// or method
Class memberClass,
// May be NULL in case of statics
Object target,
int modifiers)
// ...
if (!Modifier.isPublic(getClassAccessFlags(memberClass))) {
isSameClassPackage = isSameClassPackage(currentClass, memberClass);
gotIsSameClassPackage = true;
if (!isSameClassPackage) {
return false;
}
}
// ...
实质上,这个方法确定currentClass
代码是否可以使用修饰符的modifiers
来查看memberClass
成员。
显然,我们应该有权访问。 我们正在public
课上调用public
方法! 但是,该代码在指定的return
语句中返回false
。 因此,我们尝试调用该方法的值的类不是public
。 (我们知道这是因为外部测试 - !Modifier.isPublic(getClassAccessFlags(memberClass))
- 传递,因为代码到达内部return
。)但Greeting
是public
!
然而, Greeting.HELLO
的类型不是a.Greeting
。 这是a.Greeting$1
! (如上面仔细的读者会注意到的。)
带有一个或多个abstract
方法的enum
类在封面下创建子类(每个常量一个)。 所以发生的事情是,“隐藏”的子类没有被标记为public
,所以我们不允许看到这些类的public
方法。 游民。
理论的确认
为了测试这个理论,我们可以在子代上调用超类enum
的greet()
方法:
public static void main(String[] args) throws Exception {
Greeting g=Greeting.HELLO;
Method greet=g.getClass().getSuperclass().getMethod("greet");
System.out.println("Greeting "+g.getClass()+" ...");
greet.invoke(g);
System.out.println("Greeted!");
}
......并且会见成功:
Greeting class a.Greeting$1 ...
Hello!
Greeted!
此外,如果我们移动a.Greeting
到b.Greeting
(同一个包b.EnumTest
),那也有用,即使没有getSuperclass()
调用。
所以...错误或否?
所以......这是一个错误? 或者,这仅仅是不受欢迎的行为,它是底层实现的人工产物? 我检查了Java语言规范的相关部分,这个语法是合法的。 此外,规范并未指定如何安排子类,因此,虽然这在技术上违反了标准(或至少是我读过的标准的一部分),但我倾向于称它为一个错误。
StackOverflow认为:这是一个错误,还是仅仅是不希望的行为? 我意识到这是一个非常规的问题,所以请原谅格式。
另外,我在Mac上(如果有问题),并且对于任何想要重现的人员, java -version
打印以下内容:
$ java -version
java version "1.7.0_21"
Java(TM) SE Runtime Environment (build 1.7.0_21-b12)
Java HotSpot(TM) 64-Bit Server VM (build 23.21-b01, mixed mode)
编辑 :有趣的是找到自1997年以来类似(至少有关)问题的bug:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4071957
编辑 :根据下面的答案,JLS确实表示使用抽象方法的enum
类的行为应该像匿名类:
一个枚举常量的可选类体隐式定义了一个匿名类声明(第15.9.5节),它扩展了直接封装的枚举类型。 班级机构由匿名班级的常规规则管理
根据上面的错误,自1997年以来,匿名类处理一直是一个“bug”。因此,关于这是否实际上是一个bug,在这一点上是有点语义的。 底线:不要这样做,因为它不起作用,将来也不太可能。 :)
不是一个错误。
仔细检查异常消息显示,问题类是a.Greeting$1
。 这是一个匿名的内部类。 该方法恰好是公开的,不相关地,封闭类和被分配给的静态字段是公开的,但实际的类是非公开的。
a.Greeting.class
和a.Greeting.HELLO.getClass().getSuperclass()
应该可以工作。
因此,我们尝试调用该方法的值的类不是公共的。
Class.getMethod
在一个类(Class)上运行而不是一个值,所以这是不相关的(除非你试图让Field
a.Greeting.HELLO
。
编辑 :从Java语言规范:
“一个枚举常量的可选类体隐式定义了一个匿名类声明(第15.9.5节),它扩展了直接封装的枚举类型。类体由匿名类的常规规则管理; ...”
所以enum
被视为一个匿名类,这就是匿名类的工作方式。
上一篇: Possible bug in sun.reflect.Reflection handling of abstract enums?