反射类提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵 Java 代码的程序。这项功能被大量地应用于 JavaBean 中,它是 Java 组建的体系结构。
能够分析类的程序称为反射。反射的功能及其强大,可以用来:
- 在运行时分析类的能力
- 在运行时查看对象。例如,编写一个 toString 方法供所有类使用
- 实现通用的数组操作代码
- 利用 Method 对象
Class 类
在程序运行期间,Java 运行时系统始终为所有的对象维护一个被称为运行时的类型标识。这个信息跟踪着每个对象所属的类。虚拟机利用这些运行时类型信息选择相应的方法执行。
然而,可以通过专门的 Java 类访问这些信息。保存这些信息的类别称为 Class,Object 类中的 getClass() 方法将会返回一个 Class 类型的实例。
Employee e;
...
Class cl = e.getClass();
如同用一个 Employee 对象表示一个特定的雇员属性一样,一个 Class 对象将表示一个特性类的属性。最常用的 Class 方法是 getName(),返回类的名字。
System.out.println(e.getClass().getName() + " " + e.getName());
e是一个经理,结果为: Manager Tom
e是一个雇员,结果为: Employee Tom
如果类在一个包中,包的名字也会作为类名的一部分。
还可以调用静态方法 forName() 获得类名对应的 Class 对象。
String className = "java.util.Random";
Class cl = Class.forName(className);
如果类名保存在字符串中,并可在运行中改变,就可以用这个方法。这个方法只能在 className 是类名或者接口名时才能够执行。否侧, forName() 方法会抛出一个 checked exception。无论何时使用这个方法,都应该提供一个异常处理器。
获得 Class 对象的第三种方法非常简单。如果 T 是任意的 Java 类型 (或者 void 关键字), T.class 将代表匹配的类对象。
Class cl1 = Random.Class; // 如果你没有 import java.util.*
Class cl2 = int.class;
Class Cl3 = Double[].class;
Tips : 一个 Class 对象实际上表示的是一个类型,而这个类型未必是一定是一种类。例如, int 不是类,但是 int.class 是一个 Class 类型的对象。
Class 类实际上是一个泛型类。Employee.class 的类型是 Class
鉴于历史原因,getName 方法应用于数组类型的人时候会翻译一个很奇怪的名字:
- Double[].class.getName() 返回 “[Ljava.lang.Double;”
- int[].class.getName() 返回 “[I”
虚拟机为每个类型管理一个 Class 对象。因此,可以利用 == 运算符实现两个类对象比较的操作。
newInstance 方法调用默认的构造器(无参构造器)初始化新创建的对象。如果没有默认的构造器,就会抛出异常。将 forName 与 newInstance 配合使用,可以根据储存在字符串中的类型创建一个对象。
String s = "java.util.Random";
Object a = Class.forName(s).newInstance();
利用反射分析类的能力
反射机制中最重要的内容---检查类的结构。
在 java.lang.reflect 包中有三个类 Field, Method 和 Constructor 分别用于描述类的域,方法和构造器。这三个类都有一个 getName 方法,用来返回项目的名称。
Field 有一个 getType 方法,用力返回描述域所述类型的 Class 对象。Method 和 Constructor 类能够报告参数类型的方法,Method 类还有一个可以报告返回类型的方法。这三个类还有一个 getModifiers 的方法,它将返回一个整形数值,用不同的为开关描述 public 和 static 这样的修饰符。还可以利用 java.lang.reflect 包中的 Modifier 类的静态方法分析 getModifiers 返回的整形数值。
下面的类打印一个类的所有信息。用户输入类型,然后输出类中的所有方法和构造器签名,以及全部的域名。
请输入类名(eg : java.util.Date) :
java.lang.Double
java.lang.Double类的所有信息如下 :
public final class java.lang.Double extend java.lang.Number
{
public java.lang.Double(double);
public java.lang.Double(java.lang.String);
public boolean equals(java.lang.Object)
public static java.lang.String toString(double)
public java.lang.String toString()
public int hashCode()
public static int hashCode(double)
public static double min(double, double)
public static double max(double, double)
public static native long doubleToRawLongBits(double)
public static long doubleToLongBits(double)
public static native double longBitsToDouble(long)
public volatile int compareTo(java.lang.Object)
public int compareTo(java.lang.Double)
public byte byteValue()
public short shortValue()
public int intValue()
public long longValue()
public float floatValue()
public double doubleValue()
public static java.lang.Double valueOf(java.lang.String)
public static java.lang.Double valueOf(double)
public static java.lang.String toHexString(double)
public static int compare(double, double)
public static boolean isNaN(double)
public boolean isNaN()
public static boolean isFinite(double)
public static boolean isInfinite(double)
public boolean isInfinite()
public static double sum(double, double)
public static double parseDouble(java.lang.String)
public final void wait()
public final void wait(long, int)
public final native void wait(long)
public final native java.lang.Class getClass()
public final native void notify()
public final native void notifyAll()
public static final double POSITIVE_INFINITY;
public static final double NEGATIVE_INFINITY;
public static final double NaN;
public static final double MAX_VALUE;
public static final double MIN_NORMAL;
public static final double MIN_VALUE;
public static final int MAX_EXPONENT;
public static final int MIN_EXPONENT;
public static final int SIZE;
public static final int BYTES;
public static final java.lang.Class TYPE;
}
这个程序可以分析 Java 解释器能够加载的任何类,而不仅仅是编译程序是可以使用的类。该程序代码如下:
package com.yhl.book.test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Scanner;
/**
* @PROJECT_NAME: DataStructures
* @ClassName ReflectionTest
* @Author YHL
* @Email 1359541394@qq.com
* @Date 2020/10/15 21:35
* @Version 1.0
* @Description TODO
*/
public class ReflectionTest {
public static void main(String[] args) {
// 读取命令参数args 或者用户输入的类名
String name;
if (args.length > 0) name = args[0];
else {
Scanner scan = new Scanner(System.in);
System.out.println("请输入类名(eg : java.util.Date) : ");
name = scan.next();
}
System.out.println(name + "类的所有信息如下 : ");
try {
//打印类名和超类 (if != Object )
Class cl = Class.forName(name);
Class supercl = cl.getSuperclass();
String modifiers = Modifier.toString(cl.getModifiers());
if (modifiers.length() > 0) {
System.out.print(modifiers + " ");
}
System.out.print("class " + name);
if (supercl != null && supercl != Object.class) {
System.out.print(" extend " + supercl.getName());
}
System.out.print("\n{\n");
printConstructors(cl);
System.out.println();
printMethods(cl);
System.out.println();
printFields(cl);
System.out.print("}");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.exit(0);
}
//打印该类所有的构造器
public static void printConstructors(Class cl) {
Constructor[] constructors = cl.getConstructors();
for (Constructor c : constructors) {
String name = c.getName();
// 打印修饰符和构造器名
System.out.print(" ");
String modifiers = Modifier.toString(c.getModifiers());
if (modifiers.length() > 0) System.out.print(modifiers + " ");
System.out.print(name + "(");
// 打印参数类型
Class[] paramTypes = c.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) {
if (i > 0) System.out.print(", ");
System.out.print(paramTypes[i].getName());
}
System.out.println(");");
}
}
//打印该类的所有方法
public static void printMethods(Class cl) {
Method[] methods = cl.getMethods();
for (Method m : methods) {
Class retType = m.getReturnType();
String name = m.getName();
// 打印修饰符,返回值类型和方法名
System.out.print(" ");
String modifiers = Modifier.toString(m.getModifiers());
if (modifiers.length() > 0) System.out.print(modifiers + " ");
System.out.print(retType.getName() + " " + name + "(");
// 打印参数类型
Class[] paramTypes = m.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) {
if (i > 0) System.out.print(", ");
System.out.print(paramTypes[i].getName());
}
System.out.println(")");
}
}
// 打印域
public static void printFields(Class cl) {
Field[] fields = cl.getFields();
for (Field f : fields) {
Class type = f.getType();
String name = f.getName();
System.out.print(" ");
String modifiers = Modifier.toString(f.getModifiers());
if (modifiers.length() > 0) System.out.print(modifiers + " ");
System.out.println(type.getName() + " " + name + ";");
}
}
}
在运行时使用反射分析对象
反射机制的默认行为受限于 Java 的访问控制。然而,如果一个 Java 程序·没有收到安全管理器的控制,就可以覆盖访问控制。为了达到这个目的,可以调用 Field, Method 或 Constructor 对象的 setAccessible 方法。
setAccessible 方法是 AccessibleObject 类的一个方法,它是 Method、Field 和 Constructor 类的公共超类。这个特性是为了调试,持久存储和相似机制提供的。
下列代码编写一个可供任意类提供 toString 方法,使用 getDeclaredFields 获取所有的数据域,使用 setAccessible 将所有的域设置为可访问的。对于每个域,获得了名字和值。
循环应用可能会导致无限递归,所示 ObjectAnalyzer 将记录已经被访问过的对象。为了查看数组内部,需要采用一种不同的方式,具体看代码。
package book.test.reflect.objectAnalyzer;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
/**
* @PROJECT_NAME: DataStructures
* @ClassName ObjectAnalyzer
* @Author YHL
* @Email 1359541394@qq.com
* @Date 2020/10/16 22:01
* @Version 1.0
* @Description TODO
*/
public class ObjectAnalyzer {
private ArrayList<Object> visited = new ArrayList<>();
// 可以使用该方法查看任意对象内部信息
public String toString(Object object) {
if (object == null) return "null";
if (visited.contains(object)) return "...";
visited.add(object);
Class cl = object.getClass();
if (cl == String.class) return (String) object;
if (cl.isArray()) {
String r = cl.getComponentType() + "[]{";
for (int i = 0; i < Array.getLength(object); i++) {
if (i > 0) r += ",";
Object val = Array.get(object, 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() + "=";
try {
Class t = f.getType();
Object val = f.get(object);
if (t.isPrimitive()) r += val;
else r += toString(val);
} catch (Exception e) {
e.printStackTrace();
}
}
}
r += "]";
cl = cl.getSuperclass();
} while (cl != null);
return r;
}
}
测试代码
package book.test.reflect.objectAnalyzer;
import java.util.ArrayList;
/**
* @PROJECT_NAME: DataStructures
* @ClassName ObjectAnalyzerTest
* @Author YHL
* @Email 1359541394@qq.com
* @Date 2020/10/16 21:55
* @Version 1.0
* @Description TODO
*/
public class ObjectAnalyzerTest {
public static void main(String[] args) {
ArrayList<Integer> sequares = new ArrayList<>();
for (int i = 1; i <= 5; i++) sequares.add(i * i);
System.out.print(new ObjectAnalyzer().toString(sequares));
}
}
运行结果
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][][]
使用反射编写泛型数组代码
java.lang.reflect 包中的 Array 类允许动态创建数组,可以将这个特性应用到 Array 类的 copyOf() 方法中。其中最关键的是 Array 类的静态方法 newInstance,它能构建新数组。需要两个参数,一个是数组元素类型,一个是数组的长度。
可以通过调用 Array.getLength(a) 获得数组的长度,也可以通过 Array 的静态 getLength 方法的返回值得到任意数组的长度。而获得新数组元素类型,需要进行一下工作:
- 首先获得 a 数组的类对象
- 确认它是一个数组
- 使用 Class 类(只能定义表示数组的类对象)的 getComponentType() 方法确定数组对应的类型。
package book.test.reflect.arrays;
import java.lang.reflect.Array;
import java.util.Arrays;
/**
* @PROJECT_NAME: DataStructures
* @ClassName CopyOfTest
* @Author YHL
* @Email 1359541394@qq.com
* @Date 2020/10/17 10:58
* @Version 1.0
* @Description TODO
*/
public class CopyOfTest {
public static void main(String[] args) {
int[] a = {1, 2, 3, 4, 5};
a = (int[]) goodCopyOf(a, 10);
System.out.println(Arrays.toString(a));
String[] b = {"Tom", "Jack", "Harry"};
b = (String[]) goodCopyOf(b, 10);
System.out.println(Arrays.toString(b));
System.out.println("The Following Call Will Generate an Exception:");
b = (String[]) badCopyOf(b, 10);
}
// 返回对象是对象数组,不能转换为具体的类型
public static Object[] badCopyOf(Object[] a, int newLength) {
Object[] newArray = new Object[newLength];
System.arraycopy(a, 0, newArray, 0, Math.min(a.length, newLength));
return newArray;
}
// 整数类型数组 int[] 可以被转换成Object ,但是不能转换为对象数组。
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;
}
}
调用任意方法
Java 设计者认为方法指针是很危险的,并常常会带来隐患。而 Java 提供接口是一种更好地解决方案。然而,反射机制允许你调用任意方法。
在 Method 类中有一个 invoke 方法,它允许调用包装在当前 Method 对象中的方法。
invoke 的方法签名是:
Object invoke(Object obj, Object... args)
第一个参数是隐式参数,其余对象提供了显式参数。假设用 ml 代表 Employee 类的 getName 方法,调用方式如下:
String n = (String) ml.invoke(harry);
如果返回类型是基本类型, invoke 方法会返回其包装器类型。例如:
double s = (Double) ml.invoke(harry);
如何获得 Method 对象呢?可以通过调用 getDeclareMethods 方法,然后对返回的 Method 对象数组进行查找,知道找到想要的方法位置。也可以通过调用 Class 类中的的 getMethod 方法得到想要的方法。
Method getMethod(String name, Class... parameterTypes);
package book.test.reflect.methods;
import java.lang.reflect.Method;
/**
* @PROJECT_NAME: DataStructures
* @ClassName MethodTableTest
* @Author YHL
* @Email 1359541394@qq.com
* @Date 2020/10/17 17:35
* @Version 1.0
* @Description TODO
*/
public class MethodTableTest {
public static void main(String[] args) throws NoSuchMethodException {
Method sequare = MethodTableTest.class.getMethod("square", double.class);
Method sqrt = Math.class.getMethod("sqrt", double.class);
printTable(1, 10, 10, sequare);
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) {
// 打印方法签名
System.out.println(f);
double dx = (to - from) / (n - 1);
for (double x = from; x <= to; x += dx) {
try {
double y = (Double) f.invoke(null, x);
System.out.printf("%10.4f | %10.4f\n", x, y);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
运行结果
public static double book.test.reflect.methods.MethodTableTest.square(double)
1.0000 | 1.0000
2.0000 | 4.0000
3.0000 | 9.0000
4.0000 | 16.0000
5.0000 | 25.0000
6.0000 | 36.0000
7.0000 | 49.0000
8.0000 | 64.0000
9.0000 | 81.0000
10.0000 | 100.0000
public static double java.lang.Math.sqrt(double)
1.0000 | 1.0000
2.0000 | 1.4142
3.0000 | 1.7321
4.0000 | 2.0000
5.0000 | 2.2361
6.0000 | 2.4495
7.0000 | 2.6458
8.0000 | 2.8284
9.0000 | 3.0000
10.0000 | 3.1623
invoke 的参数和返回值必须是 Object 类型的。这意味需要进行多次的类型转换。不仅如此,使用反射获得方法指针的代码要比仅仅直接调用方法明显慢得多。
有鉴于此,建议在必要的时候才使用 Method 对象,而最好使用接口以及 Java SE 8 中的 lambda 表达式。
作者 : General
地址 : http://www.yanghelong.top
标题:反射
作者:General
地址:https://ZZUGEneral.github.io/articles/2020/10/17/1602938025485.html
一个没有得到Java开发工作的测试员!