JavaSE实战——反射技术
2015-09-30 22:46
696 查看
转载请声明出处:http://blog.csdn.net/zhongkelee/article/details/48834343
一、简介
1.反射的应用场景
我们对Java中反射技术的理解,对日后去了解一些框架的底层原理是非常有必要的。
比如在之前的博客中,我们提到过USB扩展笔记本的例子。
原先的做法:实现接口,修改源代码。
现在的做法:实现接口,配置配置文件。
我们先复习一下,原来的做法是怎样的,代码如下:
需求:希望后期出现了设备以后,可不可以不用修改NoteBookMain的代码,还可以不断地加入新的USB设备。
所以,我们这里可以通过反射来解决问题。
2.反射的定义
到了反射技术,我们就可以在没有类的情况下,配置文件给什么类就new什么对象(以前是有什么类,就new什么对象)。
反射技术其实就是动态的获取类以及类中的成员,并可以调用该类的成员(以前是有什么类,就new什么对象。现在是没有类,给什么类就new什么对象)。而且将字节码文件封装成对象,并将字节码文件中的内容都封装成对象,这样便于操作这些成员。简单说:反射技术可以对一个类进行解剖。
3.反射的好处
好处:反射技术的出现提高了程序的扩展性。
4.反射的基本步骤
A.获得Class对象,就是获取到指定的名称的字节码文件对象。
B.实例化对象,获得类的属性、方法或构造函数。
C.访问属性、调用方法、调用构造函数创建对象。
反射技术中最重要的一点:要先获取到那个类。即:无论new什么对象,都需要先获取对应的字节码文件。那么,如何获取到类呢?
二、字节码文件的获取
1.Class类的由来
发现java已对字节码文件进行了描述,用的是Class类完成的。如何获取一个字节码文件的对象呢?
2.获取Class字节码文件对象的三种方法
A.方式一:Object的getClass()方法。
每个字节码文件对象在堆内存中都是唯一的,如下图所示:
通过对内存中的每个实体对象,我们应该都可以获取到“它的母亲”,也就是所属的Class类。即,我们可以通过Object的getClass()方法获取运行时类。
以前也用到过此种方法:Object的equals()方法用到过,类似于instanceof()的效果。
方式一的弊端:发现在反射技术里,该方法不合适。因为反射技术是不明确具体类的。
B.方式二:每一个数据类型都有一个默认的静态属性:.class,用该属性即可获取。
所有的数据类型都有自己对应的Class对象。表示方式很简单。每一个数据类型都有一个默认的静态属性:.class,用该属性就可以获取到字节码文件对象。
以前也用到过此种方法:多线程的静态同步代码块用到过。
方式二的弊端:该方法也不合适,虽然不用对象调用了,还是要用具体的类,才能调用静态属性。
C.方式三:使用的Class类中的方法,静态的forName(String)方法。
在Class类中找到了forName()方法。通过名称就可以获取对应的字节码文件对象。重点。
forName()方法去对应的classpath目录中找到指定的Person.class文件加载进内存,并封装成Class对象返回(见下图)。注意名称是类的全名,包括包名。
forName方法和new关键字的区别:
new出一个对象实例来,这个过程包含了forName的过程,forName只是完成了加载类、确定连接的过程,JVM知道有这么个类了,并在内存中也给它分配了资源,类中若有static静态代码块,也会被执行,且作为初始化过程,只会执行这一次。而接下来的newInstance()就是完成了new的后半部分,类的实例化过程。
记住:静态代码块和Class类是绑定的,Class类装载成功,就表示执行了静态代码块了,以后也就不会再走这段静态代码了。
当然,我们不能说forName后就没有实例化类,在堆内存中是有一个Class对象的,只不过这个实例化对象和一般所用的有引用变量指向的实例(new出来的)在用法上有所不同吧。
说到这里,我又不得不提一下JDBC的加载数据库驱动过程:Class.forName(DriverPath);
方法DriverManager.getConnection()是在加载器列表中找寻驱动实例,这就是一种对forName加载的实例的使用方式。
当我们已知类的路径,完全可以import它,然后new出实例来;但是一旦我们只能得到String形式的路径,就有必要采用forName方式来实例化类了。
再扯远一点,使用import语句时,并没有加载类到内存,只是相当于告诉了JVM一个import列表,当代码中真正使用类时,会到import列表中搜寻目标类的位置,此时才加载、构造,这一点在static静态代码块语句中会得到验证。所以我们大可以用import.*的形式把一个包的类都import进来,这只是增多了import列表内容,搜索类时会稍费那么一点点资源,并不是说包中任何类的static成员不管你用没用到,都会被创建出来,不是这样的。
1.创建对象的方式不一样,前者是使用类加载机制,后者是创建一个新类。
2.主要考虑到软件的可伸缩、可扩展和可重用等设计思想,Java中的工厂模式经常使用newInstance()方法来创建对象。
3.newInstance()实际上是把new这个方式分解了,首先调用Class加载方法加载某个类,然后实例化。这样的好处是可以在调用Class的静态加载方法forName时获得更好的灵活性,提供了一种降耦的手段。
newInstance:弱类型,低效率。只能调用无参构造。
new:强类型,相对较高。能调用任何public构造。
三、反射的用法
1.反射类的构造函数
演示如何根据给定的名称获取到指定的Class对象后,建立该类的对象。
A.方式一:利用Class类的newInstance()方法--调用空参构造函数
二者的内存创建过程不同:
(1)通过反射newInstance()的动态创建过程:是通过给定的类名,去classpath路径找Class字节码文件对象加载进堆内存,并newInstance()对象,不过这里默认使用的是空参构造函数。
(2)通过固定的类名直接new对象:是加载Person类进内存,堆内存开辟空间,调用构造函数进行对象初始化。
方式一的弊端:如果需要反射的Person类没有空参构造函数,调用此方法就会报错。java.lang.InstantiationException:没有调用到与之对应的构造函数。(记住了,一般被反射的类通常都有空参数的构造函数。)
那万一给定类中没有空参数的构造函数呢?
B.方式二:先获取指定构造函数getConstructor(),再通过该构造函数进行实例化newInstance()--调用带参构造函数
2.反射类的字段属性
通过上面构造函数的获取和对象的建立我们已经发现了,只要我们获取到了Class字节码文件对象,剩下的构造函数、字段、成员函数其实就相当于案板上待宰的猪,心肝脾肺肾都可以易如反掌的获取到。故我们也把反射技术形象的比喻为对类的解剖。
接下来,我们开始反射字段,获取并设置类中的成员变量。主要说两点:
A. getFiled()、getDeclaredFiled()的区别 --Class字节码文件对象中private字段的获取
B. setAccessible(true)暴力访问 -- 特定实例对象中private字段的修改和获取
3.反射类的成员方法
首先我把我们用到的Person类代码贴出来:
下面,我们来演示三种不同情况下的反射方法。
A.反射非静态、无参数的成员方法
B.反射静态、无参数的成员方法
C.反射非静态、有参数的成员方法
四、反射应用场景的解决
现在,我们在回过头来看Java反射技术的应用场景的解决。
我们只需在usb.properties文件中添加相应的USB设备配置信息,即可。
好了,JavaSE的反射技术基础,就讲解到这里。有问题请联系我:lichunchun4.0@gmail.com
源码下载
一、简介
1.反射的应用场景
我们对Java中反射技术的理解,对日后去了解一些框架的底层原理是非常有必要的。
比如在之前的博客中,我们提到过USB扩展笔记本的例子。
原先的做法:实现接口,修改源代码。
现在的做法:实现接口,配置配置文件。
我们先复习一下,原来的做法是怎样的,代码如下:
package ustc.lichunchun.reflect.test; public class NoteBook { public void run(){ System.out.println("notebook run"); } public void useUSB(USB usb){ if(usb!=null){ usb.open(); usb.close(); } } }
package ustc.lichunchun.reflect.test; public interface USB { void close(); void open(); }
package ustc.lichunchun.reflect.test; public class MouseByUSB implements USB { @Override public void close() { System.out.println("mouse close"); } @Override public void open() { System.out.println("mouse open"); } }
package ustc.lichunchun.reflect.test; public class KeyboardByUSB implements USB { @Override public void close() { System.out.println("keyboard close"); } @Override public void open() { System.out.println("keyboard open"); } }
package ustc.lichunchun.reflect.test; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Properties; public class NoteBookMain { public static void main(String[] args) throws IOException, ClassNotFoundException { NoteBook book = new NoteBook(); book.run(); book.useUSB(null); book.useUSB(new MouseByUSB()); } }因为有了鼠标,所以需要在源程序中,创建鼠标对象并传递给笔记本。这种做法虽然利用多态提高了程序的扩展性,但是还是要不断地修改源代码。book.useUSB(new MouseByUSB());
需求:希望后期出现了设备以后,可不可以不用修改NoteBookMain的代码,还可以不断地加入新的USB设备。
所以,我们这里可以通过反射来解决问题。
2.反射的定义
到了反射技术,我们就可以在没有类的情况下,配置文件给什么类就new什么对象(以前是有什么类,就new什么对象)。
反射技术其实就是动态的获取类以及类中的成员,并可以调用该类的成员(以前是有什么类,就new什么对象。现在是没有类,给什么类就new什么对象)。而且将字节码文件封装成对象,并将字节码文件中的内容都封装成对象,这样便于操作这些成员。简单说:反射技术可以对一个类进行解剖。
3.反射的好处
好处:反射技术的出现提高了程序的扩展性。
4.反射的基本步骤
A.获得Class对象,就是获取到指定的名称的字节码文件对象。
B.实例化对象,获得类的属性、方法或构造函数。
C.访问属性、调用方法、调用构造函数创建对象。
反射技术中最重要的一点:要先获取到那个类。即:无论new什么对象,都需要先获取对应的字节码文件。那么,如何获取到类呢?
二、字节码文件的获取
1.Class类的由来
发现java已对字节码文件进行了描述,用的是Class类完成的。如何获取一个字节码文件的对象呢?
2.获取Class字节码文件对象的三种方法
A.方式一:Object的getClass()方法。
每个字节码文件对象在堆内存中都是唯一的,如下图所示:
通过对内存中的每个实体对象,我们应该都可以获取到“它的母亲”,也就是所属的Class类。即,我们可以通过Object的getClass()方法获取运行时类。
public static void methodDemo_1(){ //调用getClass()方法,要先有对象。 Person p1 = new Person(); Class clazz1 = p1.getClass(); //class ustc.lichunchun.domain.Person Person p2 = new Person(); Class clazz2 = p2.getClass(); System.out.println(clazz1 == clazz2);//true,解释见上图即可。 }
以前也用到过此种方法:Object的equals()方法用到过,类似于instanceof()的效果。
方式一的弊端:发现在反射技术里,该方法不合适。因为反射技术是不明确具体类的。
B.方式二:每一个数据类型都有一个默认的静态属性:.class,用该属性即可获取。
所有的数据类型都有自己对应的Class对象。表示方式很简单。每一个数据类型都有一个默认的静态属性:.class,用该属性就可以获取到字节码文件对象。
public static void methodDemo_2() { Class clazz1 = Person.class; Class clazz2 = Person.class; System.out.println(clazz1 == clazz2);//true }
以前也用到过此种方法:多线程的静态同步代码块用到过。
方式二的弊端:该方法也不合适,虽然不用对象调用了,还是要用具体的类,才能调用静态属性。
C.方式三:使用的Class类中的方法,静态的forName(String)方法。
在Class类中找到了forName()方法。通过名称就可以获取对应的字节码文件对象。重点。
forName()方法去对应的classpath目录中找到指定的Person.class文件加载进内存,并封装成Class对象返回(见下图)。注意名称是类的全名,包括包名。
public static void methodDemo_3() throws ClassNotFoundException { String className = "ustc.lichunchun.domain.Person"; Class clazz = Class.forName(className); System.out.println(clazz);//class ustc.lichunchun.domain.Person }
forName方法和new关键字的区别:
new出一个对象实例来,这个过程包含了forName的过程,forName只是完成了加载类、确定连接的过程,JVM知道有这么个类了,并在内存中也给它分配了资源,类中若有static静态代码块,也会被执行,且作为初始化过程,只会执行这一次。而接下来的newInstance()就是完成了new的后半部分,类的实例化过程。
记住:静态代码块和Class类是绑定的,Class类装载成功,就表示执行了静态代码块了,以后也就不会再走这段静态代码了。
当然,我们不能说forName后就没有实例化类,在堆内存中是有一个Class对象的,只不过这个实例化对象和一般所用的有引用变量指向的实例(new出来的)在用法上有所不同吧。
说到这里,我又不得不提一下JDBC的加载数据库驱动过程:Class.forName(DriverPath);
方法DriverManager.getConnection()是在加载器列表中找寻驱动实例,这就是一种对forName加载的实例的使用方式。
当我们已知类的路径,完全可以import它,然后new出实例来;但是一旦我们只能得到String形式的路径,就有必要采用forName方式来实例化类了。
再扯远一点,使用import语句时,并没有加载类到内存,只是相当于告诉了JVM一个import列表,当代码中真正使用类时,会到import列表中搜寻目标类的位置,此时才加载、构造,这一点在static静态代码块语句中会得到验证。所以我们大可以用import.*的形式把一个包的类都import进来,这只是增多了import列表内容,搜索类时会稍费那么一点点资源,并不是说包中任何类的static成员不管你用没用到,都会被创建出来,不是这样的。
1.创建对象的方式不一样,前者是使用类加载机制,后者是创建一个新类。
2.主要考虑到软件的可伸缩、可扩展和可重用等设计思想,Java中的工厂模式经常使用newInstance()方法来创建对象。
String className = readfromXMlConfig;//从xml 配置文件中获得字符串 class c = Class.forName(className); factory = (ExampleInterface)c.newInstance();上面代码中已经不存在Example的类名称,这样的优点是无论Example怎么变化,上述代码不变。
3.newInstance()实际上是把new这个方式分解了,首先调用Class加载方法加载某个类,然后实例化。这样的好处是可以在调用Class的静态加载方法forName时获得更好的灵活性,提供了一种降耦的手段。
newInstance:弱类型,低效率。只能调用无参构造。
new:强类型,相对较高。能调用任何public构造。
三、反射的用法
1.反射类的构造函数
演示如何根据给定的名称获取到指定的Class对象后,建立该类的对象。
A.方式一:利用Class类的newInstance()方法--调用空参构造函数
public static void getObject1() throws ClassNotFoundException, InstantiationException, IllegalAccessException { //1.根据给定的类名获取Class对象。 String className = "ustc.lichunchun.domain.Person"; Class clazz = Class.forName(className); Object obj = clazz.newInstance();//Person run,创建一个Person对象。默认调用该类的空参构造函数。 }上面三行代码就相当于:
Person p = new Person();但是因为我们不知道类的名称是什么,所以不能这么写。
二者的内存创建过程不同:
(1)通过反射newInstance()的动态创建过程:是通过给定的类名,去classpath路径找Class字节码文件对象加载进堆内存,并newInstance()对象,不过这里默认使用的是空参构造函数。
(2)通过固定的类名直接new对象:是加载Person类进内存,堆内存开辟空间,调用构造函数进行对象初始化。
方式一的弊端:如果需要反射的Person类没有空参构造函数,调用此方法就会报错。java.lang.InstantiationException:没有调用到与之对应的构造函数。(记住了,一般被反射的类通常都有空参数的构造函数。)
那万一给定类中没有空参数的构造函数呢?
B.方式二:先获取指定构造函数getConstructor(),再通过该构造函数进行实例化newInstance()--调用带参构造函数
public static void getObject2() throws Exception { /* * 万一给定类中没有空参数的构造函数呢? * 可以先获取指定的构造函数,再通过该构造函数进行实例化。 */ String className = "ustc.lichunchun.domain.Person"; Class clazz = Class.forName(className); //1.通过Class获取指定构造函数。比如带两个参数。注意参数类型! Constructor cons = clazz.getConstructor(String.class,int.class); //2.通过指定的构造器对象进行对象的初始化。 Object obj = cons.newInstance("lichunchun",23); }上面这四行代码就相当于:
Person p = new Person("lichunchun",23);
2.反射类的字段属性
通过上面构造函数的获取和对象的建立我们已经发现了,只要我们获取到了Class字节码文件对象,剩下的构造函数、字段、成员函数其实就相当于案板上待宰的猪,心肝脾肺肾都可以易如反掌的获取到。故我们也把反射技术形象的比喻为对类的解剖。
接下来,我们开始反射字段,获取并设置类中的成员变量。主要说两点:
A. getFiled()、getDeclaredFiled()的区别 --Class字节码文件对象中private字段的获取
B. setAccessible(true)暴力访问 -- 特定实例对象中private字段的修改和获取
package ustc.lichunchun.reflect.field; import java.lang.reflect.Field; public class ReflectFieldDemo { public static void main(String[] args) throws Exception { /* * 获取并设置类中的成员变量。 * 反射字段。 */ getFieldDemo(); } public static void getFieldDemo() throws Exception { String className = "ustc.lichunchun.domain.Person"; Class clazz = Class.forName(className); //获取指定age字段。需求是是获取Class对象中的字段,和new不new对象无关。 //Field field = clazz.getField("age"); //java.lang.NoSuchFieldException //getField()方法只获取公有的字段(包括父类的),而age是私有的字段。 Field field = clazz.getDeclaredField("age"); //getDeclaredField()方法获取子类自身公有、私有、默认、保护的字段(不包括继承的)。 //System.out.println(field);//private int ustc.lichunchun.domain.Person.age ============================================================================ //要对非静态的字段操作必须有对象。 Object obj = clazz.newInstance(); //使用Field的父类AccessibleObject的setAccessible(true)方法将访问权限检查功能取消。 field.setAccessible(true);//暴力访问 field.set(obj, 40);//如果没有暴力访问,则java.lang.IllegalAccessException System.out.println(field.get(obj));//40。如果没有暴力访问,则java.lang.IllegalAccessException //无效访问,因为age这个field字段是私有的。 //上述代码等效于: //Person p = new Person(); //p.age = 23; } }
3.反射类的成员方法
首先我把我们用到的Person类代码贴出来:
package ustc.lichunchun.domain; public class Person { private String name; private int age; public Person() { super(); System.out.println("Person run"); } public Person(String name, int age) { this.name = name; this.age = age; System.out.println("Person run run"); } public void show(){ System.out.println("Person show run"); } public static void staticShow(){ System.out.println("Person static show run"); } public void paramShow(String name, int age){ System.out.println("show:"+name+"---"+age); } }
下面,我们来演示三种不同情况下的反射方法。
A.反射非静态、无参数的成员方法
//反射方法。非静态,无参数的show方法。 public static void getMethodDemo1() throws Exception { String className = "ustc.lichunchun.domain.Person"; Class clazz = Class.forName(className); Method method = clazz.getMethod("show", null); Object obj = clazz.newInstance(); method.invoke(obj, null);//Person show run //Person p = new Person(); //p.show(); }
B.反射静态、无参数的成员方法
//反射方法。静态,无参数的show方法。 public static void getMethodDemo2() throws Exception { String className = "ustc.lichunchun.domain.Person"; Class clazz = Class.forName(className); Method method = clazz.getMethod("staticShow", null); //静态方法,不需要创建对象,既可以调用。 <span style="white-space:pre"> </span>method.invoke(null, null);//Person static show run }
C.反射非静态、有参数的成员方法
//反射方法。非静态,有参数的show方法。 public static void getMethodDemo3() throws Exception { String className = "ustc.lichunchun.domain.Person"; Class clazz = Class.forName(className); Method method = clazz.getMethod("paramShow", String.class, int.class); Object obj = clazz.newInstance(); method.invoke(obj, "lichunchun", 23);//show:lichunchun---23 }
四、反射应用场景的解决
现在,我们在回过头来看Java反射技术的应用场景的解决。
package ustc.lichunchun.reflect.test; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Properties; public class NoteBookMain { public static void main(String[] args) throws IOException, ClassNotFoundException { NoteBook book = new NoteBook(); book.run(); book.useUSB(null); //因为有了鼠标,所以需要在源程序中,创建鼠标对象并传递给笔记本。 //虽然利用多态提高了程序的扩展性,但是还是要不断地修改源代码。 //book.useUSB(new MouseByUSB()); //需求:希望后期出现了设备以后,可不可以不用修改NoteBookMain的代码,还可以不断地加入新的USB设备。 //通过反射来解决问题。 //1.对外提供配置文件。 File configFile = new File("usb.properties"); if(!configFile.exists()){ configFile.createNewFile(); } //2.读取流和配置文件关联。 FileInputStream fis = new FileInputStream(configFile); Properties prop = new Properties(); //3.将流中的数据加载到prop。 prop.load(fis); for (int x = 1; x <= prop.size(); x++) { String className = prop.getProperty("usb" + x); //4.对指定的类进行加载。 Class clazz = Class.forName(className); try { USB usb = (USB)clazz.newInstance(); book.useUSB(usb); } catch (Exception e) { System.out.println("配置文件错误,指定类没有正确实现USB接口"); } } fis.close(); } }
我们只需在usb.properties文件中添加相应的USB设备配置信息,即可。
好了,JavaSE的反射技术基础,就讲解到这里。有问题请联系我:lichunchun4.0@gmail.com
源码下载
相关文章推荐
- JAVA 实例 判断偶数和判断瑞年
- java重写与重载的区别
- eclipse中使用真机(已root)测试android程序时,看不到程序包data文件夹下的数据
- JAVA 运算符和条件结构
- 【Java EE 学习 51】【Spring学习第三天】【cglib动态代理】【AOP和动态代理】【切入点表达式】
- WebService(三)—JDK内置JAX-RS实现Rest WebService
- java中scanner的使用(二)
- 多线程系列三——java线程间通信
- 【Java笔记一】Filter过滤器
- java中Scanner 的使用(一)
- WebService(二)—JDK内置JAX-WS实现SOAP WebService
- 谈谈对Spring IOC的理解
- SSH搭建web java
- Java核心技术第3章(7)
- 多线程系列二——java线程间的互斥与同步
- java的内部类(一)
- Spring MVC 实现增删改查
- 学习笔记:DataTables之数据读取和操作(java对象转换为json对象)
- 浅析java异常处理机制——理论篇
- JAVA 输入输出