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

1、反射机制概述

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

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

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

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

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

java反射API中常用的类介绍:

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

2、关于Class类

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

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

1、获得类相关的方法:

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

2、获得类中属性相关的方法:

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

3、获得类中注解相关的方法:

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

4、获得类中构造器相关的方法:

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

5、获得类中方法相关的方法:

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

6、类中其他重要的方法:

  • 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);

3、获取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)来获取。而且无论使用哪种方式获取,结果都是同一个类。

4、获取成员变量

在我们正常创建对象的实例时,其内部的私有元素是不能访问的,但是使用反射则可以轻易的获取,前面创建的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");

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

        //2、通过反射单个获取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("------------------------");

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

5、获取方法并且调用

获取方法用到的是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");

        //1、通过反射获取所有方法
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            //2、获取方法的名称
            System.out.println("名称:"+method.getName());

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

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

            //5、获取形参列表
            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);
    }
}

6、获取构造器

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

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

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

        //2、获取所有的构造器
        Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor);
        }
    }
}

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

7、小结

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

猜你喜欢

转载自www.cnblogs.com/tang-hao-/p/11498376.html