今天偶然想起之前和朋友讨论过的一个问题: 如何唯一确定一个 Java 类? 我相信大多数朋友遇到这个问题的回答都是: 类的全路径呗. 但事实上, 唯一确定一个 Java 类, 单单靠类路径是不够的, 还要多加上一个东西: 类加载器. 也就是说, 类加载器 + 类路径才唯一确定一个 Java 类.
为了证明我所说的, 我们来做一个简单的实验.
- // 自定义一个类加载器
- ClassLoader myLoader = new ClassLoader() {
- @Override
- public Class<?> loadClass(String name) throws ClassNotFoundException {
- try {
- String fileName=name.substring(name.lastIndexOf(".")+1)+".class";
- InputStream is=getClass().getResourceAsStream(fileName);
- if( is == null ){
- return super.loadClass(name);
- }
- byte[] bytes = new byte[is.available()];
- is.read(bytes); // 通过自定义类加载器读取 class 文件的二进制流
- return defineClass(name, bytes, 0,bytes.length);
- } catch (IOException e) {
- e.printStackTrace();
- throw new ClassNotFoundException(name);
- }
- }
- };
- // 比较类是否相同
- Object obj = myLoader.loadClass("com.chenshuyi.UniqueClass").newInstance();
- System.out.println(obj.getClass());
- System.out.println(UniqueClass.class);
- System.out.println(obj instanceof UniqueClass);
在上面这段代码中, 我首先定义了一个自定义类加载器 myLoader, 之后让其去加载 com.chenshuyi.UniqueClass 类, 之后调用 newInstance() 获得该类的实例 obj.
接着分别打印输出 obj 对象的类路径, 以及 UniqueClass 类的类路径, 最后使用 instanceof 符号判断 obj 对象是否是 UniqueClass 类的实例. 最后的输出结果是:
- class com.chenshuyi.UniqueClass
- class com.chenshuyi.UniqueClass
- false
上面的结果显示: obj 对象和 UniqueClass 类的类路径完全相同, 都是 com.chenshuyi.UniqueClass. 但是 obj 对象却不是 UniqueClass 类的实例. 这就验证了我的说法, 即: 类加载器 + 类路径才唯一确定一个 Java 类.
其实在 Java 语言中, 还有一个与之非常类似的情况: 如何唯一确定类中的一个方法? 按照我们一直以来的直觉, 我们会回答: 方法名, 形参类型, 形参个数. 例如下面的两个方法虽然方法名相同, 但是参数类型和个数不同, 所以他们是不同的方法.
- public void Hello(String name)
- public void Hello(String name, int age)
但下面两个方法虽然返回类型不同, 但他们的方法名和参数类型是一致的, 所以他们无法通过编译.
- public void Hello(String name)
- public String Hello(String name)
但是其实对于 JVM 来说, 在同一个类中是可以存在方法名相同并且参数类型相同的方法名的. 也就是说, 在 JVM 中判断一个方法的要素是: 类名, 方法名以及方法描述符. 与 Java 源码中的不同在于方法描述符这个概念. 方法描述符由方法的参数类型和返回类型所构成. 例如下面的这个方法, 方法描述符就是 name 这个参数, 以及 String 这个返回类型.
public String Hello(String name)
为了证明我上面的观点, 我们再做一个简单的实验.
下面的代码声明了一个方法 a 和 方法 b, 方法名不同, 返回类型不同.
- public class UniqueMethod {
- public void a(){}
- public String b(){
- return "b";
- }
- public static void main(String[] args) {
- System.out.println("Hello");
- }
- }
为了证明在 JVM 对于方法唯一性判断, 我将通过修改字节码的方式, 让 UniqueMethod 字节码变成下面这样. 即有两个相同的 a 方法, 它们的方法名, 形参类型, 形参个数都相同, 但是返回参数类型不同.
- public class UniqueMethod {
- public void a(){}
- public String a(){
- return "b";
- }
- public static void main(String[] args) {
- System.out.println("Hello");
- }
- }
那么实验开始了!
首先我们用 javac 命令编译出字节码 class 文件, 接着使用 asmtools 工具将 class 文件再转为 jasm 文件. 我们打开 jasm 文件看看:
可以看到里面有三个方法, 分别是 a 方法, b 方法和 main 方法. 此时我们将 b 方法名称直接修改成 a 方法, 接着使用 asmtools 工具将 jasm 文件转为 class 文件. 通过这种方式, 我们就可以在一个类中拥有两个名为 a 的方法了. 这两个 a 方法, 它们的方法名, 形参类型, 形参个数都相同, 但是返回参数类型不同.
生成修改后的 class 文件之后, 我们运行 java UniqueMethod 命令, 顺利打印出字符: Hello. 这说明 class 文件并没有任何错误, JVM 对于方法名, 形参类型, 形参个数都相同, 但是返回参数类型不同的方法, 是完全接受的.
让我们再用 javap 命令来看看 class 文件的字节码结构, 我们会发现确实是存在了两个名称为 a 的方法的.
最后让我们来总结一下: 在 JVM 中, 类路径和类加载器唯一确定一个 Java 类, 方法名, 形参类型, 形参个数, 返回参数类型唯一确定一个 Java 类中的方法.
其实不仅仅是类与方法的唯一性, 在很多方面 JVM 和 Java 语言规范真是有很大的差别. 很多在 Java 中成立的东西, 到了 JVM 其实就不一定成立了. 例如: Java 的泛型, Java 的 lambla 表达式等等, 其实只在 Java 语言层面存在, 而在 JVM 中其实是不存在的.
好了, 今天的分享到此为止了. 喜欢这篇文章的话, 那就分享给朋友一起看吧!
来源: https://www.cnblogs.com/chanshuyi/p/how_to_confirm_an_unique_class.html