Java反射探索-----从类加载说起

林炳文Evankaka原创作品。转载请注明出处http://blog.csdn.net/evankaka

        摘要:本文主要讲了Java类加载的机制,这是学习反射的入门基础。

一、类加载

JVM和类       当我们调用Java命令运行某个Java程序时,该命令将会启动一条Java虚拟机进程,不管该Java程序有多么复杂,该程序启动了多少个线程,它们都处于该Java虚拟机进程里。正如前面介绍的,同一个JVM的所有线程、所有变量都处于同一个进程里,它们都使用该JVM进程的内存区。当系统出现以下几种情况时,JVM进程将被终止: 1、程序运行到最后正常结束。 2、程序运行到使用System.exit()或Runtime.getRuntime().exit()代码结束程序。 3、程序执行过程中遇到未捕获的异常或错误而结束。 3、程序所在平台强制结束了JVM进程。 从上面的介绍可以看出,当Java程序运行结束时,JVM进程结束,该进程在内存中状态将会丢失。

类的生命周期

类的加载/类初始化      当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过加载、连接、初始化三个步骤来对该类进行初始化,如果没有意外,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或类初始化。

加载:查找并加载类的二进制数据

     1、通过一个类的全限定名来获取定义此类的二进制字节流。 2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 3、在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。

注意:将编译后的java类文件(也就是.class文件)中的二进制数据读入内存,并将其放在运行时数据区的方法区内,然后再堆区创建一个Java.lang.Class对象,用来封装类在方法区的数据结构。即加载后最终得到的是Class对象,并且更加值得注意的是:该Java.lang.Class对象是单实例的,无论这个类创建了多少个对象,他的Class对象时唯一的! 连接:         1、验证:确保被加载的类的正确性         2、准备:为类的静态变量分配内存,并将其初始化为默认值         3、解析:把类中的符号引用转换为直接引用。 初始化:为类的静态变量赋予正确的初始值。

注意:连接和初始化阶段,其实静态变量经过了两次赋值:第一次是静态变量类型的默认值;第二次是我们真正赋给静态变量的值。

我简单画了个图,其过程如下:

类加载指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。事实上,每个类是一批具有相同特征的对象的抽象(或者说概念),而系统中所有的类,它们实际上也是对象,它们都是java.lang.Class的实例。     加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是我们前面所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。 通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源: 1、从本地文件系统来加载class文件,这是绝大部分示例程序的类加载方式。 2、从JAR包中加载class文件,这种方式也是很常见的,前面介绍JDBC编程时用到的数据库驱动类就是放在JAR文件中,JVM可以从JAR文件中直接加载该class文件。 3、通过网络加载class文件。 4、把一个Java源文件动态编译、并执行加载。 类加载器通常无须等到“首次使用”该类时才加载该类,Java虚拟机规范允许系统预先加载某些类。

Java程序对类的使用方式 主动使用 1、创建类的实例 2、方法某个类或接口的静态变量,或者对该静态变量赋值 3、调用类的静态方法 4、反射(如 Class.forName(“com.itzhai.Test”)) 5、初始化一个类的子类 6、Java虚拟机启动时被标明为启动类的类(Main Class) 被动使用 除了以上6中方式,其他对类的使用都是被动使用,都不会导致类的初始化。类的初始化时机正是java程序对类的首次主动使用, 所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用”时才初始化它们。

对象初始化 在类被装载、连接和初始化,这个类就随时都可能使用了。对象实例化和初始化是就是对象生命的起始阶段的活动,在这里我们主要讨论对象的初始化工作的相关特点。 Java 编译器在编译每个类时都会为该类至少生成一个实例初始化方法--即"<init>()" 方法。此方法与源代码中的每个构造方法相对应,如果类没有明确地声明任何构造方法,编译器则为该类生成一个默认的无参构造方法,这个默认的构造器仅仅调用父类的无参构造器,与此同时也会生成一个与默认构造方法对应的 "<init>()" 方法. 通常来说,<init>() 方法内包括的代码内容大概为:调用另一个 <init>() 方法;对实例变量初始化;与其对应的构造方法内的代码。 如果构造方法是明确地从调用同一个类中的另一个构造方法开始,那它对应的 <init>() 方法体内包括的内容为:一个对本类的 <init>() 方法的调用;对应用构造方法内的所有字节码。 如果构造方法不是通过调用自身类的其它构造方法开始,并且该对象不是 Object 对象,那 <init>() 法内则包括的内容为:一个对父类 <init>() 方法的调用;对实例变量初始化方法的字节码;最后是对应构造子的方法体字节码。 如果这个类是 Object,那么它的 <init>() 方法则不包括对父类 <init>() 方法的调用。

