依据java核心技术卷I的5.7内容,加上之前学过的视频课程,总结一下java反射的相关内容。
按照我的习惯,我会这么介绍反射:
接下来按照这个思路来进行介绍:
书上的定义说:能够分析类能力的程序叫反射。
我的理解是,反射就是一种方法,使得再运行程序时,我们可以对运行着的程序进行一些操作,可以获取类本身、类的所有成员变量和方法,类的对象,还可以在运行过程中动态创建类、调用类方法。
看了上面这些玩意,最大的感觉难道不是:反射就是编译器?咋啥都能干。确实,反射机制主要是用于系统程序设计,用于开发框架,而在编写应用程序时用的较少。
java核心技术卷I书上的内容:
显然,这是为了书的内容安排说的比较片面。反射是框架设计的灵魂,因为很多系统应用框架就是基于反射原理来做的,很多很强的功能就是基于反射实现的。
这个按照我的脑图来介绍吧。
这里盗一张图,原文的链接在这。Java是面向对象的语言,一切皆对象,所以java认为 这些编译后的 class文件,这种事物也是一种对象,它也给抽象成了一种类,这个类就是Class,也就是类对象,这个类对象中包含了我们创建类的实例时所需要的模板信息,也就是源代码中的成员变量和方法等。
java核心技术卷I这本书中对Class的描述是:程序运行期间,java运行时系统始终为所有对象维护一个运行时类型标识,这个信息会跟踪每个对象所属类,虚拟机利用运行时类型信息选择要执行的正确方法。可以使用一个特殊的java类访问这些信息,保存这些信息的类名为Class。
是啥意思呢?我们获取这个Class类就可以拿到类和对象的信息了。
看到这里,我们可以拿到我们要分析的类的Class信息了,但是怎么获取这个类或者对象的信息呢?
通过上述方法,我们可以获取成员变量、方法、构造函数、修饰符、包、父类、父接口…
通过一个class类可以获取好多细节信息,具体用法先看看图有个印象好了。
上面这些呢,只是大致告诉我们使用反射这么操作能够做到这些事情。具体怎么使用呢?下面介绍。
以下例子都是书籍“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()函数不太清楚。
定时执行任意类的任意方法。云计算的效果。
源码获取所有声明的属性,对其进行赋值操作,每一个获取其的值。底层使用反射来做的。
路途遥远,慢一点是为了走的更快。