Java教程

java反射

本文主要是介绍java反射,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

文章目录

  • 前言:
  • 1、反射是什么?
  • 2、反射能干啥?
  • 3、反射怎么用?
    • 3.1、反射基础
    • 3.2、反射用途
      • 3.2.1 加载类的资源文件
      • 3.2.2 分析类
      • 3.2.3 分析对象
      • 3.2.4 编写泛型数组
      • 3.2.5 调用任意方法和构造器
    • 3.3、反射应用
      • 3.3.1 数据库连接
      • 3.3.2 数组扩充器
      • 3.3.3 动态执行方法
      • 3.3.4 json和java对象互转
      • 3.3.5 Tomcat的Servlet对象创建
      • 3.3.6 MyBatis的OR/M
      • 3.3.7 Spring的Bean容器
  • 总结

前言:

依据java核心技术卷I的5.7内容,加上之前学过的视频课程,总结一下java反射的相关内容。
在这里插入图片描述
按照我的习惯,我会这么介绍反射:

  • 反射是啥?
  • 反射能干啥?
  • 怎么用反射?

接下来按照这个思路来进行介绍:

1、反射是什么?

书上的定义说:能够分析类能力的程序叫反射

我的理解是,反射就是一种方法,使得再运行程序时,我们可以对运行着的程序进行一些操作,可以获取类本身、类的所有成员变量和方法,类的对象,还可以在运行过程中动态创建类、调用类方法。

看了上面这些玩意,最大的感觉难道不是:反射就是编译器?咋啥都能干。确实,反射机制主要是用于系统程序设计,用于开发框架,而在编写应用程序时用的较少。

2、反射能干啥?

java核心技术卷I书上的内容:

  • 在运行时分析类的能力
  • 在运行时检查对象
  • 实现泛型数组操作
  • 利用Method对象。

显然,这是为了书的内容安排说的比较片面。反射是框架设计的灵魂,因为很多系统应用框架就是基于反射原理来做的,很多很强的功能就是基于反射实现的。

3、反射怎么用?

这个按照我的脑图来介绍吧。

3.1、反射基础

在这里插入图片描述
这里盗一张图,原文的链接在这。Java是面向对象的语言,一切皆对象,所以java认为 这些编译后的 class文件,这种事物也是一种对象,它也给抽象成了一种类,这个类就是Class,也就是类对象,这个类对象中包含了我们创建类的实例时所需要的模板信息,也就是源代码中的成员变量和方法等。

java核心技术卷I这本书中对Class的描述是:程序运行期间,java运行时系统始终为所有对象维护一个运行时类型标识,这个信息会跟踪每个对象所属类,虚拟机利用运行时类型信息选择要执行的正确方法。可以使用一个特殊的java类访问这些信息,保存这些信息的类名为Class

是啥意思呢?我们获取这个Class类就可以拿到类和对象的信息了。

那该怎么拿到类或者对象的Class呢?
image-20210602152501283
以上三种获取方式:

  • 类名.class()
  • 对象名.getClass()
  • Class.forName(具体的类名)

看到这里,我们可以拿到我们要分析的类的Class信息了,但是怎么获取这个类或者对象的信息呢?
image-20210602152929748
通过上述方法,我们可以获取成员变量、方法、构造函数、修饰符、包、父类、父接口…

通过一个class类可以获取好多细节信息,具体用法先看看图有个印象好了。

  • Field成员变量:

    • getFields()可以获取本类以及父类所有的public字段

    • getDeclareFields()可以获取本类申明的所有方法(包括private字段)

      f.setAccessible(true)方法可以使private方法临时性转为public方法,进行访问。

      image-20210602154139914
  • Method成员方法:

    image-20210602154234163

    非静态的方法需要传入一个对象才能调用这个方法m.invoke(obj,null)

  • Constructor构造函数。注意判别如何构造有参的构造函数和无参的构造函数。
    image-20210602160023680

  • 父类/父接口

    image-20210602160148087

上面这些呢,只是大致告诉我们使用反射这么操作能够做到这些事情。具体怎么使用呢?下面介绍。

