在Java学习中,反射机制是一个非常重要的特性,Java反射机制中是在程序的运行期,对于任意一个对象,都能够调用它的任意方法和属性;这种动态的获取信息以及动态调用对象的功能称为Java的反射机制。总的来说,反射机制指的是程序在运行时能够获取自身的信息。在Java中只要给定类的名字,就可以通过反射机制来获得类的所有信息。
Java的反射机制主要提供了以下的功能,这些功能都存在于java.lang.reflect包:
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时判断任意一个类所具有的成员变量和方法。
- 在运行时调用任意一个对象的方法。
- 生成动态代理。
Class类:
我们要知道一个类的属性和方法,必须要先得到该类的字节码文件对象。获取该类的信息时,就需要使用Class类中的方法。所以我们要先获取到每一个.class字节码文件对应的Class对象。
以String类为例,我们获取一个类的Class文件可以有3种方式:
- 通过类名访问class
- 通过实例访问getClass()
- 通过Class类的静态方法forName(类名)
代码实例如下:
//方式1:通过类名访问class
Class stringCls1=String.class;
//方式2:通过实例访问getClass()
String s="";
Class stringCls2=s.getClass();
//方式3:通过Class类的静态方法forName(类名)
Class stringCls3=Class.forName("java.lang.String");
System.out.println(stringCls1.hashCode());
System.out.println(stringCls2.hashCode());
System.out.println(stringCls3.hashCode());
从运行结果来看,我们可以得知每个类的Class对象只会加载一次,无论创建几次,同一个类的Class对象都是同一个。
每一个Class对象中都包含了该class的所有完整信息:包名,类名,父类,所有方法,实现的接口,字段(成员变量)等。因此,如果获取了某个Class实例,我们就可以通过这个实例获取该实例对应的class的所有信息,这种通过class实例获取class实例信息的方法称为反射(Reflection)。
获取class实例信息的代码参考如下:
System.out.println("类(接口)名"+cls.getSimpleName());
System.out.println("完全限定名"+cls.getName());
System.out.println("类(接口)的类型名"+cls.getTypeName());
Class superCls=cls.getSuperclass();
System.out.println("类的父类"+superCls);
Class[] iClass=cls.getInterfaces();
for (Class class1 : iClass) {
System.out.println("类实现的接口"+class1);
}
Package pck=cls.getPackage();
if(pck!=null) {
System.out.println("类所在的包名"+cls.getName());
}
//判断信息
System.out.println("是否为接口"+cls.isInterface());
System.out.println("是否为数组"+cls.isArray());
System.out.println("是否为枚举"+cls.isEnum());
System.out.println("是否为基本类型"+cls.isPrimitive());
Constructor类:
当我们需要使用反射来创建一个对象时,可以通过Class类的newInstance()方法:
Object obj1=cls.newInstance();
但使用newInstance()方法时,局限是我们只能创建该类的public修饰的无参的构造方法,如果这个构造方法有参数或者不是用public修饰时,就无法使用Class.newInstance()来创建实例。
为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象是一个构造方法,调用的结果也会返回一个实例:
代码参考如下:
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
//获取类的class对象
Class cls=Example.class;
//调用无参构造方法创建Example对象
Example exp1=(Example)cls.newInstance();
System.out.println(exp1);
//获得所有的构造方法
Constructor[] constructors=cls.getConstructors();
System.out.println("所有的构造方法:");
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
System.out.println("----------------------------");
//1.获取指定参数类型的构造方法
//无参构造
//Constructor constructor=cls.getConstructor();
//Example exp2=(Example)constructor.newInstance();
//有一个参数的构造方法
//Constructor constructor=cls.getConstructor(int.class);
//Example exp2=(Example)constructor.newInstance(6);
//有2个参数的构造方法
Constructor constructor=cls.getConstructor(int.class,double.class);
//2.执行构造方法,创建Example类型的对象
Example exp2=(Example)constructor.newInstance(6,7.90);
System.out.println(exp2);
}
class Example{
private Example(String s) {
System.out.println("s="+s);
}
public Example() {
System.out.println("无参构造方法");
}
public Example(int a) {
System.out.println("a="+a);
}
public Example(int a,double b) {
System.out.println("a="+a+",b="+b);
}
}
获取继承关系:
在得到一个Class实例后,我们还可以得到它的父类的Class:
//父类
Class superClass=FileInputStream.class.getSuperclass();
//获取父类的名称
System.out.println("父类:"+superClass.getName());
//获取父类的父类
System.out.println("父类的父类:"+superClass.getSuperclass().getName());
除了父类,我们也可以获得Class实例实现的interface接口:
//接口
System.out.println("实现的接口列表:");
Class[] interfaceClass=MyStringComparator.class.getInterfaces();
for (Class inf : interfaceClass) {
System.out.println(inf.getName());
}
注意: getInterfaces()返回的是当前类直接实现的接口类型,并不包含其父类实现的接口类型。此外,对所有interface的Class调用getSuperclass()返回的是null,获取接口的父接口要用getInterfaces()。如果一个类没有实现任何interface,那么getInterfaces()返回空数组
继承关系:
当我们需要判断一个实例是否为某个类型时,我们使用的时instanceof操作符:
注意:如果一个类继承了某个父类或实现了某些接口,那么实例和父类和接口的运算结果也为true
// instanceof 运算符
Object obj=Integer.valueOf(1098);
System.out.println("是否为Integer类型?"+(obj instanceof Integer));//true
System.out.println("是否为Double类型?"+(obj instanceof Double));//false
System.out.println("是否为Comparable类型?"+(obj instanceof Comparable));//true
System.out.println("是否为Number类型?"+(obj instanceof Number));//true
当我们要判断一个类是否可以向上转型时,就可以调用isAssignableFrom()方法:
//isAssignableFrom()方法:判断“类型”和“类型”之间的关系
System.out.println("Integer <= Integer ?"+Integer.class.isAssignableFrom(Integer.class));
System.out.println("Integer <= Number ?"+Integer.class.isAssignableFrom(Number.class));
System.out.println("Number <= Integer ?"+Number.class.isAssignableFrom(Integer.class));
System.out.println("Integer <= Comparator ?"+Comparable.class.isAssignableFrom(Integer.class));
获取Field字段
对任意的一个Object实例,只要我们获取了它的class,就可以获取它的一切信息。比如通过一个Class实例获取字段信息,我们获取字段的方式有:
- Field getField(name):根据字段名获取当前类中某个public的field(包括父类)
- Field getDeclaredField(name):根据字段名获取当前类中定义的某个field(不包括父类)
- Field[] getFields():获取所有public的field(包括父类)
- Field[] getDeclaredFields():获取当前类中定义的所有field(不包括父类)
代码参考如下:
public static void main(String[] args) {
Class cls=Books.class;
//所有public访问修饰符修饰的字段
// Field[] fields=cls.getFields();
//所有定义的字段
Field[] fields=cls.getDeclaredFields();
for (Field field : fields) {
System.out.println("成员变量访问修饰符(int)"+field.getModifiers());
System.out.println("成员变量访问修饰符"+Modifier.toString(field.getModifiers()));
System.out.println("成员变量类型"+field.getType());
System.out.println("成员变量名称"+field.getName());
System.out.println();
}
}
class Books{
private String bookName;
public String stock;
private double price;
private int number;
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public String getStock() {
return stock;
}
public void setStock(String stock) {
this.stock = stock;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public String toString() {
return "Books [bookName=" + bookName + ", stock=" + stock + ", price=" + price + ", number=" + number + "]";
}
}
利用Field实例,我们可以通过这个实例拿到对应的该字段的值,代码参考如下:
public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
Books books=new Books();
books.setStock("北京出版社");
books.setBookName("淘气包马小跳");
books.setNumber(90);
books.setPrice(98.5);
printInfo(books);
}
public static void printInfo(Object obj) throws IllegalArgumentException, IllegalAccessException {
Class cls=obj.getClass();
Field[] fields=cls.getDeclaredFields();
for (Field field : fields) {
System.out.println("成员变量名:"+field.getName());
//判断是否可以访问
if(!field.isAccessible()) {
field.setAccessible(true);
}
System.out.println("成员变量内容:"+field.get(obj));
}
}
当要访问的字段是priavte修饰的时,我们要先调用field1.setAccessible(true)来突破private的限制。
另外,通过Field不仅可以获取字段值,也可以设置字段的值。设置字段值是通过Field.set(Object, Object)实现的,其中第一个Object参数是指定的实例,第二个Object参数是待修改的值。代码参考如下:
public static void main(String[] args) {
//运行期
//使用反射的方式,完成对成员变量存值
Class cls=Books.class;
try {
Object obj=cls.newInstance();
//按照字段名称,获取指定字段
Field field1=cls.getDeclaredField("bookName");
//往私有的成员变量里存值
field1.setAccessible(true);
//参数1:目标Books对象
//参数2:存入成员变量的值
field1.set(obj, "笑猫日记");
System.out.println(obj);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
调用方法:
我们已经能通过Class实例获取所有Field对象,同样的,可以通过Class实例获取所有方法(Method类型的对象)。Class类提供了以下几个方法来获取Method:
- Method getMethod(name, Class...):获取某个public的Method(包括父类)
- Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)
- Method[] getMethods():获取所有public的Method(包括父类)
- Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
代码参考如下:
public static void main(String[] args) {
Class cls=Books.class;
//获取所有public的方法(包含父类)
// Method[] methods=cls.getMethods();
//获取所有定义的方法(仅包含当前类)
Method[] methods=cls.getDeclaredMethods();
for (Method method : methods) {
System.out.println("方法的名称"+method.getName());
System.out.println("方法的访问修饰符"+Modifier.toString(method.getModifiers()));
System.out.println("方法的返回值类型"+method.getReturnType());
//获取所有的参数对象
Parameter[] parameters=method.getParameters();
System.out.println("方法的参数列表");
for (Parameter parameter : parameters) {
System.out.println(parameter);
System.out.println("----------------------");
}
}
}
调用静态方法:
如果获取到的Method表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke方法传入的第一个参数永远为null。我们以Integer.parseInt(String)为例:
public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Class cls=Math.class;
//反射调用
Method method=cls.getMethod("log10", double.class);
int size=Double.valueOf((double)method.invoke(null, 1987654)).intValue()+1;
System.out.println(size);
}
调用非public方法:
和字段和构造方法一样,当需要调用一个非public的方法时,我们只需要调用一setAccessible(true)方法后再继续嗲用方法。但于FIelds不同的是,Method实例的setAccessible(true)可能会失败。如果JVM运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。
多态:
在使用反射调用方法时,也依然遵循多态的原则:当用子类的实例调用方法时,不论方法是子类自己的还是父类的Class中获取的,只要是子类的实例调用,就一定是子类的方法。
代码实例如下:
public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Method method=Person.class.getMethod("hello");
method.invoke(new Student());
}
class Person{
public void hello() {
System.out.println("Person:hello");
}
}
class Student extends Person{
public void hello() {
System.out.println("Student:hello");
}
}
动态代理:
代理模式:是为了给某个对象提供一个代理,并由代理对象来控制对真实对象的访问,代理模式是一种结构型设计模式。
代理模式中有3种角色:
Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,通常被设计成接口;
RealSubject(真实主题角色):真正实现业务逻辑的类;
Proxy(代理主题角色):用来代理和封装真实主题;
代理模式可以根据创建时间分为“静态代理”和“动态代理” :
- 所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了。
- 而动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件
静态代理:
//接口
public interface Subject {
void request();
}
//实现类
public class RealSubject implements Subject{
@Override
public void request() {
System.out.println("业务逻辑1");
System.out.println("业务逻辑2");
System.out.println("业务逻辑3");
}
}
//代理类
public class SubjectProxy implements Subject{
//目标对象
private Subject target;
public SubjectProxy() {
target=new RealSubject();
}
@Override
public void request() {
//通过代理模式,扩展逻辑
System.out.println("begin----------");
target.request();//通过目标对象,实现真正的业务逻辑
System.out.println("end------------");
}
}
//测试类
public static void main(String[] args) {
Subject subject=new SubjectProxy();
subject.request();
}
通过静态代理,我们可以达到功能增强的目的,而且不会影响原有的代码结构,但由于静态代码比较简单,缺点也很明显:
1. 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:
●只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
●新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类
2. 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护。
动态代理:
JDK动态代理主要涉及两个类:java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler。我们通过编写一个调用逻辑处理器 LogHandler 类案例来提供日志增强功能,并实现 InvocationHandler 接口;在 LogHandler 中维护一个目标对象,这个对象是被代理的对象(真实主题角色);在 invoke()方法中编写方法调用的逻辑处理。
代码参考如下:
//接口
public interface UserService {
void select();
void update();
}
// 真正的实现类
public class UserServiceImpl implements UserService {
@Override
public void select() {
System.out.println("select * ..................");
System.out.println("数据库中完成用户信息的查询执行!");
}
@Override
public void update() {
System.out.println("update ...................");
System.out.println("数据库中用户状态的更新执行!");
}
}
//动态代理的处理类
public class LogInvocationHandlerImpl implements InvocationHandler{
private Object target;
public LogInvocationHandlerImpl(Object target) {
this.target=target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.printf("方法%s开始执行....\n " ,method.getName( ));
Object result=method.invoke(target, args);
System.out.printf("方法%s结束执行.....\n " ,method.getName( ));
return result;
}
}
//测试类
public static void main(String[] args) {
//创建一个将传给代理类的调用请求处理器,处理所有的代理对象上的方法调用
//这里创建的是一个自定义的日志处理器,须传入实际的执行对象 userServiceImpl
InvocationHandler handler1=new LogInvocationHandlerImpl(new UserServiceImpl());
//创建UserService接口的动态代理对象
UserService proxy1=(UserService)Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[] {UserService.class},
handler1);
//通过代理对象调用方法 => LogInvocationHandlerImpl实现类的invoke
proxy1.select();
}