class="markdown_views prism-github-gist">
本篇文章是在深入Java虚拟机(四)
的基础上产生的一些想法,从另一个方面研究下类加载问题。
面向接口编程
在项目工程目录定义了一个获取钱包余额的接口,而作为普通人会返回50大洋,供你吃喝玩乐。
class="prism language-class ="tags" href="/tags/JAVA.html" title=java>java">class="token keyword">public class="token keyword">interface class="token class -name">Pocket class="token punctuation">{
class="token keyword">int class="token function">getBalance class="token punctuation">( class="token punctuation">) class="token punctuation">;
class="token punctuation">}
class="token keyword">public class="token keyword">class class="token class -name">NormalUser class="token keyword">implements class="token class -name">Pocket class="token punctuation">{
class="token annotation punctuation">@Override
class="token keyword">public class="token keyword">int class="token function">getBalance class="token punctuation">( class="token punctuation">) class="token punctuation">{
class="token keyword">return class="token number">50 class="token punctuation">;
class="token punctuation">}
class="token punctuation">}
我在桌面编译了另一个版本的NormalUser
,给我的钱包提提额度。
class="prism language-class ="tags" href="/tags/JAVA.html" title=java>java">class="token keyword">public class="token keyword">class class="token class -name">NormalUser class="token keyword">implements class="token class -name">Pocket class="token punctuation">{
class="token annotation punctuation">@Override
class="token keyword">public class="token keyword">int class="token function">getBalance class="token punctuation">( class="token punctuation">) class="token punctuation">{
class="token keyword">return class="token number">50000 class="token punctuation">;
class="token punctuation">}
class="token punctuation">}
现在我想改善下生活品质,于是我做了下面的事情:
在项目中自定义一个类加载器:
class="prism language-class ="tags" href="/tags/JAVA.html" title=java>java">class="token keyword">public class="token keyword">class class="token class -name">LocalClassLoader class="token keyword">extends class="token class -name">ClassLoader class="token punctuation">{
class="token annotation punctuation">@Override
class="token keyword">protected Classclass="token operator">< class="token operator">? class="token operator">> class="token function">findClass class="token punctuation">( String nameclass="token punctuation">) class="token keyword">throws ClassNotFoundException class="token punctuation">{
String path class="token operator">= class="token string">"/Users/lijie/Desktop/NormalUser.class " class="token punctuation">;
class="token keyword">try class="token punctuation">{
FileInputStream ins class="token operator">= class="token keyword">new class="token class -name">FileInputStream class="token punctuation">( pathclass="token punctuation">) class="token punctuation">;
class="token keyword">int size class="token operator">= insclass="token punctuation">. class="token function">available class="token punctuation">( class="token punctuation">) class="token punctuation">;
class="token keyword">byte class="token punctuation">[ class="token punctuation">] clazzBytes class="token operator">= class="token keyword">new class="token class -name">byte class="token punctuation">[ sizeclass="token punctuation">] class="token punctuation">;
class="token keyword">if class="token punctuation">( insclass="token punctuation">. class="token function">read class="token punctuation">( clazzBytesclass="token punctuation">) class="token operator">> class="token number">0 class="token punctuation">) class="token punctuation">{
class="token keyword">return class="token function">defineClass class="token punctuation">( class="token string">"hua.lee.class loader.NormalUser" class="token punctuation">, clazzBytesclass="token punctuation">, class="token number">0 class="token punctuation">, sizeclass="token punctuation">) class="token punctuation">;
class="token punctuation">} class="token keyword">else class="token punctuation">{
class="token keyword">throw class="token keyword">new class="token class -name">ClassNotFoundException class="token punctuation">( class="token punctuation">) class="token punctuation">;
class="token punctuation">}
class="token punctuation">} class="token keyword">catch class="token punctuation">( class="token class -name">IOException eclass="token punctuation">) class="token punctuation">{
eclass="token punctuation">. class="token function">printStackTrace class="token punctuation">( class="token punctuation">) class="token punctuation">;
class="token punctuation">}
class="token keyword">throw class="token keyword">new class="token class -name">ClassNotFoundException class="token punctuation">( class="token punctuation">) class="token punctuation">;
class="token punctuation">}
class="token punctuation">}
LocalClassLoader
写的比较死板,但是挣钱的思路是有了的。。。。
LocalClassLoader
重写了 findClass
方法,没有重写loadClass
,这样双亲委托机制
还有效。对于系统中查找不到的类型,都会走到这里,而在findClass
方法中,我就会把桌面的NormalUser
加载进来。
我们看下测试代码:
class="prism language-class ="tags" href="/tags/JAVA.html" title=java>java">class="token keyword">public class="token keyword">class class="token class -name">ClassLoaderTest class="token punctuation">{
class="token keyword">public class="token keyword">static class="token keyword">void class="token function">main class="token punctuation">( Stringclass="token punctuation">[ class="token punctuation">] argsclass="token punctuation">) class="token keyword">throws ClassNotFoundExceptionclass="token punctuation">, IllegalAccessExceptionclass="token punctuation">, InstantiationException class="token punctuation">{
Classclass="token operator">< class="token operator">? class="token operator">> clazz class="token operator">= Classclass="token punctuation">. class="token function">forName class="token punctuation">( class="token string">"hua.lee.class loader.NormalUser" class="token punctuation">) class="token punctuation">;
Systemclass="token punctuation">. outclass="token punctuation">. class="token function">println class="token punctuation">( class="token string">"系统自带 ClassLoader=" class="token operator">+ clazzclass="token punctuation">. class="token function">getClassLoader class="token punctuation">( class="token punctuation">) class="token punctuation">) class="token punctuation">;
Pocket pocket class="token operator">= class="token punctuation">( Pocketclass="token punctuation">) clazzclass="token punctuation">. class="token function">newInstance class="token punctuation">( class="token punctuation">) class="token punctuation">;
Systemclass="token punctuation">. outclass="token punctuation">. class="token function">println class="token punctuation">( class="token string">"系统自带:" class="token operator">+ pocketclass="token punctuation">. class="token function">getBalance class="token punctuation">( class="token punctuation">) class="token punctuation">) class="token punctuation">;
class="token comment">//没有重写 loadClass,维持双亲委派机制
LocalClassLoader lcl class="token operator">= class="token keyword">new class="token class -name">LocalClassLoader class="token punctuation">( class="token punctuation">) class="token punctuation">;
class="token comment">//我们加个后缀名,这样双亲委托查找失败了,就调用LocalClassLoader.findClass 方法加载
class="token comment">//其实 findClass 方法还是加载 NormalUser类,只是换了个位置和方式:
class="token comment">//defineClass("hua.lee.class loader.NormalUser", clazzBytes, 0, size);
Classclass="token operator">< class="token operator">? class="token operator">> clazzOut02 class="token operator">= lclclass="token punctuation">. class="token function">loadClass class="token punctuation">( class="token string">"hua.lee.class loader.NormalUser_Temp" class="token punctuation">) class="token punctuation">;
class="token comment">//双亲委托机制先检查当前类加载器中有没有这个类的 Class 实例,没有的话再搜寻父类,
class="token comment">//查找到 hua.lee.class loader.NormalUser 的 Class 实例,就返回该对象
Classclass="token operator">< class="token operator">? class="token operator">> clazzOut01 class="token operator">= lclclass="token punctuation">. class="token function">loadClass class="token punctuation">( class="token string">"hua.lee.class loader.NormalUser" class="token punctuation">) class="token punctuation">;
Systemclass="token punctuation">. outclass="token punctuation">. class="token function">println class="token punctuation">( class="token string">"clazzOut01 自定义 ClassLoader=" class="token operator">+ clazzOut01class="token punctuation">. class="token function">getClassLoader class="token punctuation">( class="token punctuation">) class="token punctuation">) class="token punctuation">;
pocket class="token operator">= class="token punctuation">( Pocketclass="token punctuation">) clazzOut01class="token punctuation">. class="token function">newInstance class="token punctuation">( class="token punctuation">) class="token punctuation">;
Systemclass="token punctuation">. outclass="token punctuation">. class="token function">println class="token punctuation">( class="token string">"clazzOut01 自定义加载器获取余额:" class="token operator">+ pocketclass="token punctuation">. class="token function">getBalance class="token punctuation">( class="token punctuation">) class="token punctuation">) class="token punctuation">;
Systemclass="token punctuation">. outclass="token punctuation">. class="token function">println class="token punctuation">( class="token string">"clazzOut02 自定义 ClassLoader=" class="token operator">+ clazzOut02class="token punctuation">. class="token function">getClassLoader class="token punctuation">( class="token punctuation">) class="token punctuation">) class="token punctuation">;
pocket class="token operator">= class="token punctuation">( Pocketclass="token punctuation">) clazzOut02class="token punctuation">. class="token function">newInstance class="token punctuation">( class="token punctuation">) class="token punctuation">;
Systemclass="token punctuation">. outclass="token punctuation">. class="token function">println class="token punctuation">( class="token string">"clazzOut02 自定义加载器获取余额:" class="token operator">+ pocketclass="token punctuation">. class="token function">getBalance class="token punctuation">( class="token punctuation">) class="token punctuation">) class="token punctuation">;
Systemclass="token punctuation">. outclass="token punctuation">. class="token function">println class="token punctuation">( clazzclass="token operator">== clazzOut01class="token punctuation">) class="token punctuation">;
Systemclass="token punctuation">. outclass="token punctuation">. class="token function">println class="token punctuation">( clazzclass="token operator">== clazzOut02class="token punctuation">) class="token punctuation">;
Systemclass="token punctuation">. outclass="token punctuation">. class="token function">println class="token punctuation">( clazzOut01class="token operator">== clazzOut02class="token punctuation">) class="token punctuation">;
class="token punctuation">}
控制台输出是这样的:
系统自带 ClassLoader=sun.misc.Launcher$AppClassLoader@18b4aac2
系统自带:50
clazzOut01 自定义 ClassLoader=hua.lee.class loader.LocalClassLoader@60e53b93
clazzOut01 自定义加载器获取余额:50000
clazzOut02 自定义 ClassLoader=hua.lee.class loader.LocalClassLoader@60e53b93
clazzOut02 自定义加载器获取余额:50000
false //clazz==clazzOut01
false //clazz==clazzOut02
true //clazzOut01==clazzOut02
感觉找到了致富的方向。虚拟机里的加载约束
没看到呢.
面向抽象类编程
上面的致富之路好像很顺利,那我们尝试用如下的方式:
class="prism language-class ="tags" href="/tags/JAVA.html" title=java>java">class="token keyword">public class="token keyword">abstract class="token keyword">class class="token class -name">Pocket class="token punctuation">{
class="token keyword">abstract class="token keyword">int class="token function">getBalance class="token punctuation">( class="token punctuation">) class="token punctuation">;
class="token punctuation">}
class="token keyword">public class="token keyword">class class="token class -name">NormalUser class="token keyword">extends class="token class -name">Pocket class="token punctuation">{
class="token annotation punctuation">@Override
class="token keyword">public class="token keyword">int class="token function">getBalance class="token punctuation">( class="token punctuation">) class="token punctuation">{
class="token keyword">return class="token number">50 class="token punctuation">;
class="token punctuation">}
class="token punctuation">}
同样,我在桌面编译了一个富人版本:
class="prism language-class ="tags" href="/tags/JAVA.html" title=java>java">class="token keyword">public class="token keyword">class class="token class -name">NormalUser class="token keyword">extends class="token class -name">Pocket class="token punctuation">{
class="token annotation punctuation">@Override
class="token keyword">public class="token keyword">int class="token function">getBalance class="token punctuation">( class="token punctuation">) class="token punctuation">{
class="token keyword">return class="token number">50000 class="token punctuation">;
class="token punctuation">}
class="token punctuation">}
LocalClassLoader
和测试代码没变,但是打印结果确实这样的:
系统自带 ClassLoader=sun.misc.Launcher$AppClassLoader@18b4aac2
系统自带:50
clazzOut01 自定义 ClassLoader=hua.lee.class loader.LocalClassLoader@60e53b93
Exception in thread "main" class ="tags" href="/tags/JAVA.html" title=java>java.lang.AbstractMethodError
at hua.lee.class loader.ClassLoaderTest.main(ClassLoaderTest.class ="tags" href="/tags/JAVA.html" title=java>java:25)
虚拟机在解析抽象类实现
和接口实现
的时候有什么区别么 ?
不成熟的推测
首先面向接口
中的模式应该是躲过了装载约束的,或者虚拟机设计上是支持接口这么搞的。
既然提示AbstractMethodError
,那我把测试代码改一下,不再用抽象类Pocket
,直接指向NormalUser
:
class="prism language-class ="tags" href="/tags/JAVA.html" title=java>java">class="token keyword">public class="token keyword">class class="token class -name">ClassLoaderTest class="token punctuation">{
class="token keyword">public class="token keyword">static class="token keyword">void class="token function">main class="token punctuation">( Stringclass="token punctuation">[ class="token punctuation">] argsclass="token punctuation">) class="token keyword">throws ClassNotFoundExceptionclass="token punctuation">, IllegalAccessExceptionclass="token punctuation">, InstantiationException class="token punctuation">{
Classclass="token operator">< class="token operator">? class="token operator">> clazz class="token operator">= Classclass="token punctuation">. class="token function">forName class="token punctuation">( class="token string">"hua.lee.class loader.NormalUser" class="token punctuation">) class="token punctuation">;
Systemclass="token punctuation">. outclass="token punctuation">. class="token function">println class="token punctuation">( class="token string">"系统自带 ClassLoader=" class="token operator">+ clazzclass="token punctuation">. class="token function">getClassLoader class="token punctuation">( class="token punctuation">) class="token punctuation">) class="token punctuation">;
class="token comment">//请注意此处⬇️
NormalUser pocket class="token operator">= class="token punctuation">( NormalUserclass="token punctuation">) clazzclass="token punctuation">. class="token function">newInstance class="token punctuation">( class="token punctuation">) class="token punctuation">;
class="token comment">//请注意此处⬆️
Systemclass="token punctuation">. outclass="token punctuation">. class="token function">println class="token punctuation">( class="token string">"系统自带:" class="token operator">+ pocketclass="token punctuation">. class="token function">getBalance class="token punctuation">( class="token punctuation">) class="token punctuation">) class="token punctuation">;
class="token comment">//没有重写 loadClass,维持双亲委派机制
LocalClassLoader lcl class="token operator">= class="token keyword">new class="token class -name">LocalClassLoader class="token punctuation">( class="token punctuation">) class="token punctuation">;
class="token comment">//我们加个后缀名,这样双亲委托查找失败了,就调用LocalClassLoader.findClass 方法加载
class="token comment">//其实 findClass 方法还是加载 NormalUser类,只是换了个位置和方式:
class="token comment">//defineClass("hua.lee.class loader.NormalUser", clazzBytes, 0, size);
Classclass="token operator">< class="token operator">? class="token operator">> clazzOut02 class="token operator">= lclclass="token punctuation">. class="token function">loadClass class="token punctuation">( class="token string">"hua.lee.class loader.NormalUser_Temp" class="token punctuation">) class="token punctuation">;
class="token comment">//双亲委托机制会自动搜寻父类,
class="token comment">//查找到 hua.lee.class loader.NormalUser 的 Class 实例,就返回该对象
Classclass="token operator">< class="token operator">? class="token operator">> clazzOut01 class="token operator">= lclclass="token punctuation">. class="token function">loadClass class="token punctuation">( class="token string">"hua.lee.class loader.NormalUser" class="token punctuation">) class="token punctuation">;
Systemclass="token punctuation">. outclass="token punctuation">. class="token function">println class="token punctuation">( class="token string">"clazzOut01 自定义 ClassLoader=" class="token operator">+ clazzOut01class="token punctuation">. class="token function">getClassLoader class="token punctuation">( class="token punctuation">) class="token punctuation">) class="token punctuation">;
pocket class="token operator">= class="token punctuation">( NormalUserclass="token punctuation">) clazzOut01class="token punctuation">. class="token function">newInstance class="token punctuation">( class="token punctuation">) class="token punctuation">;
Systemclass="token punctuation">. outclass="token punctuation">. class="token function">println class="token punctuation">( class="token string">"clazzOut01 自定义加载器获取余额:" class="token operator">+ pocketclass="token punctuation">. class="token function">getBalance class="token punctuation">( class="token punctuation">) class="token punctuation">) class="token punctuation">;
Systemclass="token punctuation">. outclass="token punctuation">. class="token function">println class="token punctuation">( class="token string">"clazzOut02 自定义 ClassLoader=" class="token operator">+ clazzOut02class="token punctuation">. class="token function">getClassLoader class="token punctuation">( class="token punctuation">) class="token punctuation">) class="token punctuation">;
pocket class="token operator">= class="token punctuation">( NormalUserclass="token punctuation">) clazzOut02class="token punctuation">. class="token function">newInstance class="token punctuation">( class="token punctuation">) class="token punctuation">;
Systemclass="token punctuation">. outclass="token punctuation">. class="token function">println class="token punctuation">( class="token string">"clazzOut02 自定义加载器获取余额:" class="token operator">+ pocketclass="token punctuation">. class="token function">getBalance class="token punctuation">( class="token punctuation">) class="token punctuation">) class="token punctuation">;
Systemclass="token punctuation">. outclass="token punctuation">. class="token function">println class="token punctuation">( clazzclass="token operator">== clazzOut01class="token punctuation">) class="token punctuation">;
Systemclass="token punctuation">. outclass="token punctuation">. class="token function">println class="token punctuation">( clazzclass="token operator">== clazzOut02class="token punctuation">) class="token punctuation">;
Systemclass="token punctuation">. outclass="token punctuation">. class="token function">println class="token punctuation">( clazzOut01class="token operator">== clazzOut02class="token punctuation">) class="token punctuation">;
class="token punctuation">}
class="token punctuation">}
惊喜的事情来了,输出变成了下面这样:
系统自带 ClassLoader=sun.misc.Launcher$AppClassLoader@18b4aac2
系统自带:50
clazzOut01 自定义 ClassLoader=hua.lee.class loader.LocalClassLoader@60e53b93
Exception in thread "main" class ="tags" href="/tags/JAVA.html" title=java>java.lang.ClassCastException: hua.lee.class loader.NormalUser cannot be cast to hua.lee.class loader.NormalUser
at hua.lee.class loader.ClassLoaderTest.main(ClassLoaderTest.class ="tags" href="/tags/JAVA.html" title=java>java:24)
这个异常说明了一个问题我们自定义加载进来的hua.lee.class loader.NormalUser
不是虚拟机想要的hua.lee.class loader.NormalUser
。
第一个问题:虚拟机怎么知道这两个NormalUser
不一样的?
和定义类装载器
有关系,同一个全限定名的类型,如果不是同一个定义类装载器
,虚拟机就认为不是同一个类型。(按照装载约束应该报错的)
啥是定义类装载器?就是真正把 class 文件装载进来的那个ClassLoader
第二个问题:为什么接口可以这样跑呢?
那是因为面向接口实现
的版本没有明确指名类型NormalUser
,类型转换都是按照Pocket
类型执行方法,虚拟机自动查找接口类型的实现。只要是Pocket
的实现就行。
如果你把pocket = (Pocket) clazzOut01.newInstance();
换成pocket = (NormalUser) clazzOut01.newInstance();
,此时,面向接口
的版本也会报ClassCastException
。
对于虚拟机来说,在接口的实现类,它并不 care 你的实现类的来源(哪个加载器加载的无所谓啦),符合接口定义就可以了。
但是当你指定类型转换的时候,它就要去做类型检查了。
第三个问题:抽象类为什么不行?
首先 YY 一下class ="tags" href="/tags/JAVA.html" title=java>java.lang.AbstractMethodError
:
当Pocket
为抽象类时,根据抽象类的定义,要有特定实现类来执行。 而对于pocket = (Pocket) clazzOut01.newInstance();
这句话,Pocket
的强制类型转换是成功(因为解析Poket
类型需要执行双亲委派
逻辑,解析后发现大家都是同一个Poket
),但是当寻找实现类时就发现这个clazzOut01.newInstance()
对象对应的类型NormalUser
不是自己想要的,虚拟机以为没有对应的实现,所以当执行到pocket.getBalance()
时,虚拟机以为程序要单纯的执行Pocket
的抽象方法,于是报错class ="tags" href="/tags/JAVA.html" title=java>java.lang.AbstractMethodError
。
如果上面的理解了,那么class ="tags" href="/tags/JAVA.html" title=java>java.lang.ClassCastException
也就好理解了:
对于pocket = (NormalUser) clazzOut01.newInstance();
这句话,虚拟机会把clazzOut01.newInstance()
产生的对象强制转换为NormalUser
,到这里就回到第一个问题的答案了:clazzOut01
所代表的NormalUser
和要强转的NormalUser
不是一个类型,虚拟机就直接报错了
结语
为啥这个要有结语呢?
因为不成熟的推测
真的是本人 YY 的结果,不敢保证真实有效。如有错误之处,还望不吝赐教。
下一篇会续上Java虚拟机(五)垃圾收集
(老是感觉要写写垃圾分类啥的,猪都不能吃的那种)