Java反射
什么是反射
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象的方法的功能称为java语言的反射机制。反射 就是将java类中的各种成分映射成一个个的Java对象。
java反射框架提供的功能:
- 运行时判断任意一个对象所属的类
- 运行时构造任意一个类的对象
- 运行时判断任意一个类所具有的成员变量和方法
- 运行时调用任意一个对象的方法
- 生成动态代理
反射的用途
Java 强类型语言,但是我们在运行时有了解、修改信息的需求,包括类信息、成员信息等。反射机制主要用于在运行时动态加载需要加载的对象。例如,在通用框架开发过程中,根据配置文件加载不同的类和对象,调用不同的方法。我们平时使用反射主要用于获取类型的相关信息、动态调用方法、动态构造对象、从程序集中获得类型。
举个栗子:
不使用反射的情况:
interface fruit{ public abstract void eat(); } class Apple implements fruit{ public void eat(){ System.out.println("Apple"); } } class Orange implements fruit{ public void eat(){ System.out.println("Orange"); } } // 构造工厂类 // 也就是说以后如果我们在添加其他的实例的时候只需要修改工厂类就行了 class Factory{ public static fruit getInstance(String fruitName){ fruit f=null; if("Apple".equals(fruitName)){ f=new Apple(); } if("Orange".equals(fruitName)){ f=new Orange(); } return f; } } class hello{ public static void main(String[] a){ fruit f=Factory.getInstance("Orange"); f.eat(); } }
可以发现,每当我们要添加一种新的水果的时候,我们将不得不改变Factory中的源码,而往往改变原有正确代码是一种十分危险的行为。而且随着水果种类的增加,你会发现你的factory类会越来越臃肿。
使用反射的情况:
interface fruit{ public abstract void eat(); } class Apple implements fruit{ public void eat(){ System.out.println("Apple"); } } class Orange implements fruit{ public void eat(){ System.out.println("Orange"); } } 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; } } class hello{ public static void main(String[] a){ fruit f=Factory.getInstance("Reflect.Apple"); if(f!=null){ f.eat(); } } }
在出现新品种水果的时候,你完全不用去修改原有代码
我们为什么要用反射?通俗一些讲,因为我们有时候并没有想好要具体使用哪个类,比如我们有一个接口,这个接口有许多具体的实现类,并且在后来我可能会添加更多的实现类。如果我在使用这个接口的实现类的对象的时候就写清楚我具体要使用的类,那么代码就会很不灵活,一旦我以后想要一个新的实现类,那么我就需要修改所有用到这个类的地方,这很不灵活。而反射呢,就是根据一个“类全名字符串” ,获得代表这个类的class对象,然后根据这个class对象构造出这个类的真正实例对象 ,如果我们把这个类全名字符串写到配置文件中,那么以后再想升级的时候只需要更新下配置文件中的类名字符串就好。
预备知识
在了解java反射之前首先补充几个知识:
Class类:在面向对象编程中,万物皆对象,所以用户编写的任何一个类都是对象,它是java.lang.class类的对象实例。即在java中存在一个类,叫class类,这个类的实例对象就是我们编写的类。这样会好理解一些吗:我们编写了很多类,这些类可以创建很多对象实例,但是其实我们创建的类它本身也是特殊的对象实例,它是Class类的对象实例。
类的加载方式
为了运行java程序,需要把程序中定义的类加载到jvm中,根据类加载的时机,可以将类的加载方式分为静态加载和动态加载两种:
1)静态加载类:在编译时刻就把类加载到jvm中,通过这种方式加载的类,在编译时就加载所有可能用到的类,在程序代码中使用“new 类名“创建对象,这是最常见的类加载方式。
2)动态加载类:这种加载方式下,在编译时类还没有加载的jvm中,而是在程序运行时刻再去加载类,运行到某句代码,需要使用某个类,这时候再通过使用反射机制动态加载需要的类。由于jvm还未加载类,不知道类名,也就不能通过new来创建对象,但是我们后续的代码中又需要使用这个类的对象,该怎么办呢?Java提供了一种反射机制,通过类类型来创建对象。
类类型(class type):用户类对应的class类的对象实例。如下文中的c、c2、c3是Student类的类类型。我们编写的.java文件经过编译后得到.class文件,即对应一个Class实例对象。类类型中存储了该类的全部信息,因此可以通过类类型可以获得该类的对象实例以及该类的变量、方法信息。Class类的实例对象是由jvm在类加载的时候创建的,且仅在第一次使用该类时加载。在运行期间,一个用户类只产生一个Class类的对象实例,那么我们如何获取这个对象呢?
反射的基本使用方法
一般情况下,我们在使用类的时候都是通过类名,创建该类对象,然后通过对象调用方法。这种方式可以理解为“正”。而反射,则是在程序编写时我们并不知道要创建并初始化的类对象是什么,自然就无法使用new来创建对象,此时可以使用反射来创建对象、调用方法。
其实我们使用反射的目的就是要使用某个类、创建这个类的实例对象,调用类里的方法、获取或设置类中属性的值,这点跟我们传统使用类的目的是一致的。只是 相对于传统使用类的的方式有所区别,所以在学习反射的使用时,无非是学习如何通过反射来创建类对象、调用类的方法和属性等。下面一一作出介绍。
反射就是对任意一个类可以知道他的全部属性和方法,这些信息就存储在该类对应的Class对象实例中,所以如果想要使用某个类,那么必须先获得这个类的类类型,即获取该类对应的Class的对象实例:
- 获取Class的对象实例的三种方式
1)使用用户类的静态class属性
Class c = Student.class;
任何数据类型(包括基本数据类型)都有一个静态的class属性,通过用户类的类名获得类类型。
2)使用用户类对象实例的getClass()方法
Class c2 = student1.getClass();
getClass()是Object类的成员方法,Java类全部继承自Object类,所以任意一个类对象都有getClass()方法。通过用户类的实例获得类类型。
3)通过Class类的静态方法<常用>
//Class.forName(String className) className为用户类包含包名的完整类名 Class c3 = Class.forName("com.example.reflact.Student")
通过Class类名调用静态方法forName(),传入用户类的完整类名(包含包名)获得用户类。forName()方法在运行时可能会抛出ClassNotFoundException,编程时候需要注意处理异常。
在获得类类型之后,我们就可以进一步获取这个类中的构造方法、成员方法、属性等信息,从而使用该类。
1)获取用户类的构造方法
前面提到过,反射就是把java类中的各种成分(方法、属性等)映射成一个个的Java对象,其中,构造方法是java.lang.Constructor类的对象。
- 批量获取构造方法
1 public Constructor[] getConstructors() //获取所有"公有的"构造方法 2 public Constructor[] getDeclaredConstructors() //获取所有的构造方法(包括私有、受保护、默认、公有)
- 获取单个构造方法,会抛出NoSuchMethodException异常
public Constructor getConstructor(Class... parameterTypes)//获取单个的"公有的"构造方法,
//传入构造方法若干参数的类型,注意传入的必须是类型(如int.class),返回的是描述这个构造方法的类对象 public Constructor getDeclaredConstructor(Class... parameterTypes)//获取"某个构造方法"可以是私有的,或受保护、默认、公有;
- 调用构造方法
Constructor-->newInstance(Object... initargs) //传入构造方法的若干具体参数
示例:
package com.jing.reflect; import java.lang.reflect.Constructor; import java.lang.reflect.Method; public class Test { public static void main(String[] args){ try { Class c = Class.forName("com.jing.reflect.Student"); Constructor[] cs = c.getDeclaredConstructors();//获取全部构造方法 System.out.println("====批量获取全部构造方法==="); for(Constructor constructor : cs){ System.out.println(constructor); } System.out.println("===获取单个构造方法==="); Constructor constructor = c.getConstructor(String.class);//获取单个构造函数 System.out.println(constructor); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } } }
2)通过反射获取用户创建的类的对象实例
通过反射创建类对象的方式有两种:
- 1>通过Class对象的newInstance()方法,会抛出InstantiationException。只能使用无参构造方法
Student s1 = (Student)c.newInstance();
- 2>通过Constructor对象的newInstance()方法,会抛出InvocationTargetException异常。可以选择构造方法
System.out.println("===获取构造方法===");
Constructor constructor = c.getConstructor(String.class);//获取单个构造函数
System.out.println("===创建对象===");
Student s2 = (Student)constructor.newInstance("Jing");
3)获得用户类中成员方法信息
成员方法是java.lang.reflect.Method类的对象
- 批量获取:
public Method[] getMethods():获取所有"公有方法";(包含了父类的方法也包含Object类) public Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的)
- 单个获取:
public Method getMethod(String name,Class<?>... parameterTypes): 参数: name : 方法名; Class ... : 形参的Class类型对象 public Method getDeclaredMethod(String name,Class<?>... parameterTypes)
- 调用成员方法:
Method --> public Object invoke(Object obj,Object... args):会抛出IllegalAccessException、InvocationTargetException
参数说明: obj : 要调用方法的对象; args:调用方法时所传递的实参;
示例:
System.out.println("===获取全部成员方法==="); Method[] methods = c.getDeclaredMethods(); for(Method method : methods){ Class[] paramTypes = method.getParameterTypes();//获取方法参数列表中参数类型 System.out.print("方法名:" + method.getName() + "--方法返回类型:" + method.getReturnType() + "--方法参数列表:"); for(Class par : paramTypes){ System.out.print(par + " "); } System.out.println(); } System.out.println("===获取单个成员方法==="); Method method = c.getDeclaredMethod("showAge", int.class); System.out.println(method); Object obj = c.getConstructor().newInstance();//实例化一个Student对象 System.out.println("===调用方法==="); method.setAccessible(true);//解除私有限定 Object result = method.invoke(obj, 20);//此处解除了私有限定,所以参数可以使用obj // 或,不解除私有限定,使用s1调用方法(s1被强制类型转换为了Student类型,可以调用private变量) System.out.println("返回结果:" + result);
4)获取用户类中成员变量信息
成员变量是java.lang.reflect.Field类的对象
- 批量获取
public Field[] getFields():获取所有的"公有字段" public Field[] getDeclaredFields():获取所有字段,包括:私有、受保护、默认、公有;
- 单个获取
public Field getField(String fieldName):获取某个"公有的"字段; public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的) ,会抛出NoSuchFieldException 异常
- 设置字段值
Field --> public void set(Object obj,Object value): 参数说明: obj:要设置的字段所在的对象;
value:要为字段设置的值
示例:
System.out.println("===批量获取成员变量==="); Field[] fields = c.getDeclaredFields(); for(Field f : fields){ System.out.println(f); } System.out.println("===单个获取成员变量==="); Field f = c.getDeclaredField("age"); System.out.println("设置前年龄:" + f.get(obj)); f.set(obj, 11);//设置成员变量值 System.out.println("设置后年龄:" + f.get(obj));
一般情况下我们使用反射获取一个对象的步骤:
- 获取类的 Class 对象实例
- 根据 Class 对象实例获取 Constructor 对象
- 使用 Constructor 对象的 newInstance 方法获取反射类对象
而如果要调用某一个方法,则需要经过下面的步骤:
- 获取方法的 Method 对象
- 利用 invoke 方法调用方法
完整示例代码:
用户定义Student类
package com.jing.reflect; public class Student { String name; int age; public Student(){ System.out.println("---调用无参构造方法---"); } public Student(String name){ this.name = name; System.out.println("---调用有参构造方法---"); } public String showName(String name){ System.out.println("show...." + name); return name; } private int showAge(int age){ System.out.println("show...." + age); return age; } }
测试类:
package com.jing.reflect; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class Test { public static void main(String[] args){ try { Class c = Class.forName("com.jing.reflect.Student"); Constructor[] cs = c.getDeclaredConstructors();//获取全部构造方法 System.out.println("====批量获取全部构造方法==="); for(Constructor constructor : cs){ System.out.println(constructor); } System.out.println("===获取单个构造方法==="); Constructor constructor = c.getConstructor(String.class);//获取单个构造函数 System.out.println(constructor); System.out.println("===创建对象==="); Student s1 = (Student)c.newInstance(); Student s2 = (Student)constructor.newInstance("Jing"); System.out.println("===获取全部成员方法==="); Method[] methods = c.getDeclaredMethods(); for(Method method : methods){ Class[] paramTypes = method.getParameterTypes();//获取方法参数列表中参数类型 System.out.print("方法名:" + method.getName() + "--方法返回类型:" + method.getReturnType() + "--方法参数列表:"); for(Class par : paramTypes){ System.out.print(par + " "); } System.out.println(); } System.out.println("===获取单个成员方法==="); Method method = c.getDeclaredMethod("showAge", int.class); System.out.println(method); Object obj = c.getConstructor().newInstance();//实例化一个Student对象 System.out.println("===调用方法==="); method.setAccessible(true);//解除私有限定 Object result = method.invoke(obj, 20);//此处解除了私有限定,所以参数可以使用obj // 或,不解除私有限定,使用s1调用方法(s1被强制类型转换为了Student类型) System.out.println("返回结果:" + result); System.out.println("===批量获取成员变量==="); Field[] fields = c.getDeclaredFields(); for(Field f : fields){ System.out.println("成员变量类型:" + f.getType() + " 成员变量名称" + f.getName()); } System.out.println("===单个获取成员变量==="); Field f = c.getDeclaredField("age"); System.out.println("设置前年龄:" + f.get(obj)); f.set(obj, 11);//设置成员变量值 System.out.println("设置后年龄:" + f.get(obj)); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } } }
运行结果:
反射的应用
1)通过反射运行配置文件内容
利用反射和配置文件,可以使:应用程序更新时,对源码无需进行任何修改,只需要添加新类,并修改配置文件使用新类即可。
程序实现时,首先从配置文件中读取类包含报名的全名;然后,根据类名构造这个类的实例。
- 用户定义类
package com.jing.reflect; public class Student2 { public void show(){ System.out.println("Student2类"); } }
- 配置文件
className = com.jing.reflect.Student2
methodName = show
- 测试类
package com.jing.reflect; import java.io.FileReader; import java.io.IOException; import java.lang.reflect.Method; import java.util.Properties; public class Demo { public static void main(String[] args) throws Exception{ //获取Class类对象 Class stuClass = Class.forName(getValue("className")); //获取show方法 Method method = stuClass.getDeclaredMethod(getValue("methodName")); //获取Student构造方法,创建实例,并通过该实例调用show方法 method.invoke(stuClass.getConstructor().newInstance()); } //此方法接收一个key,在配置文件中获取相应的value public static String getValue(String key) throws IOException { Properties pro = new Properties();//获取配置文件的对象 FileReader proIn = new FileReader("I:\\CodePractice\\reflect\\src\\com\\jing\\reflect\\pro.txt");//获取输入流 pro.load(proIn);//将输入流加载到配置文件对象中 proIn.close(); return pro.getProperty(key);//返回根据key获取的value值 } }
运行结果
系统升级时,不再需要Student2类,此时重新写一个Student类,在配置文件中指明,原代码不动,完成系统的升级。
- 新创建的Student类
package com.jing.reflect; public class Student { public void show(){ System.out.println("Studen类"); } }
- 修改pro.txt文件
className = com.jing.reflect.Student methodName = show
执行程序后结果:
2)利用反射越过类型检查
Java集合中的泛型是为了防止错误输入的,泛型检查仅仅在编译阶段有效,编译后的集合是去泛型化的。反射的操作都是运行时的操作,使用反射可以越过泛型检查,实现往集合中添加不同类型的对象。
package com.jing.reflect; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; public class Demo2 { public static void main(String[] args){ //声明String类型的list ArrayList<String> list = new ArrayList<>(); list.add("Hello,"); //获取list的类类型 Class listClass = list.getClass(); try { //获取list的add方法 Method addMethod = listClass.getMethod("add", Object.class); //反射调用list对象的add方法,插入整型2019,越过泛型检查 addMethod.invoke(list, 2019); } catch (Exception e) { e.printStackTrace(); } //输出list内容,注意list元素是Object类型 for(Object obj : list){ System.out.print(obj); } } }
运行结果:
参考:
Java基础之—反射(非常重要)<强烈推荐>
大白话说Java反射:入门、使用、原理<强烈推荐>