二、Class.forName、实例对象.class(属性)、实例对象getClass()的区别

1、相同点: 通过这几种方式,得到的都是Java.lang.Class对象(这个是上面讲到的类在加载时获得的最终产物) 例如:

[java] view plaincopy

  1. package com.lin;  
  2. /**
  3.  * 功能概要:
  4.  * 
  5.  * @author linbingwen
  6.  * @since  2015年10月20日 
  7.  */
  8. public class people {  
  9. /**
  10.      * @author linbingwen
  11.      * @since  2015年10月20日 
  12.      * @param args    
  13.      */
  14. public static void main(String[] args) throws Exception {  
  15.         System.out.println("..............使用不同的方式加载类...................");  
  16.         System.out.println(people.class);//通过类.class获得Class对象
  17.         people a = new people();  
  18.         System.out.println(a.getClass());//通过 实例名.getClass()获得Class对象
  19.         System.out.println(Class.forName("com.lin.people"));//通过Class.forName(全路径)获得Class对象
  20.         System.out.println("..............使用不同的方式创建对象...................");  
  21.         System.out.println(a);//使用不同的方式创建对象
  22.         System.out.println(people.class.newInstance());  
  23.         System.out.println(a.getClass().newInstance());  
  24.         System.out.println(Class.forName("com.lin.people").newInstance());   
  25.     }  
  26. }  

结果:

从上面可以看到不同的方式加载类。其实这一过程只发生一次!

2、区别:

下面用一个实例来说说它们的区别

如下新建一个类

[java] view plaincopy

  1. package com.lin;  
  2. /**
  3.  * 功能概要:
  4.  * 
  5.  * @author linbingwen
  6.  * @since  2015年10月20日 
  7.  */
  8. public class Cat {  
  9. static {  
  10.         System.out.println("生成了一只猫");  
  11.     }  
  12. }  

然后开始使用:

[java] view plaincopy

  1. package com.lin;  
  2. /**
  3.  * 功能概要:
  4.  * 
  5.  * @author linbingwen
  6.  * @since  2015年10月20日 
  7.  */
  8. public class CatTest {  
  9. /**
  10.      * @author linbingwen
  11.      * @since  2015年10月20日 
  12.      * @param args    
  13.      */
  14. public static void main(String[] args) throws Exception{  
  15.         System.out.println("---------------Cat.class开始------------------");  
  16.         System.out.println(Cat.class);//通过类.class获得Class对象
  17.         System.out.println("---------------Cat.class结束------------------");  
  18.         System.out.println("---------------Class.forName开始------------------");  
  19.         System.out.println(Class.forName("com.lin.Cat"));//通过Class.forName(全路径)获得Class对象
  20.         System.out.println("---------------Class.forName结束------------------");  
  21.         System.out.println("---------------cat.getClass()开始------------------");  
  22.         Cat cat = new Cat();  
  23.         System.out.println(cat.getClass());//通过Class.forName(全路径)获得Class对象
  24.         System.out.println("---------------cat.getClass()结束------------------");  
  25.     }  
  26. }  

输出结果:

如果,将Class.forName()去掉:

如下:

[java] view plaincopy

  1. package com.lin;  
  2. /**
  3.  * 功能概要:
  4.  * 
  5.  * @author linbingwen
  6.  * @since  2015年10月20日 
  7.  */
  8. public class CatTest {  
  9. /**
  10.      * @author linbingwen
  11.      * @since  2015年10月20日 
  12.      * @param args    
  13.      */
  14. public static void main(String[] args) throws Exception{  
  15.         System.out.println("---------------Cat.class开始------------------");  
  16.         System.out.println(Cat.class);//通过类.class获得Class对象
  17.         System.out.println("---------------Cat.class结束------------------");  
  18. //      System.out.println("---------------Class.forName开始------------------");
  19. //      System.out.println(Class.forName("com.lin.Cat"));//通过Class.forName(全路径)获得Class对象
  20. //      System.out.println("---------------Class.forName结束------------------");
  21.         System.out.println("---------------cat.getClass()开始------------------");  
  22.         Cat cat = new Cat();  
  23.         System.out.println(cat.getClass());//通过Class.forName(全路径)获得Class对象
  24.         System.out.println("---------------cat.getClass()结束------------------");  
  25.     }  
  26. }  

