您的位置:首页 > 职场人生

黑马程序员---高级-反射

2015-06-21 15:34 471 查看
——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-

一、类的加载、连接和初始化

在介绍反射之前,需要了解一下类的加载有关知识。

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统就会通过加载、连接、初始化三个步骤来对该类进行初始化。

(1)过程

1.类加载指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。

2.类的连接,当类被加载之后,系统会为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。

3.类的初始化,虚拟机负责对类进行初始化,主要就是对类变量进行初始化。

(2)类初始化时机

当Java程序首次通过下面6中方式来使用某个类或接口是,系统就会初始化该类或接口。

1.创建该类的实例

2.调用某个类的类方法(静态方法)

3.访问某个类或接口的类变量,或为该类变量赋值

4.使用反射方式来强制创建某个类或接口对应的java.lang.Class对象

5.初始化某个类的子类

6.直接使用java.exe命令来运行某个主类

二、类加载器

类加载器负责将.class文件加载到内存中,并为之生成对应的java.lang.Class对象。

类加载器的组成

※1.Bootstrap ClassLoader:根类加载器

也被称为引导类加载器,它负责加载Java的核心类,比如System,String等。在JDK中JRE的lib目录下rt.jar文件中

※2.Extension ClassLoader:扩展类加载器

负责加载JRE的扩展目录,比如System,String等。在JDK中JRE的lib目录下rt.jar文件中

※3.System ClassLoader:系统类加载器

也被称为应用类加载器,负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径,程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。

三、反射

终于等到反射登场了。

反射就是通过class文件对象,去使用该文件中的成员变量,构造方法,成员方法。

(1)获取Class对象

前面已经介绍过了,每个类被加载之后,系统就会为该类生成一个对应的Class对象,通过该Class对象就可以访问到JVM中的这个类。在Java中获得Class对象通常有三种方式。

※方式一:调用对象的getClass()方法。它是Object类中的方法。

此方法是普通方法,不是静态的,要通过类的对象的引用去调用。

Student stu = new Studetn();

Class stuClass = stu.getClass();

※方式二:任何的”数据类型”都有一个”静态的属性”–class属性,这个属性可以

获取这个类的Class对象

※方式三(常用):调用Class类中的静态方法forName()可以获取某个类的Class对象;

public static Class forName(String className)

下面是具体的程序例子:

首先有个Student类

public class Student {

}


接着在测试类中运用上述三个方法来获取Class对象。

public class Demo {
public static void main(String[] args) throws ClassNotFoundException {
Student stu = new Student();
//方式一:
Class stuClass1 = stu.getClass();
//方式二:
Class stuClass2 = Student.class;
//方式三:
//  Class stuClass3 = Class.forName("Student");//运行时异常。这里需要的是:全名限定的类名(带包的类名)
Class stuClass3 = Class.forName("cn.itcast.demo01_获取Class对象的三种方式.Student");

//对于Student的Class对象,三种方式获取的都是同一个Class对象。这个Class对象,在内存中只产生一个。
System.out.println("stuClass1 == stuClass2 :" + (stuClass1 == stuClass2));
System.out.println("stuClass1 == stuClass3 : " + (stuClass1 == stuClass3));;
}
}


程序运行的结果是:



(2)获取构造方法

※批量获取

1.public Constructor[] getConstructors():获取所有公共构造方法

2.public Constructor[] getDeclaredConstructors():获取所有构造方法。包括公有,受保护,默认,私有

※获取单个的构造方法:

1.public Constructor getConstructor(Class… parameterTypes):获取类中的指定的”公有构造方法”。 参数:parameterTypes:构造方法形参的类型的Class

2.public Constructor getDeclaredConstructor(Class…parameterTypes):获取类中的任何的构造方法,包括私有的

调用构造方法:通过Constructor去调用。使用方法:public T newInstance(Object… initargs)

我们先来一个Student类,里面有被各种权限修饰符修饰的构造方法、成员变量和成员方法。

