Java中是否可以加载相同类名字的问题

在Java中,每个类都有一个唯一的全限定名(fully qualified name)。这个全限定名是由类的包名和类的名字组成的。根据Java语言规范,不同的类必须有不同的全限定名,因此不能同时存在两个具有相同全限定名的类。

类加载器(Class Loader)的作用

在Java中,类的加载是由类加载器(Class Loader)来完成的。类加载器负责将类的字节码加载到内存中,并生成对应的Class对象。Java中的类加载器有三种类型:

  1. 引导类加载器(Bootstrap Class Loader):负责加载Java的核心类库,如java.lang包中的类。
  2. 扩展类加载器(Extension Class Loader):负责加载Java的扩展类库,如javax包中的类。
  3. 应用程序类加载器(Application Class Loader):负责加载应用程序的类。

在Java中,类的加载器是按照一定的层次进行加载的。引导类加载器是最先被加载的,然后是扩展类加载器,最后是应用程序类加载器。类加载器通过双亲委派模型(Parent Delegation Model)来加载类。

双亲委派模型

双亲委派模型是Java类加载机制的一个重要概念。它的原理是当一个类加载器收到加载请求时,首先将加载任务委托给父类加载器去完成。如果父类加载器找不到该类,才由子类加载器来加载。

这个模型的好处是可以保证类的唯一性。因为如果类加载器已经加载了某个类,那么它的父类加载器也会加载同一个类,所以不会出现重复加载的情况。

类的同名问题

根据双亲委派模型的原理,如果两个类加载器加载的类具有相同的全限定名,那么由于父类加载器优先加载,实际上只会加载父类加载器加载的那个类。也就是说,Java中不同类加载器加载的同名类是不同的。

我们来通过一个代码示例来验证这个结论。假设我们有如下两个类:

// MyClass1.java
public class MyClass {
    public void print() {
        System.out.println("MyClass1");
    }
}

// MyClass2.java
public class MyClass {
    public void print() {
        System.out.println("MyClass2");
    }
}

为了方便验证,我们分别将这两个类放在不同的包中,分别为com.example.package1com.example.package2

现在我们编写一个测试类,通过不同的类加载器分别加载这两个类并创建对象:

public class Test {
    public static void main(String[] args) throws Exception {
        ClassLoader classLoader1 = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                if ("com.example.package1.MyClass".equals(name)) {
                    try {
                        Path path = Paths.get("path/to/MyClass1.class");
                        byte[] bytes = Files.readAllBytes(path);
                        return defineClass(name, bytes, 0, bytes.length);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                return super.loadClass(name);
            }
        };

        ClassLoader classLoader2 = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                if ("com.example.package2.MyClass".equals(name)) {
                    try {
                        Path path = Paths.get("path/to/MyClass2.class");
                        byte[] bytes = Files.readAllBytes(path);
                        return defineClass(name, bytes, 0, bytes.length);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                return super.loadClass(name);
            }
        };

        Class<?> myClass1 = classLoader1.loadClass("com.example.package1.MyClass");
        Class<?> myClass2 = classLoader2.loadClass("com.example.package2.MyClass");

        Object obj1 = myClass1.newInstance();
        Object obj2 = myClass2.newInstance();

        myClass1.getMethod("print").invoke(obj1);
        myClass2.getMethod("print").invoke(obj2);
    }
}

在上面的代码中,我们通过自定义的类加载器分