一、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构造。