Java反射机制详解

来源:互联网 发布:dnf补丁制作软件 编辑:程序博客网 时间:2024/06/07 07:10

前言

通过本篇的学习可以到达以下目标

  • 了解反射的基本原理
  • 掌握Class类的使用
  • 通过Class类并结合java.lang.reflect包取得一个类的完整结构
  • 通过反射机制动态调用类中的指定方法,并能向这些方法传递参数

在Java中较为重要的就是反射机制,那么什么是反射机制呢?举个简单的例子来说,正常情况下如果已经有一个类,则肯定可以通过类创建对象;那么如果现在要求通过一个对象找到一个类找到一个类的名称,此时就需要用到反射机制。如果要完成反射操作,则首先应该认识的就是Class类。

在反射操作的学习中,读者一定要把握一个核心的概念:”一切的操作都将使用Object完成,类,数组的引用都可以使用Object进行接收”,只有把握了这个概念才能更清楚的掌握反射机制的作用。

认识Class类

在正常情况下,需要先有一个类的完整路径引入之后才可以按照固定的格式产生实例化对象,但是在Java中也允许通过一个实例化对象找到一个类的完整信息,那么这就是class类的功能。在讲解Class之前,先来看以下一段代码。

通过一个对象得到完整的的包.类名称

package org.ljz.getclassdemo;class X{}public class GetClassDemo01{public static void main(String[] args){X x=new X();  //实例化x对象System.out.println(x.getClass().getName());//得到对象所在类  }}  

程序运行结果:

org.ljz.getclassdemo.X

从程序的运行结果中可以发现,通过一个对象得到了对象所在的完整的“包.类”名称,那些有些读者会觉得奇怪,getClass()方法是那里定义的呢?从之前学习过的知识的读者可以知道,任何一个类如果没有明确地声明继承自哪个父类时,则默认继承Object类,所以getClass()方法是从Object类继承而来的,此方法的定义如下:

public final Class getClass()

以上方法返回值的类型是一个Class类,实际上此类是Java反射的源头。所谓反射从程序的运行结果来看也很好理解,即可以通过对象反射求出类的名称,如图所示:
这里写图片描述

提示

所有类的对象实际上都是Class类的实例。
在Java中Object类是一切类的父类,那么所有类的对象实际上也都是java.lang.Class类的实例,所以所有的对象都可以转变为java.lang.Class类型表示。

在定义Class类时也是用了泛型声明,所以为了让程序不出现警告信息,可以指定好其操作的泛型类型,下面介绍Class类的作用。

Class类表示一个类的本身,通过Class可以完整地得到一个类中的完整结构,包括此类中的方法定义,属性定义等。
这里写图片描述

在class类中本身没有定义任何的构造方法,所以如果要使用则首先必须通过forName()的静态方法实例化对象。除此之外,也可以使用”类.class” 方法实例化

实例化类对象

package org.ljz.getclassdemo;class X{}public class GetClassDemo02{public static void main(String args){Class<?> c1=null;  //指定泛型Class<?> c2=null;  //指定泛型Class<?> c3=null;  //指定泛型try{c1=Class.forName("org.ljz.getclassdemo.X"); //最常用的形式}catch(ClassNotFountException e){e.printStackTrace();}c2=new X().getClass(); //通过Object类中方法实例c3=X.class;   //通过类.class实例化System.out.println("类名称:" +c1.getName());System.out.println("类名称:"+c2.getName());System.out.println("类名称:"+c3.getName());  } } 

程序运行结果:

类名称:org.ljz.demo.getclassdemo.X
类名称:org.ljz.demo.getclassdemo.X
类名称:org.ljz.demo.getclassdemo.X

从程序的运行结果中可以发现,3中实例化Class对象的方式是一样的,但是使用forName()的静态方法实例化Class对象的是较为常见的一种方式,读者应该重点掌握。

Class类的使用

了解了Class类的实例化过程,那么到底该如何去使用Class类呢?
实际上Class类在开发中最常见的做法就是实例化对象的操作,既可以通过一个给定的字符串(此字符串包含了完整的“包.类”的路径)来实例化一个类的对象。

通过无参构造实例化对象

如果想要通过Class类本身实例化其他类的对象,则可以使用newInstance()方法,但是必须要保证被实例化的类存在一个无参构造方法,代码如下:

package org.ljz.instancedemoclass Person{   //定义Person类private String name;  //定义name属性private int age;  //定义age属性public String getName(){   //取得name属性return name;}public void setName(String name){ //设置name属性 this.name=name;}public int getAge(){  //取得age属性return age;} public void setAge(int age){ //设置age属性this.age=age;}public String toString(){retur "姓名:"+this.name+",年龄"+this.age;  }}public class InstanceDemo01{public static void main(String[] args){Class<?> c=null;   //指定泛型try{c=Class.forname("org.ljz.instancedemo");//传入要实例化的完整包.类名称}catch(ClassNotFoundException e){e.printStackTrace();}Person per=null; //声明一个person对象try{per=(Person)c.newInstance(); //实例化Person对象}catch(Exception e){e.printStackTrace();}per.setName("ljz");  //设置姓名per.setAge(23);    //设置年龄System.out.println(per);//内容输出,调用toString()   }}     

程序运行结果:

姓名:ljz,年龄:23

从程序的运行结果可以发现,通过Class.forName()方法实例化Class对象之后,直接调用newInstance()方法就可以根据传入的完整”包.类”名称的方式进行对象的实例化操作,完全取代了之前使用关键字new的操作方式。
但是在使用上操作时读者一定要记住一点,被实例化对象的类中必须存在无参构造方法,如果不存在,则肯定是无法实例化的。

提示

各种高级应用中都提倡类中存在无参的构造方法。
在实际的Java程序开发中,反射是最为重要的操作原理,在现在的开发中大量地应用了反射处理机制,如Struts,Spring框架等;在大部分的操作中基本上都是操作无参的构造方法,所以希望读者日后使用反射开发时一定要保留无参构造方法。

反射的应用——取得类的结构

在实际开发中,以上程序就是反射应用最多的地方。当然,反射机制所提供的功能远不止如此,还可以通过反射得到一个类的完整结构,那么这就要使用到java.lang.reflect包中的以下几个类。

  • Constructor:表示类中的构造方法
  • Field:表示类中的属性
  • Method:表示类中的方法
  • 这3个类都是AccessibleObject类的子类,如图所示:
    这里写图片描述

下面通过以上几个类和Class类共同完成类的反射操作。
Person.java

package org.ljz.demointerface China{  //定义China接口public static final String NATIONAL="China"; //定义全局常量public static final String AUTHOR="ljz"; //定义全局常量public void sayChina(); //定义无参的抽象方法public String sayHello(String name,int age);//定义有参的抽象方法}public class Person implements China{ //定义Person类实现接口private String name;private int age;public Person(){  //声明无参的构造方法}public Person(String name){  //声明一个参数的构造方法this.name=name;}public Person(Strin name,int age){ //声明两个参数的构造方法this(name);this.setAge(age);}public String getName(){   //取得name属性内容return name;}public void setName(String name){ //设置name属性内容this.name=name;}public int getAge(){   //取得age属性内容return age;}public void setAge(int age) //设置age属性内容this.age=age;}public void sayChina(){  //覆写方法输出信息System.out.println("作者:"+AUTHOR+",国籍:"+NATIONAL);}public String sayHello(String name,int age){  //覆写方法,返回信息return name+",你好!我今年"+age+"岁了!";  }}

取得实现的全部接口

要取得一个类实现的全部接口,则必须使用Class类中的getInterface()方法。此方法定义如下:

public Class[] getInterfaces()

getInterfaces()方法返回一个Class类的对象数组,之后直接利用Class类中的getName()方法取得类的名称。

取得Person类实现的全部接口

package org.ljz.classinfodemo;public class GetInterfaceDemo{public static void main(String[] args){Class<?> c1=null; //声明一个class对象try{c1=Class.forName("org.ljz.demo.Person"); //实例化Class对象}catch(ClassNotFountException e){e.printStackTrace();}Class<?> c[]=c1.getInterfaces(); //取得实现的全部接口for(int i=0;i<c.length;i++){System.out.println("实现的接口名称:"+c[i].getName()); //输出接口名称    }   } }    

程序运行结果:

实现的接口名称:org.ljz.demo.China

因为接口是类的特殊形式,而且一个类可以实现多个接口,所以此时以Class数组的形式将全部的接口对象返回,并利用循环的方式将内容依次输出。

取得父类

一个类可以实现多个接口,但是只能继承继承一个父类,所以如果要取的一个类的父类,可以直接使用Class类中的getSuperclass()方法,此方法的定义如下:

public Class<? super T> getSuperclass()

getSuperclass()方法返回的是Class实例,和之前的到的接口一样,可以通过getName()方法取得类的名称。

取得Person的父类

package org.ljz.demo.classinfodemo;public class GetSuperDemo{public static void main(String[] args){Class<?> c1=null;  //声明Class对象try{c1=Class.forName("org.ljz.demo.Person"); //实例化class对象 }catch(ClassNotFountException e){e.printStackTrace();}Class<?> c2=c1.getSuperclass();  //取得父类System.out.println("父类名称:"+c2.getName()); //输出信息 }}

程序运行结果:

父类名称:java.lang.Object

Person类在编写时没有明确地继承一个父类,所以默认继承Object类

取得全部构造方法

要取得一个类的全部构造方法,则必须使用Class类中的getConstructors()方法。
Constructor的常用方法如下图所示:
这里写图片描述

取得一个类的全部构造方法,并使用Modifier还原修饰符

package org.ljz.demo.classinfodemo;import java.lang.reflect.Constructor;import java.lang.reflect.Modifier;public class GetConstructorDemo{public static void main(String[] args){Class<?> c1=null;  //声明Class对象try{c1=Class.forName("org.ljz.demo.Person");  //实例化Class对象}catch(ClassNotFoundException e){e.printStackTrace();}Constructor<?> con[]=c1.getConstructor(); //得到全部构造for(int i=0;i<con.length;i++){    //循环输出Class<?> p[]=con[i].getParameterTypes(); //列出构造中的参数类型System.out.print("构造方法");int mo=con[i].getModifiers(); //取出权限System.out.print(Modifier.toString(mo)+" ");  //还原权限System.out.print(con[i].getName());  //输出构造方法的名称System.out.print("(");    //输出"("for(int j=0;j<p.length;j++){  //循环输出System.out.print(p[i].getName()+"arg"+i); //打印参数类型if(j<p.length-1){   //判断是否要输出“,”System.out.print(","); }}System.out.println("){});  //输出"){}"    }   } }     

程序运行结果:

构造方法:public org.ljz.demo.Person(){}
构造方法:public org.ljz.demo.Person(java.lang.String arg1,int arg1){}
构造方法:public org.ljz.demo.Person(java.lang.String arg2){}

从程序的运行结果可以发现,使用Modifier将取出来的修饰符数字还原成用户可以看的懂的权限修饰符。

取得全部方法

要取得一个类的全部方法,可以使用Class类中的getMethods()方法,此方法返回一个Method类的对象数组。而如果要想进一步取得方法的具体信息,例如,方法的参数,抛出的异常声明等,则就必须依靠Method类,此类中的常用方法如下所示:
这里写图片描述

取得一个类的全部方法定义

package org.ljz.demo.classinfodemo;import java.lang.reflect.Method;import java.lang.reflect.Modifer;public class GetMethodDemo{public static void main(String[] args){Class<?> c1=null; //声明一个Class对象try{c1=Class.forName("org.ljz.demo.Person"); //实例化Class对象}catch(ClassNotFoundException e ){e.printStackTrace();}Method m[]=c1.getMethods(); //取得全部的方法for(int i=0;i<m.length;i++){ //循环输出Class<?> r=m[i].getReturnType(); //得到方法的返回值类型Class<?> p[]=m[i].getParameterTypes(); //得到全部的参数属性int xx=m[i].getModifiers();  //得到方法的修饰符    System.out.print(Modifier.toString(xx) + " ") ; // 输出修饰符            System.out.print(r + " ") ;            System.out.print(m[i].getName()) ;            System.out.print("(") ;            for(int j=0;j<p.length;j++){                System.out.print(p[j].getName() + " " + "arg" + j) ;                if(j<p.length-1){                    System.out.print(",") ;                }            }            Class<?> ex[] = m[i].getExceptionTypes() ;  // 取出异常            if(ex.length>0){                System.out.print(") throws ") ;            }else{                System.out.print(")") ;            }            for(int j=0;j<ex.length;j++){                System.out.print(ex[j].getName()) ;                if(j<p.length-1){                    System.out.print(",") ;                }            }            System.out.println() ;        }    }};

程序运行结果:

public voi setAge(int arg0)
public int getAge()
public void sayChina()
public java lang.String sayHello(java.lang.String arg0,int arg1)
public java lang.String getName()
public void setName(java.lang.String arg0)
public final void wait() throws java.lang.InterruptedException
public final void wait(long arg0, int arg1 )throwsjava.lang.Interrupted Exception
public final native void wait(long arg0) throws java.lang.Interrupted Exception
public native int hashCode(0
public final native java.lang.Class.getClass()
public boolean equals(java.lang.Object arg0)
public java.lang.String toString()
public final native void notify()
public final native void notifyAll()

从程序的运行结果可以发现,程序不仅将Person类的方法输出,也把Object类中继承而来的方法同样进行输出。

全的全部属性

在反射中也同样可以取得一个类中的全部属性,但是取得属性时有两种不同的操作。

  • 得到实现的接口或父类的公共属性:public Field[] getFields() throws SecurityException
  • 得到本类中的全部属性:public Field[] getDeclareFields() throws SecurityException.

以上方法返回的都是Field数组,每一个Field对象表示类中的一个属性,而要想取的属性的进一步信息,就需要使用下表所示的方法:
这里写图片描述

详细的方法可以查询JDK

代码如下

import java.lang.reflect.Field ;    // 导入构造方法的包import java.lang.reflect.Modifier ; // 导入构造方法的包public class GetFieldDemo{    public static void main(String args[]){        Class<?> c1 = null ;        // 声明Class对象        try{            c1 = Class.forName("org.ljz.demo.Person") ; // 实例化对象        }catch(ClassNotFoundException e){            e.printStackTrace() ;        }        {   // 本类属性            Field f[] = c1.getDeclaredFields() ;    // 取得本类中的属性            for(int i=0;i<f.length;i++){                Class<?> r = f[i].getType() ;   // 得到属性类型                int mo = f[i].getModifiers() ;  // 得到修饰符的数字                String priv = Modifier.toString(mo) ; // 还原修饰符                System.out.print("本类属性:") ;                System.out.print(priv + " ") ;                  System.out.print(r.getName() + " ") ;   // 得到属性类型                System.out.print(f[i].getName()) ;  // 输出属性名称                System.out.println(" ;") ;            }        }        {   // 公共属性            Field f[] = c1.getFields() ;    // 取得本类中的公共属性            for(int i=0;i<f.length;i++){                Class<?> r = f[i].getType() ;   // 得到属性类型                int mo = f[i].getModifiers() ;  // 得到修饰符的数字                String priv = Modifier.toString(mo) ; // 还原修饰符                System.out.print("公共属性:") ;                System.out.print(priv + " ") ;                  System.out.print(r.getName() + " ") ;   // 得到属性类型                System.out.print(f[i].getName()) ;  // 输出属性名称                System.out.println(" ;") ;            }        }    }};

程序运行结果:

本类属性:private java.lang.Stringname;
本类属性: private int age;


公共属性:public static final java.lang.String NATIONAL;
公共属性: public static final java.lang.String AUTHOR;

由于时间有限,本篇文章就先介绍到这里,下一篇文章会重点介绍Java反射机制的深入应用,请耐心等待!

原创粉丝点击