夯实Java基础(二十一)——Java反射机制

一、反射机制概述

Java反射机制是指程序在运行状态中,对于任何一个类,咱们都可以知道这个类的全部属性和方法(包括private、protected等)。对于任何一个对象,咱们都可以对它的属性和方法进行调用。咱们把这种动态获取对象信息和调用对象方法的功能称之为反射机制。html

在程序运行时,当一个类加载完成以后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只会对应一个Class对象,绝对不会产生第二个),这个Class对象就包含了完整的类的结构信息。咱们建立该类的对象后就能够经过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构。因此,咱们形象的称之为:反射。这么说很是的抽象,不能理解,可是经过后面代码的体现就会变得容易理解,暂时先了解一下便可。java

到后面还会学习到框架的知识(如Spring、Hibernate等等),框架的组成结构能够理解为这个公式:框架=反射+注解+设计模式。其中反射就是框架的灵魂所在。因此反射在Java的学习中很是重要。设计模式

那么Java反射机制主要提供的功能有哪些:数组

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具备的成员变量和方法
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时处理注解
  • 生成动态代理

java反射API中经常使用的类介绍:框架

  • java.lang.Class类:表明一个类
  • java.lang.reflect.Field类:表明类的成员变量(成员变量也称为类的属性)
  • java.lang.reflect.Method类:表明类的方法
  • java.lang.reflect.Constrctor类:表明类的构造方法

二、关于Class类

Class类是用来描述类的类,它是一个十分特殊的类,没有构造方法。Class对象的加载过程以下:当程序运行时,咱们编写的每个类,编译完成后,都会生成 类名.class 文件,当咱们咱们new对象或者类加载器加载的时候,JVM就会加载咱们的 类名.class 文件,而且加载到内存中,而后在将 类名.class 文件读取而且同时会生成该类惟一的一个Class对象,用于表示该类的全部信息。ide

Class表明一个类,在运行的Java应用程序中表示类或接口。在Class类中提供了不少有用的方法,这里对他们简单的介绍。此处参考学习

一、得到类相关的方法:测试

  • asSubclass(Class<U> clazz):把传递的类的对象转换成表明其子类的对象
  • Cast:把对象转换成表明类或是接口的对象
  • getClassLoader():得到类的加载器
  • getClasses():返回一个数组,数组中包含该类中全部公共类和接口类的对象
  • getDeclaredClasses():返回一个数组,数组中包含该类中全部类和接口类的对象
  • forName(String className):根据类名返回类的对象
  • getName():得到类的完整路径名字
  • newInstance():建立类的实例,它调用的是此类的默认构造方法(没有默认无参构造器会报错)
  • getPackage():得到类的包
  • getSimpleName():得到类的名字
  • getSuperclass():得到当前类继承的父类的名字
  • getInterfaces():得到当前类实现的类或是接口

二、得到类中属性相关的方法:ui

  • getField(String name):得到某个公有的属性对象
  • getFields():得到全部公有的属性对象
  • getDeclaredField(String name):得到某个属性对象
  • getDeclaredFields():得到全部属性对象

三、得到类中注解相关的方法:this

  • getAnnotation(Class<A> annotationClass):返回该类中与参数类型匹配的公有注解对象
  • getAnnotations():返回该类全部的公有注解对象
  • getDeclaredAnnotation(Class<A> annotationClass):返回该类中与参数类型匹配的全部注解对象
  • getDeclaredAnnotations():返回该类全部的注解对象

四、得到类中构造器相关的方法:

  • getConstructor(Class...<?> parameterTypes):得到该类中与参数类型匹配的公有构造方法
  • getConstructors():得到该类的全部公有构造方法
  • getDeclaredConstructor(Class...<?> parameterTypes):得到该类中与参数类型匹配的构造方法
  • getDeclaredConstructors():得到该类全部构造方法

五、得到类中方法相关的方法:

  • getMethod(String name, Class...<?> parameterTypes):得到该类某个公有的方法
  • getMethods():得到该类全部公有的方法
  • getDeclaredMethod(String name, Class...<?> parameterTypes):得到该类某个方法
  • getDeclaredMethods():得到该类全部方法