public class Student {
//********一些构造方法***********//
//公有的
public Student(){
System.out.println("调用了公有的,无参的构造方法");
}
public Student(boolean b){
System.out.println("调用了公有的,带参的构造方法。b = " + b);
}
//受保护的
protected Student(int n){
System.out.println("调用了受保护的构造方法 n = " + n);
}
//默认的
Student(String s){
System.out.println("调用了默认的构造方法。s = " + s);
}
//私有的
private Student(char c){
System.out.println("调用了私有的构造方法。c = " + c);
}

//*****************成员属性***************//
public String stuName;
protected int age;
char sex;
private String address;
//*****************成员方法*******************//

public void show(){
System.out.println("调用了公有,无参的show()方法");
}
protected void show(String s){
System.out.println("调用了受保护的,带参的show()方法。参数s = " + s);
}
void show(int n){
System.out.println("调用了默认的,带参的show()方法。参数n = " + n);
}
private int  show(char c){
System.out.println("调用了私有的,带参的带返回值的show()方法。参数c = " + c);
return 10 + 20;
}

}


接着在测试类中来获取这些构造方法:

import java.lang.reflect.Constructor;

public class ReflectGetConstrutorDemo {
public static void main(String[] args) throws Exception {
//1.获取Student的Class对象
Class stuClass = Class.forName("com.pizhihui.getconstructor.Student");
//2.获取构造方法:
System.out.println("************获取所有\"公有\"构造方法*************");
//getConstructors():获取所有公共构造方法
Constructor[] conArray = stuClass.getConstructors();
for(Constructor c : conArray ){
System.out.println(c);
}
System.out.println("**********获取\"所有\"构造方法,包括公有、受保护、默认、私有************");
Constructor[] conArray2 = stuClass.getDeclaredConstructors();
for(Constructor c : conArray2){
System.out.println(c);
}

System.out.println("***********获取单个,公有的,无参的构造方法******* ***");
Constructor con1 = stuClass.getConstructor();
System.out.println(con1);
System.out.println("*******获取单个,公有的,带参的构造方法***************");
Constructor con2 = stuClass.getConstructor(boolean.class);
System.out.println(con2);

//  Constructor con3 = stuClass.getConstructor(int.class);//java.lang.NoSuchMethodException
//  System.out.println(con3);
System.out.println("*********获取单个,受保护的构造方法**************");

Constructor con3 = stuClass.getDeclaredConstructor(int.class);
System.out.println(con3);
System.out.println("***********获取单个,私有的构造方法*************");
Constructor con4 = stuClass.getDeclaredConstructor(char.class);
System.out.println(con4);

System.out.println("**********调用公有,无参的构造方法*********");
Object obj = con1.newInstance();
System.out.println("obj = " + obj);
System.out.println("*******调用公有,带参的构造方法************");
Student stu = (Student)con2.newInstance(true);
System.out.println("**********调用受保护的构造方法***********");
obj = con3.newInstance(20);
System.out.println("*******调用私有的构造方法***************");
//由于要访问是私有成员,所以这里要设置"取消访问限制检查",进行暴力访问
con4.setAccessible(true);
obj = con4.newInstance('男');

}
}


程序运行的结果是:



(3)获取成员变量

※ 批量获取:

1.Field[] getFields() :所用”公有”的成员属性

2.Field[] getDeclaredFields() :所有的成员属性,包括私有的:

※获取单个的属性

1.Field getField(String fieldName):获取单个的,公有的成员属性

2.Field getDeclaredField(String fieldName):获取单个的成员属性,可以是任何的访问级别

怎样设置和获取属性的值?

Field:

set(Object obj,Object value):设置值。注意:设置前,一定要先实例化一个此类的对象。

get(Object obj)

Student类还是用上一个类

测试类代码:

import java.lang.reflect.Method;

public class ReflectGetVariableDemo {
public static void main(String[] args) throws Exception{
//1.获取Student的Class对象
Class stuClass = Class.forName("com.pizhihui.getvariable.Student");
//2.批量获取成员方法
System.out.println("**************获取所有\"公有\"的成员方法************");
Method[] methodArray = stuClass.getMethods();//包含了父类的方法
for(Method m : methodArray){
System.out.println(m);
}
System.out.println("**********获取所有的成员方法,包括私有的**************");
Method[] methodArray2 = stuClass.getDeclaredMethods();//不包含父类的。
for(Method m : methodArray2){
System.out.println(m);
}

System.out.println("******获取单个\"公有\"的\"无参\"的成员方法*********");
Method m1 = stuClass.getMethod("show");
System.out.println(m1);

System.out.println("*********获取单个\"私有\"的\"带参\"的成员方法*********");
Method m2 = stuClass.getDeclaredMethod("show", char.class);
System.out.println(m2);

System.out.println("**********调用\"公有\"的\"无参\"的成员方法*********");
//先获取对象
Object stuObj = stuClass.getConstructor().newInstance();
m1.invoke(stuObj);

System.out.println("********调用\"私有\"的\"带参\"的\"带返回值\"的成员方法*********");
//暴力访问
m2.setAccessible(true);
Object result = m2.invoke(stuObj, '男');
System.out.println("返回值为:" + result);
}
}


