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

黑马程序员--Java反射

2015-08-11 18:00 363 查看
——- android培训java培训、期待与您交流! ———-

学习反射首先需要了解类的加载,当程序使用某个类时,如果该类还未被加载到内存,则系统通过加载,连接,初始化三步来实现对这个类的初始化。

加载

指将class文件读入到内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象。

连接

验证是否有正确的内部结构,并和其他类协调一致

准备负责为类的静态成员分配内存,并设置默认初始化值。

解析将类的二进制数据中的符号引用替换为直接引用

初始化

就是通过构造方法进行初始化

类的加载器

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

类加载器的组成

Bootstrap ClassLoader 根类加载器

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

Extension ClassLoader 扩展类加载器

负责JRE的扩展目录中jar包的加载,在JDK中JRE的lib目录下ext目录

System ClassLoader系统类加载器

负责在JVM启动时加载来自Java命令的class文件,以及classpath环境变量所指定的jar包和类路径。

Java反射机制是在运行状态中,对任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

要想解剖一个类,必须先要获取到该类的字节码文件对象,而解剖使用的就是Class类中的方法,所有先要获取到每一个字节码文件对应的Class类型的对象。

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

使用反射首先必须得到class文件对象,其实也就是得到Class类的对象。

获取class文件对象的方式:

Object类的getClass()方法,返回是一个Class类。

数据类型的静态属性class

Class类中的静态方法:public static Class forName(String className),参数需要传入.class文件的绝对路径

public class ClassDemo {
public static void main(String[] args) throws ClassNotFoundException {
// 方式1
Person p = new Person();
Class c = p.getClass();

// 方式2
Class c2 = Person.class;

// 方式3
Class c3 = Class.forName("com.kxg_01.Person");
}
}


推荐使用第三种,因为第三种是一个字符串,而不是一个具体的类名。这样我们就可以把这样的字符串配置到配置文件中。

获取构造方法

定义一个类

package com.kxg_01;

public class Person {
private String name;
int age;
public String adress;

public Person() {
}

Person(String adress, int age) {
this.adress = adress;
this.age = age;
}

private Person(int age) {
this.age = age;
}

public Person(String name, int age, String adress) {
this.name = name;
this.adress = adress;
this.age = age;
}

public void show() {
System.out.println("show");
}

public void show2(String s) {
System.out.println("show2"+s);
}

public String show3(String s) {
return "show3:" + s;
}

private void show4(String s) {
System.out.println("show4");
}

@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", adress=" + adress
+ "]";
}
}


使用反射获取这个类中的构造方法

package com.kxg_01;
import java.lang.reflect.Constructor;

public class ClassDemo2 {
public static void main(String[] args) throws Exception{
//      获取字节码文件
Class c = Class.forName("com.kxg_01.Person");

// public Constructor[] getConstructors()
// 返回一个包含某些 Constructor 对象的数组,
// 这些对象反映此 Class 对象所表示的类的所有公共构造方法。也就是public修饰的构造方法
Constructor[] cons = c.getConstructors();
for (Constructor con : cons) {
System.out.println(con);
}
System.out.println("------------------------------------");

// public Constructor[] getDeclaredConstructors()
// 返回 Constructor 对象的一个数组, 这些对象反映此 Class 对象表示的类声明的所有构造方法。
// 它们是公共、保护、默认(包)访问和私有构造方法。
Constructor[] cons2 = c.getDeclaredConstructors();
for (Constructor con : cons2) {
System.out.println(con);
}

// public Constructor getConstructor(Class<?>... parameterTypes)
// 返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法
// 参数表示的是你要获取的构造方法的构造参数个数以及数据类型的class字节码文件对象
Constructor con = c.getConstructor(String.class, int.class);
}
}


通过得到的构造方法创建对象

package com.kxg_01;

import java.lang.reflect.Constructor;

public class ClassDemo3 {
public static void main(String[] args) throws Exception{
//      使用Constructor类中的public T newInstance(Object... initargs)方法创建一个对象
Class c   = Class.forName("com.kxg_01.Person");

// 获取无参构造,创建对象
Constructor con = c.getConstructor();
Object obj = con.newInstance(null);
System.out.println(obj);

// 获取有参构造,创建对象
Constructor con2 = c.getConstructor(String.class, int.class,
String.class);
Object obj2 = con2.newInstance("周星驰", 30, "中国");
System.out.println(obj2);
}
}


利于反射获取私有修饰的构造方法并创建对象

package com.kxg_01;

import java.lang.reflect.Constructor;

public class ClassDemo4 {
public static void main(String[] args) throws Exception {
// public Constructor getDeclaredConstructor(Class<?>...
// parameterTypes)
// Class类中的此方法可以获取任意修饰符修饰的构造方法

Class c = Class.forName("com.kxg_01.Person");

Constructor con = c.getDeclaredConstructor(int.class);

// 用私有方法创建对象,需要取消 Java 语言访问检查
// Constructor类的父类中public void setAccessible(boolean flag)
// 实现这个效果
con.setAccessible(true);
Object obj = con.newInstance(30);
System.out.println(obj);

}
}


获取成员变量

通过反射获取成员变量并进行赋值

package com.kxg_02;

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

