Java反射总结

目录

Java反射概述

Class类

获取Class实例的方法

通过Class实例创建对应类的实例

Class类常用方法

动态加载机制

反射的用途

1. 调用构造方法

2. 获取继承关系

判断是否为某个类型

判断向上转型是否成立

3. 访问字段

4. 调用方法 

获取方法

调用方法

反射机制的应用----动态代理

代理模式

静态代理

动态代理 


Java反射概述

Java反射机制就是程序在运行状态中,对于任意一个类,我们都能得到它的Class对象,来获取关于该类的所有信息,并根据需求调用它的任意方法和属性进行一些操作,这种动态获取信息以及动态调对象的属性或方法的功能成为Java的反射机制。

Class类

那么上面的话中提到了任意类的Class对象,那么Class对象是什么?

在Java中,除了int等基本类型外,Java的其他类型在第一次类加载的时候都会为其创建对应的Class类型的Class对象(包括interface),这个Class对象中包含了该类的所有信息,例如:

  • String
  • Object
  • Runnable
  • Exception
  • ... 

 当我们想要获取类的相关信息时,使用的就是Class类的方法,所以先要获取到每一个字节码文件(.class)对应的Class类型的对象。

class(包括interface)的本质是数据类型(Type)。class是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class类型时,将其加载进内存。每加载一种class,JVM就为其创建一个Class类型的实例,并关联起来。

以String类为例,当JVM加载String类时,它首先读取String.class文件到内存,然后,为String类创建一个Class实例并关联起来:

Class cls = new Class(String);

这个Class实例是JVM内部创建的,如果我们查看JDK源码,可以发现Class类的构造方法是private,只有JVM能创建Class实例,我们自己的Java程序是无法创建Class实例的。

// final声明不允许继承

public final class Class {

// 私有的构造方法

private Class() {}

}

所以每个Class实例都指向一个数据类型(class或者interface),所以该实例中保存了对应数据类型的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段(成员变量)等。

因此,如果我们获取到了某个Class实例,是不是就可以通过它获取对应class的所有信息,这种通过Class实例获取class信息的方法我们称为反射(Reflection)

获取Class实例的方法

1. 通过类名.class获取Class实例:

Class cls = String.class;

2. 通过class实例的getClass()方法获取Class实例:

String s = "";
Class cls = s.getClass();

3.  通过Class类的静态方法Class.forName(),传入一个完整类名的字符串,获取Class实例:

Class cls = Class.forName("java.lang.String");

 因为Class实例在JVM中是唯一的,所以使用相同的类名不管根据哪种方式创建的Class实例都是同一个。

Class cls1 = String.class; // 方式1

String s = "str";
Class cls2 = s.getClass(); // 方式2

boolean sameClass = cls1 == cls2; // true

通过Class实例创建对应类的实例

调用Class.newInstance()方法可以创建类实例,但是在创建类实例的时候,只能调用public的无参构造方法。带参数的构造方法,或者非public的构造方法都无法通过Class.newInstance()被调用。

// 获取String的Class实例
Class cls = String.class;

// 创建String实例
String s = (String)cls.newInstance();

 Class类常用方法

类型访问方法返回值类型说明
包路径getPackage()Package 对象获取该类的存放路径
类名称getName()String 对象获取该类的名称
继承类getSuperclass()Class 对象获取该类继承的类
实现接口getlnterfaces()Class 型数组获取该类实现的所有接口
构造方法

getConstructors(),getDeclaredContruectors()

getConstructor(),getDeclaredContruector()

Constructor 型组,Constructor 对象

不带Declared:获取权限为 public 的构造方法。(包含父类)

带有Declared:获取当前对象的所有构造方法。(不包含父类)

方法

getMethods(),getDeclaredMethods()

getMethod(),getDeclaredMethod()

Methods 型数组,Methods 对象

不带Declared:获取权限为 public 的方法。(包含父类)

带有Declared:获取当前对象的所有方法。(不包含父类)

成员变量

getFields(),getDeclareFileds()

getField(),getDeclareFiled()

Field 型数组,Field 对象

不带Declared:获取权限为 public 的成员变量。(包含父类)

带有Declared:获取当前对象的所有成员变量。(不包含父类)

动态加载机制

JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载,例如:

public class Main {

static {

System.out.println("Main被加载");

}

public static void main(String[] args) {

int rand = new Random().nextInt(10);

if (rand > 5) {

create(rand);

}

}



static void create(int no) {

Person p = new Person(no);

}

}



class Person{

static {

System.out.println("Person类被加载");

}

public Person(int no) {

System.out.println("Person类的有参构造方法");

}

}

当执行Main.java时,由于用到了Main,因此,JVM首先会把Main.class加载到内存。然而,并不会加载Person.class,除非程序执行到create()方法,JVM发现需要加载Person类时,才会首次加载Person.class。如果没有执行create()方法,那么Person.class根本就不会被加载。
动态加载class的特性对于Java程序非常重要。利用JVM动态加载class的特性,我们才能在运行期根据条件加载不同的实现类。

反射的用途

我们通过Class实例可以拿到所有关于类的信息,那么我们拿到这些信息就完了吗?答案并不是,我们希望通过Class实例来完成不同反射操作,比如创建类实例,调用类的构造方法,获取继承关系,设置类的字段值,调用类中的方法,以及把这种方式应用在程序的设计模式上(代理)。

1. 调用构造方法

前面提到,使用Class实例的newInstance()方法只能调用该类的public无参构造方法,如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。

为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象是一个构造方法,调用结果总是返回实例:

// 反射的方式调用构造方法
public class Demo04 {
	public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
		Class cls = Example.class;
		// newInstance()方法调用无参构造方法创建Example类型的对象
		Example ex1 = (Example)cls.newInstance();
		System.out.println(ex1);
		
		// 调用getConstructors()方法获取一个包含所有构造方法的数组
		System.out.println("所有的构造方法:");
		Constructor[] constructors = cls.getDeclaredConstructors();
		for(Constructor constructor: constructors) {
			System.out.println(constructor);
		}
		System.out.println("----------------------------------------------");
		

		// 获取指定参数类型的构造方法
		Constructor constructor = cls.getDeclaredConstructor(String.class);
		constructor.setAccessible(true);
		// 执行构造方法,传入所需的参数,创建对象
		Example ex2 = (Example)constructor.newInstance("just");
		System.out.println(ex2);
	}
}
class Example{
	
	private Example(String s) {
		System.out.println("private构造方法: "+s);
	}
	
	protected  Example(boolean b) {
		System.out.println("protected构造方法: "+ b);	
	}
	
	public Example() {
		System.out.println("无参构造方法");
	}
	
	public Example(int a) {
		System.out.println("一个参数的构造方法");
	}
	
	public Example(int a,double b) {
		System.out.println("两个参数的构造方法");
	}
}

通过Class实例获取Constructor的方法如下:

  • getConstructor(Class...):获取某个public的Constructor,包含父类;
  • getDeclaredConstructor(Class...):获取某个定义的Constructor,不包含父类;
  • getConstructors():获取所有public的Constructor,包含父类;
  • getDeclaredConstructors():获取所有定义的Constructor,不包含父类。

 2. 获取继承关系

有了Class实例,我们还可以获取它的继承关系,以及它都实现了哪些接口。

  • 通过Class实例的getSuperclass()方法获取父类的Class实例;
  • 通过Class实例的getInterfaces()方法获取该类实现的接口,返回的是接口的Class数组。
public class Demo01 {
	public static void main(String[] args) {
		// 父类
		Class cls = FileInputStream.class.getSuperclass();
		System.out.println("父类: "+cls.getName());
		System.out.println("父类的父类: "+cls.getSuperclass().getName());
		System.out.println("--------------------------");
		
		// 接口
		System.out.println("实现的接口列表: ");
		Class[] cls2 = String.class.getInterfaces();
		for(Class intef:cls2) {
			System.out.println(intef.getName());
		}
		System.out.println("--------------------------");
    }
}

要特别注意:getInterfaces()只返回当前类直接实现的接口类型,并不包括其父类实现的接口类型。此外,对所有interface的Class调用getSuperclass()返回的是null,获取接口的父接口要用getInterfaces()。如果一个类没有实现任何interface,那么getInterfaces()返回空数组。

判断是否为某个类型

使用 instanceof 操作符,可以判断一个实例是否是某个类型:

Object n = Integer.valueOf(123);

boolean isDouble = n instanceof Double; // false

boolean isInteger = n instanceof Integer; // true

