故事背景

小白是个程序猿,刚毕业两年,最近交了一个女朋友,是同事介绍的。女朋友和闺蜜住在一起。小白早上很早接到女朋友电话,昨天她的一个文件错放到了他的电脑包,希望他帮忙送到她住的地方,她今天要向她boss汇报的。

救急如救火,为了好好表现自己,小白赶紧打了个车到女朋友的小区,然后在小区门口等她。早上7点,人流如织,等了许久,没有见到,遂电话之,被女友的闺蜜告知,女友未化妆素颜下来找他,未带电话,请他好好辨别。

小白知道有一句话特别流行,说现在女生有着三分的长相,画着五分的妆容,看着美颜中七分的自己。小伙伴们都说了女生化妆到底有多厉害?

这个怎么办呢?小白灵机一动,敌不动我不动,敌一动,我以静制动。遂带上眼镜,举着文件,站在一个显眼的位置,自己观察走来的女生,天见可怜,最终将文件交到一个看着有点熟悉又有点陌生的女孩手里,完成任务。

java反射的故事

  工作中,小白也碰到同样的一个问题,他希望下面的程序打印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 = it.getClass().getMethod("hasNext");
System.out.println(m.invoke(it));
}

运行时,报错如下:

Exception in thread "main" java.lang.IllegalAccessException: Class com.javapuzzle.davidwang456.ReflectorTest can not access a member of class java.util.HashMap$HashIterator with modifiers "public final"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
at java.lang.reflect.Method.invoke(Method.java:491)
at com.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
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));
}

经验教训

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

参考资料:

【1】https://new.qq.com/omn/20190527/20190527A07COH.html?pc

【2】https://docs.oracle.com/javase/specs/jls/se12/html/jls-6.html#jls-6.6.1

【3】java解惑

如何在女友卸妆后,正确的找到她?---java中使用反射的小秘密的更多相关文章

  1. IDEA运行编译后配置文件无法找到,或配置文件修改后无效的问题

    1.触发事件 今天正好在学习log4j,为了测试其配置文件log4j.properties中的各种配置,进行了频繁修改和程序启动以确认效果,因为是使用的IDEA建立的Web项目,接着问题就来了,配置文 ...

  2. 管道命令'|' 和xargs find命令找到后把所有找到的删除

    管道符号,是unix功能强大的一个地方,符号是一条竖线:"|", 用法: command 1 | command 2 他的功能是把第一个命令command 1执行的结果作为comm ...

  3. IM开发基础知识补课(四):正确理解HTTP短连接中的Cookie、Session和Token

    本文引用了简书作者“骑小猪看流星”技术文章“Cookie.Session.Token那点事儿”的部分内容,感谢原作者. 1.前言 众所周之,IM是个典型的快速数据流交换系统,当今主流IM系统(尤其移动 ...

  4. 如何在 Java 中正确使用 wait, notify 和 notifyAll(转)

    wait, notify 和 notifyAll,这些在多线程中被经常用到的保留关键字,在实际开发的时候很多时候却并没有被大家重视.本文对这些关键字的使用进行了描述. 在 Java 中可以用 wait ...

  5. 如何在 Java 中正确使用 wait, notify 和 notifyAll – 以生产者消费者模型为例

    wait, notify 和 notifyAll,这些在多线程中被经常用到的保留关键字,在实际开发的时候很多时候却并没有被大家重视.本文对这些关键字的使用进行了描述. 在 Java 中可以用 wait ...

  6. 【算法】数组与矩阵问题——找到无序数组中最小的k个数

    /** * 找到无序数组中最小的k个数 时间复杂度O(Nlogk) * 过程: * 1.一直维护一个有k个数的大根堆,这个堆代表目前选出来的k个最小的数 * 在堆里的k个元素中堆顶的元素是最小的k个数 ...

  7. 已知前序(后序)遍历序列和中序遍历序列构建二叉树(Leetcode相关题目)

    1.文字描述: 已知一颗二叉树的前序(后序)遍历序列和中序遍历序列,如何构建这棵二叉树? 以前序为例子: 前序遍历序列:ABCDEF 中序遍历序列:CBDAEF 前序遍历先访问根节点,因此前序遍历序列 ...

  8. 如何正确实现 Java 中的 HashCode

    相等 和 Hash Code 从一般角度来看,Equality 是不错的,但是 hash code 更则具技巧性.如果我们在 hash code上多下点功夫,我们就能了解到 hash code 就是用 ...

  9. Java web 开发填坑记 2 -如何正确的创建一个Java Web 项目

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/72566261 本文出自[赵彦军的博客] Java web 开发填坑记 1-如何正确 ...

随机推荐

  1. 【阿里云IoT+YF3300】4.Alink物模型之事件触发

    名词解释:设备的功能模型之一,设备运行时的事件,事件一般包含需要被外部感知和处理的通知信息,可包含多个输出参数.如,某项任务完成的信息,或者设备发生故障或告警时的温度等,事件可以被订阅和推送. 在工控 ...

  2. Spring学习之旅(十五)--SpringBoot

    在使用 Spring 的过程中,有时候会出现一些 ClassNotFoundException 异常,这是因为 JAR 依赖之间的版本不匹配所导致的.而 Spring Boot 就能避免绝大多数依赖版 ...

  3. 你知道JavaScript这六种错误类型吗?

    前言 今日话题,了解JavaScript的错误处理机制. 一.ReferenceError 引用一个不存在的变量时发生的错误.将一个值分配给无法分配的对象,比如对函数的运行结果或者函数赋值. 举栗子 ...

  4. 在C和C++中struct与typedef struct的区别详细介绍

    c中测试code struct Cmstruct { int c; } Cm; // Cm是一个变量 typedef struct MyStruct //这里的 Mystruct 可以省略 { int ...

  5. Netty源码分析 (一)----- NioEventLoopGroup

    提到Netty首当其冲被提起的肯定是支持它承受高并发的线程模型,说到线程模型就不得不提到NioEventLoopGroup这个线程池,接下来进入正题. 线程模型 首先来看一段Netty的使用示例 pa ...

  6. codeforces 284 E. Coin Troubles(背包+思维)

    题目链接:http://codeforces.com/contest/284/problem/E 题意:n种类型的硬币,硬币的面值可能相同,现在要在满足一些限制条件下求出,用这些硬币构成t面值的方案数 ...

  7. mybatis_plus插件——生成器

    最近在学习mybatis框架,虽然已经简化了一些Dao代码,但是还想更上一层楼吗?不再被基本的pojo层,controller层,service层,dao层基本重复代码所困恼吗?这里,让我们来学习一下 ...

  8. Vert.x学习之 Web Client

    Vert.x Web Client 原文档 组件源码 组件示例 中英对照表 Pump:泵(平滑流式数据读入内存的机制,防止一次性将大量数据读入内存导致内存溢出) Response Codec:响应编解 ...

  9. hadoop 通过distcp进行并行复制

    通过distcp进行并行复制 前面的HDFS访问模型都集中于单线程的访问.例如通过指定文件通配,我们可以对一部分文件进行处理,但是为了高效,对这些文件的并行处理需要新写一个程序.Hadoop有一个叫d ...

  10. mysql生成主键

    在mysql中,可以使用uuid 来生成主键,但是用mysql的uuid()函数 ,生成的uuid是36位的,其中包含32个字符以及4个分隔符(-), 往往这个分隔符对我们来说是没有用的,可以使用my ...