public class ReflectDemo {
public static void main(String[] args) throws Exception {
// 获取字节码文件对象
Class c = Class.forName("com.kxg_01.Person");

// public Field[] getDeclaredFields():获取全部成员变量对象数组
Field[] fields = c.getDeclaredFields();
for (Field f : fields) {
System.out.println(f);
}
System.out.println("-----------------------------");

// public Field getFields():获取成员变量对象数组,只能获取公共的
Field[] field = c.getFields();
for (Field f : field) {
System.out.println(f);
}

// public Field getField(String name):获取单个指定成员变量
Field adressField = c.getField("adress");

// 对成员变量进行赋值
// 使用Field类中的public void set(Object obj,Object value)方法
// 将指定对象变量上此 Field 对象表示的字段设置为指定的新值
// 想要为一个成员变量进行赋值,首先需要有一个对象
Constructor con = c.getConstructor();
Object obj = con.newInstance();

adressField.set(obj, "中国");// 给obj对象的adressField字段赋值为中国
System.out.println(obj);
}
}


获取方法

获取无参无返回值方法并调用

package com.kxg_02;

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

public class MethodDemo {
public static void main(String[] args) throws Exception {
Class c = Class.forName("com.kxg_01.Person");

// public Method[] getMethods():获取除了私有修饰的所有方法,包括从父类继承过来的
Method[] methods = c.getMethods();
for (Method m : methods) {
System.out.println(m);
}
System.out.println("-----------------------");

// public Method getMethod(String name,Class<?>... parameterTypes)
// 第一个参数是方法名,第二个参数是该方法参数的class类型,没有就不写,或者写null
Method m = c.getMethod("show", null);

// public Object invoke(Object obj,Object... args)
// 第一个参数是要调用方法的对象,第二个参数是该方法的实际参数,没有就不写,或者写null
// 创建对象
Constructor con = c.getConstructor();
Object obj = con.newInstance();

// 调用方法
m.invoke(obj, null);
}
}


有参无返和有参有返方法

package com.kxg_02;

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

public class MethodDemo2 {
public static void main(String[] args) throws Exception {
// 获取字节码文件对象
Class c = Class.forName("com.kxg_01.Person");

// 创建无参对象
Constructor con = c.getConstructor();
Object obj = con.newInstance();

// 获取有参无返回值方法
Method m = c.getMethod("show2", String.class);

// 获取有参有返回值方法
Method m2 = c.getMethod("show3", String.class);

// 调用方法
m.invoke(obj, "中国");

String s = (String) m2.invoke(obj, "河南");
System.out.println(s);
}
}


反射越过泛型检查添加元素

有一个ArrayList<Integer>的对象,如果想要添加一个String类型的元素,可以使用反射。泛型只作用于代码的编译时期,而且此时add()方法的源码,参数是任意类型,并不是泛型。利用反射可以跳过泛型的检查,调用add()方法,直接添加到集合中去。

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

public class ArrayListDemo {
public static void main(String[] args) throws Exception {
ArrayList<Integer> al = new ArrayList<Integer>();

// 得到al对象的字节码文件对象
Class c = al.getClass();

// 获取构造方法
Constructor con = c.getConstructor();

// 通过构造方法创建对象
Object obj = con.newInstance();

// 通过字节码对象获取add()方法
Method m = c.getMethod("add", Object.class);

// 调用add()方法添加String类型元素
m.invoke(al, "跳过泛型添加元素");
System.out.println(al);
}
}


动态代理

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

首先定义一个接口

public interface Student {
public void login();

public void regist();
}


然后创建一个该接口的实现类

public class StudentImpl implements Student {
public void login() {
System.out.println("登录功能");
}

public void regist() {
System.out.println("注册功能");
}
}


使用Proxy类中的public static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)方法创建一个代理对象。

此方法中第一参数为要代理对象的构造器,第二个参数为该对象要实现的接口列表,第三个参数为指派方法调用的调用处理程序。

第三个参数类型为InvocationHandler接口,实际需要的是实现该接口的实现类。所以继续定义一个实现了InvocationHandler接口的实现类。

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 {
// 第一个参数是需要进行代理的对象,第二个参数是对象的方法,第三个参数是对象方法的参数
// 注意:该方法的调用是底层调用,看不到
System.out.println("权限校验");
Object result = method.invoke(target, args);
System.out.println("日志记录");
return result;// 返回的就是代理对象
}
}


得到此实现类以后,就可以创建新的代理对象

import java.lang.reflect.Proxy;

public class Test {
public static void main(String[] args) {
// Student接口的实现类
Student s = new StudentImpl();

// InvocationHandler接口的实现类
MyInvocationHandler mi = new MyInvocationHandler(s);

// 因为是对Student接口进行动态代理,所以返回值的类型也是Student接口
Student proxy = (Student) Proxy.newProxyInstance(s.getClass()
.getClassLoader(), s.getClass().getInterfaces(), mi);

// 调用方法
proxy.login();
proxy.regist();
}
}


总结

反射这个部分需要理解的非常透彻,不同以往的创建对象方式,成员变量赋值以及方法的调用,感觉是这个部分最大的难点,思路要清晰,不要和之前的知识点混淆在一块。牢记反射中要用的几个重要的方法,例如获取指定构造方法,指定成员变量和指定方法。

使用过程中需要注意修饰符问题,如果是私有,就用Constructor类的父类AccessibleObject中的setAccessible()方法取消访问检查。

获取构造方法,成员变量和成员方法的时候,分清楚各自的返回值以及参数类型。

一个典型的动态代理创建对象步骤:

创建一个实现IvocationHandler 接口的实现类

调用Proxy类的newProxyInstance方法传入参数获取动态代理对象

Proxy只支持interface代理,不过后面还有更强大的代理cglib。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: