依据java核心技术卷I的5.7内容,加上之前学过的视频课程,总结一下java反射的相关内容。
按照我的习惯,我会这么介绍反射:
接下来按照这个思路来进行介绍:
书上的定义说:能够分析类能力的程序叫反射。
我的理解是,反射就是一种方法,使得再运行程序时,我们可以对运行着的程序进行一些操作,可以获取类本身、类的所有成员变量和方法,类的对象,还可以在运行过程中动态创建类、调用类方法。
看了上面这些玩意,最大的感觉难道不是:反射就是编译器?咋啥都能干。确实,反射机制主要是用于系统程序设计,用于开发框架,而在编写应用程序时用的较少。
java核心技术卷I书上的内容:
显然,这是为了书的内容安排说的比较片面。反射是框架设计的灵魂,因为很多系统应用框架就是基于反射原理来做的,很多很强的功能就是基于反射实现的。
这个按照我的脑图来介绍吧。
这里盗一张图,原文的链接在这。Java是面向对象的语言,一切皆对象,所以java认为 这些编译后的 class文件,这种事物也是一种对象,它也给抽象成了一种类,这个类就是Class,也就是类对象,这个类对象中包含了我们创建类的实例时所需要的模板信息,也就是源代码中的成员变量和方法等。
java核心技术卷I这本书中对Class的描述是:程序运行期间,java运行时系统始终为所有对象维护一个运行时类型标识,这个信息会跟踪每个对象所属类,虚拟机利用运行时类型信息选择要执行的正确方法。可以使用一个特殊的java类访问这些信息,保存这些信息的类名为Class。
是啥意思呢?我们获取这个Class类就可以拿到类和对象的信息了。
那该怎么拿到类或者对象的Class呢?
以上三种获取方式:
看到这里,我们可以拿到我们要分析的类的Class信息了,但是怎么获取这个类或者对象的信息呢?
通过上述方法,我们可以获取成员变量、方法、构造函数、修饰符、包、父类、父接口…
通过一个class类可以获取好多细节信息,具体用法先看看图有个印象好了。
Field
成员变量:
getFields()
可以获取本类以及父类所有的public字段
getDeclareFields()
可以获取本类申明的所有方法(包括private字段)
f.setAccessible(true)
方法可以使private方法临时性转为public方法,进行访问。
Method
成员方法:
非静态的方法需要传入一个对象才能调用这个方法m.invoke(obj,null)
Constructor
构造函数。注意判别如何构造有参的构造函数和无参的构造函数。
父类/父接口
上面这些呢,只是大致告诉我们使用反射这么操作能够做到这些事情。具体怎么使用呢?下面介绍。
以下例子都是书籍“java核心技术卷I”5.7章的源码,这里主要记录一下其重点的功能。
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
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+";"); } } }
首先要介绍的是:
这段代码有几个问题:
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这个类的类图。
可以看出这个类的父类是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对象的属性有:
只有value值是非静态的。integer的父类是number,number只有private static final long java.lang.Number.serialVersionUID这个属性,接着父类是Object,而且Object没有属性。
根据上面的分析我们就知道为啥输出的是这样一串字符串了,这个代码设计的还是很精巧。这种思路也很棒,很多时候我们需要查看java的API才能够了解到很多信息,如果能阅读源码那自然更棒。
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但是不能转换为对象数组。
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方法在反射中用的很多,这里先按下不表。
这里介绍课程中几个反射的应用,后面几个我也不熟,先记录着。
java需要连接很多种数据库,采用反射的技术,构建java和数据库之间不同的桥梁介质。
Class.forname("")
就是将这个类名加载到JVM中去。DriverManager类会在系统中挑选加载哪些合适的驱动类,采用getConnection来进行连接,采用newInstance的办法,通过getconnection返回连接。
这样就实现了java可以连接各种不同的数据库。
给定一个数组,将其长度扩大一倍
这一块不是很懂,Array.newInstance()函数不太清楚。
定时执行任意类的任意方法。云计算的效果。
源码获取所有声明的属性,对其进行赋值操作,每一个获取其的值。底层使用反射来做的。
数据库的关系表转化为OBJ对象
路途遥远,慢一点是为了走的更快。