前几天被问到了反射,当时没有回答出来多少,后来去看了一下,这里大概总结一下!java
首先,咱们要知道反射机制,那么什么是反射呢?程序员
答:反射是程序能够访问、检测和修改他自己状态或行为的一种能力。那么java语言是如何支持反射的呢?别急,咱们来慢慢聊。数组
咱们之前的学习中有遇到过Java中万事万物皆为对象之说,那么静态变量呢?还有基本数据类型的数据呢?它们也是面向对象的吗?咱们都知道静态是属于类的,不是哪一个类的对象的,基本数据类型是属于包装类的,那么难道类也是对象?答案就是是。Java中的每个类都是java.lang.Class类的对象。框架
1、Class类的使用jvm
在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.this
这里还有一个问题,咱们怎么区分foo1和c一、c2等呢?咱们知道foo1是类Foo的一个对象,那么就是Foo的对象,官网上对于c1,c2有一种说法,是Class Type----->类类型,也就是c1,c2是Foo的类类型,其实咱们还能够这样区别,c1,c2是Foo对象,而foo1是Foo的对象。spa
既然咱们已经获得了Foo的类类型,里面含有Foo类的相关信息,那么咱们有一个大胆的想象,可否用Foo类类型获得Foo的某个对象呢?答案是固然能够。Class对象有一个newInstance()方法,代码演示以下:3d
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();建立出来的对象。
下面咱们来聊聊方法的反射。
2、方法的反射
要想了解方法的反射,咱们首先要得到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的反射操做
3、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; } }
4、构造方法的反射
首先咱们仍是来获取构造方法的信息,构造方法的获取所须要的方法同上面的普通方法、成员变量大概类似,这里不作赘述。直接看代码便可理解:
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("构造方法被反射了"); } }
经过反射咱们还能够查看泛型的本质原理:
5、经过反射了解泛型的本质
泛型指的是集合中的数据只能输入指定的数据类型的数据。以下所示:
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成功插入了集中由此可得出泛型其实是在编译时设置了“路障”,在编译后会驱泛型化,它的存在只是为了放置错误的输入,只在编译时有效,绕过编译则无效。
反射大概就总结这些,接下来咱们小小的聊聊动态加载类。
6、动态加载类
类的加载分为静态加载和动态加载,静态加载指的是编译时加载,动态加载指的是运行时加载。
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;便可继续添加。
这是编写代码的一种新思想,从高强大的依赖种脱身而出。
总结:今天总结关于反射的问题,就到这里了。这里没有提到反射的数组和动态代理等关于反射更多、更复杂的东西,只是基础的知识。