3.2、反射用途

以下例子都是书籍“java核心技术卷I”5.7章的源码,这里主要记录一下其重点的功能。

3.2.1 加载类的资源文件

package resource;

import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;

import javax.swing.ImageIcon;
import javax.swing.JOptionPane;

public class ResourceTest {
	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		Class cl = ResourceTest.class;
		URL aboutURL = cl.getResource("about.gif");
		ImageIcon icon = new ImageIcon(aboutURL);
		
		InputStream stream = cl.getResourceAsStream("data/about.txt");
		String about = new String(stream.readAllBytes(),"UTF-8");
		
		InputStream stream1 = cl.getResourceAsStream("/corejava/title.txt");
		var title = new String(stream1.readAllBytes(),StandardCharsets.UTF_8).trim();
		
		JOptionPane.showMessageDialog(null,about,title,JOptionPane.INFORMATION_MESSAGE,icon);
	}
}

运行效果呢?
在这里插入图片描述
重点代码分析:

		Class cl = ResourceTest.class;
		URL aboutURL = cl.getResource("about.gif");
		InputStream stream1 = cl.getResourceAsStream("/corejava/title.txt");

第一行语句呢,是获取可以拥有资源的类的对象
第二行语句呢,是介绍描述资源位置的URL
第三行语句呢,是使用getResourceAsStream方法获得一个输入流来读取文件中数据。

这两个都是java.lang.Class的方法,可以自己查看API
在这里插入图片描述

3.2.2 分析类

package reflection;

import java.lang.reflect.Modifier;
import java.lang.reflect.*;
import java.util.*;
import java.util.Scanner;

public class ReflectionTest {

	public static void main(String[] args) throws ClassNotFoundException {
		String name;
		if(args.length>0) name = args[0];
		else {
			var in = new Scanner(System.in);
			System.out.println("Enter class name(e.g. jaca.util.Date):");
			name = in.next();
		}
		//可能会报错:这个类没找到
		Class cl = Class.forName(name);
		//获取这个类的父类
		Class supercl = cl.getSuperclass();
		//获取这个类的描述符,看他是public还是static这种修饰符
		String modifiers = Modifier.toString(cl.getModifiers());
		//这里是啥还需要再百度一下。
		if(modifiers.length()>0) System.out.println(modifiers+" ");
		System.out.println("class "+name);
		// 他的父类不是Object的时候再看看
		if(supercl != null && supercl != Object.class) System.out.println("extends "+supercl.getName());
		
		System.out.println("{");
		printConstructor(cl);
		System.out.println();
		printMethods(cl);
		System.out.println();
		printFields(cl);
		
		
	}
	/**
	 * 打印一个类的所有构造函数
	 * @param cl:一个Class类
	 */
	public static void printConstructor(Class cl) {
		Constructor[] constructors = cl.getDeclaredConstructors();
		
		for(Constructor c:constructors) {
			String name = c.getName();
			System.out.print("   ");
			String modifiers = Modifier.toString(c.getModifiers());
			if(modifiers.length()>0) System.out.println(modifiers+"  ");
			System.out.print(name+"(");
			
			Class[] paramTypes = c.getParameterTypes();
			for(int j=0;j<paramTypes.length;j++) {
				if(j>0) {
					System.out.print(", ");
				}
				System.out.print(paramTypes[j].getName());
			}
			System.out.println(")");
		}
	}
	/**
	 * 打印一个类所有的方法
	 * @param cl:一个Class类
	 */
	public static void printMethods(Class cl) {
		Method[] methods = cl.getDeclaredMethods();
		
		for(Method m:methods) {
			String name = m.getName();
			System.out.print("   ");
			String modifiers = Modifier.toString(m.getModifiers());
			if(modifiers.length()>0) System.out.println(modifiers+"  ");
			System.out.print(name+"(");
			
			Class[] paramTypes = m.getParameterTypes();
			for(int j=0;j<paramTypes.length;j++) {
				if(j>0) {
					System.out.print(", ");
				}
				System.out.print(paramTypes[j].getName());
			}
			System.out.println(")");
		}
	}
	/**
	 * 打印一个类所有的字段
	 * @param cl:一个Class类
	 */
	public static void printFields(Class cl) {
		Field[] fields = cl.getDeclaredFields();
		
		for(Field f:fields) {
			//获取变量的类型
			Class type = f.getType();
			//获取对象的名称
			String name = f.getName();
			System.out.println("    ");
			String modifiers = Modifier.toString(f.getModifiers());
			if(modifiers.length()>0)System.out.print(modifiers+" ");
			System.out.println(type.getName()+" "+name+";");
		}
		
	}

}

