如何在Java中加载两个类全限定名相同的类?

我们知道在Java中类全限定名由两部分组成,包名类名,当然网上也有说法是由三部分组成,包名、子包名以及类名,这里我把包相关的统称为包名。

比如说在某个Java项目中com.knight包下有一个类A,那么这个类A的类全限定名就为:com.knight.A。我们如果在相同包路径有相同的类名,往往编译是通过不了的。

那么是否有可能在同一个Java项目中加载类全限定名完全相同实现上不同的两个类?如果不可以,是否就代表代表类的唯一路径就是类全限定名?如果可以,那类的全限定名在java中真的唯一标识了一个类吗?

以下内容涉及到的知识点:Java的反射机制双亲委派模型。如果不太了解的同学,建议先了解后再来看本篇文章哦。

洪爵准备先进行实操,把最终的结果先落地,然后再根据结果来讨论。

首先我们创建一个Java项目,洪爵命名为Main,然后创建包路径com.knight,在该包路径下创建一个A类,在这个A类里,我们创建一个public方法,返回一个String,方法名为getVersion()。

项目树状图:
在这里插入图片描述

A.java代码:

public class A {
    public String getVersion() {
        return "1.0";
    }
}

我们运行javac编译A.java,会在com.knight包下生成A.class文件。

javac ./src/com/knight/A.java

然后我们把A.class文件移到项目根目录下(连同包名文件夹一起)。
在这里插入图片描述

然后修改A.java的代码,让getVersion的方法返回值为"2.0"。

现在洪爵想创建一个Main.java文件,在这个Java文件里,洪爵会尝试导入这两个类全限定名相同的A类。首先如果洪爵什么都不做,直接去import这两个类,无疑是不行的,大家肯定都尝试过,那这里的解法就需要提到双亲委派模型了。

我们知道除了应用类加载器、拓展类加载器和启动类加载器外,还有一种自定义类加载器,洪爵尝试使用自定义类加载器看是否能把这两个A类都加载进来。

class MyClassLoader extends ClassLoader {
    private final String classPath;

    // 自定义前缀
    private static final String PREFIX = "prefix.";

    public static String getPrefix() {
        return PREFIX;
    }

    public MyClassLoader(String classPath) {
        this.classPath = classPath;
    }

    // 读取文件
    private byte[] loadByte(String name) throws Exception {
        name = name.replaceAll("\\.", "/");
        FileInputStream fileInputStream = new FileInputStream(classPath + "/" + name + ".class");
        int len = fileInputStream.available();
        byte[] data = new byte[len];
        fileInputStream.read(data);
        fileInputStream.close();
        return data;
    }

