jvm加载包名和类名相同的类的规则,以及如何加载包名和类名相同的类

本文详细介绍了JVM的三种类加载器:bootstrapclassloader、extensionclassloader和自定义类加载器,并阐述了如何通过不同的类加载器避免加载包名和类型相同的类时产生的冲突。此外,文章还提供了实例说明了IDE环境下处理此类冲突的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

jvm包括三种类加载器:

第一种:bootstrap classloader:加载java的核心类。

第二种:extension classloader:负责加载jre的扩展目录中的jar包。

第三种:它负责在JVM被启动时,加载来自在命令java中的-classpath或者java.class.path系统属性或者 CLASSPATH操作系统属性所指定的JAR类包和类路径。

以上参考博客原文路径为:http://blog.youkuaiyun.com/xrt95050/article/details/4413998

jvm 加载包名和类名相同的类时,先加载classpath中jar路径放在前面的,包名类名都相同,那jvm没法区分了,如果使用ide一般情况下是会提示发生冲突而报错,若不报错,只有第一个包被引入(在classpath路径下排在前面的包),第二个包会在classloader加载类时判断重复而忽略。


如果加载包名和类型相同的类可以使用不同的类加载器,也就是说使用自定义类加载器加载同包同名的类。

<think>我们正在使用Java读取class文件,并从中获取类名。 根据引用[2],我们知道Java的文件(.class)含了的字节码,而每个都会被编译成一个单独的class文件。 我们的目标是从class文件中提取类名。 注意:class文件是二进制格式,我们可以使用一些工具来解析它。Java本身提供了一些反射机制,但反射通常要求已经被加载JVM中。 而我们的需求可能是直接读取class文件(尚未加载)来获取类名。因此,我们需要解析class文件的结构。 根据Java虚拟机规范,class文件的结构如下(简要): - 魔数(4字节) - 版本号(4字节) - 常量池(变长) - 访问标志(2字节) - 索引(2字节) - 父索引(2字节) - 接口索引集合(变长) - 字段表集合(变长) - 方法表集合(变长) - 属性表集合(变长) 其中,类名信息存储在常量池中。索引指向常量池中的一个CONSTANT_Class_info,而CONSTANT_Class_info又指向一个CONSTANT_Utf8_info,该Utf8常量存储了的全限定(即+类名,以'/'分隔,例如java/lang/Object)。 因此,步骤大致为: 1. 读取class文件。 2. 解析常量池。 3. 获取索引,然后找到对应的全限定字符串。 4. 将全限定中的'/'替换为'.',然后就可以拆分类名。 但是,我们也可以使用第三方库来简化解析,比如ASM、Javassist等。这里我们使用Java标准库中的`ClassReader`(来自ASM库)?但注意,题目要求使用Java标准库,所以我们可以自己解析,但比较复杂。或者,我们可以使用`javax.lang.model`相关的工具?但通常用于源代码。 实际上,如果我们只是想要获取类名,而不想加载,我们可以使用以下方法: 方法一:使用反射(需要已经被加载): - 如果我们能加载这个,那么直接使用`Class`对象的`getName()``getPackage()`方法即可。但问题是我们可能不想加载(比如在路径中不存在,或者我们只是处理class文件)。 方法二:解析class文件(不需要加载): - 我们可以自己写一个简单的解析器,读取class文件,定位到常量池,然后找到全限定。 考虑到复杂性效率,我们可以使用ASM库来解析,因为ASM是一个轻量级的字节码操作框架,可以很容易地解析class文件。 但是,问题要求使用Java读取,并没有限制使用第三方库。不过,如果要求纯Java标准库,那么我们就需要自己解析。 这里,我们假设可以使用ASM库(因为它是处理class文件的事实标准),那么步骤如下: 1. 添加ASM依赖(如果项目允许)。 2. 使用ClassReader读取class文件,然后通过一个ClassVisitor来获取类名。 如果不用ASM,我们也可以使用Java标准库中的DataInputStream来手动解析,但需要了解class文件格式。 考虑到时间,我们给出两种方案: 方案一(使用ASM): 步骤: 1. 导入ASM库(如asm-9.x.jar)。 2. 编写代码使用ClassReader解析class文件。 示例代码: ```java import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Opcodes; import java.io.IOException; import java.io.InputStream; public class ClassParser { public static void main(String[] args) throws IOException { // 假设我们有一个class文件的输入流 InputStream in = ClassParser.class.getResourceAsStream("YourClass.class"); ClassReader reader = new ClassReader(in); // 创建一个ClassVisitor,我们只关心类名 reader.accept(new ClassVisitor(Opcodes.ASM9) { @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { // 这里的name就是的内部全限定(用'/'分隔),例如com/example/MyClass String className = name.replace('/', '.'); // 提取:找到最后一个点之前的部分 int lastDot = className.lastIndexOf('.'); String packageName = (lastDot != -1) ? className.substring(0, lastDot) : ""; String simpleClassName = (lastDot != -1) ? className.substring(lastDot+1) : className; System.out.println("Package: " + packageName); System.out.println("Class: " + simpleClassName); super.visit(version, access, name, signature, superName, interfaces); } }, 0); in.close(); } } ``` 方案二(纯Java,手动解析): 由于手动解析class文件比较复杂,这里只给出关键步骤的伪代码: 1. 读取文件头(魔数版本号)跳过。 2. 读取常量池数量,然后逐个读取常量池项。 3. 常量池的每一项都有一个标签(1字节),根据标签不同,后面的内容不同。 4. 我们关注的是CONSTANT_Class_info(标签为7)CONSTANT_Utf8_info(标签为1)。 5. 索引在常量池之后,读取索引(2字节),它指向常量池中的一个CONSTANT_Class_info项,而该CONSTANT_Class_info项又含一个指向CONSTANT_Utf8_info的索引(2字节),该Utf8字符串就是的全限定。 具体步骤: - 读取4字节魔数(0xCAFEBABE),4字节的版本号(小版本2字节,大版本2字节)。 - 读取常量池数量(2字节,注意实际项数为count-1,索引从1开始)。 - 遍历常量池(索引从1到count-1): - 读取1字节的tag。 - 根据tag,读取不同长度的数据: - 如果是1(Utf8),则读取2字节的长度,然后读取长度为len的字符串(UTF-8编码)。 - 如果是7(Class),则读取2字节的name_index(指向一个Utf8字符串的索引)。 - 其他型的常量项也有固定格式,需要跳过。 6. 跳过访问标志(2字节)。 7. 读取索引(2字节)索引(2字节)。 8. 索引指向一个CONSTANT_Class_info项,我们通过这个项找到对应的Utf8字符串,即全限定。 注意:常量池中每一项的索引是连续的,我们可以用一个数组来存储常量池项。但注意,常量池中的LongDouble占两个位置(索引会跳过一个)。 由于手动解析代码较长,这里不展开,但我们可以参考引用[2]中提到的解析过程。 根据引用[3][4],我们也可以知道,在Java中可以通过加载器来获取路径下的,但那是获取已加载,而不是直接解析class文件。 因此,如果条件允许,推荐使用ASM库来解析。 现在,我们回答用户的问题:如何使用Java读取class文件并获取类名? 我们提供两种方法: 方法1:使用ASM库(简单高效,推荐) 方法2:手动解析class文件(复杂,但不需要第三方库) 下面分别给出示例代码: 方法1:使用ASM 步骤: 1. 下载ASM库(如从Maven仓库获取)并添加到项目中。 2. 编写代码读取class文件并解析。 方法2:手动解析(仅关键步骤,不完整) 由于手动解析非常复杂,我们这里只提供使用ASM的完整示例。 示例代码(使用ASM): ```java import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Opcodes; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; public class ClassFileParser { public static void parseClassFile(String filePath) throws IOException { Path path = Paths.get(filePath); try (InputStream in = Files.newInputStream(path)) { ClassReader reader = new ClassReader(in); reader.accept(new ClassVisitor(Opcodes.ASM9) { @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { // 将内部形式的类名(用'/'分隔)转换为外部形式(用'.'分隔) String fullClassName = name.replace('/', '.'); int lastDotIndex = fullClassName.lastIndexOf('.'); String packageName = lastDotIndex == -1 ? "" : fullClassName.substring(0, lastDotIndex); String className = lastDotIndex == -1 ? fullClassName : fullClassName.substring(lastDotIndex + 1); System.out.println("Package Name: " + packageName); System.out.println("Class Name: " + className); } }, 0); } } public static void main(String[] args) { try { parseClassFile("path/to/YourClass.class"); } catch (IOException e) { e.printStackTrace(); } } } ``` 注意:使用ASM时,需要确保class文件路径正确。 如果用户不想使用第三方库,我们可以提供一个简化版的手动解析方法,仅用于获取类名(不完整,仅用于演示): 手动解析示例(仅解析常量池索引,不处理所有常量型): ```java import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; public class ManualClassParser { public static void main(String[] args) throws IOException { Path path = Paths.get("path/to/YourClass.class"); try (InputStream in = Files.newInputStream(path); DataInputStream data = new DataInputStream(in)) { // 读取魔数 int magic = data.readInt(); if (magic != 0xCAFEBABE) { throw new IOException("Invalid class file"); } // 跳过版本号 int minorVersion = data.readUnsignedShort(); int majorVersion = data.readUnsignedShort(); // 读取常量池数量 int constantPoolCount = data.readUnsignedShort(); // 常量池索引从1开始,所以创建一个大小为constantPoolCount的数组(索引0不使用) Object[] constantPool = new Object[constantPoolCount]; // 注意:常量池中每一项的索引从1到constantPoolCount-1 for (int i = 1; i < constantPoolCount; i++) { int tag = data.readUnsignedByte(); switch (tag) { case 1: // CONSTANT_Utf8 int length = data.readUnsignedShort(); byte[] bytes = new byte[length]; data.readFully(bytes); constantPool[i] = new String(bytes, "UTF-8"); break; case 7: // CONSTANT_Class int nameIndex = data.readUnsignedShort(); constantPool[i] = nameIndex; // 存储的是指向Utf8字符串的索引 break; case 9: // CONSTANT_Fieldref case 10: // CONSTANT_Methodref case 11: // CONSTANT_InterfaceMethodref data.readInt(); // 跳过4字节(两个索引,每个2字节) break; case 8: // CONSTANT_String data.readShort(); // 跳过2字节 break; case 3: // CONSTANT_Integer case 4: // CONSTANT_Float data.readInt(); // 4字节 break; case 5: // CONSTANT_Long case 6: // CONSTANT_Double data.readLong(); // 8字节,注意:这些项占两个常量池位置 i++; // 因为LongDouble在常量池中占两个位置 break; case 12: // CONSTANT_NameAndType data.readInt(); // 跳过4字节(两个索引) break; case 15: // CONSTANT_MethodHandle data.readByte(); data.readShort(); break; case 16: // CONSTANT_MethodType data.readShort(); break; case 18: // CONSTANT_InvokeDynamic data.readShort(); data.readShort(); break; default: throw new IOException("Unknown constant pool tag: " + tag); } } // 跳过访问标志(2字节) data.readShort(); // 读取索引(2字节) int classIndex = data.readUnsignedShort(); // 父索引(2字节)跳过 data.readShort(); // 现在,索引指向常量池中的一个CONSTANT_Class_info项 // 而CONSTANT_Class_info项存储了一个索引(指向一个Utf8字符串) int classNameIndex = (Integer) constantPool[classIndex]; String className = (String) constantPool[classNameIndex]; // 类名在常量池中是以内部形式存储的(用'/'分隔),我们转换为外部形式 String fullClassName = className.replace('/', '.'); int lastDot = fullClassName.lastIndexOf('.'); String packageName = lastDot == -1 ? "" : fullClassName.substring(0, lastDot); String simpleClassName = lastDot == -1 ? fullClassName : fullClassName.substring(lastDot+1); System.out.println("Package Name: " + packageName); System.out.println("Class Name: " + simpleClassName); } } } ``` 注意:手动解析代码没有处理所有情况,并且假设常量池中的CONSTANT_Class_info项存储的是整数索引,且该索引指向的项是CONSTANT_Utf8。此外,常量池中LongDouble的处理需要跳过下一个索引。此代码仅作为示例,可能无法处理所有class文件。 综上所述,推荐使用ASM库。 相关问题: 1. 如何在不加载的情况下获取class文件的版本信息? 2. 解析class文件时,除了类名,还能获取哪些信息? 3. 如何使用Java反射获取已加载类名? 4. 在Java中,如何通过加载器获取指定下的所有?</think>### Java 读取 class 文件获取类名 要直接解析 class 文件获取类名(无需加载),可通过以下两种方法实现: #### 方法一:使用 ASM 字节码框架(推荐) ASM 是轻量级字节码操作框架,可高效解析 class 文件格式: ```java import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Opcodes; import java.io.InputStream; public class ClassParser { public static void parse(InputStream classStream) throws Exception { ClassReader reader = new ClassReader(classStream); reader.accept(new ClassVisitor(Opcodes.ASM9) { @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { // name 格式: com/example/MyClass String fullName = name.replace('/', '.'); int lastDot = fullName.lastIndexOf('.'); String packageName = (lastDot > 0) ? fullName.substring(0, lastDot) : ""; String className = (lastDot > 0) ? fullName.substring(lastDot + 1) : fullName; System.out.println("Package: " + packageName); System.out.println("Class: " + className); } }, ClassReader.SKIP_CODE); // 跳过方法体解析 } } ``` **使用示例**: ```java InputStream in = MyClass.class.getResourceAsStream("YourClass.class"); ClassParser.parse(in); ``` #### 方法二:手动解析 class 文件(无依赖) 通过解析 class 文件二进制格式获取全限定: ```java import java.io.DataInputStream; import java.io.InputStream; public class ClassFileParser { public static void parse(InputStream input) throws Exception { try (DataInputStream data = new DataInputStream(input)) { // 验证魔数 (0xCAFEBABE) if (data.readInt() != 0xCAFEBABE) throw new IllegalArgumentException("Invalid class file"); // 跳过版本号 data.readShort(); // minor data.readShort(); // major // 读取常量池 int constantPoolCount = data.readUnsignedShort(); String[] constantPool = new String[constantPoolCount]; for (int i = 1; i < constantPoolCount; i++) { int tag = data.readUnsignedByte(); switch (tag) { case 1: // CONSTANT_Utf8 int length = data.readUnsignedShort(); byte[] bytes = new byte[length]; data.readFully(bytes); constantPool[i] = new String(bytes, "UTF-8"); break; case 7: // CONSTANT_Class data.readShort(); // 存储索引,后续处理 break; default: // 跳过其他型 skipConstantPoolItem(tag, data); } } // 获取索引 int classIndex = data.readUnsignedShort(); // 访问标志 classIndex = data.readUnsignedShort(); // this_class // 从常量池获取全限定 String className = constantPool[classIndex]; System.out.println("Full Qualified Name: " + className.replace('/', '.')); } } private static void skipConstantPoolItem(int tag, DataInputStream data) throws Exception { switch (tag) { case 3: case 4: data.readInt(); break; // Integer/Float case 5: case 6: data.readLong(); data.readLong(); break; // Long/Double (占2个位置) case 8: data.readShort(); break; // String case 9: case 10: case 11: case 12: data.readInt(); break; // Field/Method 等 case 15: data.readByte(); data.readShort(); break; // MethodHandle case 16: data.readShort(); break; // MethodType case 18: data.readShort(); data.readShort(); break; // InvokeDynamic } } } ``` #### 关键说明: 1. **class 文件结构**: - 前 4 字节为魔数 `0xCAFEBABE` - 常量池存储所有符号引用,以 `CONSTANT_Class` 形式存在 - 索引指向常量池中的全限定(`/` 分隔格式) 2. **类名分离**: - 全限定如 `com/example/MyClass` - = 最后一个 `/` 前的部分 → `com.example` - 类名 = 最后一个 `/` 后的部分 → `MyClass` 3. **与反射的区别**: - 反射需加载:`Class.forName("com.example.MyClass").getPackage().getName()` - 文件解析直接读取二进制数据,无需加载器介入[^2] ### 相关问题 1. **如何获取 class 文件的 JDK 编译版本?** class 文件第 5-8 字节存储次版本号主版本号(如 Java 8 对应 `0x00 00 00 34`) 2. **如何解析 class 文件中的方法签?** 需分析常量池中的 `CONSTANT_NameAndType` `CONSTANT_Utf8` 项,结合访问标志位解析 3. **如何获取内部的 class 文件信息?** 内部会生成独立 class 文件(如 `Outer$Inner.class`),解析方式相同[^2] 4. **如何通过扫描路径下所有?** 使用 `ClassLoader.getResources()` 遍历路径,配合 ASM 解析文件[^3][^4] [^1]: 加载过程通过 `Class.forName()` 实现,但会触发初始化 [^2]: class 文件是 JVM 平台无关性的基础,元数据字节码 [^3]: Spring 的 `ClassPathScanningCandidateComponentProvider` 实现扫描 [^4]: 可通过 `ClassLoader.getSystemResources(packagePath)` 获取路径资源
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值