1.什么是Java的反射呢?
Java的反射机制是在编译并不确定是哪个类被加载了,而是在程序运行的时候才加载、探知、自审。使用在编译期并不知道的类。这样的特点就是反射。
反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。
2.Java反射有什么作用呢?
假如我们有两个程序员,一个程序员在写程序的时候,需要使用第二个程序员所写的类,但第二个程序员并没完成他所写的类。那么第一个程序员的代码能否通过编译呢?这是不能通过编译的。利用Java反射的机制,就可以让第一个程序员在没有得到第二个程序员所写的类的时候,来完成自身代码的编译。
Java的反射机制它知道类的基本结构,这种对Java类结构探知的能力,我们称为Java类的“自审”。大家都用过IDEA和eclipse。当我们构建出一个对象的时候,去调用该对象的方法和属性的时候。一按点,编译工具就会自动的把该对象能够使用的所有的方法和属性全部都列出来,供用户进行选择。这就是利用了Java反射的原理,是对我们创建对象的探知、自审。(反射是一种功能强大且复杂的机制。使用它的主要人员是工具构造者,而不是应用程序员。)
3.Class类
要正确使用Java反射机制就得使用java.lang.Class这个类。它是Java反射机制的起源。当一个类被加载以后,Java虚拟机就会自动产生一个Class对象。通过这个Class对象我们就能获得加载到虚拟机当中这个Class对象对应的方法、成员变量以及构造方法的声明和定义等信息。
4.获取class类对象
既然class对象如此重要,那么我们如何获取class对象呢?这里有三种方法:
4.1 使用类对象的getClass()方法使用类对象的getClass()方法
4.2 Class.forName(classname)使用 Class.forName(classname) 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象
如果className不是类名或接口名,则forname抛出一个checked exception异常所以应该给这个方法提供一个异常处理器
4.3 .class 方法
5.通过反射创建类对象既然通过上文我们知道了如何获取class对象,那么我们是不是就可以根据这个类对象来创建实例对象呢?当然可以
通过反射创建类对象主要有两种方式:通过 Class 对象的 newInstance() 方法、通过 Constructor 对象的 newInstance() 方法。
5.1 Class 对象的 newInstance() 方法newlnstance方法调用默认的构造器(没有参数的构造器)初始化新创建的对象。如果这个类没有默认的构造器, 就会抛出一个异常。
5.2 Constructor 对象的 newInstance() 方法通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。
这里getConstructor和newInstance使用时需要设置异常处理,我这里是直接在main后面throws了
6.获取类属性、方法、构造器的结构我们已经成功获取了class类对象,并学会了如何创建对象,现在我们还可以看看对象内部的结构是什么样的,比如属性、方法和构造器。
在java.lang.reflect 包中有三个类 Field、Method 和 Constructor分别用于描述类的属性、 方法和构造器
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
下面介绍一下 Field、Method 和 Constructor三个类的常用方法Field类的getType 方法, 用来返回属性所属类型的 Class 对象
Method 类有一个getReturnType方法,返回return值所属类型的Class对象
Method 和 Constructor 类有一个共同的方法getParameterTypes,返回方法参数所属类型的Class对象
Field、Method 和 Constructor都有一个getName 方法,返回方法名的字符串
Field、Method 和 Constructor都有一个getModifiers方法,它将返回一个整型数值,用不同的位开关描述 public 和 static 这样 的修饰符使用状况。可以利用 Modifier.toString方法将 修饰符打印出来。
for (Method m : methods) {
String modifiers = Modifier.toString(m.getModifiers());
}该修饰符是java.lang.reflect.Modifier的静态属性。这里是用十进制表示的,源码里面是十六进制表示的。
对应表如下:
PUBLIC: 1
PRIVATE: 2
PROTECTED: 4
STATIC: 8
FINAL: 16
SYNCHRONIZED: 32
VOLATILE: 64
TRANSIENT: 128
NATIVE: 256
INTERFACE: 512
ABSTRACT: 1024
STRICT: 2048可以使用Modifiei类中的 isPublic、 isPrivate 或 isFinal 判断方法或构造器是否是 public、 private 或 final
Class类中的 getFields、 getMethods 和 getConstructors方 法将 分 别 返 回 类 提 供 的 所有public 属性、 方法和构造器数组, 其中包括超类的公有成员。
Class 类的 getDeclareFields、 getDeclareMethods 和getDeclaredConstructors方法将分别返回类中声明的全部属性、 方法和构 造器, 其中包括private和protected成员,但不包括超类的成员。
下面是一个代码案例,显示了如何打印一个类的全部信息的方法。
这个程序提醒用户输入一个类名,然后输出类中所有的属性、方法、构造器。里面有一些数字是我用来测试,类似-m8-
package JavaSE.Chapter5.Section57.cs573;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Scanner;
/**
* This program uses reflection to print all features of a class.
*
* @author Cay Horstmann
* @version 1.1 2004-02-21
* 利用反射分析类的能力,查看属性、构造器、方法的结构
*/
public class ReflectionTest {
public static void main(String[] args) {
String name = "JavaSE.Chapter5.Section57.cs571.Employee";
try {
//获取输入字符串的类对象
Class cl = Class.forName(name);
System.out.println(cl + "-1-");
//获取父类对象
Class supercl = cl.getSuperclass();
System.out.println(supercl + "-2-");
//获取类的访问修饰符和属于public、private、还是final
String modifiers = Modifier.toString(cl.getModifiers());
System.out.println(cl.getModifiers() + "-----cl.getModifiers");
System.out.println(modifiers + "-4-");
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");
System.out.println("------------打印构造器方法-----------");
printConstructors(cl);
System.out.println("------------打印非构造器方法-----------");
printMethods(cl);
System.out.println("------------打印属性信息-----------");
printFields(cl);
System.out.println("}");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.exit(0);
}
/**
* Prints all constructors of a class
*
* @param cl a class
*/
public static void printConstructors(Class cl) {
/*返回反映Constructor对象表示的类声明的所有Constructor对象的数组类 。
这些是public,protected,default(package)访问和私有构造函数。
返回的数组中的元素不会排序,并且不是任何特定的顺序。
如果类有一个默认构造函数,它将包含在返回的数组中。
如果类对象表示接口,原始类型,数组类或空值,则此方法返回长度为0的数组。
*/
Constructor[] constructors = cl.getDeclaredConstructors();
System.out.println(Arrays.toString(constructors) + "-c5-");
for (Constructor c : constructors) {
String name = c.getName();
System.out.print(" ");
String modifiers = Modifier.toString(c.getModifiers());
System.out.println(c.getModifiers() + "-----Counstructor.getModifiers");
//打印构造方法的访问修饰符
if (modifiers.length() > 0) System.out.print(modifiers + " ");
//打印构造方法的名字
System.out.print(name + "(");
//获取类构造器的参数类型数组
Class[] paramTypes = c.getParameterTypes();
System.out.println("-6-" + Arrays.toString(paramTypes) + "-c6-");
for (int j = 0; j < paramTypes.length; j++) {
if (j > 0) System.out.print(", ");
//打印参数类型名字
System.out.print(paramTypes[j].getName());
}
System.out.println(");");
}
}
/**
* Prints all methods of a class
*
* @param cl a class
*/
public static void printMethods(Class cl) {
Method[] methods = cl.getDeclaredMethods();
System.out.println(Arrays.toString(methods) + "-m7-");
for (Method m : methods) {
Class retType = m.getReturnType();
System.out.println(retType + "-m8-");
String name = m.getName();
System.out.print(" ");
// print modifiers, return type and method name
String modifiers = Modifier.toString(m.getModifiers());
System.out.println(m.getModifiers() + "-----Method.getModifiers");
//打印方法的访问修饰符
if (modifiers.length() > 0) System.out.print(modifiers + " ");
//打印方法返回类型和方法名
System.out.print(retType.getName() + " " + name + "(");
// print parameter types
Class[] paramTypes = m.getParameterTypes();
System.out.println("-m9-" + Arrays.toString(paramTypes) + "-m9-");
for (int j = 0; j < paramTypes.length; j++) {
if (j > 0) System.out.print(", ");
//打印方法参数类型
System.out.print(paramTypes[j].getName());
}
System.out.println(");");
}
}
/**
* Prints all fields of a class
*
* @param cl a class
*/
public static void printFields(Class cl) {
Field[] fields = cl.getDeclaredFields();
for (Field f : fields) {
Class type = f.getType();//返回属性所属类型的 Class 对象
System.out.println(type + "-f10-");
String name = f.getName();
System.out.print(" ");
String modifiers = Modifier.toString(f.getModifiers());
System.out.println(f.getModifiers() + "-----Field.getModifiers");
//打印属性的访问修饰符
if (modifiers.length() > 0) System.out.print(modifiers + " ");
//打印属性的类型名和属性名字
System.out.println(type.getName() + " " + name + ";");
}
}
}
7.获取或设置类对象的属性值在编写程序时, 如果知道想要査看的属性和类型,查看指定的属性值是一件很容易的事情。而利用反射机制可以查看在编译时还不清楚的属性值。
Class t = f.getType();//获取属性类型,f为Field对象。
我们可以用f.get(obj)获取 obj 属性的当前值。f为Field对象,obj是一个Object对象。
可以获得就可以设置。调用f.set(obj,value)可以将 obj 对象的 f 属性设置成新值。f为Field对象。
(1)如果我们要查看某个private属性的值,由于受限于java的访问机制,我们需要调用Field、Method 或 Constructor 对象的 setAccessible 方法,来设置private的值的可访问性,x. setAccessible(true);,x为Field、Method 或 Constructor的对象。
(2)也可以使用AccessibleObject.setAccessible(x, true);来设置private值的可访问性,它是 Field、 Method 和 Constructor 类的公共超类,x为Field、Method 或 Constructor 对象的数组引用。
接下来的一个例子将使用上面所说的方法,来查看访问对象的属性值
如下一个可供任意类使用的通用 toString方法。 其中使用 getDeclaredFileds 获得所有的数据属性, 然后使用 setAccessible 将所有的属性设置为可访问的。 对于每个属性,获得了名字和值。递归调用 toString方法,将每个值转换成字符串。(这个例子是java核心技术卷一里面的,这个例子看懂我感觉还是需要花时间的,有的地方我还没看懂……)
package JavaSE.Chapter5.Section57.cs574;
import JavaSE.Chapter5.Section57.cs571.Employee;
import java.lang.reflect.Field;
/**
* 在运行时使用反射分析对象,查看对象当前的各个属性值
*/
public class ObjectAnalyzerTest {
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, InstantiationException {
Employee s = new Employee();
System.out.println(new ObjectAnalyzer().toString(s));
System.out.println("--------------------------------");
String[] str = {"str11", "str22", "str33"};
System.out.println(new ObjectAnalyzer().toString(str));
System.out.println("--------------------------------");
Class em = s.getClass();
Object obj = em.newInstance();
Field f = em.getDeclaredField("name");
f.setAccessible(true);
Object val = f.get(obj);//获取属性的值
System.out.println(val);
f.set(obj, "BitHachi");
Employee em2 = (Employee) obj;
System.out.println(em2.getName());
}
}
package JavaSE.Chapter5.Section57.cs574;
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 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()) {
//getComponentType返回对象数组的的Class类对象。 如果此类不表示数组类,则此方法返回null。
String r = cl.getComponentType() + "[]{";
System.out.println(r + "-1-");
//返回指定数组对象的长度Array.getLength(obj)
for (int i = 0; i < Array.getLength(obj); i++) {
if (i > 0) r += ",";
//返回指定数组对象中的索引组件的值。
Object val = Array.get(obj, i);
System.out.println(val + " -val -2-");
/*确定指定类对象表示一个基本类型。
有九个预定类对象代表八个原始类型和void。
这些是由Java虚拟机创建,并且具有相同的名称为他们所代表的基本类型,
即boolean , byte , char , short , int , long , float和double 。
isPrimitive返回一个boolean值*/
if (cl.getComponentType().isPrimitive()) r += "@" + val + "@";
else r += toString(val);
System.out.println(r + "-3-");
}
return r + "}";
}
String r = cl.getName();
System.out.println(r + "-4-");
// 检查此类和所有超类的字段
do {
r += "[";
//获取类的所有属性得一个数组
Field[] fields = cl.getDeclaredFields();
/*setAccessible()为反射对象设置可访问标志。 true 表明屏蔽 Java语言的访问检查,
使得对象的 private私有属性也可以被査询和设置。 */
AccessibleObject.setAccessible(fields, true);
//获取所有属性的名字和值
for (Field f : fields) {
if (!Modifier.isStatic(f.getModifiers())) {
if (!r.endsWith("[")) r += ",";
r += f.getName() + "=";
System.out.println(r + "-5-");
try {
Class t = f.getType();//获取属性类型
Object val = f.get(obj);//获取属性的值
System.out.println(val + " -val -6-");
if (t.isPrimitive()) {
r += val;
System.out.println(r + "-7-");
} else {
r += toString(val);
System.out.println(r + "-7-");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
r += "]";
cl = cl.getSuperclass();
}
while (cl != null);
return r;
}
}
8.利用反射调用任意方法反射机制可以允许调用任意的方法
在Method类中有一个invoke方法,它可以允许调用包装在Method对象中的方法
下面是一个代码示例:
package JavaSE.Chapter5.Section57.cs576;
import java.lang.reflect.Method;
public class MethodTableTest {
public static void main(String[] args) throws Exception {
// 获取相应方法的Method对象,通过类对象来获取方法对象
Method square = MethodTableTest.class.getMethod("square", double.class);
Method sqrt = Math.class.getMethod("sqrt", double.class);
// 打印x和y值表
printTable(1, 10, 10, square);
printTable(1, 10, 10, sqrt);
}
public static double square(double x) {
return x * x;
}
/**
* Prints a table with x- and y-values for a method
*
* @param from the lower bound for the x-values 上限
* @param to the upper bound for the x-values 下限
* @param n the number of rows in the table 个数
* @param f a method with a double parameter and double return value
*/
public static void printTable(double from, double to, int n, Method f) {
// print out the method as table header
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();
}
}
}
}
9. 使用反射编写泛型数组代码,复制数组我们可以利用反射来扩充一个数组的容量
package JavaSE.Chapter5.Section57.cs575;
import java.lang.reflect.Array;
import java.util.Arrays;
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));
System.out.println("--------------------");
String[] b = {"Tom", "Dick", "Harry"};
b = (String[]) goodCopyOf(b, 10);
System.out.println(Arrays.toString(b));
}
public static Object goodCopyOf(Object a, int newLength) {
//第一步:获取a数组的类对象
Class cl = a.getClass();
System.out.println(cl + "---1");
//第二步:判断a数组的类对象是否是一个数组
if (!cl.isArray()) return null;
//第三步:使用Class类的getComponentType方法确定数组对应的类型
Class componentType = cl.getComponentType();
System.out.println(componentType + "---2");
//获取数组的长度
int length = Array.getLength(a);
System.out.println(length + "---3");
//构造新数组newInstance方法
//返回一个具有给定类型、给定长度的新数组
Object newArray = Array.newInstance(componentType, newLength);
/*arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
将指定源数组中的数组从指定位置复制到目标数组的指定位置。*/
System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
return newArray;
}
}
10. 可以用==比较两个Class对象虚拟机为每个类型管理一个 Class 对象。因此, 可以利用== 运算符实现两个类对象比较的操作。 例如:
11.收获与感受反射里面用到了多态的特性,这一点真的很重要,特别是Object与其它对象之间的互转,不懂得话,很容易懵圈
4-8的应用是反射里面的核心点,当然还有很多的API没办法一次性讲完,其实只要懂了核心的部分,其它的API就比较好懂了
关于反射的内容和应用还有很多,以后在工作中遇到了相关内容再进行补充叭,现在作为初学者,先总结整理这么多叭。
感谢你看到这里,我是程序员麦冬,一个java开发从业者,深耕行业六年了,每天都会分享java相关技术文章或行业资讯
欢迎大家关注我的专栏:程序员麦冬zhuanlan.zhihu.com
里面不定期分享Java架构技术知识点及解析,还会不断更新BATJ面试专题,欢迎大家前来探讨交流,如有好的文章也欢迎投稿。
注意专栏简介的介绍获取最新一线大厂Java面试题总结资料!