boolean isNumber = n instanceof Number; // true
判断向上转型是否成立

如果是两个Class实例,要判断一个向上转型是否成立,可以调用 isAssignableFrom()

// Number n = ?

Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number

// Integer i = ?

Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer

 3. 访问字段

通过一个Class实例获取字段信息,Class类提供了以下几个方法来获取字段:

  • Field getField(name):根据字段名获取当前类中某个public的field(包括父类)
  • Field getDeclaredField(name):根据字段名获取当前类中定义的某个field(不包括父类)
  • Field[] getFields():获取所有public的field(包括父类)
  • Field[] getDeclaredFields():获取当前类中定义的所有field(不包括父类)

 一个Field对象包含了一个字段的所有信息:

  • getName():返回字段名称,例如,"name";
  • getType():返回字段类型,也是一个Class实例,例如,String.class;
  • getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的含义。

拿到了Field对象,我们的目的是获取字段值,或者给某个字段设置值:

  • 用Field.get(Object obj)获取指定实例的指定字段的值;
  • 设置字段值是通过Field.set(Object obj, Object arg)实现的,其中第一个Object参数是指定的实例,第二个Object参数是待修改的值。

 注意,如果访问的是private字段,应该在访问前执行:

field.setAccessible(true);

调用Field.setAccessible(true)的意思是,别管这个字段是不是public,一律允许访问。

4. 调用方法 

获取方法

同样的,可以通过Class实例获取所有方法(Method类型的对象)。Class类提供了以下几个方法来获取Method:

  • Method getMethod(name, Class...):获取某个public的Method(包括父类)
  • Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)
  • Method[] getMethods():获取所有public的Method(包括父类)
  • Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)

 一个Method对象包含一个方法的所有信息:

  • getName():返回方法名称,例如:"getScore";
  • getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class;
  • getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class};
  • getModifiers():返回方法的修饰符,它是一个int,不同的value表示不同的访问修饰符;
// 获取所有定义的方法,仅包含当前类
		Method[] methods = cls.getDeclaredMethods();
		for(Method m: methods) {
			System.out.println();
			System.out.println("方法的访问修饰符: "+ Modifier.toString(m.getModifiers()));
			System.out.println("方法的返回值类型: "+ m.getReturnType());
			System.out.println("方法的名称: "+ m.getName());
			
			// 获取所有的参数对象
			Parameter[] paras = m.getParameters();
			for(Parameter p : paras) {
				System.out.println(p.getName());
				System.out.println(p.getType());
			}
		}
class Student extends Person {
 public int score;
 private int grade;
 public boolean isPassed;
@Override
public String toString() {
	return "Student [score=" + score + ", grade=" + grade + "]";
}
 public static void dosth() {
	 
 }
 
 public static int dosth2(int a, int b) {
	 return a+b;
 }
 
}
class Person {
 public String name;
}

运行结果:

 调用方法

如果使用反射进行方法的调用,我们使用Method.invoke(Object obj,Object value)方法,第一个参数代表某个实例对象,第二个参数代表该方法需要传入的参数值。

public class Demo03 {
	public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
		Class cls = Base.class;
		Object obj = cls.newInstance();
		Method method = cls.getMethod("create",int.class);
		int ret = (int)method.invoke(obj,1000);
		
		Method method2 = cls.getMethod("create");
		int ret2 = (int)method2.invoke(obj);
		System.out.println(ret2);
		System.out.println(ret);
	}
}
class Base{
	public int create() {
		return (int)((Math.random())*100);
	}
	public int create(int random) {
		return (int)((Math.random())*random);
	}
}

以上所调用的方法都是非静态方法,那么如何调用静态的方法呢?

如果获取到的Method表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke方法传入的第一个参数永远为null,如:  

Method method3 = cls.getMethod("createDouble", double.class);
		double ret3 = (double)method3.invoke(null, 3.1415);
