一. 什么是反射:
反射之中包含了一个“反”的概念,所以要想解释反射就必须先从“正”开始解释,一般而言,当用户使用一个类的时候,应该先知道这个类,而后通过这个类产生实例化对象,但是“反”指的是通过对象找到类。
所以,为什么要反向来找这个类呢?
假如你写了一段代码:Object o=new Object(); 运行了起来!
首先JVM会启动,你的代码会编译成一个.class文件,然后被类加载器加载进jvm的内存中,你的类Object加载到方法区中,创建了Object类的class对象到堆中,注意这个不是new出来的对象,而是类的类型对象,每个类只有一个class对象,作为方法区类的数据结构的接口。jvm创建对象前,会先检查类是否加载,寻找类对应的class对象,若加载好,则为你的对象分配内存,进行初始化也就是代码: new Object()。
上面的程序对象是自己new的,是静态加载类,相当于写死了给jvm去跑。在编译时进行加载,假如一个服务器上突然遇到某个请求要用到某个类,但没加载进jvm,是不是要停下来自己写段代码,new一下,启动一下服务器呢??显然。这很不科学…而且如果程序中其他的类有问题,那么没有问题的类也是无法执行的,解决这个问题可以使用动态加载
反射是什么呢?当我们的程序在运行时,需要动态的加载一些类这些类可能之前用不到所以不用加载到jvm,而是在运行时根据需要才加载,这样的好处对于服务器来说不言而喻,举个例子我们的项目底层有时是用mysql,有时用oracle,需要动态地根据实际情况加载驱动类,这个时候反射就有用了,假设 com.java.dbtest.myqlConnection,com.java.dbtest.oracleConnection这两个类我们要用,这时候我们的程序就写得比较动态化,通过Class tc = Class.forName(“com.java.dbtest.TestConnection”);通过类的全类名让jvm在服务器中找到并加载这个类,而如果是oracle则传入的参数就变成另一个了。这时候就可以看到反射的好处了,这个动态性就体现出java的特性了!
二. 反射的基本使用
1. 获取class对象,即类类型
Demo d = new Demo();
//方法1:通过该类的对象获取Demo类的类类型
Class d1 = d.getClass();
//方法2:Demo类的类类型d2指的是Class的对象
//实际在告诉我们任何一个类都有一个隐含的静态成员变量class
Class d2 = Demo.class;
//方法3:使用Class类的forName静态方法
Class d3 = Class.forName("com.nic.reflect.Demo");
2. 根据得到的类类型,创建该类的实例
通过反射来生成对象主要有两种方式。
(1)使用Class对象的newInstance()方法来创建Class对象对应类的实例。前提是需要有无参数的构造方法
Class c = String.class;
Object str = c.newInstance();
(2)先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例。
//获取String所对应的Class对象
Class c = String.class;
//获取String类带一个String参数的构造器
Constructor constructor = c.getConstructor(String.class);
//根据构造器创建实例
Object obj = constructor.newInstance("23333");
System.out.println(obj);
3. 获取成员变量对象及其信息
成员变量是java.lang.reflect.Field的对象
- Field类封装了关于成员变量的操作
- Field[] fs = c.getFields()方法获取所有public的成员变量Field[]信息
- c.getDeclaredFields() 获取的是该类自己声明的成员变量信息
- field.getType()获得成员类型的类类型
- field.getName()获得成员的名称
4. 获取构造函数对象及其信息
构造函数是java.lang.Constructor类的对象
- 通过Class.getConstructor()获得Constructor[]所有公有构造方法信息
- 建议getDeclaredConstructors()获取自己声明的构造方法
- Constructor.getName():String
- Constructor.getParameterTypes():Class[]
5. 获取方法对象及方法信息
package com.nic.reflect;
import java.lang.reflect.Method;
public class ClassUtil {
public static void pringClassMessage(Object object){
//要获取类的信息,首先要获取类的类型
Class c=object.getClass();//传递的是哪个子类的对象,c就是该子类的类类型
//获取类的名称
System.out.println("类的名称是:"+c.getName());
/*
* Method类,即方法对象
* 一个成员方法就是一个Method对象
* --getMethods()方法获取的是所有public函数,包括父类继承而来的
* --getDeclaredMethods() 返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法
* */
Method[] ms=c.getMethods();
for (int i = 0; i < ms.length; i++) {
//获取返回值类型-->得到的是方法的返回值类型的类类型
Class returnType=ms[i].getReturnType();
System.out.println(returnType.getName());
//得到方法名
System.out.println(ms[i].getName()+"(");
//获取参数类型-->得到的是参数列表的类型的类类型
Class[] paramType=ms[i].getParameterTypes();
for (Class class1: paramType) {
System.out.println(class1.getName()+",");
}
System.out.println(")");
}
}
}
6. 方法反射的基本操作
public class MethodDemo {
public static void main(String[] args) throws Exception {
Class c = Class.forName("A");
/*
* 获取方法,方法的名称和方法的参数列表可以唯一决定一个方法
* getMethod()返回的是public的方法
* getDeclaredMethod()返回的是自己声明的方法
*/
Method method = c.getMethod("print", int.class,int.class);
/*
* 方法的反射操作
*/
method.invoke(c, 20,30);
}
}
class A{
public static void print(int a,int b){
System.out.println(a+b);
}
}
方法的反射的好处就是解耦,比如说a,b,c对象都要调用 print()方法,正常的想法就是要创建每个对象,并且a.print() b.print() c.print() ,但是使用反射的话,就可以通过 print()方法的对象.invoke(对象,参数列表)
想要用哪个对象就用哪个对象
三. 通过反射了解集合泛型的本质
public class MethodDemo {
public static void main(String[] args) throws Exception {
ArrayList list1 = new ArrayList();
ArrayList<String> list2 = new ArrayList<String>();
System.out.println(list1.getClass()==list2.getClass());
//结果为true说明编译之后的集合是去泛型化的
Class c = list2.getClass();
Method m =c.getMethod("add", Object.class);
m.invoke(list2, 20);
System.out.println(list2.size()); //1
//反射的操作都是编译之后的操作;就是运行阶段
//java中集合的泛型是防止错误输入的;只在编译阶段有效,只要绕过编译就无效了
//这里的例子违背了list2只能输入String类型,但是可以看到仍然可以添加到集合中,验证了以上两点
}
}