出自《java puzzle》
这个谜题举例说明了一个关于反射的简单应用。这个程序会打印出什么呢?
Java代码
import java.util.*;
import java.lang.reflect.*;
public class Reflector {
public static void main(String[] args) throws Exception {
Set<String> s = new HashSet<String>();
s.add("foo");
Iterator it = s.iterator();
Method m = it.getClass().getMethod("hasNext");
System.out.println(m.invoke(it));
}
}
import java.util.*;
import java.lang.reflect.*;
public class Reflector {
public static void main(String[] args) throws Exception {
Set<String> s = new HashSet<String>();
s.add("foo");
Iterator it = s.iterator();
Method m = it.getClass().getMethod("hasNext");
System.out.println(m.invoke(it));
}
}
这个程序首先创建了一个只包含单个元素的集合(set),获得了该集合上的迭代器,然后利用反射调用了迭代器的hasNext方法,最后打印出此该方法调用的结果。由于该迭代器尚未返回该集合中那个唯一的元素,hasNext方法应该返回true。然而,运行这个程序却得到了截然不同的结果:
Exception in thread "main" java.lang.IllegalAccessException:
Class Reflector can not access a member of class HashMap$HashIterator with modifiers "public"
at Reflection.ensureMemberAccess(Reflection.java:65)
at Method.invoke(Method.java:578)
at Reflector.main(Reflector.java:11)
这是怎么发生的呢?正如这个异常所显示的,hasNext方法当然是公共的,所以它在任何地方都是可以被访问的。那么为什么这个基于反射的方法调用是非法的呢?这里的问题并不在于该方法的访问级别(access level),而在于该方法所在的类型的访问级别。这个类型所扮演的角色和一个普通方法调用中的限定类型(qualifying type)是相同的[JLS 13.1]。在这个程序中,该方法是从某个类中选择出来的,而这个类型是由从it.getClass方法返回的Class对象表示的。这是迭代器的动态类型(dynamic type),它恰好是私有的嵌套类(nested class) java.util.HashMap.KeyIterator。出现 IllegalAccessException 异常的原因就是这个类不是公共的,它来自另外一个包:访问位于其他包中的非公共类型的成员是不合法的[JLS 6.6.1]。无论是一般的访问还是通过反射的访问,上述的禁律都是有效的。下面这段没有使用反射的程序也违反了这条规则。
Java代码
package library;
public class Api{
static class PackagePrivate{}
public static PackagePrivate member = new PackagePrivate();
}
package client;
import library.Api;
class Client{
public static void main(String[] args){
System.out.println(Api.member.hashCode());
}
}
package library;
public class Api{
static class PackagePrivate{}
public static PackagePrivate member = new PackagePrivate();
}
package client;
import library.Api;
class Client{
public static void main(String[] args){
System.out.println(Api.member.hashCode());
}
}
尝试编译这段程序会得到如下的错误:
Client.java:5: Object.hashCode() isn't defined in a public
class or interface; can't be accessed from outside package
System.out.println(Api.member.hashCode());
^
这个错误与前面那个由含有反射的程序所产生的运行期错误具有相同的意义。Object类型和hashCode方法都是公共的。问题在于hashCode方法是通过一个限定类型调用的,但用户访问不到这个类型。该方法调用的限定类型是library.Api.PackagePrivate,这是一个位于其他包的非公共类型。
这并不意味着Client就不能调用Api.member的hashCode方法。要做到这一点,只需要使用一个可访问的限定类型即可,在这里可以将Api.member转型成Object。经过这样的修改之后,Client类就可以顺利地编译和运行了:
System.out.println(((Object)Api.member).hashCode());
实际上,这个问题并不会在普通的非反射的访问中出现,因为API的编写者在他们的公共API中只会使用公共的类型。即使这个问题有可能发生,它也会以编译期错误的形式显现出来,所以比较容易修改。而使用反射的访问就不同了,object.getClass().getMethod(“methodName”) 这种惯用法虽然很常见,但是却有问题的,它不应该被使用。就像我们在前面的程序中看到的那样,这种用法很容易在运行期产生一个 IllegalAccessException。
在使用反射访问某个类型时,请使用表示某种可访问类型的Class对象。回到我们前面的那个程序,hasNext方法是声明在一个公共类型 java.util.Iterator 中的,所以它的类对象应该被用来进行反射访问。经过这样的修改后,这个Reflector程序就会打印出true:
Method m = Iterator.class.getMethod("hasNext");
你完全可以避免这一类的问题,你应该只有在实例化时才使用反射,而方法调用都通过使用接口进行[EJ Item 35]。这种使用反射的用法,可以将那些调用方法的类与那些实现这些方法的类隔离开,并且提供了更高程度的类型安全。这种用法在“服务提供者框架”(Service Provider Frameworks)中很常见。这种模式并不能解决反射访问中的所有问题,但是如果它可以解决你所遇到的问题,请务必使用它。
总之,访问其他包中的非公共类型的成员是不合法的,即使这个成员同时也被声明为某个公共类型的公共成员也是如此。不论这个成员是否是通过反射被访问的,上述规则都是成立的。这个问题很有可能只在反射访问中才会出现。对于平台的设计者来说,这里的教训与谜题67中的一样,应该让错误症状尽可能清晰地显示出来。对于运行期的异常和编译期的提示都还有些东西需要改进。
分享到:
相关推荐
curl https://raw.githubusercontent.com/<your>/laptop/master/install.sh | bash 注意:没有sudo 注意:如果您不喜欢卷入bash,则可以安装git并克隆此仓库,然后运行./install.sh 。它是如何工作的?建筑有4种类型...
《PyPI官网下载:深入理解handy-dandy-0.1.3.tar.gz》 PyPI(Python Package Index)是Python编程语言的官方软件仓库,它为开发者提供了一个平台,可以发布并分享他们的Python库。在PyPI官网上,我们可以找到各种...
磁力云播 一个在任何地方都能解析磁力链接和种子文件的网站 它使用了 WebTorrent - 种子文件和磁力链接解析客户端的网站 WebTorrent 使用了 WebRTC进行真正的点对点传输。...实例:https://magnet.vmgirls.com
Jorden 和 Dandy Weyn。 - **出版社**:Wiley Publishing, Inc.。 - **出版日期**:2006年。 - **ISBN-13**:978-0-470-02565-9。 - **ISBN-10**:0-470-02565-4。 ### 四、主要内容概览 #### 1. SQL Server 2005...
《人事管理系统(dandy) V13.10.19 中文绿色免费版:解析与应用》 在信息化时代,人事管理系统已经成为企业管理中不可或缺的一部分。本文将深入探讨“人事管理系统(dandy) V13.10.19 中文绿色免费版”这一应用软件的...
**DWA PDF 生成器** 是一个专门针对 Dandy-Walker 联盟(DWA)的 Node.js 应用程序,它利用了 Express 框架来实现动态生成 PDF 文件的功能。在 IT 领域,PDF 生成是常见的需求,尤其是在数据报告、文档输出和电子...
Competiton中DAND团队(我愿意接受其他有关名字的其他建议-Fine和DANDy?,DANDy Lions?)条目的存储库。 该团队由Udacity的Data Analyst Nanodegree的毕业生组成。 这项竞赛的目的是建立一种算法,可以从头顶照片...
如:一个数据库名为dandy.db的,拷贝到本地中叫seeker.db 代码如下: /** * 拷贝数据库到sd卡 * * @deprecated <uses android:name=android.permission.WRITE_EXTERNAL_STORAGE/> */ public static void...
《dandy-diagram:探索draw.io的图表世界》 在数字化的世界中,图表已经成为表达想法、展示数据和规划项目的重要工具。"dandy-diagram"是基于流行在线绘图工具draw.io的一个项目,它提供了丰富的图形资源和强大的...
Notepad++是一款非常受欢迎的免费源代码编辑器,尤其在编程领域中被广泛使用。它基于微软的Windows操作系统,支持多种编程语言,并且是用C++编写的,以提高效率和减少内存占用。Notepad++6.92是该软件的一个特定版本...
* printf函数用于输出格式化的字符串,如printf("Candy\nIs dandy\nBut liquor\nIs quicker.\n")。 * scanf函数用于输入格式化的数据,如scanf("%d", &x)。 3. 数组和指针(Arrays and Pointers): * 数组是存储...
您是否曾经因为仅仅为了保存和加载数据而使Java变得如此荒谬而诅咒Java? 您是否曾经说过这样的话:“我要做的就是保存用户的数据!我不想处理您的BufferedWriters和HashMapThingies之类的东西!” ...都以明显的...
Dialob文档 具有Material UI风格的React-Typescript文档应用程序以及具有dandy-doc库的解析/渲染markdown文件。 可在此处查看文档站点: : dandy-doc库回购: : 构建和部署 ... 它们将由dandy-doc自
这个方便的dandy扩展是一个有用的套件,可以保留在浏览器的reptoire中。它监视加载到选项卡中的主URL,并在URL中识别公共拼写错误。当找到拼写错误时,齿轮图标显示在地址栏中,纠正拼写错误,轻松单击,或用一个按...
例如,原句"My friend Dandy never tells my secrets to others." 可以转化为 "My friend Dandy can keep secrets for me.","There is nothing else in the bowl." 可以简化为"There is nothing else." 10. 重复与...
【标题】"D_E31192292_Dandy-Akbar-Firmansyah_WebFramework" 提示我们这是一个与Web框架相关的项目,很可能是一个由Dandy Akbar Firmansyah开发或参与的个人作品。这个项目的核心可能是用PHP语言实现的一个Web应用...
《基于Qt5和OpenCV4的图像编辑软件解析》 本文将深入探讨“ImageEditor.zip”这个压缩包中所包含的图像编辑软件,该软件利用了Qt5和OpenCV4的强大功能,尤其值得一提的是它对Qt plugin插件机制的巧妙应用。...
:oncoming_fist: ES6-Webapp 在底层使用 Babel 将 ES6 转译为 ES5,然后使用 Browserify 将所有依赖项打包到一个 dandy 文件中。较少的LESS 很棒,开箱即用。 由于所有 CSS 都是有效的 LESS,因此您可以直接
"handy_dandy_tools" 是一个与Python相关的项目,它可能包含一系列实用工具或脚本,旨在帮助用户解决日常编程任务。尽管描述中提到“希望有一天,这将是一大堆有用的东西”,并且提及了“带有一些实际有用的自述文件...
"Jim Dandy"这个标题可能指的是一个特定的字体或者与字体设计相关的工作。在IT行业中,字体设计是用户界面和视觉传达的重要组成部分。字体不仅影响文本的可读性,还传达了品牌、网站或应用程序的风格和氛围。下面将...