运行结果如下:



(4)获取成员方法

※批量获取:

1.Method[] getMethods() :获取所有的”公有”方法;

2.Method[] getDeclaredMethods() :获取所有方法,包括私有的。

※获取单个的:

1.

:获取某个公有的方法。 形参:name:方法名;parameterTypes:形参的类型列表;

2.

:获取某个方法。可以是私有的。

调用方法:

public Object invoke(Object obj,Object…args):调用此方法;注意:调用方法前,一定要先实例化一个此类对象。参数:obj : 方法所属的对象;args: 调用方法需要的实参

Student类依然用上面的那个。

接下来是测试类:

import java.lang.reflect.Method;

public class ReflectGetMethodDemo {
public static void main(String[] args) throws Exception{
//1.获取Student的Class对象
Class stuClass = Class.forName("com.pizhihui.getmethod.Student");
//2.批量获取成员方法
System.out.println("********获取所有\"公有\"的成员方法*********");
Method[] methodArray = stuClass.getMethods();//包含了父类的方法
for(Method m : methodArray){
System.out.println(m);
}
System.out.println("********获取所有的成员方法,包括私有的*********");
Method[] methodArray2 = stuClass.getDeclaredMethods();//不包含父类的。
for(Method m : methodArray2){
System.out.println(m);
}

System.out.println("********获取单个\"公有\"的\"无参\"的成员方法********");
Method m1 = stuClass.getMethod("show");
System.out.println(m1);

System.out.println("*******获取单个\"私有\"的\"带参\"的成员方法********");
Method m2 = stuClass.getDeclaredMethod("show", char.class);
System.out.println(m2);

System.out.println("********调用\"公有\"的\"无参\"的成员方法************");
//先获取对象
Object stuObj = stuClass.getConstructor().newInstance();
m1.invoke(stuObj);

System.out.println("****调用\"私有\"的\"带参\"的\"带返回值\"的成员方法********");
//暴力访问
m2.setAccessible(true);
Object result = m2.invoke(stuObj, '男');
System.out.println("返回值为:" + result);
}
}


程序运行的结果是:



四、反射的应用

*(1)通过反射运行配置文件内容

首先有三个类Line1,Line2和Line3。后面的都是对前面的升级

public class Line1 {
public void show(){
System.out.println("线路1");
}
}

public class Line2 {
public void fun(){
System.out.println("线路2");
}
}

public class Line3 {
public void method(){
System.out.println("线路3");
}
}


接下来是测试类中的代码:

import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Properties;

public class Demo {
public static void main(String[] args) throws Exception {
//  Line1 line = new Line1();
//  line.show();

//升级
//  Line2 line = new Line2();
//  line.fun();

//通过反射,反向加载LineXxxx类
//  Class aClass = Class.forName("cn.itcast.demo05_练习_通过反射运行配置文件内容.Line2");//更改点1
Class aClass = Class.forName(getValue("className"));
//实例化对象
Object obj = aClass.getConstructor().newInstance();
//获取fun方法
Method m = aClass.getMethod(getValue("methodName"));//更改点二
//调用fun方法
m.invoke(obj);

}

//一个方法,通过某个键,获取配置文件中的值
public static String getValue(String key) throws IOException{
Properties pro = new Properties();
FileReader fileIn = new FileReader("line.properties");
pro.load(fileIn);
fileIn.close();

return pro.getProperty(key);

}
}


通过对反射的运用,可以极大的提高程序的维护性,不用担心需求改变后,或者功能升级后,大片的改动程序内容,运用反射后就只需要改变配置文件内容即可。

(2)通过反射越过泛型的检查

在使用结合的时候,jdk1,5以后加入了泛型,使得对添加到结合里的元素的类型做了规定,那么我们可以还能添加别的类型的元素吗?答案是可以的,使用反射就可以达到这个目的。

import java.lang.reflect.Method;
import java.util.ArrayList;

/*
* 定义一个具有Integer类型泛型的集合对象,问:怎么可以向这个集合中添加一个字符串;
*/
public class Demo {
public static void main(String[] args) throws Exception{
ArrayList<Integer> intList = new ArrayList<>();

intList.add(10);
//  intList.add("abc");

//通过反射,直接去加载ArrayList的Class,然后调用它的add方法
Class listClass = ArrayList.class;
//获取add方法
Method addMethod = listClass.getMethod("add", Object.class);
//调用add方法
addMethod.invoke(intList, "abc");

//遍历集合
System.out.println("遍历集合:");
for(Object o : intList){
System.out.println(o);
}
}
}


程序运行的结果是:



(3)写一个方法可以设置某个对象的某个属性为指定的值

首先有个Dog和Cat类:

public class Dog {
private int age;

public int getAge(){
return this.age;
}
}

public class Cat {
private String name;

public String getName(){
return this.name;
}
}


接下来是测试类中的内容:

import java.lang.reflect.Field;

/*
* 写一个通用的设置某个对象的某个属性为指定的值的方法
*/
public class Demo {
public static void main(String[] args) throws Exception {
//将Cat对象的name属性设置为"波斯猫"
Cat c = new Cat();
setValue(c,"name","波斯猫");
System.out.println("验证:取值:" + c.getName());

//为Dog对象的age属性赋值
Dog d = new Dog();
setValue(d,"age",2);
System.out.println("验证:" + d.getAge());
}

//定义一个方法,这个方法能够为某个对象的某个属性,设定为某个值;
public static void setValue(Object obj , String fieldName,Object value) throws Exception{
//使用反射
//1.获取obj的Class对象
Class aClass = obj.getClass();
//2.获取fileName属性。可能是私有的
Field f = aClass.getDeclaredField(fieldName);
//3.有可能是私有的,在访问之前,暴力一下
f.setAccessible(true);
//4.赋值
f.set(obj, value);
}
}


程序运行的结果是:



五、动态代理

在Java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口可以生成JDK动态代理类或动态代理对象。

如果在程序中为一个或多个接口动态地生成实现类,就可以使用Proxy来创建动态代理类;如果需要为一个或多个接口动态地创建实例,一个使用Proxy来创建动态代理类。

※Proxy提供的创建动态代理实例的方法是:

static Object newProxyInstance(ClassLoader loader,Class

[]interfaces,InvocationHandler h)

这个方法直接创建一个动态代理对象,该代理对象的实现类实现了interfaces指定的系类接口,执行代理对象的每个方法时都会被替换执行InvocationHandler对象的invoke方法。

※使用动态代理的步骤:

1).自定义一个类,实现InvocationHandler接口,重写内部的invoke()方法;

2).在需要代理类的时候,使用Proxy的newProxyInstance()方法获取代理类的对象

这里首先有个IDAO接口:

public interface IDAO {
public void add();
public void update();
public void delete();
public void find();
}


接着有TeacherDao和UserDao类实现了IDAO这个接口:

public class UserDao implements IDAO{
public void add(){
System.out.println("添加一条User信息");
}
public void update(){
System.out.println("修改一条User信息");
}
public void delete(){
System.out.println("删除一条User信息");
}
public void find(){
System.out.println("查询一条User信息");
}
}

public class TeacherDao implements IDAO {

@Override
public void add() {
System.out.println("添加一条Teacher信息");
}

@Override
public void update() {
System.out.println("修改一条Teacher信息");
}

@Override
public void delete() {
System.out.println("删除一条Teacher信息");
}

@Override
public void find() {
System.out.println("查询一条Teacher信息");
}

}


然后有个MyInvocationHandler类实现了InvocationHandler接口,重写了里面的invoke方法。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {
private Object target;//需要代理的对象;
public MyInvocationHandler(Object target){
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//  check();
//调用原方法
Object result = method.invoke(target, args);//这里调用的就是我们在Demo的main()方法中想执行的方法
log();
return result;
}

private void check(){
System.out.println("权限检查......");
}
private void log(){
System.out.println("写入日志......");
}

}


最后测试类中的代码:

public class Demo {
public static void main(String[] args) {
IDAO userDao = (IDAO)Proxy.newProxyInstance(UserDao.class.getClassLoader(),
UserDao.class.getInterfaces(),
new MyInvocationHandler(new UserDao()));
userDao.add();
userDao.update();
userDao.delete();
userDao.find();

//为TeacherDao生成一个代理对象
IDAO teaDao = (IDAO)Proxy.newProxyInstance(TeacherDao.class.getClassLoader(),
TeacherDao.class.getInterfaces(),
new MyInvocationHandler(new TeacherDao()));

teaDao.add();
teaDao.update();
teaDao.delete();
teaDao.find();
}
}


程序运行的结果是:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: