学了这么久的java反射机制,你知道class.forName和classloader的区别吗?

简介: 前两天头条有朋友留言说使用class.forName找不到类,可以使用classloader加载。趁此机会总结一下,正好看到面试中还经常问到。

一、类加载机制


上面两种加载类的方式说到底还是为了加载一个java类,因此需要先对类加载的过程进行一个简单的了解。我们写好的程序,然后run运行,过程可以直接看下面这张图:

v2-ea7d3f0ee1b94079ecd3b50965145e56_1440w.jpg

往细了看大致分为5个阶段:


(1)加载:java类运行时候会生成一个class字节码文件,加载的过程就是去我们的操作系统寻找这个class文件。


(2)链接:这个过程就是把class文件加载到java虚拟机。


(3)初始化:在虚拟机中根据class文件进行初始化。


(4)使用:这个过程大家都明白。


(5)卸载:使用完了,java虚拟机进行清理。


对于class.forName和classloader来说针对的就是第一个过程,也就是加载过程。不过这俩虽然有一定的相似性,但是区别还是挺大的。


二、使用举例


我们使用代码,先看看如何使用。注意包的范围,避免加载不了。

第一步:定义User类

public class User {
    private static int a = 10;
    {
        System.out.println("普通代码块");
    }
    static{
        System.out.println("静态变量a:"+a);
        System.out.println("静态代码块");
    }
}

第二步:测试

public class FDDTest {
    public static void main(String[] args) {
        //注意,我在com.fdd.reflect包下建的类
        String user = "com.fdd.reflect.User";
        test(user);
    }   
    public static void test(String user) {
        try {
            ClassLoader loader = ClassLoader.getSystemClassLoader();
            System.out.println("classloader testing...");
            Class<?> loaderUser = loader.loadClass(user);
            System.out.println("user " + loaderUser.getName());
            System.out.println("---------------------------------------"); 
            Class forNameUser = Class.forName(user);
            System.out.println("Class.forName testing...");
            System.out.println("user " + forNameUser.getName());
        } catch (ClassNotFoundException e){
            e.printStackTrace();
        }
    }
}

我们在上面的test方法中,使用了两个加载方法。现在我们测试一下:

classloader testing...
user com.fdd.reflect.User
---------------------------------------
静态变量a:10
静态代码块
Class.forName testing...
user com.fdd.reflect.User

是不感觉有点区别。现在是先给出一个大体的使用,下面我们分析一下他们的区别。


三、区别


1、class.forName


class.forName()前者除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。注意这里的静态块指的是在类初始化时的一些数据。但是classloader却没有,想要弄清楚这个原因,还是直接到源码中看看。

@CallerSensitive
public static Class<?> forName(String className) 
                    throws ClassNotFoundException {
   Class<?> caller = Reflection.getCallerClass();
   return forName0(className, 
                   true, 
                   ClassLoader.getClassLoader(caller), 
                   caller);
}

在这个源码中我们会发现,其实底层真正实现的是forName0方法,那这几个参数又是什么意思呢?


(1)className:表示我们要加载的类名


(2)true:指Class被加载后是不是必须被初始化。 不初始化就是不执行static的代码即静态代码,在这里默认为true,也就是默认实现类的初始化。


(3)ClassLoader.getClassLoader(caller):表示类加载器,到这你会发现forNanme其实也是使用的ClassLoader类加载器加载的。


(4)caller:指定类加载器。


所以,在这里你可以指定是否在class加载后被初始化。而且底层还是使用的classloader加载的。


2、classloader


在上面的案例中我们发现,classloader并没有初始化静态块,原因最好还是到源码中看。


首先我们先进入到loadclass方法中的源码。

public Class<?> loadClass(String name) 
            throws ClassNotFoundException {
    return loadClass(name, false);
}

这一步看起来还看不明白,没关系这里真正实现的是内部的loadclass,我们再跟进去看看。

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException{
    synchronized (getClassLoadingLock(name)) {
        // 首先检查这个类是否已经被加载
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
               //没有被加载使用父加载器继续加载
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {}
            if (c == null) {
                long t1 = System.nanoTime();
                c = findClass(name);
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);        
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        //如果已经加载了,那就重新加载
       if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

这个才是真正实现的方法,在这里的步骤其实很简单,大致流程是先判断class是否已经被加载,如果被加载了那就重新加载,如果没有加载那就使用双亲委派原则加载。加载的时候并没有指定是否要进行初始化。


所以现在他们的区别基本上很少,总结一下:


(1)class.forName()除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。当然还可以指定是否执行静态块。


(2)classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。


有一个小问题需要注意:我在网上看了几篇文章,亲测有错误,那就是class.forName其实是不会执行静态方法的,但是会初始化静态变量。错误的例子是使用了静态方法为静态变量赋值了


ok,一个小知识点。如有问题,还请批评指正。

相关文章
|
1月前
|
Java
Java isBlank和isEmpty的区别
JavaisBlank和isEmpty的区别
17 0
|
1月前
|
开发框架 Java API
java反射机制的原理与简单使用
java反射机制的原理与简单使用
17 1
|
1天前
|
设计模式 Java Spring
来聊聊Java的反射机制(下)
来聊聊Java的反射机制(下)
5 0
|
2天前
|
JavaScript 前端开发 Oracle
java和JavaScript的区别
java和JavaScript的区别
6 3
|
3天前
|
Java 数据库连接
深入理解Java异常处理机制
【4月更文挑战第24天】本文将探讨Java中的异常处理机制,包括异常的概念、分类、捕获和抛出等方面。通过深入了解异常处理机制,可以帮助我们编写更加健壮的程序,提高代码的可读性和可维护性。
|
19天前
|
Java 关系型数据库 MySQL
大厂面试题详解:Java抽象类与接口的概念及区别
字节跳动大厂面试题详解:Java抽象类与接口的概念及区别
40 0
|
21天前
|
安全 Java 调度
深入理解Java中的线程安全与锁机制
【4月更文挑战第6天】 在并发编程领域,Java语言提供了强大的线程支持和同步机制来确保多线程环境下的数据一致性和线程安全性。本文将深入探讨Java中线程安全的概念、常见的线程安全问题以及如何使用不同的锁机制来解决这些问题。我们将从基本的synchronized关键字开始,到显式锁(如ReentrantLock),再到读写锁(ReadWriteLock)的讨论,并结合实例代码来展示它们在实际开发中的应用。通过本文,读者不仅能够理解线程安全的重要性,还能掌握如何有效地在Java中应用各种锁机制以保障程序的稳定运行。
|
27天前
|
Java 程序员 开发者
深入理解Java异常处理机制
在Java编程中,异常处理是确保程序健壮性与稳定性的重要组成部分。本文旨在深度剖析Java异常处理机制的核心概念、结构及其实际应用策略,帮助开发者更好地理解并运用异常处理来优化程序设计。我们将从Java异常体系结构入手,探讨try-catch-finally语句块的执行流程,分析自定义异常的必要性与实现方式,并通过实例演示如何有效地管理和处理异常情况。
23 3
|
30天前
|
Java
java的 isEmpty 和 isBlank 区别?
java的 isEmpty 和 isBlank 区别?
8 0
|
1月前
|
设计模式 XML 存储
java中的反射机制
java中的反射机制
12 1