反射Reflection

        在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();
				
	}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值