首先要介绍的是:

  • java.lang.reflect包中有三个类:Field、Method和Constructor分别描述类的字段、方法和构造器。
  • 三个类都有一个名为getModifiers的方法,返回一个整数描述前面的修饰方法。可以用Modifier.toString方法将其打印出来。

这段代码有几个问题:

  1. main函数参数,String[] args是咋回事?具体看这个blog。
  2. 可以根据上述三个方法查看相关方法怎么用的。

3.2.3 分析对象

package reflection;

import java.util.ArrayList;

public class ObjectAnalyzerTest {

	public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
		// TODO Auto-generated method stub
		var squares = new ArrayList<Integer>();
		for(int i=1;i<=5;i++) {
			squares.add(i*i);
		}
		System.out.println(new ObjectAnalyzer().toString(squares));
	}
}
package reflection;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;

public class ObjectAnalyzer {
	private ArrayList<Object> visited = new ArrayList<>();
	
	public String toString(Object obj) throws IllegalArgumentException, IllegalAccessException {
		if(obj == null) {
			return "null";
		}
		if(visited.contains(obj)) {
			return "...";
		}
		visited.add(obj);
		Class cl = obj.getClass();
		if(cl==String.class) {
			return (String)obj;
		}
		if(cl.isArray()) {
			String r = cl.getComponentType()+"[]{";
			for(int i=0;i<Array.getLength(obj);i++) {
				if(i>0)r+=",";
				Object val = Array.get(obj, i);
				if(cl.getComponentType().isPrimitive())r+=val;
				else r+=toString(val);
			}
			return r+"}";
		}
		String r = cl.getName();
		do {
			r+="[";
			Field[] fields = cl.getDeclaredFields();
			AccessibleObject.setAccessible(fields, true);
			for(Field f:fields) {
				if(!Modifier.isStatic(f.getModifiers())) {
					if(!r.endsWith("["))r+=",";
					r+=f.getName()+"=";
					Class t = f.getType();
					Object val = f.get(obj);
					if(t.isPrimitive()) r+=val;
					else r+=toString(val);
				}
			}
			r+="]";
			cl = cl.getSuperclass();
		}while(cl!=null);
		return r;
	}
}

程序会输出下面的内容:

java.util.ArrayList[elementData=class java.lang.Object[]{java.lang.Integer[value=1][][],java.lang.Integer[value=4][][],java.lang.Integer[value=9][][],java.lang.Integer[value=16][][],java.lang.Integer[value=25][][],null,null,null,null,null},size=5][modCount=5][][]

看了我老半天都没看懂为啥会输出这个玩意,跟了一下程序运行和相关blog才大致看懂哈。

首先是ArrayList这个类的类图。
img
可以看出这个类的父类是AbstractList,它的祖父类是AbstractCollection。然后这个类的属性是:

private static final long java.util.ArrayList.serialVersionUID //序列号
//集合的默认大小
private static final int DEFAULT_CAPACITY = 10;
//空的数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
//这也是一个空的数组实例,和EMPTY_ELEMENTDATA空数组相比是用于了解添加元素时数组膨胀多少
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存储 ArrayList集合的元素,集合的长度即这个数组的长度
//1、当 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时将会清空 ArrayList
//2、当添加第一个元素时,elementData 长度会扩展为 DEFAULT_CAPACITY=10
transient Object[] elementData;
//表示集合的长度
private int size;

首先会输出它的elementData这个非静态变量,这是一个Object[]的数组,然后会依次输出数组的元素。因为我们只构建了5个元素,这个ArrayList会初始化扩展为10各元素,后面的元素全部是null。