    @Override
    protected Class<?> findClass(String name) {
        try {
            name = name.substring(PREFIX.length());

            // 查找指定名称的类文件
            byte[] data = loadByte(name);
            // 将字节数组形式的类文件数据转换为一个Class对象
            return defineClass(name, data, 0, data.length);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // 检查该类是否已经被加载过 如果已加载则返回对应的Class对象
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                // 如果没有加载过 先让父类进行加载
                if (!name.startsWith(PREFIX)) {
                    c = super.loadClass(name, resolve);
                } else {
                    // 父类不加载 则自己加载
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
}

然后我们尝试开始加载这两个A类,本项目中的A.java自然不必多说,正常new一个就行,另外一个A.class我们需要使用反射去生成对象,并调用getVersion方法:

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        MyClassLoader myClassLoader = new MyClassLoader("./");

        Class<?> clazz = myClassLoader.loadClass(MyClassLoader.getPrefix() + "com.knight.A");
        System.out.println(clazz);
        Method method = clazz.getMethod("getVersion");
        System.out.println(method.invoke(clazz.newInstance()));

        System.out.println(A.class);
        A a2 = new A();
        System.out.println(a2.getVersion());

    }
}

这是对应的输出:

class com.knight.A
1.0
class com.knight.A
2.0

天!同一个项目中,竟然让洪爵成功加载了两个类全限定名完全相同的A类!

这到底是怎么做到的?让洪爵来稍微解释一下,首先你得知道加载器其中3个比较常用的方法的含义:loadClassfindClassdefineClass

loadClass方法会检查该类是否已经被加载过,如果已加载则直接返回对应的Class对象。如果没有加载过,则调用父ClassLoader的loadClass方法,如果父ClassLoader也无法加载,则调用findClass方法来实际查找和加载类。findClass用于查找指定名称的类文件。defineClass用于将字节数组形式的类文件数据转换为一个Class对象。

如果自定义类加载器不想违背双亲委派模型,一般只需要重写findClass方法即可,如果想违背双亲委派模型,则还需要重写loadClass方法。虽然我们重写了loadClass方法,但是大体上还是按照双亲委派模型的方式,如果找不到会先去让父类加载,那么我在那里设置了MyClassLoader的父类呢?其实因为MyClassLoader是继承了ClassLoader,而ClassLoader的默认protected构造函数,会设置默认的父类为应用类加载器,源码如下图:

// ClassLoader.java
protected ClassLoader() {
	this(checkCreateClassLoader(), null, getSystemClassLoader());
}

洪爵在loadClass有一行比较与众不同的代码,我会判断这个包路径是否是PREFIX开头的,如果是则走自己的加载逻辑,然后在自己的findClass方法中,再把PREFIX去掉,露出了真正的包名,这个时候去做加载。

但是核心问题是,为什么jvm允许两个类全限定名相同的A类被加载进来?我们深扒源码,发现ClassLoader的源码里有一个map,这个map的key是对应的包路径,value是对应的package对象,所以自定义类加载器、应用类加载器等都自己维护了一个包路径到package对象的映射,等同于每个加载器都有自己的命名空间

// ClassLoader.java
// The packages defined in this class loader.  Each package name is
// mapped to its corresponding NamedPackage object.
//
// The value is a Package object if ClassLoader::definePackage,
// Class::getPackage, ClassLoader::getDefinePackage(s) or
// Package::getPackage(s) method is called to define it.
// Otherwise, the value is a NamedPackage object.
private final ConcurrentHashMap<String, NamedPackage> packages = new ConcurrentHashMap<>();

因此在同一个Java项目中可以出现类全限定名相同的类,类全限定名并不能唯一标识一个类。那么在一个Java项目中,怎么唯一标识一个类呢?
除了类全限定名外,还需要加上所使用的类加载器就可以唯一定位、标识一个类了,即类加载器 + 类全限定名

不知道你看完本篇文章,是否有收获?有需要讨论的地方也可以来找洪爵~

愿每个人都能带着怀疑的态度去阅读文章并探究其中原理。

道阻且长,往事作序,来日为章。

期待我们下一次相遇。

【b站搜Knight洪爵 微信可搜KNIGHT洪爵】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/335302.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

解决一个mysql的更新属性长度问题

需求背景&#xff1a; 线上有一个 platform属性&#xff0c;原有长度为 varchar(10)&#xff0c;但是突然需要填入一个11位长度的值&#xff1b;而偏偏这个属性在线上100张表中有50张都存在&#xff0c;并且名字各式各样&#xff0c;庆幸都包含 platform&#xff1b;例如 platf…

创建非模态的静态文本并更改它的位置

我是写在钩子里&#xff0c;动态显示静态文本的哦&#xff0c;效果我放在下面了&#xff0c;不知道怎么做动态图片&#xff0c;你们可以教我一下&#xff0c;哈哈。 //这个就是放在钩子里跟随鼠标动态显示坐标信息&#xff0c;或者提示信息 HWND statichandleNULL; HWND NXha…

php isset和array_key_exists区别

在PHP中&#xff0c;可以使用array_key_exists函数或者isset函数来判断一个字典&#xff08;关联数组&#xff09;中是否存在某个下标。 使用 array_key_exists 函数: $myArray array("key1" > "value1", "key2" > "value2",…

网络爬虫采集工具

在当今数字化的时代&#xff0c;获取海量数据对于企业、学术界和个人都至关重要。网络爬虫成为一种强大的工具&#xff0c;能够从互联网上抓取并提取所需的信息。本文将专心分享关于网络爬虫采集数据的全面指南&#xff0c;深入探讨其原理、应用场景以及使用过程中可能遇到的挑…

校园水电抄表系统

校园水电抄表系统是一种现代化的水电管理方式&#xff0c;它通过高科技手段实现对校园内水电使用情况的实时监测和数据化管理&#xff0c;从而提高水电资源的利用效率&#xff0c;降低管理成本&#xff0c;为构建绿色、环保、节约型校园奠定基础。 一、系统概述 校园水电抄表…

【富文本编辑器实战】03 Vuex 的配置编写

Vuex 的配置编写 目录 Vuex 的配置编写Vuex 是什么&#xff1f;什么是“状态管理模式”&#xff1f;什么情况下我应该使用 Vuex&#xff1f;安装 Vuex开始使用 VuexAction 文件Mutations-types 文件Mutation 文件Index Vuex 是什么&#xff1f; 这里我们来看看官方网站是如何介…

HugggingFace 推理 API、推理端点和推理空间相关模型部署和使用以及介绍

HugggingFace 推理 API、推理端点和推理空间相关模型部署和使用以及介绍。 Hugging Face是一家开源模型库公司。 2023年5月10日&#xff0c;Hugging Face宣布C轮1亿美元融资&#xff0c;由Lux Capital领投&#xff0c;红杉资本、Coatue、Betaworks、NBA球星Kevin Durant等跟投…

Java程序设计:选实验5 GUI初级应用

使用JLabel、JTextArea、JButton等控件实现句子的中译英demo&#xff0c;该demo包含四个文本框&#xff0c;在第一个文本框输入一句英文&#xff0c;在第二个和第三个文本框显示该句的英文翻译&#xff08;要求使用百度翻译API、有道翻译API或其他API中的两种&#xff1b;自行上…

深度学习记录--mini-batch gradient descent

batch vs mini-batch gradient descent batch&#xff1a;段&#xff0c;块 与传统的batch梯度下降不同&#xff0c;mini-batch gradient descent将数据分成多个子集&#xff0c;分别进行处理&#xff0c;在数据量非常巨大的情况下&#xff0c;这样处理可以及时进行梯度下降&…

Text:字体相关设置

效果如下&#xff1a; import QtQuickWindow {width: 640height: 480visible: truetitle: qsTr("Text")Text {id: t1text: "你好&#xff0c;世界&#xff01;"color: "#29acc0" /*字体颜色*/font.pixelSize: 40 /*字体大小*/font.family: &quo…

在 Python 中检查一个数字是否是同构数

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 同构数&#xff0c;又称为自守数或自同构数&#xff0c;是一类特殊的数字&#xff0c;它们具有一种有趣的性质&#xff1a;将其平方后的数字&#xff0c;可以通过某种方式重新排列得到原来的数字。本文将详细介绍…

以后要做GIS开发的话是学GIS专业还是学计算机专业好一些?

GIS开发其实严格来说分为前后端以及底层开发。不同的方向&#xff0c;代表了不同的开发语言。 所以大家首先要了解自己具体要做的岗位类型是什么&#xff0c;其次才是选择专业侧重点。 但是严格来说&#xff0c;选择某个专业&#xff0c;到就业方向这个过程&#xff0c;并不是…

(C++) list底层模拟实现

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 首先&#xff0c;list底层是一个带头双向循环链表&#xff0c;再一个&#xff0c;我们还要解决一个问题&#xff0c;list的迭代器&#xff0c;vector和string的迭代器可以直接&#xff0c;是因为他们的地址空间是连续的&…

【AJAX框架】AJAX入门与axios的使用

文章目录 前言一、AJAX是干什么的&#xff1f;二、AJAX的安装2.1 CDN引入2.2 npm安装 三、基础使用3.1 CDN方式3.2 node方式 总结 前言 在现代Web开发中&#xff0c;异步JavaScript和XML&#xff08;AJAX&#xff09;已经成为不可或缺的技术之一。AJAX使得网页能够在不刷新整个…

hadoop-common: CMake failed with error code 1

问题 在编译hadoop源码时遇到如下错误 hadoop-common: CMake failed with error code 1 看了这个错误表示一脸懵逼 排查 在mvn 的命令中增加 -X 和 -e mvn clean package -e -X -Pdist,native -DskipTests -Dmaven.javadoc.skip -Dopenssl.prefix/usr/local/bin/openssl 在…

3.C语言——函数

函数 1.什么是函数2.函数的分类1.库函数2.自定义函数 3.函数的参数1.实际参数&#xff08;实参&#xff09;2.形式参数&#xff08;形参&#xff09; 4.函数的声明1.同一个文件的函数声明2.多文件的函数声明 5.函数的调用6.函数的嵌套调用和链式访问1.嵌套调用2.链式访问 7.函数…

P1059 [NOIP2006 普及组] 明明的随机数————C++、Python

目录 [NOIP2006 普及组] 明明的随机数题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示 解题思路Code——CCode——Python运行结果 [NOIP2006 普及组] 明明的随机数 题目描述 明明想在学校中请一些同学一起做一项问卷调查&#xff0c;为了实验的客观性&#xff0…

力扣 | 438. 找到字符串中所有字母异位词

滑动窗口解题 示例 在s里面控制一个p字符串长度的滑动窗口&#xff0c;统计该滑动窗口中的每种字符出现的次数 import java.util.ArrayList; import java.util.Arrays; import java.util.List;public class Problem_438_FindAnagrams {public List<Integer> findAnagram…

开放签开源工具版更新至1.1版本,进一步提升电子签名服务能力

本周开放签开源工具版增加了SDK与API能力&#xff0c;更新至1.1版本&#xff0c;使开放签电子签章工具能力进一步提升。 SDK将便于java用户直接使用CA证书颁发和签名能力。API接口采用HTTP&#xff08;S&#xff09;通讯&#xff0c;JSON报文格式&#xff0c;具有跨平台、跨语…

力扣hot100 最长有效括号 动态规划

Problem: 32. 最长有效括号 文章目录 思路Code 思路 &#x1f468;‍&#x1f3eb; 参考题解 Code ⏰ 时间复杂度: O ( n ) O(n) O(n) &#x1f30e; 空间复杂度: O ( n ) O(n) O(n) class Solution {public int longestValidParentheses(String s){int n s.length();…