1. 什么是反射
反射是一种功能强大且复杂的机制。Java反射说的是在运行状态中,对于任何一个类,我们都能够知道这个类有哪些方法和属性。对于任何一个对象,我们都能够对它的方法和属性进行调用。我们把这种动态获取对象信息和调用对象方法的功能称之为反射机制。
2. 反射可以用来做什么
反射能够分析类的能力,反射的机制十分强大,主要可以用来:
- 在运行时分析类的能力
- 在运行时查看对象
- 实现通用的数组操作代码
- 利用Method对象,实现C++中函数指针的功能
3. Class类
所谓的反射其实是获取类的字节码文件,也就是.class文件,而获取Class这个对象主要有三种方式:
getClass()函数、.class、forName()方法,代码如下:
package core.java;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
ArrayList<Integer> test = new ArrayList<>();
//第一种方法
Class a = test.getClass();
System.out.println(a);
//第二种方法
Class b = Test.class;
System.out.println(b);
//第三种方法
Class c = Class.forName("java.lang.Math");
System.out.println(c);
}
}
效果截图:
4. 利用反射分析类
在reflect包中有三个类:Field、Method和Constructor分别用于描述类的域、方法和构造器。这三个类都有一个叫做getName()的方法,用来返回项目的名称。Field类有一个getType方法,用来返回描述域所属类型的Class对象。这三个类共同有一个getModifiers的方法,他将返回一个整型数值,来描述修饰符的情况。可以使用Modifier类的静态方法分析其返回的整型数值,也可以使用其toString()方法将对应的修饰符打印出来。
Class类中的getFields、getMethods和getConstructors方法将分别返回类提供的public域、方法和构造器数组,其中包括超类的共有成员。
Class类中的getDeclareFields、getDeclareMethods和getDeclaredConstructors方法将分别返回类中声明的全部域、方法和构造器,其中包括私有和受保护的成员,但不包括超类的成员。
实例代码:
package core.java.reflect;
import java.util.*;
import java.lang.reflect.*;
public class ReflectionTest
{
public static void main(String[] args)
{
// 读取类名
String name;
if (args.length > 0) name = args[0];
else
{
Scanner in = new Scanner(System.in);
System.out.println("Enter class name (e.g. java.util.Date): ");
name = in.next();
}
try
{
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(" extends "
+ supercl.getName());//输出超类
System.out.print("\n{\n");
printConstructors(cl); //输出构造器数组
System.out.println();
printMethods(cl); //输出方法
System.out.println();
printFields(cl); //输出域
System.out.println("}");
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
System.exit(0);
}
public static void printConstructors(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.print(modifiers + " ");
System.out.print(name + "("); //构造器签名
// print parameter types
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(");");
}
}
public static void printMethods(Class cl)
{
Method[] methods = cl.getDeclaredMethods();
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 + "(");
// print parameter types
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(");");
}
}
public static void printFields(Class cl)
{
Field[] fields = cl.getDeclaredFields(); //获取域数组
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 + ";");
}
}
}
运行效果:
分析了这个类的所有能力。
5. 利用反射分析对象
Java安全机制只允许查看任意对象有那些域,而不允许读取他们的值,而反射机制的默认行为刚好受限于Java的这种访问控制。然而,如果一个java程序没有收到安全管理器的控制,就可以覆盖访问控制,为了达到这个目的,需要调用Field、Method或Constructor对象的setAccessible方法。
下面程序显示了如何编写一个可供任意类使用的通用的toString()方法,其中使用getDeclaredFileds获得所有的数据域,然后使用setAccessible将所有的域设置为访问的。对于每个域,获得名字和值。
实例代码:
package core.java.reflect;
import java.util.ArrayList;
public class ObjectAnalyzerTest
{
public static void main(String[] args)
{
ArrayList<Integer> squares = new ArrayList<>();
for (int i = 1; i <= 5; i++)
squares.add(i * i);
System.out.println(new ObjectAnalyzer().toString(squares));
}
}
package core.java.reflect;
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)
{
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() + "[]{";//返回类数组的组件类型的Class,如果此类不表示数组,则此方法返回null
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);
// get the names and values of all fields
for (Field f : fields)
{
if (!Modifier.isStatic(f.getModifiers()))//getModifiers方法返回用于描述构造器、方法或域的修饰符的整型数值,可以使用Modifier.toString()返回这个修饰符
{
if (!r.endsWith("[")) r += ",";
r += f.getName() + "=";
try
{
Class t = f.getType();
Object val = f.get(obj);
if (t.isPrimitive()) r += val;
else r += toString(val);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
r += "]";
cl = cl.getSuperclass();
}
while (cl != null);
return r;
}
}
执行效果:
6. 利用反射编写泛型数组
Array类中的copyOf方法已经实现了动态的创建数组而且可以用于扩展已经填满的数组,下面将给出一个通用的方法:
执行代码:
package core.java.reflect;
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));
String[] b = { "Tom", "Dick", "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;
}
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);//使用newInstance方法生成一个新的数组,类型为相应的类型,长度为新的数组的长度
System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));//将原始数组拷贝到新的数组中
return newArray;
}
}
执行效果:
其中,badCopyOf的返回值进行类型转换将会抛出一个异常,因为不能转换为对象数组。
7. 利用反射实现函数指针的功能
在C++中,可以从函数指针执行任意函数,从表面上看,Java没有提供方法指针,但是反射机制运行调用任意的方法。在Method类中有一个invoke方法,它允许调用包装在当前Method对象中的方法。
Object invoke(Object obj, Object...args)
第一个参数是隐式参数,其余的对象提供了显示参数。对于静态方法,第一个参数可以被忽略,即可以将他设置为null。
实例代码:
package core.java.reflect;
import java.lang.reflect.*;
public class MethodTableTest
{
public static void main(String[] args) throws Exception
{
Method square = MethodTableTest.class.getMethod("square", double.class);//获取此类的square方法
Method sqrt = Math.class.getMethod("sqrt", double.class);//获取Math类的sqrt方法
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)
{
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);//使用invoke()函数直接调用Method所指向的方法
System.out.printf("%10.4f | %10.4f%n", x, y);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
}
执行效果:
上述程序表明,可以使用Method对象实现函数指针的所有操作。这里需要注意的是:invoke的参数和返回值必须是Object类型的,这就意味着要进行多次的类型转换。这样做将会使编译器错过检查代码的机会,而且执行速度会变慢,所以不在必要的时候不要使用Method对象。
8. 小结
反射机制可以在运行时查看域和方法,让程序员编写出更具有通用性的程序。但是反射是很脆弱的,即编译器很难帮助人们发现程序中的错误,所以只有在运行时发现错误并导致异常而不是在编译时。