一、什么是反射?
在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有),这种动态获取的信息,及调用对象的方法的功能就称为Java的反射机制。
二、获取类对象
1、什么是类对象?
创建Dog类和Cat类
public class Dog { private String name; private int age; public Dog() { } public Dog(String name, int age) { this.name = name; this.age = age; } /*getter and setter*/ }
public class Cat { private String name; private String hobby; /*getter and setter*/ }
实例化两个Dog类的对象
Dog dog1 = new Dog("大黄", 5); Dog dog2 = new Dog("小黑", 3);
在理解类对象之前,先说我们熟悉的对象之间的区别:
dog1和dog2都是Dog对象,他们的区别在于,各自有不同的名称、年龄。
然后说说类之间的区别
Dog和Cat都是类,他们的区别在于有不同的方法,不同的属性。
类对象,就是描述一个类都有什么属性,什么方法的。所有的类,都存在一个类对象,这个类对象用于提供类本身的信息.
2、获取类对象的三种方式
1. Class.forName
2. Dog.class
3. new Dog().getClass()
try { Class class1 = Class.forName("yingshe.Dog"); System.out.println(class1); Class class2 = Dog.class; System.out.println(class2); Class class3 = new Dog().getClass(); System.out.println(class3); System.out.println(class1 == class2); System.out.println(class1 == class3); } catch (ClassNotFoundException e) { e.printStackTrace(); }
执行结果
在一个JVM中,一种类,只会有一个类对象存在。所以以上三种方式取出来的类对象,都是一样的。
3、获取类对象的时候会导致类属性被初始化
Dog类加上代码
static String msg; static { System.out.println("初始化msg"); msg="被初始化了"; }
获取类对象
无论什么途径获取类对象,都会导致静态属性被初始化,而且只会执行一次。(除了直接使用 Class c = Hero.class 这种方式,这种方式不会导致静态属性被初始化)
三、创建对象
与传统的通过new 来获取对象的方式不同,反射机制,会先拿到Hero的“类对象”,然后通过类对象直接创建对象或获取“构造器对象” ,再通过构造器对象创建一个对象
T newInstance()
创建由此 类对象表示的类的新实例。
Constructor getConstructor()
返回一个 Constructor对象
import java.lang.reflect.Constructor; public class Test2 { public static void main(String[] args) { //获取类对象 Class classDog = Dog.class; try { //用类对象直接创建 Dog dog1 = (Dog)classDog.newInstance(); dog1.setName("大黄"); dog1.setAge(5); System.out.println(dog1); //获取构造器再创建 //构造器 Constructor constructor = classDog.getConstructor(); //通过构造器实例化 Dog dog2 = (Dog) constructor.newInstance(); dog2.setName("小黑"); dog2.setAge(4); System.out.println(dog2); } catch (Exception e) { e.printStackTrace(); } } }
四、访问属性
为了访问属性,将name修改为public属性
对于private修饰的成员,需要使用setAccessible(true)才能访问和修改。
通过反射修改属性值
import java.lang.reflect.Field; public class Test3 { public static void main(String[] args){ Dog dog =new Dog(); //使用传统方式修改name的值为大黄 dog.name = "大黄"; System.out.println(dog.name); try { //获取类Dog的名字叫做name的字段 Field f1= dog.getClass().getDeclaredField("name"); //修改这个字段的值 f1.set(dog, "小黑"); //打印被修改后的值 System.out.println(dog.name); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
五、部分函数
1、Field[] getFields()
返回包含一个数组 Field对象反射由此表示的类或接口的所有可访问的公共字段、类对象。
2、Field[] getDeclaredFields()
动态的返回的数组 Field对象反映此表示的类或接口声明的所有字段 类对象
增加Dog属性
public class Dog { public String name; //public private int age; private String health; //默认 protected int weigth; //protected //其他函数 }
import java.lang.reflect.Field; public class Test4 { public static void main(String[] args){ Class classDog=Dog.class; Field[] fields1 = classDog.getFields(); System.out.println("类的public属性"); for (Field f : fields1){ System.out.println(f.getName()); } Field[] fields2 = classDog.getDeclaredFields(); System.out.println("类的所有属性"); for (Field f : fields2){ System.out.println(f.getName()); } } }
执行结果
3、Method[] getMethods()
返回包含一个数组 方法对象反射由此表示的类或接口的所有公共方法 类对象,包括那些由类或接口和那些从超类和超接口继承的声明。
4、Method[] getDeclaredMethods()
返回包含一个数组 方法对象反射的类或接口的所有声明的方法,通过此表示 类对象,包括公共,保护,默认(包)访问和私有方法,但不包括继承的方法。
5、Object invoke(Object obj, Object… args)
在具有指定参数的 方法对象上调用此 方法对象表示的底层方法
例如:取得setName方法映射给method,method调用invoke函数第一个对象为操作对象,第二个参数为参数(可以是数组)
import java.lang.reflect.Method; public class Test5 { public static void main(String[] args) { Class classDog = Dog.class; try { Dog dog = (Dog) classDog.newInstance(); Method method = classDog.getMethod("setName", String.class); method.invoke(dog, "名字"); System.out.println(dog.name); } catch (Exception e) { e.printStackTrace(); } } }
六、反射有什么用?
1、首先准备两个业务类,这两个业务类很简单,就是各自都有一个业务方法,分别打印不同的字符串
package reflection; public class Service1 { public void doService1(){ System.out.println("业务方法1"); } }
package reflection; public class Service2 { public void doService2(){ System.out.println("业务方法2"); } }
2、非反射方式
当需要从第一个业务方法切换到第二个业务方法的时候,使用非反射方式,必须修改代码,并且重新编译运行,才可以达到效果
package reflection; public class Test { public static void main(String[] args) { // new Service1().doService1(); new Service2().doService2(); } }
3、反射方式
使用反射方式,首先准备一个配置文件,就叫做spring.txt吧, 放在src目录下。 里面存放的是类的名称,和要调用的方法名。
在测试类Test中,首先取出类名称和方法名,然后通过反射去调用这个方法。
当需要从调用第一个业务方法,切换到调用第二个业务方法的时候,不需要修改一行代码,也不需要重新编译,只需要修改配置文件spring.txt,再运行即可。
这也是Spring框架的最基本的原理,只是它做的更丰富,安全,健壮。
spring.txt
class=yingshe.Service1 method=doService1
TestF.java
import java.io.File; import java.io.FileInputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Properties; public class TestF { @SuppressWarnings({"rawtypes", "unchecked"}) public static void main(String[] args) throws Exception { //从spring.txt中获取类名称和方法名称 File springConfigFile = new File("g:\\spring.txt"); Properties springConfig = new Properties(); springConfig.load(new FileInputStream(springConfigFile)); String className = (String) springConfig.get("class"); String methodName = (String) springConfig.get("method"); //根据类名称获取类对象 Class clazz = Class.forName(className); //根据方法名称,获取方法对象 Method m = clazz.getMethod(methodName); //获取构造器 Constructor c = clazz.getConstructor(); //根据构造器,实例化出对象 Object service = c.newInstance(); //调用对象的指定方法 m.invoke(service); } }
执行结果