因为传进去的是Integer对象,Integer对象的属性有:
img
只有value值是非静态的。integer的父类是number,number只有private static final long java.lang.Number.serialVersionUID这个属性,接着父类是Object,而且Object没有属性。

根据上面的分析我们就知道为啥输出的是这样一串字符串了,这个代码设计的还是很精巧。这种思路也很棒,很多时候我们需要查看java的API才能够了解到很多信息,如果能阅读源码那自然更棒。

3.2.4 编写泛型数组

package reflection;

import java.lang.reflect.*;
import java.util.*;


public class CopyOfTest {
	public static void main(String[] args) {
		int[] a = {1,2,3};
		a = (int[])goodCopyOf(a,10);
		System.out.println(Arrays.toString(a));
		
	}
	
	public static Object[] badCopyOf(Object[] a, int newLength) {
		var newArray = new Object[newLength];
		System.arraycopy(a,0,newArray,0,Math.min(a.length, newLength));
		return newArray;
	}
	
	public static Object goodCopyOf(Object a, int newLength) {
		Class cl = a.getClass();
		if(!cl.isArray()) return null;
		Class componentType = cl.getComponentType();
		int length = Array.getLength(a);
		Object newArray = Array.newInstance(componentType, newLength);
		System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
		return newArray;
	}
}

这里有两种方法,为啥第一种不行呢?
原因是:new Object[newLength]不能强制转换为传进来的参数类型,因为父类不能转为子类。第二种方法将传入的那个类临时转为Object[],然后再转回来。注意这里必须要创建与原数组类型相同的新数组,因此调用了Array.newInstance()方法。

注意这里的类型全部为Object而不是Object[],因为对象列表如int[]可以转换为Object但是不能转换为对象数组。

3.2.5 调用任意方法和构造器

package reflection;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MethodTableTest {

	public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		Method square = MethodTableTest.class.getMethod("square", double.class);
		Method sqrt = Math.class.getMethod("sqrt",double.class);
		
		printTable(1,10,10,square);
		printTable(1,10,10,sqrt);

	}
	
	public static double square(double x) {
		return x*x;
	}
	
	public static void printTable(double from, double to, int n,Method f) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		System.out.println(f);
		double dx = (to-from)/(n-1);
		for(double x = from; x<=to;x+=dx) {
			double y = (Double)f.invoke(null, x);
			System.out.printf("%10.4f | %10.4f%n",x,y);		}
	}
}

这里主要是invoke方法的使用,Method对象调用,实现C#中委托的功能。这个invoke方法在反射中用的很多,这里先按下不表。

3.3、反射应用

这里介绍课程中几个反射的应用,后面几个我也不熟,先记录着。

3.3.1 数据库连接

image-20210602162218425

java需要连接很多种数据库,采用反射的技术,构建java和数据库之间不同的桥梁介质。
Class.forname("")就是将这个类名加载到JVM中去。DriverManager类会在系统中挑选加载哪些合适的驱动类,采用getConnection来进行连接,采用newInstance的办法,通过getconnection返回连接。

这样就实现了java可以连接各种不同的数据库。

3.3.2 数组扩充器

给定一个数组,将其长度扩大一倍

  • java数组一旦创建,其长度是不再更改的
  • 新建一个大数组(相同类型),然后将就数组的内容拷贝过去。

image-20210602165657066

这一块不是很懂,Array.newInstance()函数不太清楚。

3.3.3 动态执行方法

image-20210603091020822

定时执行任意类的任意方法。云计算的效果。

3.3.4 json和java对象互转

image-20210603091250068

源码获取所有声明的属性,对其进行赋值操作,每一个获取其的值。底层使用反射来做的。

3.3.5 Tomcat的Servlet对象创建

image-20210603091452054

3.3.6 MyBatis的OR/M

image-20210603091620780
数据库的关系表转化为OBJ对象

3.3.7 Spring的Bean容器

image-20210603091728453

总结

路途遥远,慢一点是为了走的更快。

这篇关于java反射的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!