Java 基础之详解 Java 反射机制
一、什么是 Java 的反射机制?
反射(Reflection)是Java的高级特性之一,是框架实现的基础,定义:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
一般而言,当用户使用一个类的时候,应该先知道这个类,而后通过这个类产生实例化对象,但是使用反射则可以相反的通过对象找到类。
通俗的讲反射就是可以在程序运行的时候动态装载类,查看类的信息,生成对象,或操作生成的对象。它允许运行中的 Java 程序获取自身的信息,自己能看到自己,就像照镜子一样。
二、Java 反射机制常见方法介绍。
1、Java反射实现的关键点之Class
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。jvm中有N多的实例,每个类的实例都有Class对象。(包括基本数据类型)
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了。
如果知道一个实例,那么可以通过实例的“getClass()”方法获得运行实例的Class(该类型的字节码文件对象),如果你知道一个类型,那么你可以使用“.class”的方法获得运行实例的Class(该类型的字节码文件对象)。
**方法2~17都是类 Class的方法 **
java.lang.Class 继承自java.lang.Object
2、getName()方法
String getName();返回此 Member 表示的底层成员或构造方法的简单名称。
3、forName()方法
public static Class <?> forName(String className)
throws ClassNotFoundException
返回与带有给定字符串名的类或接口相关联的 Class 对象。
参数:className - 所需类的完全限定名。
4、getSuperclass()方法
public Class<? super T> getSuperclass()
返回:此对象所表示的类的超类。
5、getInterfaces()方法
public Class< ? >[] getInterfaces()
返回:该类所实现的接口的一个数组。
6、getConstructors()方法
public Constructor< ? >[] getConstructors() throws SecurityException
返回:表示此类公共构造方法的 Constructor 对象数组
7、newInstance()方法
public T newInstance() throws InstantiationException, IllegalAccessException
创建此 Class 对象所表示的类的一个新实例。如同用一个带有一个空参数列表的 new 表达式实例化该类。如果该类尚未初始化,则初始化这个类。
返回:此对象所表示的类的一个新分配的实例。
8、getDeclaredConstructors()方法
public Constructor<?>[] getDeclaredConstructors() throws SecurityException
返回 Constructor 对象的一个数组,这些对象反映此 Class 对象表示的类声明的所有构造方法。它们是公共、保护、默认(包)访问和私有构造方法。返回数组中的元素没有排序,也没有任何特定的顺序。如果该类存在一个默认构造方法,则它包含在返回的数组中。 如果此 Class 对象表示一个接口、一个基本类型、一个数组类或 void,则此方法返回一个长度为 0 的数组。
返回:表示此类所有已声明的构造方法的 Constructor 对象的数组
9、getFields()方法
public Field[] getFields() throws SecurityException
返回:表示公共字段的 Field 对象的数组
10、getDeclaredFields()方法
public Field[] getDeclaredFields() throws SecurityException
返回:表示此类所有已声明字段的 Field 对象的数组,与getFields()方法的区别是getFields()方法只返回公有属性,
而getDeclaredFields()方法返回所有属性。
11、getMethods()方法
public Method[] getMethods() throws SecurityException
返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。数组类返回从 Object 类继承的所有(公共)member 方法。返回数组中的元素没有排序,也没有任何特定的顺序。如果此 Class 对象表示没有公共成员方法的类或接口,或者表示一个基本类型或 void,则此方法返回长度为 0 的数组。
类初始化方法
返回:表示此类中公共方法的 Method 对象的数组
12、getDeclaredMethods()方法
public Method[] getDeclaredMethods() throws SecurityException
返回:表示此类所有声明方法的 Method 对象的数组
13、getClassLoader()方法
public ClassLoader getClassLoader()
返回该类的类加载器。有些实现可能使用 null 来表示引导类加载器。如果该类由引导类加载器加载,则此方法在这类实现中将返回 null。
如果存在安全管理器,并且调用者的类加载器不是 null,也不同于或是请求其类加载器的类的类加载器的祖先,
则此方法通过 RuntimePermission("getClassLoader") 权限调用此安全管理器checkPermission 方法,以确保可以访问该类的类加载器。
如果此对象表示一个基本类型或 void,则返回 null。
返回:加载此对象所表示的类或接口的类加载器。
14、getInterfaces()方法
public Class<?>[] getInterfaces()
返回:该类所实现的接口的一个数组。
15、getComponentType()方法
public Class<?> getComponentType()
返回表示数组组件类型的 Class。如果此类不表示数组类,则此方法返回 null。
返回:如果此类是数组,则返回表示此类组件类型的 Class
16、getResourceAsStream()方法
public InputStream getResourceAsStream(String name)
查找具有给定名称的资源。查找与给定类相关的资源的规则是通过定义类的 class loader 实现的。此方法委托此对象的类加载器。
如果此对象通过引导类加载器加载,则此方法将委托给 ClassLoader.getSystemResourceAsStream(java.lang.String)。
参数:name - 所需资源的名称
返回:一个 InputStream 对象;如果找不到带有该名称的资源,则返回 null
17、isInstance()方法
public boolean isInstance(Object obj)
参数:obj - 要检查的对象
返回:如果 obj 是此类的实例,则返回 true
一般地,我们用instanceof关键字来判断是否为某个类的实例。同时我们也可以借助反射中Class对象的isInstance()方法来判断是否为某个类的实例,它是一个Native方法.
18、invoke()方法
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
参数:
obj - 从中调用底层方法的对象
args - 用于方法调用的参数
返回:
使用参数 args 在 obj 上指派该对象所表示方法的结果
java.lang.reflect
类 Method
java.lang.Object
继承者 java.lang.reflect.AccessibleObject
继承者 java.lang.reflect.Method
19、newProxyInstance()方法
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。此方法相当于:
Proxy.getProxyClass(loader, interfaces). getConstructor(new Class[] { InvocationHandler.class }). newInstance(new Object[] { handler });
参数:
loader - 定义代理类的类加载器
interfaces - 代理类要实现的接口列表
h - 指派方法调用的调用处理程序
返回:
一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口
java.lang.reflect
类 Proxy
java.lang.Object
继承者 java.lang.reflect.Proxy
三、Java 反射机制常见用法举例。
1、使用 Java 反射通过一个对象获取某个类的完整包名和类名。
通过实例的getClass()方法获得运行实例的字节码文件对象,然后通过getName()方法获得类的完整包名和类名。
public static void test1(){
ReflectTest testReflect = new ReflectTest();
System.out.println(testReflect.getClass().getName());
}
//结果:com.channelsoft.ssm.controller.ReflectTest
2、使用 Java 反射获取 Class 对象(三种方式)
方式1.通过Class类的静态方法获取Class类对象 (推荐)
方式2.因为所有类都继承Object类。因而可通过调用Object类中的getClass方法来获取
方式3.任何数据类型(包括基本数据类型)都有一个“静态”的class属性
public static void test2() throws Exception{
Class<?> class1 = null;
Class<?> class2 = null;
Class<?> class3 = null;
class1 = Class.forName("com.channelsoft.ssm.controller.ReflectTest");
//这一new 产生一个ReflectTest对象,一个Class对象。
class2 = new ReflectTest().getClass();
class3 = ReflectTest.class;
System.out.println("类名称 1: " + class1.getName());
System.out.println("类名称 2: " + class2.getName());
System.out.println("类名称 3: " + class3.getName());
}
运行结果:
3、使用 Java 反射获取一个对象的父类和实现的接口。
public static void test3() throws ClassNotFoundException{
Class<?> clazz = Class.forName("com.channelsoft.ssm.controller.ReflectTest");
// 取得父类
Class<?> parentClass = clazz.getSuperclass();
System.out.println("父类为:" + parentClass.getName());
// 获取所有的接口
Class<?> intes[] = clazz.getInterfaces();
System.out.println("实现的接口有:");
for (int i = 0; i < intes.length; i++) {
System.out.println((i + 1) + ":" + intes[i].getName());
}
}
注:ReflectTest实现了Serializable接口
运行结果:
4、使用 Java 反射获取某个类的全部构造函数并调用私有构造方法。
Student类:
package com.channelsoft.ssm.domain;
public class Student {
public Student(){
System.out.println("调用了公有、无参构造方法。。。");
}
public Student(char name){
System.out.println("姓名:" + name);
}
public Student(String name ,int age){
System.out.println("姓名:"+name+"年龄:"+ age);
}
//受保护的构造方法
protected Student(boolean b){
System.out.println("受保护的构造方法 b = " + b);
}
//私有构造方法
private Student(int age){
System.out.println("私有的构造方法,年龄:"+ age);
}
}
获取Student类的全部构造函数,并使用Class对象的newInstance()方法来创建Class对象对应类Student的实例来调用私有构造方法
package com.channelsoft.ssm.controller;
import java.lang.reflect.Constructor;
public class Constructors {
public static void main(String[] args) throws Exception {
//1.加载Class对象
Class clazz = Class.forName("com.channelsoft.ssm.domain.Student");
//2.获取所有公有构造方法
System.out.println("**********************所有公有构造方法****************************");
Constructor[] conArray = clazz.getConstructors();
for(Constructor c : conArray){
System.out.println(c);
}
System.out.println("************所有的构造方法(包括:私有、受保护、默认、公有)***************");
conArray = clazz.getDeclaredConstructors();
for(Constructor c : conArray){
System.out.println(c);
}
System.out.println("*****************获取公有、无参的构造方法***************************");
//1、因为是无参的构造方法所以类型是一个null,不写也可以:这里需要的是一个参数的***类型***,
//2、返回的是描述这个无参构造函数的类对象。
Constructor con = clazz.getConstructor(null);
System.out.println("con = " + con);
//调用构造方法
Object obj = con.newInstance();
System.out.println("obj = " + obj);
System.out.println("******************获取私有构造方法,并调用**************************");
con = clazz.getDeclaredConstructor(int.class);
System.out.println(con);
//调用构造方法
con.setAccessible(true);//暴力访问(忽略掉访问修饰符)
obj = con.newInstance(26);
}
}
运行结果:
5、使用 Java 反射获取某个类的全部属性。
Student类
package com.channelsoft.ssm.domain;
public class Student {
public String name;
protected int age;
char sex;
private String phoneNum;
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", sex=" + sex
+ ", phoneNum=" + phoneNum + "]";
}
}
获取Student类的全部属性并调用
package com.channelsoft.ssm.controller;
import java.lang.reflect.Field;
import com.channelsoft.ssm.domain.Student;
public class Fields {
public static void main(String[] args) throws Exception {
//1.获取Class对象
Class stuClass = Class.forName("com.channelsoft.ssm.domain.Student");
//2.获取字段
System.out.println("************************获取所有公有的字段**************************************");
Field[] fieldArray = stuClass.getFields();
for(Field f : fieldArray){
System.out.println(f);
}
System.out.println();
System.out.println("************************获取所有的字段(包括私有、受保护、默认的)**********************");
fieldArray = stuClass.getDeclaredFields();
for(Field f : fieldArray){
System.out.println(f);
}
System.out.println();
System.out.println("*************************获取公有字段并调用**************************************");
Field f = stuClass.getField("name");
System.out.println(f);
//获取一个对象
Object obj = stuClass.getConstructor().newInstance();
f.set(obj, "laughitover");
//验证
Student stu = (Student)obj;
System.out.println("验证姓名:" + stu.name);
System.out.println();
System.out.println("**************************获取私有字段并调用**************************************");
f = stuClass.getDeclaredField("phoneNum");
System.out.println(f);
f.setAccessible(true);//暴力反射,解除私有限定
f.set(obj, "13812345678");
System.out.println("验证电话:" + stu);
}
}
运行结果:
6、使用 Java 反射获取某个类的全部方法。
package com.channelsoft.ssm.domain;
public class Student {
public void show1(String s){
System.out.println("调用了:公有的,String参数的show1(): s = " + s);
}
protected void show2(){
System.out.println("调用了:受保护的,无参的show2()");
}
void show3(){
System.out.println("调用了:默认的,无参的show3()");
}
private String show4(int age){
System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): age = " + age);
return "abcd";
}
}
获取某个类的全部方法并且调用共有方法show1()和私有方法show4()
public static void test6() throws Exception {
//1.获取Class对象
Class stuClass = Class.forName("fanshe.method.Student");
//2.获取所有公有方法
System.out.println("***************获取所有的”公有“方法*******************");
stuClass.getMethods();
Method[] methodArray = stuClass.getMethods();
for(Method m : methodArray){
System.out.println(m);
}
System.out.println("***************获取所有的方法,包括私有的*******************");
methodArray = stuClass.getDeclaredMethods();
for(Method m : methodArray){
System.out.println(m);
}
System.out.println("***************获取公有的show1()方法*******************");
Method m = stuClass.getMethod("show1", String.class);
System.out.println(m);
//实例化一个Student对象
Object obj = stuClass.getConstructor().newInstance();
m.invoke(obj, "laughitover");
System.out.println("***************获取私有的show4()方法******************");
m = stuClass.getDeclaredMethod("show4", int.class);
System.out.println(m);
m.setAccessible(true);//解除私有限定
Object result = m.invoke(obj, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参
System.out.println("返回值:" + result);
}
运行结果:
7、使用 Java 反射调用某个类的方法。
Java 反射获取 Class 对象并使用invoke()方法调用方法reflect1()和reflect2()
public static void test7() throws Exception{
Class<?> clazz = Class.forName("com.channelsoft.ssm.controller.ReflectTest");
Method method = clazz.getMethod("reflect1");
method.invoke(clazz.newInstance());
method = clazz.getMethod("reflect2", int.class, String.class);
method.invoke(clazz.newInstance(), 20, "张三");
}
public void reflect1() {
System.out.println("Java 反射机制 - 调用某个类的方法1.");
}
public void reflect2(int age, String name) {
System.out.println("Java 反射机制 - 调用某个类的方法2.");
System.out.println("age -> " + age + ". name -> " + name);
}
运行结果:
8、反射机制的动态代理实例。
如果想获取实现Subject()接口的其它实例,只需新建类实现接口Subject(),使用MyInvocationHandler.bind()方法获取即可实现调用。
public static void test8(){
MyInvocationHandler demo = new MyInvocationHandler();
Subject sub = (Subject) demo.bind(new RealSubject());
String info = sub.say("语文", 90);
System.out.println(info);
}
如果想要完成动态代理,首先需要定义一个InvocationHandler接口的子类,完成代理的具体操作。
package com.channelsoft.ssm.controller;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyInvocationHandler implements InvocationHandler {
private Object obj = null;
public Object bind(Object obj) {
this.obj = obj;
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object temp = method.invoke(this.obj, args);
return temp;
}
}
package com.channelsoft.ssm.service;
public interface Subject {
public String say(String name, int score);
}
package com.channelsoft.ssm.controller;
import com.channelsoft.ssm.service.Subject;
public class RealSubject implements Subject {
public String say(String name, int score) {
return name + " " + score;
}
}
运行结果:
9、通过反射越过泛型检查。
泛型用在编译期,编译过后泛型擦除(消失掉)。所以是可以通过反射越过泛型检查的
public static void test10() throws Exception{
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(100);
list.add(200);
Method method = list.getClass().getMethod("add", Object.class);
method.invoke(list, "Java反射机制实例。");
//遍历集合
for(Object obj : list){
System.out.println(obj);
}
}
运行结果:
10、通过反射机制获得数组信息并修改数组的大小和值。
通过反射机制分别修改int和String类型的数组的大小并修改int数组的第一个值
public static void test12() throws Exception{
int[] temp = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int[] newTemp = (int[]) arrayInc(temp, 15);
print(newTemp);
Array.set(newTemp, 0, 100);
System.out.println("修改之后数组第一个元素为: " + Array.get(newTemp, 0));
print(newTemp);
String[] atr = { "a", "b", "c" };
String[] str1 = (String[]) arrayInc(atr, 8);
print(str1);
}
// 修改数组大小
public static Object arrayInc(Object obj, int len) {
Class<?> arr = obj.getClass().getComponentType();
Object newArr = Array.newInstance(arr, len);
int co = Array.getLength(obj);
System.arraycopy(obj, 0, newArr, 0, co);
return newArr;
}
// 打印
public static void print(Object obj) {
Class<?> c = obj.getClass();
if (!c.isArray()) {
return;
}
Class<?> arr = obj.getClass().getComponentType();
System.out.println("数组类型: " + arr.getName());
System.out.println("数组长度为: " + Array.getLength(obj));
for (int i = 0; i < Array.getLength(obj); i++) {
System.out.print(Array.get(obj, i) + " ");
}
System.out.println();
}
运行结果:
11、将反射机制应用于工厂模式。
对于普通的工厂模式当我们在添加一个子类的时候,就需要对应的修改工厂类。 当我们添加很多的子类的时候,会很麻烦
package com.channelsoft.ssm.service;
public interface Fruit {
public abstract void eat();
}
package com.channelsoft.ssm.controller;
import com.channelsoft.ssm.service.Fruit;
public class Orange implements Fruit {
public void eat() {
System.out.println("Orange");
}
}
package com.channelsoft.ssm.controller;
import com.channelsoft.ssm.service.Fruit;
public class Apple implements Fruit {
public void eat() {
System.out.println("Apple");
}
}
package com.channelsoft.ssm.controller;
import com.channelsoft.ssm.service.Fruit;
public class Factory {
public static Fruit getInstance(String ClassName) {
Fruit f = null;
try {
f = (Fruit) Class.forName(ClassName).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return f;
}
}
public static void test13(String fruit) throws Exception{
Fruit f = Factory.getInstance(fruit);
if (f != null) {
f.eat();
}
}
还可以通过加载配置文件的方式
public static String getValue(String key) throws IOException{
Properties pro = new Properties();//获取配置文件的对象
InputStream in=new Demo().getClass().getResourceAsStream("/pro.txt");
pro.load(in);//将流加载到配置文件对象中
in.close();
return pro.getProperty(key);//返回根据key获取的value值
}
pro.txt内容如下:
className = com.channelsoft.ssm.domain.Apple
运行结果:
12、通过Java反射机制获取ClassLoader类加载器
package com.channelsoft.ssm.controller;
public class ReflectTest{
public static void main(String[] args) throws Exception {
testClassLoader();
}
public static void testClassLoader() throws ClassNotFoundException {
//1、获取一个系统的类加载器
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
System.out.println("系统的类加载器-->" + classLoader);
//2、获取系统类加载器的父类加载器(扩展类加载器(extensions classLoader))
classLoader = classLoader.getParent();
System.out.println("扩展类加载器-->" + classLoader);
//3、获取扩展类加载器的父类加载器
//输出为Null,无法被Java程序直接引用
classLoader = classLoader.getParent();
System.out.println("启动类加载器-->" + classLoader);
//4、测试当前类由哪个类加载器进行加载 ,结果就是系统的类加载器
classLoader = Class.forName("com.channelsoft.ssm.controller.ReflectTest").getClassLoader();
System.out.println("当前类由哪个类加载器进行加载-->"+classLoader);
//5、测试JDK提供的Object类由哪个类加载器负责加载的
classLoader = Class.forName("java.lang.Object").getClassLoader();
System.out.println("JDK提供的Object类由哪个类加载器加载-->" + classLoader);
}
}
四、Java 反射机制相关面试题。
1、反射创建类实例的三种方式是什么?
答案参见:本篇文章:Java 反射机制常见用法举例2
2、反射中,Class.forName 和 ClassLoader 区别。
Class.forName(className)方法,其实调用的方法是Class.forName(className,true,classloader);
第2个参数表示在loadClass后必须初始化。根据jvm加载类的知识,我们知道在执行过此方法后,目标对象的 static块代码已经被执行,static参数也已经被初始化。
而ClassLoader.loadClass(className)方法,实际调用的方法是ClassLoader.loadClass(className,false);
第2个 参数表示目标对象被装载后不进行链接,这就意味着不会去执行该类静态块中间的内容。
例如,在JDBC使用中,常看到这样的用法,Class.forName("com.mysql.jdbc.Driver"),
如果换成了 getClass().getClassLoader().loadClass("com.mysql.jdbc.Driver"),
就不行。 根据com.mysql.jdbc.Driver的源代码,Driver在static块中会注册自己到java.sql.DriverManager。而static块就是在Class的初始化中被执行。所以这个地方就只能用Class.forName(className)。
com.mysql.jdbc.Driver的源代码:
// Register ourselves with the DriverManager
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
java类的加载过程:
在Java中,类装载器把一个类装入Java虚拟机中,要经过三个步骤来完成:装载、链接和初始化,其中链接又可以分成校验、准备和解析三
步.
装载:查找和导入类或接口的二进制数据;
链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的;
校验:检查导入类或接口的二进制数据的正确性;
准备:给类的静态变量分配并初始化存储空间;
解析:将符号引用转成直接引用;
初始化:激活类的静态变量的初始化Java代码和静态Java代码块。
3、描述动态代理的几种实现方式,分别说出相应的优缺点。
jdk动态代理和cglib动态代理。jdk动态代理是由Java内部的反射机制来实现的,cglib动态代理底层则是借助asm框架来实现的,实现了无反射机制进行代理,利用空间来换取了时间,代理效率高于jdk 。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。
五、总结。
很多人认为反射在实际的Java开发应用中并不广泛,其实不然。我们常用的jdbc中有一行代码:
Class.forName('com.MySQL.jdbc.Driver.class').newInstance();
这里用到的就是Java反射机制,除此之外很多框架也都用到反射机制。
实际上反射机制就是非常规模式,如果说方法的调用是 Java 正确的打开方式,那反射机制就是Java的后门。为什么会有这个后门呢?这涉及到了静态和动态的概念:
静态编译:在编译时确定类型,绑定对象
动态编译:运行时确定类型,绑定对象
两者的区别在于,动态编译可以最大程度地支持多态,多态最大的意义在于降低类的耦合性,而Java语言确是静态编译(如:int a=3;要指定类型),Java反射很大程度上弥补了这一不足:解耦以及提高代码的灵活性,所以Java也被称为是准动态语言。
尽管反射有很多优点,但是反射也有缺点:
1、由于反射会额外消耗一定的系统资源,因此,反射操作的效率要比那些非反射操作低得多。
2、由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用--代码有功能上的错误。
所以能用其它方式实现的就尽量不去用反射。