前几天被问到了反射,当时没有回答出来多少,后来去看了一下,这里大概总结一下!
首先,我们要知道反射机制,那么什么是反射呢?
答:反射是程序可以访问、检测和修改他本身状态或行为的一种能力。那么java语言是如何支持反射的呢?别急,我们来慢慢聊。
我们以前的学习中有遇到过Java中万事万物皆为对象之说,那么静态变量呢?还有基本数据类型的数据呢?它们也是面向对象的吗?我们都知道静态是属于类的,不是哪个类的对象的,基本数据类型是属于包装类的,那么难道类也是对象?答案就是是。Java中的每一个类都是java.lang.Class类的对象。
一、Class类的使用
在Java中,每一个class都有一个相应的Class对象。换句话说,就是当我们编写一个类时,编译完成后,在生成的.class文件中,就会产生一个Class对象,用于表示这个类的类型信息。也就是说,Class类的实例对象表示java应用程序运行时的类或者接口。虚拟机为每种类型管理一个独一无二的Class对象,也就是说,每个类型都有一个Class对象,运行程序时,jvm首先检查所要加载的类对应的Class对象是否已经加载,如果没有加载,jvm就会根据类名查找.class文件并将其Class对象载入。
可以通过类名.class、类的对象.getClass()、Class.forName()三种方法获取Class对象。具体的使用如下:
package Reflect; public class ClassRefelect { public static void main(String[] args){ //获得Foo的类对象 Foo foo1 = new Foo(); /** * 获得Foo类对象有三种方法 * 1.类名.class 每一个类都有一个隐含的成员变量,class * 2.类对象.getClass(); * 3.Class.forName(包名+类名); * 下面的c1,c2,c3官网称之为“Class Type" 类类型 */ /** * 万事万物皆为对象,那么类也是对象,类是什么的对象呢》 *java.lang.Class的对象 * 该类中封装了类的相关操作 */ Class c1 = Foo.class; Class c2 = foo1.getClass(); Class c3 = null; try { c3 = Class.forName("Reflect.Foo"); } catch (ClassNotFoundException e) { e.printStackTrace(); } /** *那么这三个类类型是否相同呢? * 答案是相同,为什么呢? * 因为每一个类只可能有一种类类型, * 就比如每一个对象只有一个类一样, * 这个类类型是Class的实例对象 */ System.out.println(c1==c2); //true System.out.println(c3==c2); //true } } class Foo{ public void print(){ System.out.println("创建了Foo类的对象"); } }
可以看到上面的代码中有对三个Class对象的引用变量进行比较,答案当然是true,因为它们都是Foo类对象,而上面我们也提到了,每一个类都有一个独一无二的Class对象,所以结果应该是true.
这里还有一个问题,我们怎么区分foo1和c1、c2等呢?我们知道foo1是类Foo的一个对象,那么就是Foo的对象,官网上对于c1,c2有一种说法,是Class Type----->类类型,也就是c1,c2是Foo的类类型,其实我们还可以这样区别,c1,c2是Foo对象,而foo1是Foo的对象。
既然我们已经得到了Foo的类类型,里面含有Foo类的相关信息,那么我们有一个大胆的想象,能否用Foo类类型得到Foo的某个对象呢?答案是当然可以。Class对象有一个newInstance()方法,代码演示如下:
package Reflect; public class ClassRefelect { public static void main(String[] args){ Class c1 = Foo.class; try { Foo foo2 = (Foo) c1.newInstance(); foo2.print(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } class Foo{ public void print(){ System.out.println("创建了Foo类的对象"); } }
这里的foo2相当于new Foo();创建出来的对象。
下面我们来聊聊方法的反射。
二、方法的反射
要想了解方法的反射,我们首先要获得Class对象,才能通过Class对象获得方法的相关信息,获得Class对象的方法上面已经讲过,这里插播一条广告,上面不是提到了每种类型都有Class对象么,那么基本数据类型以及void类型有木有呢?恩,有的,看下面的代码
package Reflect; /** * 基本数据类型类类型 * 包装类类类型 * */ public class ClassType { public static void main(String[] args){ Class c1 = int.class; //int的类类型 Class c2 = String.class; //String类类型 Class c3 = Double.class; //Double包装类类型 Class c4 = double.class; //double类类型 Class c5 = void.class; //void 的类类型 System.out.println(c1.getName()); //int System.out.println(c2.getName()); //java.lang.String System.out.println(c2.getSimpleName()); //String System.out.println(c3.getName()); //java.lang.Double System.out.println(c4.getName()); //double System.out.println(c3.getSimpleName()); //Double System.out.println(c5.getName()); //void } }
既然可以拿到Class对象,那么获取方法的一系列信息就不成问题了。
方法也是对象,是Method类的对象,该类中封装了方法的一系列操作,该类是java.lang.reflect包下的类。
这里介绍几个方法,后面会用到
----------------------------------------------------------------------------------------------------------------------------------------------------------------
1.getMethods() //获得一个类中的所有public修饰的方法对象,包括继承父类的方法
2.getDeclaredMethods() //获得一个类中的所有自己声明的方法对象,不问访问权限,不包括父类的
3.getName() //获得调用者的名称
4.getReturnType() //获得方法的返回值类类型,即如果返回值是int,那么返回的是int.class
5.getParameterTypes() //获得参数列表的类类型
----------------------------------------------------------------------------------------------------------------------------------------------------------------
package Reflect; import java.lang.reflect.Method; public class ClassUtil { public static void printMessage(Object obj){ //获得类的类类型 /** * 如果传入的参数是Object类型, * 则或取的就是Object的类类型, * 如果是其子类,则获取的就是其子类的类类型 */ Class c1 = obj.getClass(); //或取类的名称 System.out.println("类的名称是:"+c1.getName()); //获取类中的方法 //获取类中的方法 Method[] ms = c1.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[] parameterType = ms[i].getParameterTypes(); for (Class class1:parameterType) { System.out.print(class1.getName()+","); } System.out.println(")"); } }
这里传入的如果是Object类型的对象,那么就会获取Object类的方法信息,如果是Object类的子类,那么获取的就是
该子类的方法信息。
这里我们测试一下:
package Reflect; public class TestClassUtil { public static void main(String[] args){ Integer s = 1; ClassUtil.printMessage(s); } }
从这里我们可以看到打印出了Integer类的所有的public方法的信息。
那么什么是方法的反射呢?方法的反射是什么样的呢?
平时我们使用方法的步骤是什么呢?是不是先获取一个类的对象(如果是实例方法),然后对象.方法()对吗?那么方法反射恰好相反,是利用方法对象操作类的对象的。
方法的反射也有几个步骤:
0.获取类类型
1.通过类类型获取某个方法的方法对象
2.方法对象.invoke(类的对象,参数列表)
invoke()方法API文档上时这样讲的:“对带有指定参数的指定对象调用由此 Method
对象表示的底层方法”,通俗的说就是可以利用invoke()方法进行反射。
下面列出反射中需要的方法:
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
****说明一下:以下提到的c是Class对象,某个类的类类型,method是1,2方法返回的方法对象****
1.getMethod("Method name","Parameter Type") //方法名称和参数列表可以唯一确定一个方法,注意这个方法 只能获取类中public 声明的方法,包括继承自父类的方法
用法举例:public void print(int a,int b){}
c.getMethod(“print",int.class,int.class) 或者 c.getMethod("print",new Class[]{int.class,int.class})
2.getDeclaredMethod("Method name","Parameter Type") //获取类中自己声明的方法,不问权限,不包括继承自父类的方法
用法举例:public void print(int a,int b){}
c.getDeclaredMethod(“print",int.class,int.class) 或者 c.getDeclaredMethod("print",new Class[]{int.class,int.class})
以上两个方法都是获取方法对象的
3.invoke(Object obj,args) //对带有args参数的obj对象调用由此Method对象表示的底层方法
用法举例:class A{
public void print(int a,int b){}
}
method.invoke(new A(),10,20) 或者 method.invoke(new A(), new Object[]{10,20});
invoke方法返回值是null,或者是Object或者Object的子类,当是Object时不存在问题,如果(Object的子类)想要具体的返回值类型,那么必须进行强制类型转换,如
Object o = method.invoke(a1,10,10);
Integer i = (Integer)method.invoke(a1,10,10);
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
接下来我们看具体的代码:
package Reflect; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class MethodRefelect { public static void main(String[] args){ /** * 1.要想获取一个方法的信息,首先需要获取类类型 * 2.获取了类类型后通过类类型得到方法对象 * 3.通过方法对象可以反射操作方法 */ A a = new A(); Class c = a.getClass(); try { Method m = c.getMethod("print",new Class[]{int.class,int.class}); try { // Object o = m.invoke(a,new Object[]{10,20}); Integer o = (Integer)m.invoke(a,10,20); System.out.println(o); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } catch (NoSuchMethodException e) { e.printStackTrace(); } try { Method m2 = c.getDeclaredMethod("print",String.class,String.class); try { Object o = m2.invoke(a,new Object[]{"Hello","ninhao"}); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } catch (NoSuchMethodException e) { e.printStackTrace(); } try { // Method m3 = c.getDeclaredMethod("print",new Class[]{}); Method m3 = c.getDeclaredMethod("print"); try { // Object o = m3.invoke(a,new Object[]{}); Object o = m3.invoke(a); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } catch (NoSuchMethodException e) { e.printStackTrace(); } } } class A{ public void print(){ System.out.println("hello"); } public int print(int a,int b){ System.out.println(a+b); return a+b; } public void print(String a,String b){ System.out.println(a.toUpperCase()+","+b.toLowerCase()); } }
方法的反射讲完后,我们说一下Field的反射操作
三、Field的反射
在讲Field的反射之前,先来了解一下如何通过Class对象获取某个类的Field的信息,如果需要获取Field的信息,首先需要获得类中所有的Filed,然后依次获得每一个Field的数据类型和名称。
接下来列出获得Field的信息的方法
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
1.getDeclaredFields() //获得一个类中所声明的所有的Filed,返回值是一个数组
2.getType() //获得调用者的类型
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
代码相对来说简单,如下:
public static void printFiled(Object obj) { Class c1 = obj.getClass();
Field[] fs = c1.getDeclaredFields(); //自己类中声明的所有的成员变量 for(Field filed: fs){ //根据每一个成员变量获取成员变量的类型 Class returnType = filed.getType(); String returnName = returnType.getName(); //返回每一个成员变量的名称 String FiledName = filed.getName(); System.out.println(returnName+" "+FiledName); } }
package Reflect; public class TestClassUtil { public static void main(String[] args){ Integer s = 1; ClassUtil.printFiled(s); } }
测试了一下,结果中列出了Integer类的Field.[C这个是数组类型的反射,这里不做详述。
接着到了我们Field的反射表演的时间了。
Field的反射操作步骤如下:
0.获得类类型
1.通过类类型获得某个Field
1.5.一般,Field是private,所以需要setAccessible(true),大概的意思是可以操作private的数据
2.对获得的Field进行相关操作
补充一下:Field(成员变量)也是面向对象的,它是java.lang.reflect.Filed的实例对象
具体的实践如下:
package Reflect; import java.lang.reflect.Field; public class FieldReflect { public static void main(String[] args){ B b = new B(); Class c = b.getClass(); //首先获取成员变量对象 try { /** * 这里的getField之所以会出错, * 是因为getField获取的是public的, * 而之前我并没有指定a的访问权限 */ // Field f = c.getField("a"); Field f = c.getDeclaredField("a"); //通过getDeclaredField("Field name");获得该属性对象 try { f.setAccessible(true); //如果不设置该标志,将不能访问私有成员 f.set(b,12); //平时我们都是b.setF(xxx);这里通过Field对象f反向操作B类的对象b System.out.println(f.get(b)); //12 } catch (IllegalAccessException e) { e.printStackTrace(); } } catch (NoSuchFieldException e) { e.printStackTrace(); } } } class B{ private int a; private int b; public void setA(int a) { this.a = a; } public void setB(int b) { this.b = b; } public int getA() { return a; } public int getB() { return b; } }
四、构造方法的反射
首先我们还是来获取构造方法的信息,构造方法的获取所需要的方法同上面的普通方法、成员变量大概相似,这里不做赘述。直接看代码即可理解:
public static void printConMessage(Object obj){ //获得类类型 Class c1 = obj.getClass(); //获得类中的自己声明的构造方法 Constructor[] constructors = c1.getDeclaredConstructors(); for(Constructor constructor:constructors){ //获得构造方法的名称 System.out.print(constructor.getName()+"("); //获得构造方法的参数列表中的参数类型 Class[] parameterTypes = constructor.getParameterTypes(); //打印出每一个构造方法的参数 for (Class para: parameterTypes) { System.out.print(para.getName()+","); } System.out.println(")"); } }
package Reflect; public class TestClassUtil { public static void main(String[] args){ Integer s = 1; ClassUtil.printConMessage(s); } }
构造方法也是对象,是java.lang.reflect.Constructor的实例对象
反射所需要的步骤:
1.根据类类型获得构造方法对象
2.通过构造方法对象创建该类的对象
下面的是构造方法的反射的代码:
package Reflect; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class ConRefelect { public static void main(String[] args){ //构造方法的反射操作 C c = new C(); Class c1 = c.getClass(); //获得构造方法 try { Constructor constructor1 = c1.getConstructor(); Constructor constructor2 = c1.getConstructor(String.class); try { /** * 类类型.newInstance()可以创建一个类对象 * 类的构造对象.newInstance()也可以创建一个类对象 * 这个的意思是 */ C o = (C)constructor1.newInstance(); C q = (C)constructor2.newInstance("hello"); q.print(); o.print(); // System.out.println(c1==constructor1); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } System.out.println(constructor1.getName()); System.out.println(constructor2.getName()); } catch (NoSuchMethodException e) { e.printStackTrace(); } } } class C{ public String a; public C(){ } public C(String a){ this.a = a; } public void print(){ System.out.println("构造方法被反射了"); } }
通过反射我们还可以查看泛型的本质原理:
五、通过反射了解泛型的本质
泛型指的是集合中的数据只能输入指定的数据类型的数据。如下所示:
ArrayList<String> list = new ArrayList<String>();
这个list中只能输入String 类型的数据,如果输入其他的不兼容的就会报错。
那么现在我们通过反射了解反射:
package Reflect; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; public class TestGeneric { public static void main(String[] args){ ArrayList list = new ArrayList(); ArrayList<String> list1 = new ArrayList<String>(); list1.add("hello"); // list1.add(20); //下面通过反射来判断泛型的本质 Class c = list1.getClass(); try { Method m = c.getMethod("add",new Class[]{Object.class}); try { Object o = m.invoke(list1,20); System.out.println(list1.size()); //2 System.out.println(list1); [hello,20] } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } catch (NoSuchMethodException e) { e.printStackTrace(); } } }
上面的代码中刚开始时在list1中插入20时编译报错,接着我们通过反射获得方法对象,操作集合的add方法,将20成功插入了集合中。为什么呢?因为反射是运行时进行的,所以上面我们绕过了编译将20成功插入了集中由此可得出泛型实际上是在编译时设置了“路障”,在编译后会驱泛型化,它的存在只是为了放置错误的输入,只在编译时有效,绕过编译则无效。
反射大概就总结这些,接下来我们小小的聊聊动态加载类。
六、动态加载类
类的加载分为静态加载和动态加载,静态加载指的是编译时加载,动态加载指的是运行时加载。
new 创建对象时是静态加载类,在编译时刻就需要将所需要的所有的类加载进来。这样有一个问题就是,当我们有一个功能类,我的功能还不是很完整,但是我需要提前搭建起来框架,但是编译时发现有一个现在不使用的类不存在,所以我现在这个功能类则编译不通过。具体的代码演示如下:
public class test{ public static void main(String[] args){ Word word = new Word(); word.start(); Excel excel = new Excel(); excel.start(); } class Word{ void start(){}; }
如上所示,当我有一个Word类时,我就想要编译运行这个test类,测试一下我的Word类是否是好的,但是现在没有Excel类,所以无法检测,这就是高耦合,依赖性太强,这在工程中是不会出现的,我们必须使得我们的代码低耦合,高内聚才能符合软件工程的要求。
那么如何改善呢?这个本质的原因还是因为类是静态加载,如果改为动态加载,那么就不会出现这个问题,我只需要在动态加载时发现导入我需要的类即可。
package Reflect; public class TestLoose { public static void main(String[] args){ try { Class a = Class.forName(args[0]); //动态加载类,在编译时根本不会出现问题,只有 try { Able able = (Able)a.newInstance(); able.start(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } catch (ClassNotFoundException e) { e.printStackTrace(); } } } interface Able{ public void start(); } class Word implements Able{ public void start(){} }
上面的代码就是一个低耦合,高内聚的代码规范,如何说呢。我们可以从编译、运行两部分来说明,首先在编译时TestLoose是不会出错的,因为我们有一个Able的接口存在,所以编译通过;当运行时我们有Word类,所以我们在运行时输入Word也是不会报错的,我们输入Excel才会出错,这样就会解决上面的如果存在一个类而不能测的问题,如果我们后边需要或者这段代码由别的程序员来继续写,它们只需要实现Able即可,如Excel implements Able;即可继续添加。
这是编写代码的一种新思想,从高强大的依赖种脱身而出。
总结:今天总结关于反射的问题,就到这里了。这里没有提到反射的数组和动态代理等关于反射更多、更复杂的东西,只是基础的知识。