反射(基础知识)

来源:互联网 发布:linux c 串口中断 编辑:程序博客网 时间:2024/06/18 03:23

一、反射的概念

反射是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 block5             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     @Override26     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 010 hashCode 011 getClass 012 notify 013 notifyAll 014 015 Reflect.Word 016 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,不可访问为falsepublic void isAccessible()返回AccessibleObject类对象对应Field、Method或Constructor的访问限制,可以访问为true。不可访问为falsepublic 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     @Override15     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 17 name 女神

 

5、数组与Class类

(1)基本数据类型对应的代号表

基本类型代   号基本类型代号booleanZintIbyteBlongJshortSdoubleDcharCfloatF

 

代码示例:

 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长度为:52 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 14 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 参数个数:14 *************下面是通过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 04 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
          修饰符 abstractFINAL
          修饰符 finalNATIVE
          修饰符 nativePRIVATE
          修饰符 privatePROTECTED
          修饰符 protectedPUBLIC
          修饰符 publicSTATIC
          修饰符 staticSTRICTFP
          修饰符 strictfpSYNCHRONIZED
          修饰符 synchronizedTRANSIENT
          修饰符 transientVOLATILE
          修饰符 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方法就可以绕过编译,所以不报错

 

 

0 0
原创粉丝点击