六、类中其余重要的方法:

  • isAnnotation():若是是注解类型则返回true
  • isAnnotationPresent(Class<? extends Annotation> annotationClass):若是是指定类型注解类型则返回true
  • isAnonymousClass():若是是匿名类则返回true
  • isArray():若是是一个数组类则返回true
  • isEnum():若是是枚举类则返回true
  • isInstance(Object obj):若是obj是该类的实例则返回true
  • isInterface():若是是接口类则返回true
  • isLocalClass():若是是局部类则返回true
  • isMemberClass():若是是内部类则返回true

Class类中的方法还有有不少,这里只列举了部分,须要学习更多的能够自行去查看Class的API文档。

注意:Class并非只有普通类或接口才能获取,其中基本数据类型、数组、枚举、注解、void等均可以获取其Class对象,甚至Class这个类自己也能够获取Class对象,简单举例:

        Class<Object> c1 = Object.class;
        Class<String> c2 = String.class;
        Class<Integer> c3 = int.class;
        Class<int[]> c4 = int[].class;
        Class<int[][]> c5 = int[][].class;
        Class<ElementType> c6 = ElementType.class;
        Class<Override> c7 = Override.class;
        Class<Class> c8 = Class.class;
        Class<Void> c9 = void.class;

        int arr[]=new int[10];
        int arr1[]=new int[100];
        Class<? extends int[]> c10 = arr.getClass();
        Class<? extends int[]> c11 = arr1.getClass();
        //只要元素类型和维度相同,就是同一个Class
        System.out.println(c10==c11);

三、获取Class的几种方式

既然反射机制必定会用到Class这个类,那么就必须先获取它,获取Class对象有四种方式。注意:反射这里全部举例都用Person类来做为演示,因此先建立一个Person类,后面会一直用这个。

public class Person {
    //公共的成员变量
    public String name;
    //default成员变量
    int age;
    //私有的成员变量
    private String address;

    public Person() {
    }
    //私有构造方法
    private Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person(String name, int age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    getter/setter方法省略

    //公共方法
    public void display1(){
        System.out.println("公共方法...");
    }
    //私有方法
    private void display2(){
        System.out.println("私有方法...");
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", address='" + address + '\'' +
                '}';
    }
}

四种方式分别是:

①、经过Object类中的getClass方法获取。这种方式要先建立类的对象,这样再使用反射就画蛇添足了,不推荐。

②、经过类名.class 直接获取。这种方式须要导入相应类的包,依赖性较强,不推荐。

③、使用Class类中静态方法forName(String className)获取。因此这种方式最经常使用。

④、使用类加载器ClassLoader来获取。这种方式了解便可,用的不多。

代码演示以下:

public class Reflection {
    public static void main(String[] args) throws ClassNotFoundException {
        //第一种方式:getClass
        Person person = new Person();
        Class<? extends Person> clazz1 = person.getClass();
        System.out.println("getClass获取:"+clazz1);

        //第二种方式:类名.class
        Class<Person> clazz2 = Person.class;
        System.out.println("类名.class获取:"+clazz2);

        //第三种方式:forName
        try {
            Class<?> clazz3 = Class.forName("com.thr.Person");
            System.out.println("forName()方法获取:"+clazz3);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        //第四种方式:类加载器ClassLoader
        ClassLoader classLoader = Reflection.class.getClassLoader();
        Class<?> clazz4 = classLoader.loadClass("com.thr.Person");
        System.out.println("类加载器ClassLoader获取:"+clazz4);

        //能够发现返回都是true,说明引用的同一个对象
        System.out.println(clazz1 == clazz2);
        System.out.println(clazz1 == clazz4);
    }
}

image

这四种方式推荐使用Class类中的静态方法forName(String className)来获取。并且不管使用哪一种方式获取,结果都是同一个类。

四、获取成员变量

在咱们正常建立对象的实例时,其内部的私有元素是不能访问的,可是使用反射则能够轻易的获取,前面建立的Person类中属性、方法和构造器都有被private关键字所修饰。因此接下来演示一下怎么获取成员变量、方法和构造器。

获取属性使用到的是Field这个类,它内部的方法主要是获取、设置某个属性的方法,例如:getXXX()、setXXX();

public class Reflection {
    public static void main(String[] args) throws Exception {

        //获取Person的Class对象
        Class<?> clazz = Class.forName("com.thr.Person");

        //一、经过反射获取全部public的成员变量,private、protected、default是不能获取到的
        Field[] fields1 = clazz.getFields();
        for (Field field : fields1) {
            System.out.println(field.getName());
        }
        System.out.println("------------------------");

        //二、经过反射单个获取public成员变量
        Field name = clazz.getField("name");
        System.out.println(name);
        Field age = clazz.getDeclaredField("age");
        System.out.println(age);
        Field address = clazz.getDeclaredField("address");
        address.setAccessible(true);
        System.out.println(address);
        System.out.println("------------------------");

        //三、经过反射获取全部变量,包括private、protected、default。
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            //四、获取成员变量名字
            System.out.println(field.getName());
            //五、获取权限修饰:0是default、1是public、2是private、4是protected。或者使用Modfier.toString(modifiers)输出
            int modifiers = field.getModifiers();
            System.out.println(modifiers);
            //六、获取成员变量数据类型
            Class<?> type = field.getType();
            System.out.println(type);
            System.out.println("---------------");
        }
    }
}

五、获取方法而且调用

获取方法用到的是Method类,内部方法也是一些getXXX()和setXXX的方法,这些方法就不演示了,和上面获取Field大同小异,能够参考上面的例子。而后咱们主要来演示一些与上面不同的方法。

为了测试获取方法上面的Annotation方法,我建立了一个MyAnnotation注解,而后让其做用在Person类的display1()方法上面。

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String[] value() default "tang_hao";
}

