Java进阶篇之反射

Posted Guarding and trust

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java进阶篇之反射相关的知识,希望对你有一定的参考价值。

不为失败找理由,只为成功找方法。所有的不甘,因为还心存梦想,所以在你放弃之前,好好拼一把,只怕心老,不怕路长。

文章目录


一、反射的概述

    反射就好比自己与镜子里的自己,镜子里可以折射出自己的身影。用Java的话说就是指对于任何有个Class类,在"运行的时候"都可以直接得到这个类的全部成分。

1. 在运行时,可以直接得到这个类的构造器对象:Constructor。
2. 在运行时,可以直接得到这个类的类的成员变量对象:Field。
3. 在运行时,可以直接得到这个类的成员方法对象:Method。

这种运行时动态获取类信息以及动态调用类种成分的能力称为Java语言的反射机制。


二、Class类

    反射的第一步都是先得到编译后的Class类对象,然后就可以得到Class的全部成分。注意,这里的Class类是在j ava.lang包下的Class类,这个类也是反射最本质的类。这个类可以找到与之对应的.class字节码文件。比如我们有个HelloWord.java,还没编译为.java;然后执行javac命令编译之后就变成了字节码.class文件,而这个操作会经过一个类,那就是Class。因此,Class类是可以获取所有的类成分。比如这个类的位置,字段,构造器、方法等,。如果还是不懂,看图说话:

三、反射的API

    反射的API,肯定是和类的结果有关联的了。那么接下来我们主要详解怎么获取类、构造器使对象实例化、字段、方法等成分。

1、获取字节码Class对象

    反射的第一步是获取该类对应的Class对象,也就是对应的字节码文件对象,我们先看API:

方法描述
static Class<?> forName(String className)返回与具有给定字符串名称的类或接口关联的类对象。
Class<?> getClass()返回此Object的运行时类。
类名.Class返回对象对应类的Class对象。

示例代码如下:

public class Student
public class TestReflect 
	public static void main(String[] args) throws Exception
		// a.使用权限名
		Class<?> cls1 = Class.forName("javaSE.reflect.Student");
		System.out.println("获取Student的Class对象:" + cls1);

		// b.使用实例化对象.getClass() 方法获取Class对象
		Class<?> cls2 = new Student().getClass();
		System.out.println("获取Student的Class对象:" + cls2);

		// c.使用类名.Class 推荐写法
		Class<?> cls3 = Student.class;
		System.out.println("获取Student的Class对象:" + cls3);
	

上述代码比较简单,首先定义了一个Student类,然后再定义一个测试类,执行获取Class对象的API,这里有三种写法,但是比较推荐的是使用 类名.class。如果不太懂,可以看如下图:

执行结果图:

2、获取构造器

    获取到了Class类,那么就可以操作类内部的各种东西了。我们首先得获取构造器,我们以前要得到一个对象,要用关键字new,而new的操作本质上就是调用构造器。所以我们先获取构造器,然后使用构造器创建对象。还是先看API:

方法描述
Constructor<?>[] getConstructors(()返回所有构造器对象的数组。
Constructor<?>[] getDeclaredConstructors()返回所有构造器对象的数组,存在就能拿到。
Constructor<?> getConstructor(Class<?>… parameterTypes)返回单个构造器对象(只能拿public的)。
Constructor<?> getDeclaredConstructor(Class<?>… parameterTypes)返回单个构造器对象,存在就能拿到。

示例代码如下:

public class Student
	//字段
	private int id;
	public String name;
	protected int age;
	String gerder;

	public Student() 
	
	private Student(int id, String name, int age) 
				super();
				this.id = id;
				this.name = name;
				this.age = age;
	
				
	public Student(int id, String name, int age, String gerder) 
		super();
		this.id = id;
		this.name = name;
		this.age = age;
		this.gerder = gerder;
	

public class TestReflect 
	public static void main(String[] args) throws Exception
		//1.获取Class对象
		Class<?> cls = Student.class;
	
		//2.获取构造器
		// a.获取public修饰的构造器
		System.out.println("=======获取所有public修饰的构造器======");
		Constructor<?>[] cons1 = cls.getConstructors(); // 获取多个
		for (Constructor<?> con : cons1) 
			System.out.println(con+ "\\t参数个数:" + con.getParameterCount());
		

		Constructor<?> con1 = cls.getConstructor(); // 获取默认public构造器
		System.out.println(con1+ "\\t参数个数:" + con1.getParameterCount());

		// 获取指定的public构造器
		Constructor<?> con2 = cls.getConstructor(int.class, String.class, int.class, String.class);
		System.out.println(con2 + "\\t参数个数:" + con2.getParameterCount());

		// b.获取任何权限修饰的构造器
		System.out.println("=======获取任何修饰的构造器=======");
		Constructor<?>[] cons2 = cls.getDeclaredConstructors(); // 获取多个
		for (Constructor<?> con : cons2) 
			System.out.println(con + "\\t参数个数:" + con.getParameterCount());
		

		 Constructor<?> con3 = cls.getDeclaredConstructor(); //获取默认构造器
		 System.out.println(con3+ "\\t参数个数:" + con3.getParameterCount());
		 
		 Constructor<?> con4 = cls.getDeclaredConstructor(int.class, String.class, int.class, String.class);
		 System.out.println(con4 + "\\t参数个数:" + con4.getParameterCount());
	

执行结果图:

由上述例子可知,构造器的API可以归纳成两类:Constructor和DeclaredConstructor,前者只能获取到public修饰的构造器;后者是可以获取任何修饰的构造器,由上图执行结果可以看出。而从获取的数量由可以分为两类: 加s的可以获取多个 ,返回数组形式,不加的就是获取单个了。后面的示例都以测试DeclaredConstructor的方法,因为这个可以获取任何权限的,也比较实用。

3、对象实例化

    获取到构造器之后,那么接下来就可以创建对象了,也就是对象实例化,这个比较简单,API就记住一个就行:

方法描述
T newInstance​(Object… initargs)使用此构造器对象表示的构造方法,使用指定的初始化参数创建和初始化构造函数声明类的新实例。
public class Student 
	//字段
	private int id;
	public String name;
	protected int age;
	String gerder;
	
	//构造器
	public Student() 
	
	private Student(int id, String name, int age) 
			super();
			this.id = id;
			this.name = name;
			this.age = age;
		
		
	public Student(int id, String name, int age, String gerder) 
		super();
		this.id = id;
		this.name = name;
		this.age = age;
		this.gerder = gerder;
	

	//getter、setter
	public int getId() 
		return id;
	

	public void setId(int id) 
		this.id = id;
	

	public String getName() 
		return name;
	

	public void setName(String name) 
		this.name = name;
	

	public int getAge() 
		return age;
	

	public void setAge(int age) 
		this.age = age;
	

	public String getGerder() 
		return gerder;
	

	public void setGerder(String gerder) 
		this.gerder = gerder;
	

	@Override
	public String toString() 
		return "Student [id=" + id + ", name=" + name + ", age=" + age + ", gerder=" + gerder + "]";
	

public class TestReflect 

	public static void main(String[] args) throws Exception
		//1.获取Class类对象
		Class cls = Student.class;
		//2.获取构造器
		Constructor<?> con = cls.getDeclaredConstructor();
		//3.对象实例化
		Student student= (Student)con.newInstance(); 
		student.setName("小李");
		System.out.println("学生对象:" + student);

执行上述代码,就可以创建出一个student对象来了。结果图如下:

4、获取成员字段

    接下来就是获取字段属性了。废话不多说,直接看API.
Class类中用于获取成员变量的方法:

方法描述
Field[] getFields()返回所有成员变量对象的数组(只能拿public的)
Field[] getDeclaredFields()返回所有成员变量对象的数组,存在就能拿到
Field getField(String name)返回单个成员变量对象(只能拿public的)
Field getDeclaredField(String name)返回单个成员变量对象,存在就能拿到

Field类中用于取值、赋值的方法:

方法描述
void set(Object obj,Object value)赋值。
Object get(Object obj)获取值。

代码示例如下:

public class TestReflect 

	public static void main(String[] args) throws Exception
		//1.获取Class类对象
		Class cls = Student.class;
		
		// 2.获取字段
		// a.获取所有字段
		Field[] fields = cls.getDeclaredFields();
		System.out.println("获取所有字段:");
		for (Field field : fields) 
			System.out.println(field);
		

		// b.获取指定字段
		System.out.println("获取指定字段:");
		Field field1 = cls.getDeclaredField("name");
		Field field2 = cls.getDeclaredField("id");
		System.out.println(field1);
		System.out.println(field2);

		// 3.给字段赋值、获取字段名称和字段值
		Student student = new Student();
		field1.set(student, "小明");
		
		field2.setAccessible(true); //暴力反射
		field2.set(student, 1);
		System.out.println(field1.getName() + ":" +field1.get(student) + "\\n" + field2.getName() +":" +field2.get(student));


实体类和上个例子都是Student类。这里需要注意的是数据私有化问题,其实在前面构造器也是会出现这种问题的:私有化的属性是不能操作的。比如我们可以知道属性id是私有的,如何按照平常这样获取是会报异常的,看下图:

可以看到报了个java.lang.IllegalAccessException:非法访问异常。如果不想报错,就加上 setAccessible(true) 方法(上述代码已加上),意思是暴力反射,不给我访问,我就用蛮力打开它,差不多可以这样理解,但也因此会破坏了类的封装性。如图:

这样就可以就操作私有的数据了。

5、获取成员方法

    接下来就是最后的方法了,上面已经获取过构造器、字段,下面的方法获取应该难道不大了,且看API:
Class类中用于获取成员方法的方法:

方法描述
Method[] getMethods()返回所有成员方法对象的数组(只能拿public的)
Method[] getDeclaredMethods()返回所有成员方法对象的数组,存在就能拿到
Method getMethod(String name)返回单个成员方法对象(只能拿public的)
Method getDeclaredMethod(String name)返回单个成员方法对象,存在就能拿到

Method类中用于调用的方法:

方法描述
Object invoke​(Object obj, Object… args)在具有指定参数的指定对象上调用此方法对象表示的基础方法。

示例代码如下:

public class TestReflect 

	public static void main(String[] args) throws Exception
		//1.获取Class类对象
		Class cls = Student.class;
		// 2.获取方法
		// a.获取所有方法
		Method[] methods = cls.getDeclaredMethods();
		System.out.println("所有方法:");
		for (Method method : methods) 
			System.out.println(method + "\\t参数个数:" + method.getParameterCount());
		

		// b.获取指定的方法
		System.out.println("获取指定的方法:");
		Method method = cls.getDeclaredMethod("toString");
		System.out.println(method + "\\t参数个数:" + method.getParameterCount());

		// 3.调用方法
		System.out.println("方法调用:");
		Student student = new Student();
		student.setName("猪猪侠");
		student.setAge(12);
		student.setGerder("男");

		String str = (String) method.invoke(student);
		System.out.println(str);

实体类也是Student,这里的方法都是公共的,其实私有化的方法也是不能操作的,这里就不贴代码了,可以仿照字段来操作以下。执行结果图如下:

四、反射的运用

1、泛型擦除

    接下来我们说以下反射在泛型中的运用。Java中的集合,在还没使用泛型之前,它是可以存储任意类型的,加上了泛型就有了限制,不过这个限制是在编译阶段的。那么我们现在知道反射是在运行阶段的,那么我们就可以使用反射来避开泛型,达到泛型擦除的效果。废话不多说,直接上代码:

public class test
 	public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException 
        //1.创建一个集合
        ArrayList<Integer> list = new ArrayList<Integer>();
		
		//2.添加元素
        list.add(1);
        list.add(2);
        list.add(3);
        //list.add("spring"); //其它类型报错

        System.out.println("未擦除泛型的:"+list);
	
		//3.使用反射获取集合的Class类对象
        Class listClass = ArrayList.class;
        //4.获取对应的方法
        Method add = listClass.getDeclaredMethod("add",Object.class);
        //5.调用add方法 添加其他类型的值
        add.invoke(list,"java");
        add.invoke(list,true);

        System.out.println("反射擦除泛型:" + list);
    

上述代码很清楚的看出使用了反射避开了编译器的追查,执行代码结果如下:

【多学一招】泛型擦除还有一种方法,这种方法不需要使用反射。我们来回忆一下集合在还没加泛型之前,是可以存储任何类型的,那么就是Object类型。既然如此,那么我们可以使用引用传递的方式来避开,代码如下:

public test
	 public static void main(String[] args)
	 	//1.创建一个集合
        ArrayList<Integer> list = new ArrayList<Integer>();
		//2.添加元素	
        list.add(1);
        list.add(2);
        list.add(3);
        
        System.out.println(list);

		//3.创建一个无泛型的集合 并装载初始集合
        ArrayList arrayList = list;
		//4.添加元素
        arrayList.add("mysql");
        arrayList.add(false);
        arrayList.add('a');
		//5.大于原集合
        System.out.println("引用擦除泛型:" + list);
    

上述代码执行之后也是可以成功把不同类型的值添加进去的,因为它们的地址是一样的,所以我们利用了引用传递这一点。执行结果图如下:

2、创造通用框架

    其实反射的最大用处是操作框架,如果您已经学过一些高级框架或者是看过一些框架的源码,可以发现它的底层是有反射的身影的。所以反射在后面的框架特别重要,这里就不详细展开了。作为j

以上是关于Java进阶篇之反射的主要内容,如果未能解决你的问题,请参考以下文章

Java进阶篇之反射

Java高级学习篇之反射

Java 基础篇之反射

学习大数据:Java基础篇之反射

Java进阶篇之抽象

Java进阶篇之抽象