结果又变成:

所以,可以得出以下结论:

1)Class cl=Cat.class; JVM将使用类Cat的类装载器,将类A装入内存(前提是:类A还没有装入内存),不对类A做类的初始化工作.返回类A的Class的对象 2)Class cl=对象引用o.getClass();返回引用o运行时真正所指的对象(因为:儿子对象的引用可能会赋给父对象的引用变量中)所属的类的Class的对象 ,如果还没装载过,会进行装载。 3)Class.forName("类名"); 装入类A,并做类的初始化(前提是:类A还没有装入内存)

三、new和newInstance()

从JVM的角度看,我们使用关键字new创建一个类的时候,这个类可以没有被加载。但是使用Class对象的newInstance()方法的时候,就必须保证:

1、这个类已经加载;

2、这个类已经连接了。而完成上面两个步骤的正是Class的静态方法forName()所完成的,这个静态方法调用了启动类加载器,即加载 java API的那个加载器。  现在可以看出,Class对象的newInstance()(这种用法和Java中的工厂模式有着异曲同工之妙)实际上是把new这个方式分解为两步,即首先调用Class加载方法加载某个类,然后实例化。这样分步的好处是显而易见的。我们可以在调用class的静态加载方法forName时获得更好的灵活性,提供给了一种降耦的手段。 

Class.forName().newInstance()和通过new得到对象的区别

1、使用newInstance可以解耦。使用newInstance的前提是,类已加载并且这个类已连接,这是正是class的静态方法forName()完成的工作。newInstance实际上是把new 这个方式分解为两步,即,首先调用class的加载方法加载某个类,然后实例化。 2、newInstance: 弱类型。低效率。只能调用无参构造。 new: 强类型。相对高效。能调用任何public构造。  3、newInstance()是实现IOC、反射、面对接口编程和依赖倒置等技术方法的必然选择,new只能实现具体类的实例化,不适合于接口编程。  4、 newInstance() 一般用于动态加载类。

5、Class.forName(“”).newInstance()返回的是object 。

6、newInstance( )是一个方法,而new是一个关键字;

注:一般在通用框架里面用的就是class.forName来加载类,然后再通过反射来调用其中的方法,譬如Tomcat源码里面,这样就避免了new关键字的耦合度,还有让不同的类加载器来加载不同的类,方便提高类之间的安全性和隔离性.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大闲人柴毛毛

三分钟理解“模板方法模式”——设计模式轻松掌握

模板方法模式的官方定义: 在模板方法模式中,只定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定...

375100
来自专栏java学习

Java基础总结大全(1)

一、基础知识: 1、JVM、JRE和JDK的区别: JVM(Java Virtual Machine):java虚拟机,用于保证java的跨平台的特性。 ...

37950
来自专栏青青天空树

2017-统计字符个数

输入:输入数据有多行,第一行是一个整数n,表示测试实例的个数,后面跟着n行,每行包括一个由字母和数字组成的字符串。

13410
来自专栏开源优测

python selenium2 - webelement操作常用方法

完整路径 C:\Python27\Lib\site-packages\selenium\webdriver\remote\webelement...

32350
来自专栏C语言及其他语言

[蓝桥杯]Hello, world!

题目描述 This is the first problem for test. Since all we know the ASCII code, your ...

36080
来自专栏魂祭心

原 Curry的js实现

36750
来自专栏xingoo, 一个梦想做发明家的程序员

剑指OFFER之复杂链表的复制(九度OJ1524)

题目描述: 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点)。 输入: 输入可能包含多个测试样例,输入以...

20790
来自专栏Android Note

Kotlin —  Destructuring Declarations(解构声明)

14220
来自专栏海天一树

小朋友学Python(3):布尔类型

本节讲解C/C++/Java/Python中的布尔类型。 一、C语言 C语言中没有布尔类型,判断时 ,0为假,非0为真。 二、C++ C++的bool是布尔类型...

307110
来自专栏小二的折腾日记

面试总结-C++

堆、栈、自由存储区、全局/静态存储区、常量存储区 自由存储区存储malloc申请的内存 (1)从静态存储区域分配 。内存在程序编译的时候就已经分配好,这块内存在...

24010

扫码关注云+社区

领取腾讯云代金券