public static double createDouble(double d) {
		return d;

如果调用的方法是非public方法,我们通过 Class.getDeclaredMethod() 获取该方法实例。为了可以调用它,我们通过Method.setAccessible(true)允许其调用即可。

反射机制的应用----动态代理

代理模式

代理模式: 给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问,是一种结构型设计模式。

代理模式中的3种角色:

  • Subject(抽象主题角色): 定义代理类和真实主题的公共对外方法,通常被设计为接口;
  • RealSubject(真实主题角色): 真正实现业务逻辑的类;
  • Proxy(代理主题角色): 用来代理和封装真实主题。

 抽象主题的存在是为了让客户端能够一致性地对待真实对象和代理对象

如果根据字节码的创建时机来分类,可以分为静态代理和动态代理:

  • 所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了。
  • 而动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件。

 静态代理

案例: 现在有UserService接口及其实现类UserServiceImpl,需要在不改变实现类的基础上,增加运行前和运行后的额外逻辑。

Subject: 

public interface UserService {

public void select();

public void update();

}

RealSubject:

public class UserServiceImpl implements UserService {

public void select() {

System.out.println("查询 selectById");

    }

public void update() {

System.out.println("更新 update");

    }

}

 静态代理通过增加一个Proxy类,同时实现这个UserSerivce接口:

public class UserServiceProxy implements UserService {

private UserService target; // 被代理的对象

// 创建对象时通过构造方法传入被代理的对象(RealSubject)
public UserServiceProxy(UserService target) {

this.target = target;

}


public void select() {

// 调用方法前的额外逻辑
before();

target.select(); // 这里才实际调用真实主题角色的方法

// 调用方法后的额外逻辑
after();

}

public void update() {

// 调用方法前的额外逻辑
before();

target.update(); // 这里才实际调用真实主题角色的方法

// 调用方法后的额外逻辑
after();

}

}

测试类:

public class Test {

public static void main(String[] args) {

UserService userServiceImpl = new UserServiceImpl();

UserService proxy = new UserServiceProxy(userServiceImpl);


proxy.select();

proxy.update();

}

}

通过静态代理,我们达到了功能增强的目的,而且没有侵入原代码,这是静态代理的一个优点。
虽然静态代理实现简单,且不侵入原代码,但是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来: 
1、 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:

  • 只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
  • 新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类

2、 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护。

动态代理 

Java中两种常见的动态代理方式:JDK原生动态代理和CGLIB动态代理(第三方开源类库)。

这里以JDK的动态代理展开说明: JDK动态代理主要涉及两个类:java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler。

我们通过编写一个调用逻辑处理器OrderInvocationHandlerImpl类案例来提供日志增强功能,并实现 InvocationHandler 接口;在 OrderInvocationHandlerImpl中维护一个目标对象,这个对象是被代理的对象(真实主题角色);在 invoke()方法中编写方法调用的逻辑处理。

OrderInvocationHandlerImpl(逻辑处理器实现类) : 

package com.proxy3;

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

public class OrderInvocationHandlerImpl implements InvocationHandler{
	
	private Object target; // 被代理的对象,实际的方法执行者
	
	public OrderInvocationHandlerImpl(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 returnValue = method.invoke(target, args);
		
		System.out.printf("%s结束执行\n",method.getName()); // 增强逻辑
		
		return returnValue; // 返回方法的执行结果
	}

}

OrderServiceImpl(被代理的对象) : 

public class OrderServiceImpl implements OrderService{
	@Override
	public void createOrder() {
		System.out.println("生成新的订单!");
	}
	
	@Override
	public void closeOrder() {
		System.out.println("关闭当前订单!");
	}
}

 测试类: 

import java.lang.reflect.Proxy;

public class Client {
	public static void main(String[] args) {
		// 创建被代理的对象
		OrderServiceImpl orderImpl = new OrderServiceImpl();
        // 创建一个调用请求处理器,处理所有被代理对象的方法
        // 需要传入实际的被代理对象
		OrderInvocationHandlerImpl orderHandler = new OrderInvocationHandlerImpl(orderImpl);
        // 参数1: 传入被代理对象的 classLoader
        // 参数2: 传入该类所实现的所有接口
        // 参数3: 传入请求处理器,处理方法的调用
		OrderService proxy = (OrderService) Proxy.newProxyInstance(OrderServiceImpl.class.getClassLoader()
				, OrderServiceImpl.class.getInterfaces()
				, orderHandler);
		
        // 调用增强功能后的方法
		proxy.createOrder();
		System.out.println("----------------------");
		System.out.println(proxy.getClass());
		proxy.closeOrder();
		
	}
}

以上就是对Java反射的所有总结,如有疏漏,请提出意见,会随时更新。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值