Java小白踩坑录 - 反射的小秘密

简介: Java小白踩坑录 - 反射的小秘密

Java 反射的故事

工作中,小白也碰到同样的一个问题,他希望下面的程序打印 true:

publicstaticvoidmain(String[] args) throwsException {
Set<String>s=newHashSet<String>();
s.add("foo");
Iterator<String>it=s.iterator();
Methodm=it.getClass().getMethod("hasNext");
System.out.println(m.invoke(it));
}

运行时,报错如下:

Exceptioninthread"main"java.lang.IllegalAccessException: Classcom.javapuzzle.davidwang456.ReflectorTestcannotaccessamemberofclassjava.util.HashMap$HashIteratorwithmodifiers"public final"atsun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
atjava.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
atjava.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
atjava.lang.reflect.Method.invoke(Method.java:491)
atcom.javapuzzle.davidwang456.ReflectorTest.main(ReflectorTest.java:15)

hasNext 方法当然是公共的,所以它在任何地方都是可以被访问的。那么为什么这个基于反射的方法调用是非法的呢?我们看一下 JSL 定义的规范【https://docs.oracle.com/javase/specs/jls/se12/html/jls-6.html#jls-6.6.1】

If a top level class or interface type is declared public and is a member of a package that is exported by a module, then the type may be accessed by any code in the same module, and by any code in another module to which the package is exported, provided that the compilation unit in which the type is declared is visible to that other module (§7.3).

If a top level class or interface type is declared public and is a member of a package that is not exported by a module, then the type may be accessed by any code in the same module.

If a top level class or interface type is declared with package access, then it may be accessed only from within the package in which it is declared.

A top level class or interface type declared without an access modifier implicitly has package access.

A member (class, interface, field, or method) of a reference type, or a constructor of a class type, is accessible only if the type is accessible and the member or constructor is declared to permit access:

If the member or constructor is declared public, then access is permitted.

All members of interfaces lacking access modifiers are implicitly public.

Otherwise, if the member or constructor is declared protected, then access is permitted only when one of the following is true:

Access to the member or constructor occurs from within the package containing the class in which the protected member or constructor is declared.

Access is correct as described in §6.6.2.

Otherwise, if the member or constructor is declared with package access, then access is permitted only when the access occurs from within the package in which the type is declared.

A class member or constructor declared without an access modifier implicitly has package access.

Otherwise, the member or constructor is declared private, and access is permitted if and only if it occurs within the body of the top level type (§7.6) that encloses the declaration of the member or constructor.

An array type is accessible if and only if its element type is accessible.

其中一条,如果类或接口在声明时没任何访问权限修饰符,那么它就隐式地被赋予了包访问权限控制。 我们看看调用情况:

1.HashSet 默认调用 HashMap 生成方式:

/**
 * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
 * default initial capacity (16) and load factor (0.75).
 */
 public HashSet() {
 map = new HashMap<>();
 }

2. 调用 HashMap.KeyIterator 类

 final class KeyIterator extends HashIterator
 implements Iterator<K> {
 public final K next() { return nextNode().key; }
 }

hasNext () 方法,调用父类 HashMap.HashIterator 的 hasNext () 方法:

 abstract class HashIterator {
  Node<K,V> next; // next entry to return要返回的下一个节点
  Node<K,V> current; // current entry 当前节点
   int expectedModCount; // for fast-fail 支持fast-fail
  int index; // current slot  当前索引
  HashIterator() {
      expectedModCount = modCount;
    Node<K,V>[] t = table;
    current = next = null;
    index = 0;
    if (t != null && size > 0) { // advance to first entry第一个节点优先
      do {} while (index < t.length && (next = t[index++]) == null);
    }
     }
  public final boolean hasNext() {
    return next != null;
  }
........
}

我们看到 HashIterator 是 HashMap 的子类,并没有授予 public 权限,那么默认情况下的访问权限是:包访问权限,即它可以被包内的类调用。


这里的问题,并不在于该方法的访问级别(access level),而在于该方法所在的类型的访问级别。这个类型所扮演的角色和一个普通方法调用中的限定类型(qualifying type)是相同的 [JLS 13.1]。在这个程序中,该方法是从某个类中选择出来的,而这个类型是由从 it.getClass 方法返回的 Class 对象表示的。这是迭代器的动态类型(dynamic type),它恰好是私有的嵌套类(nested class)java.util.HashMap.KeyIterator。出现 IllegalAccessException 异常的原因就是这个类不是公共的,它来自另外一个包:访问位于其他包中的非公共类型的成员是不合法的 [JLS 6.6.1]。无论是一般的访问还是通过反射的访问,上述的禁律都是有效的。


问题解决思路

在使用反射访问某个类型时,请使用表示某种可访问类型的 Class 对象。hasNext 方法是声明在一个公共类型 java.util.Iterator 中的,所以它的类对象应该被用来进行反射访问。经过这样的修改后,这个程序就会打印出 true。

public static void main(String[] args) throws Exception {
     Set<String> s = new HashSet<String>();
     s.add("foo");
     Iterator<String> it = s.iterator();
     Method m = Iterator.class.getMethod("hasNext");
     System.out.println(m.invoke(it));
}

经验教训

总之,访问其他包中的非公共类型的成员是不合法的,即使这个成员同时也被声明为某个公共类型的公共成员也是如此。不论这个成员是否是通过反射被访问的,上述规则都是成立的。

目录
相关文章
|
12月前
|
Java 数据库连接 Spring
反射-----浅解析(Java)
在java中,我们可以通过反射机制,知道任何一个类的成员变量(成员属性)和成员方法,也可以堆任何一个对象,调用这个对象的任何属性和方法,更进一步我们还可以修改部分信息和。
|
监控 Java
Java基础——反射
本文介绍了Java反射机制的基本概念和使用方法,包括`Class`类的使用、动态加载类、获取方法和成员变量信息、方法反射操作、以及通过反射了解集合泛型的本质。同时,文章还探讨了动态代理的概念及其应用,通过实例展示了如何利用动态代理实现面向切面编程(AOP),例如为方法执行添加性能监控。
148 5
|
安全 Java 索引
Java——反射&枚举
本文介绍了Java反射机制及其应用,包括获取Class对象、构造方法、成员变量和成员方法。反射允许在运行时动态操作类和对象,例如创建对象、调用方法和访问字段。文章详细解释了不同方法的使用方式及其注意事项,并展示了如何通过反射获取类的各种信息。此外,还介绍了枚举类型的特点和使用方法,包括枚举的构造方法及其在反射中的特殊处理。
245 9
Java——反射&枚举
|
Java
Java的反射
Java的反射。
130 2
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
安全 Java 测试技术
🌟Java零基础-反射:从入门到精通
【10月更文挑战第4天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
192 2
|
Java 程序员 编译器
Java的反射技术reflect
Java的反射技术允许程序在运行时动态加载和操作类,基于字节码文件构建中间语言代码,进而生成机器码在JVM上执行,实现了“一次编译,到处运行”。此技术虽需更多运行时间,但广泛应用于Spring框架的持续集成、动态配置及三大特性(IOC、DI、AOP)中,支持企业级应用的迭代升级和灵活配置管理,适用于集群部署与数据同步场景。
|
IDE Java 编译器
java的反射与注解
java的反射与注解
103 0
|
安全 Java 测试技术
day26:Java零基础 - 反射
【7月更文挑战第26天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
93 5
|
存储 安全 Java
扫盲java基础-反射(一)
扫盲java基础-反射(一)