一、反射的概念
反射是java语言提供的一项非常吸引人的特性,利用反射可以在运行时对程序进行动态的控制。开发使用反射特性的程序需要使用一套专门的工具类,这些类大部分都位于java.lang.reflect包中。
反射的操作都是编译之后的操作。
二、Class类
Class类属于java.lang包,不需要使用import语句引入特殊的包就可以使用,其对象代表一个类,携带类的相应信息,主要包括构造器、方法、成员变量等。其实,java程序运行过程中,每个类被加载都在内存中产生一个对应的Class类对象,在普通应用程序中这些对象是由系统自动维护的,开发人员不需要关心。
1、对类和对象的理解
(1)在面向对象的世界里,万事万物皆对象
(2)java语言中静态的成员、普通数据类型类不是对象。
(3)类是对象,类是java.lang.Class类的实例对象。(任何一个类都是Class的实例对象)
2、任何类都是Class的实例对象,这个实例对象有三种表示方式(可以通过三种方式获取Class类对象)
类类型不可以使用Class类的构造器来创建Class类对象,只能使用其提供的静态工厂方法来获取对象。
(1)实际上,在告诉我们任何类都有一个隐含的静态成员变量Class,所以可以通过下面方式获取Class类对象
Class c1=FOO.class; // FOO是一个类
(2)已知一个类的实例对象,通过getClass方法获取Class类对象
1 FOO foo=new FOO();
2 Class c1=foo.getClass();
注:c1,c2都代表了FOO类的Class类对象,但是一个类只可能是Class类的一个实例对象。
1 package Reflect; 2 3 public class Test { 4 5 public static void main(String[] args) { 6 FOO foo=new FOO(); 7 Class c=foo.getClass(); 8 Class c1=FOO.class; 9 System.out.println(c1==c); 10 } 11 }
运行结果:
true
(3)通过Class.forName(String className),className指出类的全称名。,如果找不到,就会抛出ClassNotFoundException异常
1 try { 2 Class c3=Class.forName("Reflect.FOO"); 3 } catch (ClassNotFoundException e) { 4 // TODO Auto-generated catch block 5 e.printStackTrace(); 6 } 7 }
3、Class类的常用方法
Class类没有构造方法,所以很自然创建Class类对象,不能通过构造器来创建,而只能通过其提供的静态工厂方法来获取对象。Filed、Method、Constructor为java.lang.reflect包的类,它们分别代表了成员变量、方法、构造器,分别携带对应成员变量、方法、构造器信息。package类在java.lang包中,其对象代表一个包,携带相应包的信息。
Class类的常用方法表
方法关键字 | 含义 |
public Method[] getDeclaredMethods() | 获取声明的所有的方法 |
public Method getDeclaredMethod("方法名",参数类型.class,……) | 获得特定的方法 |
public Constructor[] getDeclaredConstructors() | 获取所有声明的构造方法 |
public Constructor[] getConstructors() | 获取所有的构造方法 |
public Constructor getDeclaredConstructor(参数类型.class,……) | 获取特定的构造方法 |
public Field[] getFields() | 获取所有成员变量 |
public Field[] getDeclaredFields() | 获取所有声明的成员变量 |
public Field getField(String name) | 获取指定的成员变量 |
public Package getPackage() | 返回Class对象所对应类所在的包 |
public String getName() | 返回Class对象对应类的名称 |
public static Class forName(String className) | 通过此方法获取指定名称的类对应的Class对象,className指出类的全名称 |
Public Object newInstance() | 调用默认的无参构造器新建Class对象对应类的一个对象。 |
注:标红的两个方法是Class类实现动态加载类的核心方法。
通过newInstance()方法创建的对象与普通方式创建的对象在使用上完全相同(区别:用于动态加载类后的实例化)。
代码示例:
1 package Reflect; 2 3 public interface OfficeAble { 4 5 public void start(); 6 } 7 8 9 package Reflect; 10 11 public class Word implements OfficeAble { 12 13 private String name; 14 private String gender; 15 public Word() { 16 System.out.println("word的构造方法1~~"); 17 } 18 19 public Word(String name,String gender) { 20 this.name=name; 21 this.gender=gender; 22 System.out.println("word的构造方法1~~"+name+" "+gender); 23 } 24 25 @Override 26 public void start() { 27 System.out.println("word..start......."); 28 } 29 30 public void start1() { 31 System.out.println("word..start1......."); 32 } 33 34 } 35 36 package Reflect; 37 38 import java.lang.reflect.Constructor; 39 import java.lang.reflect.Field; 40 import java.lang.reflect.Method; 41 42 public class OfficeBetter { 43 44 public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { 45 //动态加载类,在运行时刻加载 46 Class c=Class.forName("Reflect.Word"); 47 //通过类类型,创建该类对象 48 OfficeAble oa=(OfficeAble) c.newInstance(); 49 oa.start(); 50 51 //获取Word类中所有构造函数对象,所有成员变量对象,所有方法对象 52 Constructor []cs=c.getConstructors(); 53 Field []fd=c.getFields(); 54 Method []md=c.getMethods(); 55 56 //遍历构造函数对象,成员变量对象,方法对象,打印他们的信息 57 for (Method method : md) { 58 System.out.println(method.getName()+" "+method.getParameterCount()); 59 } 60 61 for (Field field : fd) { 62 System.out.print(field.getName()+" "); 63 } 64 System.out.println(fd.length); 65 66 for (Constructor constructor : cs) { 67 System.out.println(constructor.getName()+" "+constructor.getParameterCount()); 68 } 69 } 70 71 }
运行结果:
1 word的构造方法1~~ 2 word..start....... 3 start 0 4 start1 0 5 wait 0 6 wait 2 7 wait 1 8 equals 1 9 toString 0 10 hashCode 0 11 getClass 0 12 notify 0 13 notifyAll 0 14 0 15 Reflect.Word 0 16 Reflect.Word 2
分析:仔细观察会发现,Field返回的对象的名称没有打印出来,当成员变量的属性为private时,通过正常的反射获取其成员变量将会失败,但是不会报错,当成员变量的属性改成public时,反射获取其成员变量将会成功。也就是说没有取消访问限制反射机制不能打破面向对象编程中,私有的成员变量封装在类中,不能通过外部直接访问,需要通过该类的方法才能访问。没有取消访问限制反射不能获取的是private修饰的任何信息。
4、取消访问限制
当用Class对象的getDeclaredXXXs()方法获得Field、Method或Constructor时由于访问限制的作用可能某些字段、方法或构造器是不能访问的。如果需要通过反射访问这些不允许访问的元素,则需要首先去除访问限制,然后在访问。
若希望取出访问限制,则需要使用java.lang.reflect.AccessibleObject类,其中提供了一些用来去除访问限制的方法。
AccessibleObject类中与访问限制有关的方法
方法签名 | 功能说明 |
public void setAccessible(boolean flag) | 设置AccessibleObject类对象对应的Field、Method或Constructor的访问限制,可以访问为true,不可访问为false |
public void isAccessible() | 返回AccessibleObject类对象对应Field、Method或Constructor的访问限制,可以访问为true。不可访问为false |
public static void setAccessible(AccessibleObject[] array,boolean flag) | 设置一个数组中所有AccessibleObject对象对应的Field、Method或Constructor的访问标志,可以访问为true,不可访问为false |
代码演示:
1 package Reflect; 2 3 public interface OfficeAble { 4 5 public void start(String name); 6 } 7 8 package Reflect; 9 10 public class Word implements OfficeAble { 11 12 private String name; 13 14 @Override 15 public void start(String name) { 16 System.out.println("word..start......."+name); 17 } 18 private void start1(String name) { 19 System.out.println("word..start......."+name); 20 } 21 private void start2(String name){ 22 System.out.println("word..start......."+name); 23 } 24 } 25 26 package Reflect; 27 28 import java.lang.reflect.AccessibleObject; 29 import java.lang.reflect.Constructor; 30 import java.lang.reflect.Field; 31 import java.lang.reflect.InvocationTargetException; 32 import java.lang.reflect.Method; 33 import java.lang.reflect.Modifier; 34 35 public class OfficeBetter { 36 37 public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { 38 //动态加载类,在运行时刻加载 39 Class c=Class.forName("Reflect.Word"); 40 //通过类类型,创建该类对象 41 OfficeAble oa=(OfficeAble) c.newInstance(); 42 oa.start("女神"); 43 44 //获取Word类中所有构造函数对象,所有成员变量对象,所有方法对象 45 46 Field []fd=c.getDeclaredFields(); 47 Method []md=c.getDeclaredMethods(); 48 49 //遍历构造函数对象,成员变量对象,方法对象,打印他们的信息 50 //设置单个成员变量对象访问限制为允许(设置所有方法对象可访问) 51 AccessibleObject.setAccessible(md, true); 52 for (Method method : md) { 53 StringBuffer sb=new StringBuffer(); 54 if((method.getModifiers()&Modifier.PRIVATE)!=0){ 55 sb.append(" private "); 56 } 57 System.out.println("修饰符:"+sb); 58 } 59 60 //设置单个成员变量对象访问限制为允许(设置单个成员变量对象可访问) 61 fd[0].setAccessible(true); 62 System.out.println(fd.length); 63 for (Field field : fd) { 64 65 field.set(oa, "女神"); 66 System.out.print(field.getName()+" "+field.get(oa)); 67 } 68 } 69 }
运行结果:
1 word的构造方法~~ 2 word..start.......女神 3 修饰符: 4 修饰符: private 5 修饰符: private 6 1 7 name 女神
5、数组与Class类
(1)基本数据类型对应的代号表
基本类型 | 代 号 | 基本类型 | 代号 |
boolean | Z | int | I |
byte | B | long | J |
short | S | double | D |
char | C | float | F |
代码示例:
1 package Reflect; 2 3 public class Test { 4 5 public static void main(String[] args) { 6 //创建数组对象 7 String [] stringArray=new String[4]; 8 int[][] intArray=new int[9][9]; 9 byte []btArray=new byte[2]; 10 //获取数组对象对应的Class类 11 Class sc=stringArray.getClass(); 12 Class sc1=intArray.getClass(); 13 Class sc2=btArray.getClass(); 14 //打印两个数组对应的类名 15 System.out.println("一维String数组对应的类名为:"+sc.getName()); 16 System.out.println("二维int数组对应的类名为:"+sc1.getName()); 17 System.out.println("一维byte数组对应的类名为:"+sc2.getName()); 18 }
运行结果:
1 一维String数组对应的类名为:[Ljava.lang.String; 2 二维int数组对应的类名为:[[I 3 一维byte数组对应的类名为:[B
注:要特别注意的是数组对应的类比较特殊,没有提供可用的构造器,因此无法直接使用Class类提供的静态工厂方法newInstance()来创建。
(2)利用反射动态的创建数组对象
由于数组类型比较特殊,没有提供任何可用的构造器,因此不能使用Class类或Constructor类的newInstance方法来创建数组对象。但是这并不意味着不可以使用反射技术动态创建数组,java.lang.reflect.Array类中专门提供了动态创建数组的newInstance()方法。
Arrays类中的newInstance方法表
方法签名 | 功能说明 |
pubic static Object newInstance(Class componentType, int length) | 生成指定类型的一维数组,compinenType指出数组元素的类型,length指出数组的长度 |
public static Object newInstance(Class componentType,int[] dimensions) | 生成指定类型的多维数组,componentType指出数组元素的类型,dimensions指出数组每一维的长度 |
1)如果数组元素为基本数据类型,则使用"<封装类类名>.TYPE"来指出数组元素的类型。
2)两个方法返回的都是Object类型,需要进行恰当的强制类型转换以便以后使用
代码示例:
1 package Reflect; 2 3 import java.lang.reflect.Array; 4 5 public class Test { 6 7 public static void main(String[] args) { 8 //使用反射的方式动态的创建一维int行数组 9 int[] intArray=(int[])Array.newInstance(Integer.TYPE, 5); 10 //使用反射的方式动态创建二维String型数组 11 String [][]stringArray=(String[][])Array.newInstance(String.class, new int[]{4,5}); 12 //打印两个数组的长度信息 13 System.out.println("intArray长度为:"+intArray.length); 14 System.out.println("stringArray第一维长度为:"+stringArray.length+",第二维长度:"+stringArray[0].length); 15 } 16 }
运行结果:
1 intArray长度为:5 2 stringArray第一维长度为:4,第二维长度:5
三、精确判断对象类型
1、instanceof无法用来判断对象的精确程度。如果需要判断对象的精确程度就需要使用反射技术。
1 package Reflect; 2 3 class MyFather{ 4 5 } 6 7 class MyClass extends MyFather{ 8 9 } 10 public class Test { 11 12 public static void main(String[] args) throws ClassNotFoundException { 13 //创建MyClass类对象 14 MyClass mc=new MyClass(); 15 //用instanceof判断类型 16 System.out.println("instanceof的判断结果:"); 17 if(mc instanceof MyFather){ 18 System.out.println("对象是MyFather类型的!!"); 19 }else{ 20 System.out.println("对象不是MyFather类型的!"); 21 } 22 23 //用反射精确判断类型 24 System.out.println("反射判断的结果:"); 25 if(mc.getClass()==Class.forName("Reflect.MyFather")){ 26 System.out.println("对象是MyFather类型的!!"); 27 }else{ 28 System.out.println("对象不是MyFather类型的!"); 29 } 30 31 } 32 }
运行结果:
1 instanceof的判断结果: 2 对象是MyFather类型的!! 3 反射判断的结果: 4 对象不是MyFather类型的!
2、Field类
Field类的对象代表成员变量,携带成员变量的信息。要注意的是,与Class类类似,一样不可以通过构造器创建Field类的对象,其对象都是通过Class类对象提供的get()系列方法获得的。
Field类的常用发方法表
方法签名 | 功能说明 |
public String getName() | 返回Field对象对应成员变量的名称 |
public Class getType() | 返回Field对象所对应成员变量的类型 |
public Object get(Object obj) | 返回指定对象此成员变量的值,obj为指定对象的引用,不管是何种类型的值都将成为Object类型返回 |
public xxx getXxx(Object obj) | 返回指定对象此成员变量的值,obj为指定的对象的引用,xxx代表8中基本数据类型当中的一种,如getBoolean返回boolean型值 |
public void set(Object obj,Object value) | 设置指定对象此成员变量的值,obj为指定对象的引用,value为指定的值,若为基本数据类型的值,则使用对应封装类对象。 |
package Reflect; public interface OfficeAble { public void start(); } package Reflect; public class Word implements OfficeAble { public String name; public Word() { System.out.println("word的构造方法1~~"); } public Word(String name) { this.name=name; System.out.println("word的构造方法1~~"+name); } @Override public void start() { System.out.println("word..start......."); } public void start1() { System.out.println("word..start1......."); } } package Reflect; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class OfficeBetter { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { //动态加载类,在运行时刻加载 Class c=Class.forName("Reflect.Word"); //通过类类型,创建该类对象 OfficeAble oa=(OfficeAble) c.newInstance(); oa.start(); //获取Word类中所有构造函数对象,所有成员变量对象,所有方法对象 Field []fd=c.getFields(); //遍历构造函数对象,成员变量对象,方法对象,打印他们的信息 System.out.println(fd.length); for (Field field : fd) { field.set(oa, "女神"); System.out.print(field.getName()+" "+field.get(oa)); } } }
运行结果:
1 word的构造方法1~~ 2 word..start....... 3 1 4 name 女神
分析:之前代码中就有讲过反射不能获取类中的private修饰的信息,所以Field的set()方法只能对非praivate的对象赋值。
3、Method类
Method类的对象代表一个方法,携带方法的相关信息与Field类类似,其对象也不能通过构造器创建,而是要使用Class对象提供的get()系列方法获得。
Method类的一些常用方法
方法签名 | 功能说明 |
public Object invoke(Object obj,Object []args) | 此方法用来调用Method对象的方法,返回值为调用方法的返回值 |
public String getName() | 返回此方法对应的名称 |
public Class []getParameterTypes() | 返回此方法的参数序列 |
public Class getReturnType() | 返回此方法的返回类型 |
对于invoke()方法有如下几点需要注意
(1)不管实际对应方法的返回值是什么类型,都作为Object类型返回。若返回值为基本的数据类型,则返回对应封装类的对象。
(2)obj参数指出要被调用方法所属的对象,若调用静态的方法用null值。
(3)args指出要被调用方法的参数序列,若方法没有参数则传递空数组--new Object[0],若方法有基本数据类型的参数则使用基本参数类型的封装类对象。
代码示例:
package Reflect; public interface OfficeAble { public void start(String name); } package Reflect; public class Word implements OfficeAble { public String name; public Word() { System.out.println("word的构造方法1~~"); } public Word(String name) { this.name=name; System.out.println("word的构造方法1~~"+name); } @Override public void start(String name) { System.out.println("word..start......."+name); } } package Reflect; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class OfficeBetter { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { //动态加载类,在运行时刻加载 Class c=Class.forName("Reflect.Word"); //通过类类型,创建该类对象 OfficeAble oa=(OfficeAble) c.newInstance(); oa.start("女神"); //获取Word类中所有构造函数对象,所有成员变量对象,所有方法对象 Method []md=c.getDeclaredMethods(); //遍历构造函数对象,成员变量对象,方法对象,打印他们的信息 for (Method method : md) { System.out.println(method.getName()+" 参数个数:"+method.getParameterCount()); System.out.println("*************下面是通过invoke方法调用的Method********"); method.invoke(oa, "小帅"); } } }
运行结果:
1 word的构造方法1~~ 2 word..start.......女神 3 start 参数个数:1 4 *************下面是通过invoke方法调用的Method******** 5 word..start.......小帅
4、Constructor类
Constructor类的对象代表了一个构造器,携带构造器的相关信息,与Field、Method类型类似,其对象也不能通过构造器创建,而是要使用Class对象提供的get()系列方法获得。
Constuctor类的一些常用方法
方法签名 | 功能说明 |
public Object newInstance(Object []initargs) | 此方法用啦调用Constructor对象代表的构造器,创建构造器所属类的对象。initargs为构造器参数数组。注意无论创建的对象是何种类型,此方法的返回值都为Object类型,要适当进行强制类型转换。 |
public String getName() | 返回构造器的名称 |
public Class getParameterTypes() | 返回构造器的参数序列 |
代码示例:
package Reflect; public interface OfficeAble { public void start(String name); } package Reflect; public class Word implements OfficeAble { public String name; public Word() { System.out.println("word的构造方法~~"); } public Word(String name) { this.name=name; System.out.println("word的构造方法1~~"+name); } @Override public void start(String name) { System.out.println("word..start......."+name); } } package Reflect; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class OfficeBetter { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { //动态加载类,在运行时刻加载 Class c=Class.forName("Reflect.Word"); //通过类类型,创建该类对象 OfficeAble oa=(OfficeAble) c.newInstance(); oa.start("女神"); //获取Word类中所有构造函数对象,所有成员变量对象,所有方法对象 Constructor []cs=c.getConstructors(); //Field []fd=c.getFields(); //Method []md=c.getDeclaredMethods(); //遍历构造函数对象,成员变量对象,方法对象,打印他们的信息 //System.out.println(); for (Constructor constructor : cs) { System.out.println(constructor.getName()+" "+constructor.getParameterCount()); if(constructor.getParameterCount()!=0){ constructor.newInstance("男神"); } } } }
运行结果:
1 word的构造方法1~~ 2 word..start.......女神 3 Reflect.Word 0 4 Reflect.Word 1 5 word的构造方法1~~男神
5、反射与修饰符
有关修饰符的信息,也可以通过反射获得,Class、Fileld、Method、Constructor类都提供了用于获取各自表示的类、成员变量、方法、构造器对应修饰符的方法getModifiers,下面给出了该方法的签名
public int getModifiers()
(1)Class、Field、Method、Constructor四个类提供的getModifiers()方法签名功能完全相同
(2)getModifiers方法返回值为整数,不同的整数代表不同的修饰符组合。(表示不同修饰符的整数都定义在java.lang.reflect.Modifier类中,作为Modifier类的静态常量)
Modifier类中表示修饰符静态常量
ABSTRACT 修饰符 abstract |
FINAL 修饰符 final |
NATIVE 修饰符 native |
PRIVATE 修饰符 private |
PROTECTED 修饰符 protected |
PUBLIC 修饰符 public |
STATIC 修饰符 static |
STRICTFP 修饰符 strictfp |
SYNCHRONIZED 修饰符 synchronized |
TRANSIENT 修饰符 transient |
VOLATILE 修饰符 volatile |
注:如果同时具有多个修饰符,则getModifiers()方法返回值是几个修饰符的常量和。
代码示例:(只演示Method类或的Modifier静态常量的解析)
package Reflect; public interface OfficeAble { public void start(String name); } package Reflect; public class Word implements OfficeAble { @Override public void start(String name) { System.out.println("word..start......."+name); } public static void start1(String name) { System.out.println("word..start......."+name); } } package Reflect; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; public class OfficeBetter { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { //动态加载类,在运行时刻加载 Class c=Class.forName("Reflect.Word"); //获取Word类中所有构造函数对象,所有成员变量对象,所有方法对象 ; Method []md=c.getDeclaredMethods(); //遍历构造函数对象,成员变量对象,方法对象,打印他们的信息 for (Method method : md) { StringBuffer sb=new StringBuffer(); if((method.getModifiers()&Modifier.PUBLIC)!=0){ sb.append(" public "); } if((method.getModifiers()&Modifier.STATIC)!=0){ sb.append(" static "); } System.out.println("修饰符:"+sb); } } }
运行结果:
1 修饰符: public 2 修饰符: public static
四、通过Class,Method来认识泛型的本质
反射的操作都是编译之后的操作:程序员写的java应用程序,在运行之前需要被编译成.class文件,反射的操作就是在程序在编译成.class文件之后进行的。
1 package Reflect; 2 3 import java.lang.reflect.Array; 4 import java.lang.reflect.InvocationTargetException; 5 import java.lang.reflect.Method; 6 import java.util.ArrayList; 7 8 public class Test { 9 10 public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { 11 //实例化两个ArrayList集合对象 12 ArrayList<String> list=new ArrayList<String>(); 13 ArrayList list1=new ArrayList(); 14 //同过实例的两个ArrayList集合对象各自的获取Class类对象 15 Class c=list.getClass(); 16 Class c1=list1.getClass(); 17 //判断两个集合的Class类对象是否相等,若打印true则相等,false则不相等 18 System.out.println(c==c1); 19 /* 20 * c==c1结果返回的为true说明编译之后集合的泛型是去泛型化的 21 * java中集合的泛型,是为了防止错误输入,只在编译阶段有效,绕过编译就无效了。 22 * 验证:我们通过方法的反射操作,绕过编译 23 * (反射操作都是编译后的,所以Method的invoke函数调用ArrayList类对象的add和get方法就可以绕过编译过程了) 24 */ 25 Method method=c.getMethod("add", Object.class); 26 method.invoke(list, 1); 27 Method method1=c.getMethod("get", Integer.TYPE); 28 System.out.println("list集合中添加数字:"+method1.invoke(list, 0)+" 成功"); 29 } 30 31 }
运行结果:
1 true 2 list集合中添加数字:1 成功
分析:反射操作都是编译后的,所以Method的invoke函数调用ArrayList类对象的add和get方法就可以绕过编译,所以不报错