1、认识反射
反射之中包含一个“反”的概念,所以就必须先从“正”开始解释,一般而言,当用户使用一个类的时候,应该先知道这个类,而后通过这个类产生实例化对象,但是“反”指的是通过对象找到类
package cn.test; class Person{}; // 定义一个Person类 public class ReflectDemo { public static void main(String[] args) { System.out.println(Person.class.getName()); // 得到类名 } } 打印 cn.test.Person
以上代码使用一个getClass( )方法,而后就可以得到对象所在的“包”类名称,就属于“反了”,但是在这个“反”的操作之中有一个getClass( )就作为发起一切反射操作的开端。
1.1、取得类的实例化对象
Person的父类是Object类,而上面所使用的getClass()就是Object类中所定义的方法
Class对象:public final Class<?> getClass()
返回此 Object
的运行时类。返回的 Class
对象是由所表示类的 static synchronized
方法锁定的对象,反射中的所有泛型都定义为?,返回值都是Object,而这个getClass( )方法返回的对象是Class类的对象,所以这个Class就是所有反射操作的源头。这个类最为重要,而如果想要取得这个类的实例化对象,Java中定义了三种方式:
(1) 通过Object的getClass( )方法
(2) 通过“类名.class”方式
(3) 使用Class类内部定义的一个static方法,如下描述
public static Class<?> forName(String className) throws ClassNotFoundException
返回与带有给定字符串名的类或接口相关联的 Class 对象。调用此方法等效于:
Class.forName(className, true, currentLoader)
其中
currentLoader
表示当前类的定义类加载器。例如,以下代码片段返回命名为 java.lang.Thread 的类的运行时 Class 描述符。
Class t = Class.forName("java.lang.Thread")
调用
forName("X")
将导致命名为 X 的类被初始化。参数:
className
- 所需类的完全限定名。
initialize
- 是否必须初始化类
loader
- 用于加载类的类加载器返回:具有指定名的类的 Class 对象。
抛出:
LinkageError
- 如果链接失败
ExceptionInInitializerError
- 如果此方法所激发的初始化失败
ClassNotFoundException
- 如果无法定位该类
package cn.test; class Person{}; // 定义一个Person类 public class ReflectDemo { public static void main(String[] args) { /* 方式一:使用Object类的 getClass()方法,基本不用 */ Person p = new Person(); System.out.println(p.getClass().getName()); // 或 Class<?> c = p.getClass(); System.out.println(c.getName()); /* 方式二: 使用类.Class 方式,在日后学习Hibernate开发时候使用 */ Class<?> cls = Person.class; // 取得 Class对象 System.out.println(cls.getName()); // 得到类名 // 或 System.out.println(Person.class.getName()); /*方式三:使用Class类内部定义的一个static方法,主要使用 */ try { Class<?> cl = Class.forName("cn.test.Person"); System.out.println(cl.getName()); } catch (Exception e) { e.printStackTrace(); } } }
费了这个大的周章,去到了Class类的对象又有什么用呢?对于对象的实例化操作之前一直依靠构造方法和关键字New来完成,可是有了Class类对象之后现在又提供了另一种对象实例化方法1.2、通过反射实例化对象
l.2 通过反射实例化对象:使用 newInstance()方法
public T newInstance() throws InstantiationException, IllegalAccessException 创建此 Class 对象所表示的类的一个新实例。如同用一个带有一个空参数列表的 new 表达式实例化该类。如果该类尚未初始化,则初始化这个类。 package cn.test; /* * 定义一个Person类 */ class Person{ public String toString() { return "Person Class Instance"; } }; public class ReflectDemo { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("cn.test.Person"); Person p = (Person)cls.newInstance(); // 实例化对象,相当于new关键字,然后向下转型 System.out.println(p); } } 打印: Person Class Instance
1.3、反射有什么用呢?
可是我们发现,对于对象的实例化操作,除了使用关键字new之外又多了一个反射机制操作,而对于这个操作要比之前使用的new复杂一些,可是这有什么用呢?
对于程序开发模式之前一直强调:尽量减少耦合,而减少耦合最好的做法就是使用接口,但是就算使用了接口也逃不出关键字new,所以实际上new就是造成耦合的关键元凶。
范例:回顾一下之前所编写的工厂设计模式
package cn.test; interface Fruit{ // 水果接口类 public void eat(); } class Apple implements Fruit{ // 苹果类 @Override public void eat() { System.out.println("吃苹果"); } } class Factory{ // 工厂类 public static Fruit getInstance(String className){ if("apple".equals(className)){ return new Apple(); } return null; } } public class ReflectDemo { public static void main(String[] args) throws Exception { Fruit f = Factory.getInstance("apple"); f.eat(); } } 打印: 吃苹果
反射机制实例化对象的时候实际上只需要“包类”就可以了,于是根据此操作,修改工厂设计模式。以上为之前所编写的最简单的工厂设计模式,但是在这个弓藏设计模式之中有一个最大的问题,如果现在接口的子类增加了,那么工厂类肯定需要修改,这就是它所面临的最大问题,而这个最大问题造成的关键字病因就是new,那么如果说不使用关键字new,变为了反射机制呢?
package cn.test; interface Fruit{ // 水果接口类 public void eat(); } class Apple implements Fruit{ // 苹果类 @Override public void eat() { System.out.println("吃苹果"); } } class Factory{ // 工厂类 public static Fruit getInstance(String className){ Fruit f = null; try { f = (Fruit)Class.forName(className).newInstance(); } catch (Exception e) { e.printStackTrace(); } return f; } } public class ReflectDemo { public static void main(String[] args) throws Exception { Fruit f = Factory.getInstance("cn.test.Apple"); f.eat(); } } 打印: 吃苹果
发现,这个时候即使增加了接口子类,工厂类照样完成实例化操作,这才是真正的工厂类,可以应对于所有的变化,如果单独开发角度而已,开发者关系不大,但是对于日后学习的一些框架技术这个就是它实现的命脉,在实现的程序开发上,如果发现操作过程中需要传递一个完整的“包类”名称的时候几乎都是反射机制作用
2、反射的深入应用
以上知识利用Class类作为反射实例化对象的基本应用,但是对于一个实例化对象而言,它需要调用类之中的构造方法、普通方法、属性等,而这些操作都可以通过反射机制完成。
2.1、Class类提供的相关接口
那么在得到对应类的Class对象对应后,我们就可以通过该Class对象得到它所对应的类的一些信息,比如该类的构造函数、成员(属性)、方法(函数);
Class类提供的相关接口介绍:(注:在表中,Class对象对应的类,姑且称为目标类)
接口 |
返回类型 |
接口功能实现 |
getPackage () |
Package |
得到目标类的包名对应的 Package 对象 |
getCanonicalName () |
String |
得到目标类的全名 ( 包名 + 类名 ) |
getName () |
String |
同 getCanonicalName() |
getClassLoader () |
ClassLoader |
得到加载目标类的 ClassLoader 对象 |
getClasses () |
Class<?>[] |
得到目标类中的所有的 public 内部类以及 public 内部接口所对应的 Class 对象 |
getDeclaredClasses () |
Class<?>[] |
同 getClasses() ,但不局限于 public 修饰,只要是目标类中声明的内部类和接口均可 |
getConstructors () |
Constructor<?>[] |
得到目标类的所有 public 构造函数对应的 Constructor 对象 |
getDeclaredConstructors () |
Constructor<?>[] |
同 getConstructors() ,但不局限于 public 修饰,只要是目标类中声明的构造函数均可 |
getField (String arg) |
Field |
得到目标类中指定的某个 public 属性对应的 Field 对象 |
getDeclaredField (String arg) |
Field |
同 getField ,但不局限于 public 修饰,只要是目标类中声明的属性均可 |
getFields () |
Field[] |
得到目标类中所有的 public 属性对应的 Field 对象 |
getDeclaredFields () |
Field[] |
同 getFields() ,但不局限于 public 修饰的属性 |
getMethod(String arg0, Class<?>... arg1) |
method |
得到目标类中指定的某个 public 方法对应的 Method 对象 |
getDeclaredMethod (String arg0, Class<?>... arg1) |
Method |
同 getMethod ,但不局限于 public 修饰的方法 |
getMethods () |
Method[] |
得到目标类中所有的 public 方法对应的 Method 对象 |
getDeclaredMethods () |
Method[] |
同 getMethods() ,但不局限于 public 修饰的方法 |
getEnclosingClass () |
Class |
得到目标类所在的外围类的 Class 对象 |
getGenericInterfaces () |
Type[] |
得到目标类实现的接口对应的 Type 对象 |
getGenericSuperclass () |
Type |
得到目标类继承的父类所对应的 Type 对象 |
getInterfaces () |
Class<?>[] |
得到目标类实现的接口所对应的 Class 对象 |
getSuperclass () |
Class |
得到目标类继承的父类所对应的 Class 对象 |
isMemberClass () |
boolean |
目标类是否为成员类 |
cisAnonymousClass () |
boolean |
目标类是否为匿名类 |
2.2、调用所有构造方法
package cn.test; import java.lang.reflect.Constructor; /* * 定义一个Person内部类, 包含3个构造函数 */ class Person{ public Person(){}; public Person(String name){}; public Person(String name, int age){}; }; public class ReflectDemo2 { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("cn.test.Person"); Constructor<?> cons[] = cls.getConstructors(); for (int i = 0; i < cons.length; i++) { System.out.println(cons[i]); } } } 打印: public cn.test.Person() public cn.test.Person(java.lang.String) public cn.test.Person(java.lang.String,int)
范例: 无参构造函数验证:一个简单的Java类必须存在一个无参构造函数。
package cn.test; /* * 定义一个Person内部类, 没有无参构造函数 */ class Person{ String name; public Person(String name){ this.name = name; }; public String toString(){ return "Person name = "+name; } }; public class ReflectDemo2 { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("cn.test.Person"); Person p = (Person)cls.newInstance(); System.out.println(p); } } 打印: Exception in thread "main" java.lang.InstantiationException: cn.test.Person at java.lang.Class.newInstance0(Class.java:340) at java.lang.Class.newInstance(Class.java:308) at cn.test.ReflectDemo2.main(ReflectDemo2.java:21)
因为以上的方式使用反射实例化对象时需要的是类之中要提供无参构造方法,但是及软没有无参构造方法,那么就必须明确找到一个构造方法,而后使用Constructor
类之中的新方法实例化对象
public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
返回一个
Constructor
对象,它反映此 Class 对象所表示的类的指定公共构造方法。parameterTypes
参数是 Class 对象的一个数组,这些 Class 对象按声明顺序标识构造方法的形参类型。 如果此 Class 对象表示非静态上下文中声明的内部类,则形参类型作为第一个参数包括显示封闭的实例。要反映的构造方法是此 Class 对象所表示的类的公共构造方法,其形参类型与
parameterTypes
所指定的参数类型相匹配。参数:
parameterTypes
- 参数数组返回:与指定的
parameterTypes
相匹配的公共构造方法的Constructor
对象抛出:
NoSuchMethodException
- 如果找不到匹配的方法。
SecurityException
- 如果存在安全管理器 s,并满足下列任一条件:
调用
s.checkMemberAccess(this, Member.PUBLIC)
拒绝访问构造方法调用者的类加载器不同于也不是当前类的类加载器的一个祖先,并且对
s.checkPackageAccess()
的调用拒绝访问该类的包
package cn.test; import java.lang.reflect.Constructor; /* * 定义一个Person内部类, 没有无参构造函数 */ class Person{ String name; int age; public Person(String name, int age){ this.name = name; this.age = age; }; public String toString(){ return "Person name = "+name+" ,age="+age; } }; public class ReflectDemo2 { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("cn.test.Person"); // 取得Class对象 // 取得指定参数类型的构造方法 Constructor<?> cons = cls.getConstructor(String.class,int.class); Object obj = cons.newInstance("PrettyGril",20); // 为构造方法传递参数 System.out.println(obj); } } 打印: Person name = PrettyGril ,age=20
很明显,调用无参构造方法实例化对象要比调用有参构造更加简单,方便,所以在日后所有开发中,凡是有简单Java类出现的地方,都一定要有无参构造方法
2.3、调用普通方法
1) 取得全部方法
public Method[] getMethods() throws SecurityException
2) 取得指定方法
public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
2.3.1 取得一个类中所有定义的全部方法
package cn.test; import java.lang.reflect.Method; /* * 定义一个Person内部类 */ class Person { String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }; public class ReflectDemo2 { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("cn.test.Person"); // 取得Class对象 Method met[] = cls.getMethods(); // 取得全部方法 for (int i = 0; i < met.length; i++) { System.out.println(met[i]); } } } 打印: public java.lang.String cn.test.Person.getName() public void cn.test.Person.setName(java.lang.String) public final void java.lang.Object.wait() throws java.lang.InterruptedException public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException public native int java.lang.Object.hashCode() public final native java.lang.Class java.lang.Object.getClass() public boolean java.lang.Object.equals(java.lang.Object) public java.lang.String java.lang.Object.toString() public final native void java.lang.Object.notify() public final native void java.lang.Object.notifyAll()
调用方法:使用Method类中的Invoke方法对于取得了Method类对象之后还有一个最大的功能,就是可以利用反射调用类中的方法;
public Object invoke(Object obj, Object... args)
throws IllegalAccessException,IllegalArgumentException,
InvocationTargetException
之前调用类中方法时候都是“对象.方法”,但是现在有了反射之后,可以直接利用Object类调用指定子类的操作方法(同时解释一下,为什么setter、getter方法的命名要求如此严格)
范例:利用反射调用Person类之中的setName()、getName()方法
package cn.test; import java.lang.reflect.Method; /* * 定义一个Person内部类 */ class Person { String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }; public class ReflectDemo2 { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("cn.test.Person"); // 取得Class对象 Object obj = cls.newInstance(); String attribute = "Name"; Method setMet = cls.getMethod("set"+attribute, String.class); Method getMet = cls.getMethod("get"+attribute); setMet.invoke(obj, "PrettyGirl"); System.out.println(getMet.invoke(obj)); } } 打印: PrettyGirl
在日后的所有框架技术开发之中,简单Java类都是如此应用的,所以必须按照标准进行
3、 调用成员变量
类之中最后一个组成部分就是成员(Field,也称属性)
取得本类的全部成员:
public Field[] getDeclaredFields() throws SecurityException
取得指定的成员:
public Field getDeclaredField(String name) throwsNoSuchFieldException, SecurityException
3.1 获得全部属性
package cn.test; import java.lang.reflect.Field; /* * 定义一个Person内部类 */ class Person { String name; }; public class ReflectDemo2 { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("cn.test.Person"); // 取得Class对象 Field fie[] = cls.getDeclaredFields(); // 获得全部属性 for (int i = 0; i < fie.length; i++) { System.out.println(fie[i]); } } } 打印 java.lang.String cn.test.Person.name
在Field中有2个操作方法
(1)设置属性内容(类似于:对象.属性 = 内容)
public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException
(2)取得属性内容(类似于:对象.属性)
public Object get(Object obj) throws IllegalArgumentException, IllegalAccessException
package cn.test; import java.lang.reflect.Field; /* * 定义一个Person内部类 */ class Person { private String name; }; public class ReflectDemo2 { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("cn.test.Person"); // 取得Class对象 Object obj = cls.newInstance(); Field fie = cls.getDeclaredField("name"); // 获得属性 fie.set(obj, "PrettyGril"); // 赋值 System.out.println(fie.get(obj)); } } 打印: Exception in thread "main" java.lang.IllegalAccessException: Class cn.test.ReflectDemo2 can not access a member of class cn.test.Person with modifiers "private" at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65) at java.lang.reflect.Field.doSecurityCheck(Field.java:960) at java.lang.reflect.Field.getFieldAccessor(Field.java:896) at java.lang.reflect.Field.set(Field.java:657) at cn.test.ReflectDemo2.main(ReflectDemo2.java:17)
因为设置的name访问权限是私有的,所以不能进行赋值,可是从类的开发要求而言,一直都强调类之中属性必须封装,所以现在调用之前要想办法解除封装,只需一句代码: fie.setAccessible(true); // 解除封装
package cn.test; import java.lang.reflect.Field; /* * 定义一个Person内部类 */ class Person { private String name; }; public class ReflectDemo2 { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("cn.test.Person"); // 取得Class对象 Object obj = cls.newInstance(); Field fie = cls.getDeclaredField("name"); // 获得属性 fie.setAccessible(true); // 解除封装 fie.set(obj, "PrettyGril"); // 赋值 System.out.println(fie.get(obj)); } } 打印: PrettyGril
虽然反射机制运行直接操作类之中的属性,可是不会有任何一种程序直接操作属性,都会通过setter、getter方法调用。