Java之反射机制初识

Posted on 2017-08-17 20:56  future_liu  阅读(231)  评论(0编辑  收藏  举报

       前几天被问到了反射,当时没有回答出来多少,后来去看了一下,这里大概总结一下!

       首先,我们要知道反射机制,那么什么是反射呢?

       答:反射是程序可以访问、检测和修改他本身状态或行为的一种能力。那么java语言是如何支持反射的呢?别急,我们来慢慢聊。

     我们以前的学习中有遇到过Java中万事万物皆为对象之说,那么静态变量呢?还有基本数据类型的数据呢?它们也是面向对象的吗?我们都知道静态是属于类的,不是哪个类的对象的,基本数据类型是属于包装类的,那么难道类也是对象?答案就是是。Java中的每一个类都是java.lang.Class类的对象。

一、Class类的使用

      在Java中,每一个class都有一个相应的Class对象。换句话说,就是当我们编写一个类时,编译完成后,在生成的.class文件中,就会产生一个Class对象,用于表示这个类的类型信息。也就是说,Class类的实例对象表示java应用程序运行时的类或者接口。虚拟机为每种类型管理一个独一无二的Class对象,也就是说,每个类型都有一个Class对象,运行程序时,jvm首先检查所要加载的类对应的Class对象是否已经加载,如果没有加载,jvm就会根据类名查找.class文件并将其Class对象载入。

      可以通过类名.class、类的对象.getClass()、Class.forName()三种方法获取Class对象。具体的使用如下:

 

package Reflect;

public class ClassRefelect {

    public static void main(String[] args){
        //获得Foo的类对象
        Foo foo1 = new Foo();
        /**
         * 获得Foo类对象有三种方法
         * 1.类名.class    每一个类都有一个隐含的成员变量,class
         * 2.类对象.getClass();
         * 3.Class.forName(包名+类名);
         * 下面的c1,c2,c3官网称之为“Class Type"  类类型
         */

        /**
         * 万事万物皆为对象,那么类也是对象,类是什么的对象呢》
         *java.lang.Class的对象
         * 该类中封装了类的相关操作
         */
        Class  c1 = Foo.class;

        Class c2 = foo1.getClass();

        Class c3 = null;
        try {
            c3 = Class.forName("Reflect.Foo");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        /**
         *那么这三个类类型是否相同呢?
         * 答案是相同,为什么呢?
         * 因为每一个类只可能有一种类类型,
         * 就比如每一个对象只有一个类一样,
         * 这个类类型是Class的实例对象
         */

        System.out.println(c1==c2);      //true
        System.out.println(c3==c2);      //true
    }
}

class Foo{
        public void print(){
            System.out.println("创建了Foo类的对象");
        }
}

 

 

        可以看到上面的代码中有对三个Class对象的引用变量进行比较,答案当然是true,因为它们都是Foo类对象,而上面我们也提到了,每一个类都有一个独一无二的Class对象,所以结果应该是true.

 

       这里还有一个问题,我们怎么区分foo1和c1、c2等呢?我们知道foo1是类Foo的一个对象,那么就是Foo的对象,官网上对于c1,c2有一种说法,是Class Type----->类类型,也就是c1,c2是Foo的类类型,其实我们还可以这样区别,c1,c2是Foo对象,而foo1是Foo的对象。

       既然我们已经得到了Foo的类类型,里面含有Foo类的相关信息,那么我们有一个大胆的想象,能否用Foo类类型得到Foo的某个对象呢?答案是当然可以。Class对象有一个newInstance()方法,代码演示如下:

 

package Reflect;

public class ClassRefelect {

    public static void main(String[] args){
        Class  c1 = Foo.class;
   
        try {
            Foo foo2  = (Foo) c1.newInstance();
            foo2.print();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

class Foo{
        public void print(){
            System.out.println("创建了Foo类的对象");
        }
}

 

这里的foo2相当于new Foo();创建出来的对象。

下面我们来聊聊方法的反射。

二、方法的反射

       要想了解方法的反射,我们首先要获得Class对象,才能通过Class对象获得方法的相关信息,获得Class对象的方法上面已经讲过,这里插播一条广告,上面不是提到了每种类型都有Class对象么,那么基本数据类型以及void类型有木有呢?恩,有的,看下面的代码

package Reflect;

/**
 * 基本数据类型类类型
 * 包装类类类型
 *
 */
public class ClassType {

    public static void main(String[] args){
        Class c1 = int.class;                       //int的类类型
        Class c2 = String.class;                    //String类类型
        Class c3 = Double.class;                    //Double包装类类型
        Class c4 = double.class;                    //double类类型
        Class c5 = void.class;                      //void 的类类型
        System.out.println(c1.getName());            //int
        System.out.println(c2.getName());            //java.lang.String
        System.out.println(c2.getSimpleName());      //String
        System.out.println(c3.getName());            //java.lang.Double
        System.out.println(c4.getName());            //double
        System.out.println(c3.getSimpleName());      //Double
        System.out.println(c5.getName());            //void
    }
}

      既然可以拿到Class对象,那么获取方法的一系列信息就不成问题了。

      方法也是对象,是Method类的对象,该类中封装了方法的一系列操作,该类是java.lang.reflect包下的类。

     这里介绍几个方法,后面会用到

----------------------------------------------------------------------------------------------------------------------------------------------------------------

 1.getMethods()                                          //获得一个类中的所有public修饰的方法对象,包括继承父类的方法

 2.getDeclaredMethods()                           //获得一个类中的所有自己声明的方法对象,不问访问权限,不包括父类的

 3.getName()                                             //获得调用者的名称

 4.getReturnType()                                    //获得方法的返回值类类型,即如果返回值是int,那么返回的是int.class

 5.getParameterTypes()                            //获得参数列表的类类型

----------------------------------------------------------------------------------------------------------------------------------------------------------------

package Reflect;

import java.lang.reflect.Method;

public class ClassUtil {

    public static void printMessage(Object obj){

        //获得类的类类型

        /**
         * 如果传入的参数是Object类型,
         * 则或取的就是Object的类类型,
         * 如果是其子类,则获取的就是其子类的类类型
         */
        Class c1 = obj.getClass();

        //或取类的名称
        System.out.println("类的名称是:"+c1.getName());

        //获取类中的方法
    
        //获取类中的方法
        Method[] ms = c1.getMethods();
        for(int i=0;i<ms.length;i++){
            //获取方法的返回值类型
            Class returnType = ms[i].getReturnType();
            System.out.print(returnType.getName()+" ");
            //获取方法的名称
            System.out.print(ms[i].getName()+"(");
            //获取参数类型----》获取的是参数列表的类类型
            Class[] parameterType = ms[i].getParameterTypes();
            for (Class class1:parameterType) {
                System.out.print(class1.getName()+",");

            }
            System.out.println(")");
        }
    }
 

这里传入的如果是Object类型的对象,那么就会获取Object类的方法信息,如果是Object类的子类,那么获取的就是
该子类的方法信息。

这里我们测试一下:

package Reflect;

public class TestClassUtil {
    public static void main(String[] args){
 
        Integer s = 1;
        ClassUtil.printMessage(s);
    
    }
}

       从这里我们可以看到打印出了Integer类的所有的public方法的信息。

       那么什么是方法的反射呢?方法的反射是什么样的呢?

       平时我们使用方法的步骤是什么呢?是不是先获取一个类的对象(如果是实例方法),然后对象.方法()对吗?那么方法反射恰好相反,是利用方法对象操作类的对象的。

       方法的反射也有几个步骤:

       0.获取类类型  

       1.通过类类型获取某个方法的方法对象 

  2.方法对象.invoke(类的对象,参数列表)

       invoke()方法API文档上时这样讲的:“对带有指定参数的指定对象调用由此 Method 对象表示的底层方法”,通俗的说就是可以利用invoke()方法进行反射。

下面列出反射中需要的方法:

--------------------------------------------------------------------------------------------------------------------------------------------------------------------

****说明一下:以下提到的c是Class对象,某个类的类类型,method是1,2方法返回的方法对象**** 

1.getMethod("Method name","Parameter Type")                           //方法名称和参数列表可以唯一确定一个方法,注意这个方法  只能获取类中public 声明的方法,包括继承自父类的方法

 

用法举例:public void print(int a,int b){}

                  c.getMethod(“print",int.class,int.class)  或者  c.getMethod("print",new Class[]{int.class,int.class})

2.getDeclaredMethod("Method name","Parameter Type")               //获取类中自己声明的方法,不问权限,不包括继承自父类的方法

用法举例:public void print(int a,int b){}

                  c.getDeclaredMethod(“print",int.class,int.class)  或者  c.getDeclaredMethod("print",new Class[]{int.class,int.class})

 以上两个方法都是获取方法对象的

3.invoke(Object obj,args)                                             //对带有args参数的obj对象调用由此Method对象表示的底层方法

用法举例:class A{

        public void print(int a,int b){}

      }

                 method.invoke(new A(),10,20)  或者  method.invoke(new A(), new Object[]{10,20});

invoke方法返回值是null,或者是Object或者Object的子类,当是Object时不存在问题,如果(Object的子类)想要具体的返回值类型,那么必须进行强制类型转换,如

 Object o = method.invoke(a1,10,10);

 

 Integer i   = (Integer)method.invoke(a1,10,10);

----------------------------------------------------------------------------------------------------------------------------------------------------------------------

接下来我们看具体的代码:

package Reflect;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MethodRefelect {
    public static void main(String[] args){
        /**
         * 1.要想获取一个方法的信息,首先需要获取类类型
         * 2.获取了类类型后通过类类型得到方法对象
         * 3.通过方法对象可以反射操作方法
         */

        A a  = new A();
        Class c = a.getClass();
        try {
            Method m = c.getMethod("print",new Class[]{int.class,int.class});
            try {
             //   Object o = m.invoke(a,new Object[]{10,20});
                Integer o = (Integer)m.invoke(a,10,20);
                System.out.println(o);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

        try {
            Method m2 = c.getDeclaredMethod("print",String.class,String.class);
            try {
                Object o = m2.invoke(a,new Object[]{"Hello","ninhao"});
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

        try {
          //  Method m3 = c.getDeclaredMethod("print",new Class[]{});
            Method m3 = c.getDeclaredMethod("print");
            try {
               // Object o = m3.invoke(a,new Object[]{});
                Object o = m3.invoke(a);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

class A{

    public void print(){
        System.out.println("hello");
    }
    public int print(int a,int b){
        System.out.println(a+b);
        return a+b;
    }

    public void print(String a,String b){
        System.out.println(a.toUpperCase()+","+b.toLowerCase());
    }
}

 

  方法的反射讲完后,我们说一下Field的反射操作

三、Field的反射

  在讲Field的反射之前,先来了解一下如何通过Class对象获取某个类的Field的信息,如果需要获取Field的信息,首先需要获得类中所有的Filed,然后依次获得每一个Field的数据类型和名称。

  接下来列出获得Field的信息的方法

----------------------------------------------------------------------------------------------------------------------------------------------------------------------

1.getDeclaredFields()                  //获得一个类中所声明的所有的Filed,返回值是一个数组

2.getType()                                  //获得调用者的类型

----------------------------------------------------------------------------------------------------------------------------------------------------------------------

  代码相对来说简单,如下:

public static void printFiled(Object obj) {
        
        Class c1 = obj.getClass();
       Field[] fs
= c1.getDeclaredFields(); //自己类中声明的所有的成员变量 for(Field filed: fs){ //根据每一个成员变量获取成员变量的类型 Class returnType = filed.getType(); String returnName = returnType.getName(); //返回每一个成员变量的名称 String FiledName = filed.getName(); System.out.println(returnName+" "+FiledName); } }

 

package Reflect;

public class TestClassUtil {
    public static void main(String[] args){

        Integer s = 1;
        ClassUtil.printFiled(s);
     
    }
}

    测试了一下,结果中列出了Integer类的Field.[C这个是数组类型的反射,这里不做详述。

    接着到了我们Field的反射表演的时间了。

  Field的反射操作步骤如下:

       0.获得类类型

       1.通过类类型获得某个Field

 1.5.一般,Field是private,所以需要setAccessible(true),大概的意思是可以操作private的数据

  2.对获得的Field进行相关操作

  补充一下:Field(成员变量)也是面向对象的,它是java.lang.reflect.Filed的实例对象

       具体的实践如下:

package Reflect;

import java.lang.reflect.Field;
public class FieldReflect {
    public static void main(String[] args){
        B b = new B();
        Class c = b.getClass();
        //首先获取成员变量对象
        try {
            /**
             * 这里的getField之所以会出错,
             * 是因为getField获取的是public的,
             * 而之前我并没有指定a的访问权限
             */
           // Field f = c.getField("a");
            Field f = c.getDeclaredField("a");     //通过getDeclaredField("Field name");获得该属性对象
            try {
                f.setAccessible(true);             //如果不设置该标志,将不能访问私有成员
                f.set(b,12);                       //平时我们都是b.setF(xxx);这里通过Field对象f反向操作B类的对象b
                System.out.println(f.get(b));      //12
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }


        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

class B{

    private int a;
    private int b;

    public void setA(int a) {
        this.a = a;
    }

    public void setB(int b) {
        this.b = b;
    }

    public int getA() {
        return a;
    }
    public int getB() {
        return b;
    }
}

 

四、构造方法的反射

  首先我们还是来获取构造方法的信息,构造方法的获取所需要的方法同上面的普通方法、成员变量大概相似,这里不做赘述。直接看代码即可理解:

public static void printConMessage(Object obj){
        //获得类类型
        Class c1 = obj.getClass();
        //获得类中的自己声明的构造方法
        Constructor[] constructors = c1.getDeclaredConstructors();
        for(Constructor constructor:constructors){
            //获得构造方法的名称
            System.out.print(constructor.getName()+"(");
            //获得构造方法的参数列表中的参数类型
            Class[] parameterTypes = constructor.getParameterTypes();
            //打印出每一个构造方法的参数
            for (Class para: parameterTypes) {
                System.out.print(para.getName()+",");
            }
            System.out.println(")");
        }
    }

 

package Reflect;

public class TestClassUtil {
    public static void main(String[] args){
     
        Integer s = 1;
       ClassUtil.printConMessage(s);
    }
}

 

       构造方法也是对象,是java.lang.reflect.Constructor的实例对象

      反射所需要的步骤:

     1.根据类类型获得构造方法对象

     2.通过构造方法对象创建该类的对象

下面的是构造方法的反射的代码:

package Reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class ConRefelect {
    public static void main(String[] args){
        //构造方法的反射操作
        C c = new C();
        Class c1 = c.getClass();
        //获得构造方法
        try {
            Constructor constructor1 = c1.getConstructor();
            Constructor constructor2 = c1.getConstructor(String.class);

            try {
                /**
                 * 类类型.newInstance()可以创建一个类对象
                 * 类的构造对象.newInstance()也可以创建一个类对象
                 * 这个的意思是
                 */
                C o = (C)constructor1.newInstance();
      C q = (C)constructor2.newInstance("hello");
                q.print();
                o.print();
          //      System.out.println(c1==constructor1);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }

            System.out.println(constructor1.getName());
            System.out.println(constructor2.getName());

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

    }
}

class C{
    public String a;
    public C(){

    }
    public C(String a){
        this.a = a;
    }

    public void print(){
        System.out.println("构造方法被反射了");
    }
}

 

 

 

通过反射我们还可以查看泛型的本质原理:

五、通过反射了解泛型的本质

      泛型指的是集合中的数据只能输入指定的数据类型的数据。如下所示:

      ArrayList<String> list = new ArrayList<String>();

      这个list中只能输入String 类型的数据,如果输入其他的不兼容的就会报错。

那么现在我们通过反射了解反射:

 

package Reflect;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

public class TestGeneric {
    public static void main(String[] args){

        ArrayList list = new ArrayList();

        ArrayList<String> list1 = new ArrayList<String>();
        list1.add("hello");
        // list1.add(20);
        //下面通过反射来判断泛型的本质

        Class c = list1.getClass();
        try {
            Method m = c.getMethod("add",new Class[]{Object.class});
            try {
                Object o = m.invoke(list1,20);
                System.out.println(list1.size());           //2
                System.out.println(list1);                  [hello,20]
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

    }
}

 

       上面的代码中刚开始时在list1中插入20时编译报错,接着我们通过反射获得方法对象,操作集合的add方法,将20成功插入了集合中。为什么呢?因为反射是运行时进行的,所以上面我们绕过了编译将20成功插入了集中由此可得出泛型实际上是在编译时设置了“路障”,在编译后会驱泛型化,它的存在只是为了放置错误的输入,只在编译时有效,绕过编译则无效。

  反射大概就总结这些,接下来我们小小的聊聊动态加载类。

六、动态加载类

  类的加载分为静态加载和动态加载,静态加载指的是编译时加载,动态加载指的是运行时加载。

  new 创建对象时是静态加载类,在编译时刻就需要将所需要的所有的类加载进来。这样有一个问题就是,当我们有一个功能类,我的功能还不是很完整,但是我需要提前搭建起来框架,但是编译时发现有一个现在不使用的类不存在,所以我现在这个功能类则编译不通过。具体的代码演示如下:

public class test{
    public static void main(String[] args){
            Word word = new Word();
            word.start();
    
           Excel excel = new Excel();
           excel.start();
 }

class Word{
    void start(){};
}

 

 

      如上所示,当我有一个Word类时,我就想要编译运行这个test类,测试一下我的Word类是否是好的,但是现在没有Excel类,所以无法检测,这就是高耦合,依赖性太强,这在工程中是不会出现的,我们必须使得我们的代码低耦合,高内聚才能符合软件工程的要求。

  那么如何改善呢?这个本质的原因还是因为类是静态加载,如果改为动态加载,那么就不会出现这个问题,我只需要在动态加载时发现导入我需要的类即可。

package Reflect;

public class TestLoose {
    public static void main(String[] args){
        try {
            Class a  = Class.forName(args[0]);     //动态加载类,在编译时根本不会出现问题,只有

            try {
                Able able  = (Able)a.newInstance();
                able.start();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

interface   Able{
      public void start();
}

class Word implements Able{
    public void start(){}

}

  上面的代码就是一个低耦合,高内聚的代码规范,如何说呢。我们可以从编译、运行两部分来说明,首先在编译时TestLoose是不会出错的,因为我们有一个Able的接口存在,所以编译通过;当运行时我们有Word类,所以我们在运行时输入Word也是不会报错的,我们输入Excel才会出错,这样就会解决上面的如果存在一个类而不能测的问题,如果我们后边需要或者这段代码由别的程序员来继续写,它们只需要实现Able即可,如Excel    implements Able;即可继续添加。

  这是编写代码的一种新思想,从高强大的依赖种脱身而出。

  总结:今天总结关于反射的问题,就到这里了。这里没有提到反射的数组和动态代理等关于反射更多、更复杂的东西,只是基础的知识。