须要注意的是@Retention必需要设置为RUNTIME类型,不然是获取不到的。

public class Reflection {
    public static void main(String[] args) throws Exception {

        //获取Person的Class对象
        Class<?> clazz = Class.forName("com.thr.Person");

        //一、经过反射获取全部方法
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            //二、获取方法的名称
            System.out.println("名称:"+method.getName());

            //三、获取方法上面的注解
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                System.out.println("注解:"+annotation);
            }

            //四、获取方法返回值类型
            Class<?> returnType = method.getReturnType();
            System.out.println("返回值类型:"+returnType);

            //五、获取形参列表
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (!(parameterTypes==null||parameterTypes.length==0)){
                for (Class<?> parameterType : parameterTypes) {
                    System.out.println("形参列表:"+parameterType.getName());
                }
            }
            System.out.println("----------------");
        }
    }
}

若是咱们还想要使用反射来调用方法,那么就须要使用到这个方法:

  • invoke(Object obj, Object... args):传递object对象及参数而后调用该对象所对应的方法
public class Reflection {
    public static void main(String[] args) throws Exception {

        //获取Person的Class对象
        Class<?> clazz = Class.forName("com.thr.Person");
        Object o = clazz.newInstance();
        Method display1 = clazz.getDeclaredMethod("display1");
        display1.invoke(o);
        Method display2 = clazz.getDeclaredMethod("display2");
        display2.setAccessible(true);
        display2.invoke(o);
    }
}

六、获取构造器而且建立实例

获取方法用到的是Constructor类,构造器的获取比较的简单,简单演示一下:

public class Reflection {
    public static void main(String[] args) throws Exception {
        //获取Person的Class对象
        Class<?> clazz = Class.forName("com.thr.Person");

        //一、获取public的构造器
        Constructor<?>[] constructors = clazz.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
        }
        System.out.println("-----------------------");

        //二、获取全部的构造器
        Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor);
        }
        System.out.println("-----------------------");

        //三、使用反射建立实例,默认构造器
        Constructor<?> constructor = clazz.getConstructor();
        System.out.println(constructor.getName());
        Object o = constructor.newInstance();
        Person p= (Person) o;
        System.out.println(p.toString());

        //有参构造器
        Constructor<?> constructor1 = clazz.getConstructor(String.class, int.class,String.class);
        Object o1 = constructor1.newInstance("tang_hao",20,"China");
        Person p1= (Person) o1;
        System.out.println(p1.toString());
    }
}

其实后面还有获取父类、泛型、接口、所在包等等其余的就很少说了,其实都是大同小异,理解了上面这些,后面这些应该也会了。

七、小结

从前面学到这里咱们大概对反射也应该有一个简单的了解了,利用反射能够获取成员变量、方法、构造器、注解等属性,甚至是私有的方法,这在普通建立实例的方式是不可能的。虽然咱们在平时写代码不多用到反射技术,可是咱们要知道后面会学习框架知识,而框架的灵魂就是反射,因此学好反射基础对咱们来讲是很是有必要的。

相关文章
相关标签/搜索