Java之反射机制初识

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

       首先,咱们要知道反射机制,那么什么是反射呢?程序员

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

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

1、Class类的使用jvm

      在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.this

 

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

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

 

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();建立出来的对象。

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

2、方法的反射

       要想了解方法的反射,咱们首先要得到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的反射操做

3、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;
    }
}

 

4、构造方法的反射

  首先咱们仍是来获取构造方法的信息,构造方法的获取所须要的方法同上面的普通方法、成员变量大概类似,这里不作赘述。直接看代码便可理解:

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("构造方法被反射了");
    }
}

 

 

 

经过反射咱们还能够查看泛型的本质原理:

5、经过反射了解泛型的本质

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

      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成功插入了集中由此可得出泛型其实是在编译时设置了“路障”,在编译后会驱泛型化,它的存在只是为了放置错误的输入,只在编译时有效,绕过编译则无效。

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

6、动态加载类

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

  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;便可继续添加。

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

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

相关文章
相关标签/搜索