您的位置:首页 > 编程语言 > Java开发

java中反射机制及其类加载的几种方式的异同

2015-02-15 23:55 253 查看
一、Java中反射机制总结:

反射的定义:通过反射,把Java类中的各个组成部分映射到相应的Java类

反射的优点:

1. 减少对象的依赖,调用方法更灵活,改变属性的值。

2. 通过class对象得到该类的对象,从而获取到方法等。

3. 提高程序的扩展性

首先通过程序来演示一下反射原理:

有一个实体类Student.java

package com.huaxin.reflect;

/**
* @author 赵芳
* 2015年2月15日 下午5:23:30
*/
public class Student {

public String name = "abcd";
/**
* @param string
*/
public Student(String name) {
System.out.println("调用有一个参数的构造方法创建对象");
this.name = name;
}
public Student() {
System.out.println("调用无参数构造方法创建对象");
}
@Override
public String toString() {
return "Student [name=" + name + "]";
}

public void study(){
System.out.println(name+"学习");
}

}
一个测试类Test.java

package com.huaxin.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
* java中反射机制的测试
* @author 赵芳
* 2015年1月24日  上午9:56:15
*/
public class Test {

public static void main(String[] args) throws Exception{
//通过反射获取构造方法Constructor   创建对象
Class c = Class.forName("com.huaxin.reflect.Student");
Constructor cons = c.getDeclaredConstructor(String.class);
//		cons.setAccessible(true);//访问权限
Student stu = (Student) cons.newInstance("bbb");
System.out.println(stu);

//使用new关键字创建对象
Student stu1 = new Student("aaa");
System.out.println(stu1);

// 创建此 Class 对象所表示的类的一个新实例,只能是无参
Student stu2 = (Student) c.newInstance();
System.out.println(stu2);

//通过反射获取Field,并且该属性在此类中是public的,否则会抛出NoSuchFieldException
Field field_name = c.getField("name");
System.out.println(field_name.get(stu));

//通过反射来调用方法Method
Method method = c.getMethod("study", null);
method.invoke(stu2, null);
}
}


输出结果:

调用有一个参数的构造方法创建对象

Student [name=bbb]

调用有一个参数的构造方法创建对象

Student [name=aaa]

调用无参数构造方法创建对象

Student [name=abcd]

bbb

abcd学习

每个Java程序执行前都必须经过编译、加载、连接和初始化这几个阶段,后三个阶段如下图:



(1)加载是指将编译后的java类文件(.class文件)中的二进制数据读入内存,并将其放在运行时数据区的方法区内,然后再堆区创建一个java.lang.Class对象,用来封装类在方法区的数据结构。即加载后最终得到的是Class对象,并且更加值得注意的是:该Java.lang.Class对象是单实例的,无论这个类创建了对少个对象,它的Class对象是唯一的!而加载并获取该Class对象可以通过三种途径:Class.forName(类的全名称)、实例对象.class(属性)、实例对象getClass()。

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

(3)Java对类的使用分为两种方式:主动使用和被动使用。其中主动使用的方式有以下6种,

①创建类的实例

②访问某个类或接口的静态变量,或者对该静态变量赋值

③调用类的静态方法

④反射(如Class.forName(“java.lang.String”))

⑤初始化一个类的子类

⑥Java虚拟机启动时被标明为启动类的类

而类的初始化时机正是java程序对类的首次主动使用,除了以上6种方式,其他对类的使用都是被动使用,都不会导致类的初始化。并且应注意以下几个方面:

①调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。

②当java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则并不适用于接口。

l  在初始化一个类时,并不会先初始化它所实现的接口。

l  在初始化一个接口时,并不会先初始化它的父接口。

因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定接口的静态变量时,才会导致该接口的初始化。

在这里可以看出,接口的两重性:可以把接口当做类(因为在接口中有静态变量时,可以被初始化);接口就是接口,和类无关(接口中没有构造方法,所以不能被初始化)。

二、Class.forName、实例对象.class(属性)、实例对象getClass()的异同:

1、相同点

通过这几种方式,得到的都是java.lang.Class对象

例如:







2、区别

① Classc1 = String.class;JVM将使用String类的类装载器,将String类装入内存(前提是String类还没有装入内存),不对String类做类的初始化工作,返回String类的Class对象。

② Class.forName(“类名”);装入类**,并做类的初始化

③ Class= 对象引用o.getClass();返回引用对象o运行时真正所指的对象(因为儿子对象的引用可能会赋给父对象的引用变量中)所属的类的Class对象

 

从JVM的角度看,我们使用关键字new创建一个类的时候,这个类可以没有被加载。但是使用Class对象的newInstance()方法的时候,就必须保证:1.这个类已经加载;2.这个类已经连接了。而完成上面两个步骤的正是Class的静态方法forName()所完成的,这个静态方法调用了启动类加载器,即加载java API的那个加载器。

 

    现在可以看出,Class对象的newInstance()实际上是把new这个方法分解为两步,即首先调用Class加载方法加载某个类,然后实例化。这样分布的好处:我们可以在调用Class的静态加载方法forName时获得更好的灵活性,提供给了一种降耦的手段。

 

    最后,用最简单的描述来区分new关键字和newInstance()方法的区别:

newInstance:弱类型,低效率,只能调用无参构造;

new:强类型,相对高效,能调用任何Public构造。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: