一、反射概念
在正式讲解反射之前,为了很好的去理解它我们先从一个案例说起。请看下面的代码:
public class User { private String name; private int age; public User(){} public User(String name, int age) { this.name = name; this.age = age; } public void test(){ System.out.println("年龄是:"+name); } }
这是一个最简单不过的类,当我们使用的时候直接new出来一个User对象即可。因为这个类是我们自己定义的,所以在使用的时候我们知道User有两个字段name和age,还有无参和有参构造方法,另外的test方法我们也可以直接调用(因为其是public)。
现在出现一个问题,如果这个user类不是我们自己定义的,我们从外部看不到里面有什么东西,而且我们又想去知道内部长什么样,比如说有几个字段、方法、构造方法、共有还是私有的等等,这时候该怎么办呢?这时候java语言在设计的时候为我们提供了一个机制,就是反射机制。他能够很方便的去解决我们的问题。
通过上面的例子我相信你也能对java反射机制的功能了解一二了,现在我们再来整理一下:
java反射机制允许我们程序员在 程序运行的时候获取一个类的各种内部信息,比如说modifiers(诸如public, static 等)、superclass(例如Object)、实现之interfaces(例如Cloneable),也包括fields和methods的所有信息。 更重要的是我们还能够修改这些信息。
下面我们就来好好看一下,java中的反射机制是如何在运行时获取这些类的内部信息的。
二、深入分析java反射机制
1、获取Class类
在java中万事万物皆对象,User user=new User()一行代码我们知道了user是User类的实例对象,通过Student stu=new Student()我们知道了stu是Student的实例对象,但是我们想过没,User和Student又是谁的对象呢?没错就是Class类的实例对象。那这个Class类是什么东西,内部长什么样子呢?这时候我们很自然的联想到使用反射机制。使用反射机制就可以获取到这个class。
这里有三种方式可以获取这个Class,我们来看一下代码:
//这里假设我们之前不知道User类的内部状态 public class Test { public static void main(String[] args) { User user = new User(); // 第一种表示方式:通过类名 Class c1 = User.class; // 第二中表达方式:通过对象 Class c2 = user.getClass(); //第三种表达方式 try { Class c3 = Class.forName("com.fdd.reflecttest.User"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
上面的c1、c2、c3都是Class类的实例,表示的都是User类。
当然,不仅仅是User这些类,对于基本数据类型甚至是包括void我们也可以使用这个方法。
Class c1 = int.class; Class c2 = String.class; Class c3 = double.class; Class c4 = Double.class; Class c5 = void.class;
现在就可以直接创建User类的实例了。
User user = (User)c1.newInstance(); //需要有无参数的构造方法
现在我们对反射机制中获取Class类的方法进行一个总计
2、获取类的方法
现在通过反射看一下User类内部的样子,打印一下(把这个操作封装在了一个方法中):
//打印类的信息,包括类的成员函数、成员变量(只获取成员函数) public static void printClassMethodMessage(Object obj){ //1、首先要获取类的类类型 Class c = obj.getClass(); //2、获取类的名称 System.out.println("类的名称是:"+c.getName()); //3、获取方法信息 Method[] ms = c.getMethods(); for(int i = 0; i < ms.length;i++){ //第一步:得到方法的返回值类型的类类型 Class returnType = ms[i].getReturnType(); System.out.print(returnType.getName()+" "); //第二步:得到方法的名称 System.out.print(ms[i].getName()+"("); //第三步:获取方法参数类型--->得到的是参数列表的类型的类类型 Class[] paramTypes = ms[i].getParameterTypes(); for (Class class1 : paramTypes) { System.out.print(class1.getName()+","); } System.out.println(")"); } }
下面我们把我们的User类传进去,打印一下。
public class Test { public static void main(String[] args) { User user=new User("18",20); ClassUtil.printClassMethodMessage(user); } } //output //类的名称是:com.fdd.reflecttest.User //void test() //void wait() //void wait(long,int,) //void wait(long,) //boolean equals(java.lang.Object,) //java.lang.String toString() //int hashCode() //java.lang.Class getClass() //void notify() //void notifyAll()
3、获取类的属性
(1)获取所有属性
public static void printFieldMessage(Object obj) { Class c = obj.getClass(); //Field类封装了关于成员变量的操作 // getFields()方法获取的是所有的public的成员变量的信息 // getDeclaredFields获取的是该类自己声明的成员变量的信息 //Field[] fs = c.getFields(); Field[] fs = c.getDeclaredFields(); for (Field field : fs) { //1、获取字段类型 Class fieldType = field.getType(); String typeName = fieldType.getName(); //2、获取字段名称 String fieldName = field.getName(); //打印字段类型和字段名称 System.out.println(typeName+" "+fieldName); } }
上面有两种获取属性的方法。重点是for循环。我们来测试一下
public class Test { public static void main(String[] args) { User user=new User("18",20); ClassUtil.printFieldMessage(user); } } //output //java.lang.String name //int age
直接就会输出我们的字段类型和名称。
(2)获取指定属性
在这里我们的User类中name、age字段增加getter和setter方法
public static Object printFieldMsgBySelf(Object obj) { Class c = obj.getClass(); try { //1、获取指定字段 Field age_field = c.getDeclaredField("age"); //2、实例化一个User类 Object object=c.newInstance(); //3、使用反射机制打破封装 age_field.setAccessible(true); //4、给我们实例化的对象重新设置年龄 age_field.set(object, 100); //5、返回这个object return object; } catch (Exception e) { e.printStackTrace(); } return c; }
然后我们测试一下
public class Test { public static void main(String[] args) { User userBefore=new User("18",20); System.out.println(userBefore.getAge()); User userAfter=(User) ClassUtil.printFieldMsgBySelf(userBefore); System.out.println(userAfter.getAge()); } } //output //20 //100
在这里,我们在printFieldMsgBySelf方法中通过反射重新设置了age年龄的值,输出之后已成功更改。
4、获取类的构造方法
public static void printConMessage(Object obj){ Class c = obj.getClass(); //Constructor中封装了构造函数的信息 // (1)getConstructors获取所有的public的构造函数 // (2)getDeclaredConstructors得到所有的构造函数 //第一步:获取构造函数 Constructor[] cs = c.getDeclaredConstructors(); for (Constructor constructor : cs) { //获取构造函数名字 System.out.print(constructor.getName()+"("); //获取构造函数的参数列表 Class[] paramTypes = constructor.getParameterTypes(); for (Class class1 : paramTypes) { System.out.print(class1.getName()+","); } System.out.println(")"); } }
然后我们同样的测试一下
public class Test { public static void main(String[] args) { User user=new User("18",20); ClassUtil.printConMessage(user); } } //output //com.fdd.reflecttest.User() //com.fdd.reflecttest.User(java.lang.String,int,)
跟我们之前的构造方法一样。
5、获取User类的父类和接口
我们在这里定义一个Human类(里面什么也没有),然后定义一个UserInterface接口,让User继承它就好了。
//取得父类和接口 public static void getUserSuperClassAndInterface(Object obj) { Class c = obj.getClass(); //取得父类:父类只能有一个 Object superClass = c.getSuperclass(); System.out.println(superClass); //取得接口:接口可以有很多 Object[] superInterface = c.getInterfaces(); for (Object myInter:superInterface) { System.out.println(myInter); } } //调用这个方法后的输出结果: //class com.fdd.reflecttest.Human //interface com.fdd.reflecttest.UserInterface
然后我们在Test中去测试一下就可以了,测试方法很简单,我们只需要调用这个方法就可以。
小结:在上面的案例中,我们使用反射机制能够获取类的方法、字段、构造方法、父类和接口,当然也可以获取一些其他的信息。但是这里有一点重要的知识,那就是我们不仅可以获取上面的这些信息,还可以修改它,这对一个类来说是极其的不安全的。这一点我们需要注意。
下面我们就来看看反射到底用什么用途。
三、使用反射机制
1、通过反射了解泛型的本质
java中集合的泛型是防止错误输入的;只在编译阶段有效,只要绕过编译就无效啦。比如下面的代码:
ArrayList list1=new ArrayList(); ArrayList<String> list2=new ArrayList<String>();
对于list1来说我们可以添加任何对象,但是对于list2来说,就必须输入String类型对象,这就是泛型作用。(当然这里只是简单的提一下,泛型还有很多知识)。在运行时候,是不区分输入什么的。
下面我们就来验证一下。
public class Test { public static void main(String[] args) throws Exception { ArrayList list1=new ArrayList(); ArrayList<String> list2=new ArrayList<String>(); Class c1=list1.getClass(); Class c2=list2.getClass(); //在运行期间泛型无效,所以c1和c2应该是一样的。 System.out.print(c1==c2);//true //由于泛型失效,所以此时list当然可以添加任何对象 Method m=c2.getMethod("add",Object.class); m.invoke(list2,20);//向list2集合中添加一个int 型的值;绕过编译 } } //output //true
上面的输出已经说明一切。
2、spring中使用
学习Spring的时候,我们知道Spring主要有Ioc和AOP两大思想,它利用的是反射机制,依赖注入就不用多说了,而对于Spring的核心AOP来说,使用了动态代理,其实底层也是反射。
当然还有动态代理模式、web拦截器等等,使用极其广泛。但是这里有一个窍门需要我们注意一下,那就是面试的时候,使用反射机制往往能实现违反java语言设计原则的事,比如说String类型是不可变类型的,我们使用反射机制就可以使他变成可变类型的。
OK,反射原理基本上先到这里,对于其用途,在相应的文章中会提到,这里算是反射机制的基础知识吧,因为最终是要去用的。