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对象(这个是上面讲到的类在加载时获得的最终产物)
例如:

package com.lin;

/**
 * 功能概要:
 *
 * @author linbingwen
 * @since  2015年10月20日
 */
public class people {

	/**
	 * @author linbingwen
	 * @since  2015年10月20日
	 * @param args
	 */
	public static void main(String[] args) throws Exception {
		System.out.println("..............使用不同的方式加载类...................");
		System.out.println(people.class);//通过类.class获得Class对象
		people a = new people();
		System.out.println(a.getClass());//通过 实例名.getClass()获得Class对象
		System.out.println(Class.forName("com.lin.people"));//通过Class.forName(全路径)获得Class对象
		System.out.println("..............使用不同的方式创建对象...................");
		System.out.println(a);//使用不同的方式创建对象
		System.out.println(people.class.newInstance());
		System.out.println(a.getClass().newInstance());
		System.out.println(Class.forName("com.lin.people").newInstance()); 

	}

}

结果:

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

2、区别:

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

如下新建一个类

package com.lin;

/**
 * 功能概要:
 *
 * @author linbingwen
 * @since  2015年10月20日
 */
public class Cat {
	static {
		System.out.println("生成了一只猫");
	}

}

然后开始使用:

package com.lin;

/**
 * 功能概要:
 *
 * @author linbingwen
 * @since  2015年10月20日
 */
public class CatTest {

	/**
	 * @author linbingwen
	 * @since  2015年10月20日
	 * @param args
	 */
	public static void main(String[] args) throws Exception{
		System.out.println("---------------Cat.class开始------------------");
		System.out.println(Cat.class);//通过类.class获得Class对象
		System.out.println("---------------Cat.class结束------------------");

		System.out.println("---------------Class.forName开始------------------");
		System.out.println(Class.forName("com.lin.Cat"));//通过Class.forName(全路径)获得Class对象
		System.out.println("---------------Class.forName结束------------------");

		System.out.println("---------------cat.getClass()开始------------------");
		Cat cat = new Cat();
		System.out.println(cat.getClass());//通过Class.forName(全路径)获得Class对象
		System.out.println("---------------cat.getClass()结束------------------");

	}

}

输出结果:

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

如下:

package com.lin;

/**
 * 功能概要:
 *
 * @author linbingwen
 * @since  2015年10月20日
 */
public class CatTest {

	/**
	 * @author linbingwen
	 * @since  2015年10月20日
	 * @param args
	 */
	public static void main(String[] args) throws Exception{
		System.out.println("---------------Cat.class开始------------------");
		System.out.println(Cat.class);//通过类.class获得Class对象
		System.out.println("---------------Cat.class结束------------------");

//		System.out.println("---------------Class.forName开始------------------");
//		System.out.println(Class.forName("com.lin.Cat"));//通过Class.forName(全路径)获得Class对象
//		System.out.println("---------------Class.forName结束------------------");

		System.out.println("---------------cat.getClass()开始------------------");
		Cat cat = new Cat();
		System.out.println(cat.getClass());//通过Class.forName(全路径)获得Class对象
		System.out.println("---------------cat.getClass()结束------------------");

	}

}

结果又变成:

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

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关键字的耦合度,还有让不同的类加载器来加载不同的类,方便提高类之间的安全性和隔离性.

版权声明:本文为博主林炳文Evankaka原创文章,转载请注明出处http://blog.csdn.net/evankaka

时间: 2024-10-05 02:33:15

Java反射-----从类加载说起的相关文章

初识Java反射

要详细的了解Java反射,就得要了解Java的类加载以及何为运行时动态加载等等概念.本文抛开其余概念,简单介绍Java反射,详细介绍会在以后有一个系统而全面的认识过后展开. 反射是Java被视为动态语言的关键,它允许程序在运行时取得任何类的内部信息.Java的这个能力或许在Web应用中用得不是很多,但在一些Java组件开发过程中非常常见,比如Spring.Hibernate等都以此为基础.了解并熟知Java反射机制对我们了解Java框架有很大的帮助. 我们首先写好一个Test类,并将它编译为cl

java JDK8 学习笔记——第17章 反射与类加载器

第十七章 反射与类加载器 17.1 运用反射 反射:.class文档反映了类基本信息,从Class等API取得类信息的方式称为反射. 17.1.1 Class与.class文档 1.java.lang.Class的实例代表Java应用程序运行时加载的.class文档,类.接口.Enum等编译过后,都会生成.class文档.Class类没有公开构造函数,实例时候JVM自动产生,每个.class文档加载时,JVM会自动生成对应的Class对象. 2.取得Class对象的方式: (1)通过Object

处理异常、常用类、反射、类加载与垃圾回收、java集合框架

异常处理概述 检查异常:检查异常通常是用户错误或者不能被程序员所预见的问题.(cheched) 运行时异常:运行时异常是一个程序在运行过程中可能发生的.可以被程序员避免的异常类型.(Unchecked)RentimeExeption 错误:实际上,错误根本不是异常,但却是用户或程序员所无法控制的问题. 异常是程序在执行过程中所产生的问题.JVM发生了内存溢出等... 异常处理:method()方法有三种 1 捕获这个异常,不让他沿着调用栈继续向下抛出 2 捕获这个异常,并继续向下抛出 3 从而导

乐字节Java反射之三:方法、数组、类加载器和类的生命周期

本文承接上一篇:乐字节Java发射之二:实例化对象.接口与父类.修饰符和属性 继续讲述Java反射之三:方法.数组.类加载器 一.方法 获取所有方法(包括父类或接口),使用Method即可. public static void test() throws Exception { Class<?> clz = Class.forName("com.shsxt.ref.simple.User "); //获取属性 System.out.println("======

利用java反射机制 读取配置文件 实现动态类加载以及动态类型转换

作者:54dabang 在spring的学习过程之中,我们可以看出通过配置文件来动态管理bean对象的好处(松耦合 可以让零散部分组成一个整体,而这些整体并不在意之间彼此的细节,从而达到了真正的物理上的疏散耦合,而非逻辑,有了IOC之后,我们可以让SPRING充当各框架中的整合器,把技术框架进行完美的结合). Spring实现的一个重要的机制是通过反射(java.lang.reflect)读取配置文件,通过配置文件来动态生成配置文件中的类对象.Java动态加载类主要是为了不改变主程序代码,通过修

java 反射 详解

本文来自:blog.csdn.net/ljphhj JAVA反射机制:   通俗地说,反射机制就是可以把一个类,类的成员(函数,属性),当成一个对象来操作,希望读者能理解,也就是说,类,类的成员,我们在运行的时候还可以动态地去操作他们. 理论的东东太多也没用,下面我们看看实践 Demo - Demo: package cn.lee.demo; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import

java 反射类的理解与应用

本文主要解析的类是: ClassLodaer,Class,Field,Method,Constructor. 本文的目标很简单,只是对这些常用的反射类进行简单解释.对这些类中常用方法进行介绍. JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法:这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制.Java反射机制主要提供了以下功能: 在运行时判断任意一个对象所属的类:在运行时构造任意一个类的对象:在

Java中的类加载器

转载:http://blog.csdn.net/zhangjg_blog/article/details/16102131 从java的动态性到类加载机制 我们知道,Java是一种动态语言.那么怎样理解这个"动态"呢?或者说一门语言具备了什么特性,才能称之为动态语言呢?对于java,我是这样理解的. 我们都知道JVM(java虚拟机)执行的不是本地机器码指令,而是执行一种称之为字节码的指令(存在于class文件中).这就要求虚拟机在真正执行字节码之前,先把相关的class文件加载到内存

Java反射与代理

Java反射机制与动态代理,使得Java更加强大,Spring核心概念IoC.AOP就是通过反射机制与动态代理实现的. 1       Java反射 示例: User user = new User(); user.setTime5Flag("test"); Class<?> cls = Class.forName("com.test.User"); //接口必须public,无论是否在本类内部使用!或者使用cls.getDeclaredMethod()