Java基础-反射

Java基础-反射

1.概述

Java反射机制指的是在Java程序运行状态中,对于任何一个类,都可以获得这个类的所有属性和方法;对于给定的一个对象,都能够调用它的任意一个属性和方法。这种动态获取类的内容以及动态调用对象的方法称为反射机制

通过Java中的反射机制可以实现:

(1)在运行时判定任意一个对象所属的类 ,如:逆向代码、反编译

(2) 在运行时构造任意一个类的对象 ,如:与注解相关的框架

(3) 在运行时判定任意一个类所具有的成员变量和方法 ,如:单纯反射机制应用框架

(4)在运行时调用任意一个对象的方法,如:动态生成类框架(Gson)

(5)动态代理

感觉反射就是框架设计的灵魂,优点在于提高了Java程序的灵活性和扩展性,有效降低了类之间的耦合。

缺点是反射相当于一系列解释操作,性能较差,而且使用反射会破坏抽象,模糊了内部逻辑。

2.相关概念

要理解反射需要先了解Class、Constructor 、Method、Field 四个类,以及静态加载和动态加载

2.1 Class类

认识Class对象之前,先来了解一个概念,RTTI(Run-Time Type Identification)运行时类型识别,其作用是在运行时识别一个对象的类型和类的信息。Class对象就是用于描述一个类运行时的类型信息,如有哪些构造方法,都有哪些成员方法,都有哪些成员变量等。

创建Class对象

我们知道当需要使用一个类时需要经过加载->链接->初始化三个阶段。Class对象是由JVM在加载过程创建的:

(1)通过类全限定名获取.class的二进制字节流。

  • 从zip文件中读取,如:从jar、war、ear等格式的文件中读取class文件内容
  • 从网络中读取,如:Applet
  • 动态生成,如:动态代理、ASM框架等

(2)将Class的二进制内容加载到虚拟机方法区。

(3)在内存中生成一个java.lang.Class 对象来描述刚加载的类

获取Class对象

获取Class对象主要有三种方式:

(1)Class.forName(类的全限定名) 获得指定类的Class对象。这种方式需要将class字节码文件加载到jvm中。

Class clazz = Class.forName("com.www.Test");

(2)类字面常量。当字节码已经加载进jvm,可以使用类字面常量获取

Class clazz = Person.class;
int.class

(3)对象实例.getClass() 获得对象实例的Class对象。当已经有实例对象可直接使用实例对象获得Class对象。

Class clazz = person.getClass();

Class对象的作用

Class对象可以获取类中属性的类型和名称,获取类中的方法,获取类的基类等等 。

(1)获取类加载器

​ ClassLoader getClassLoader()

(2)获取构造器

  • Constructor getConstructor(Class<?>… parameterTypes)

    获取该类中与参数类型匹配的公有构造器方法。

  • Constructor<?>[] getConstructors()

    获取该类中所有公有构造器方法。

  • Constructor getDeclaredConstructor(Class<?>… parameterTypes)
    获取与参数类型匹配的构造方法,包括私有构造方法。

  • Constructor<?>[] getDeclaredConstructors()
    获取该类中所有构造器方法。

(3)获取类中方法

  • Method getMethod(String name, Class<?>… parameterTypes)

    根据方法名称和参数类型匹配公有方法。

  • Method[] getMethods()

    返回类中所有公有方法。

  • Method getDeclaredMethod(String name, Class<?>… parameterTypes)

    获取某个指定方法。

  • Method[] getDeclaredMethods()

    获取类中所有方法。

(4)获取类中属性

  • Field getField(String name) 根据名称获取类中公有属性。
  • Field[] getFields() 获取类中公有属性的列表。
  • Field getDeclaredField(String name) 根据名称获取类属性。
  • Field[] getDeclaredFields() 获取类中属性列表。

(5)其他重要方法

  • getInterfaces() 获取类实现的接口列表。
  • getSuperclass() 获取类的父类Class对象。
  • T newInstance() 创建一个实例对象,调用无参构造函数。
  • A getAnnotation(Class annotationClass) 根据传入类型获取Class中的注解
  • Class<?> forName(String className) 根据传入类型全路径名创建Class对象

方法很多这里暂不展开。

示例

public class Person {
    public String name;
    public int age;
    // 无参构造器
    public Person() {
        this.name = "";
        this.age = 0;
    }
    // 有参构造器
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // 私有构造器
    private Person(String name){
        this.name = name;
        this.age = 99999;
    }

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

使用Class对象创建类实例对象。

Class<Person> personClass = Person.class;
System.out.println(personClass.getName());
System.out.println(personClass.getClassLoader().getClass().getName());
Person person = personClass.newInstance();
System.out.println(person);

2.2 Constructor类

Constructor代表了一个构造器该有的相关信息,当然构造器最重要的作用就是创建实例对象。

创建实例对象

调用Constructor的newInstance方法即可创建实例对象。

  • public T newInstance(Object … initargs)

代码实例:

Class<Person> clazz = Person.class;
// 1.调用有参构造函数并创建实例对象
Constructor<Person> c1 = clazz.getConstructor(new Class[]{String.class, int.class});
Person p1 = c1.newInstance("aaa", 18);

// 2.调用无参构造函数创建实例对象
Constructor c2 = clazz.getConstructor();
Person p2 = (Person) c2.newInstance();

// 3.调用私有构造函数并创建对象
Constructor<Person> c3 = clazz.getDeclaredConstructor(new Class[]{String.class});
c3.setAccessible(true);// 私有方法须开启权限
Person p3 = c3.newInstance("ccc");

2.3 Method类

Method封装了一个方法拥有的所有信息,包括方法名称、返回类型、参数类型注解等等,这些信息可通过一系列getXxx 方法得到。

调用方法

当然作为一个Method类最重要的方法是invoke

Object invoke(Object obj, Object… args) 第一个参数是要运行方法的宿主对象。

示例:

Class<Person> clazz = Person.class;
// 1. 先创建要运行方法的宿主对象实例
Person p = clazz.newInstance();

// 2. 调用公有方法
Method method1 = clazz.getMethod("sayHello", String.class);
method1.invoke(p,"Hi !");

// 3. 调用私有方法
Method method2 = clazz.getDeclaredMethod("spendMoney", float.class);
method2.setAccessible(true);
method2.invoke(p,78.88f);

2.4 Field类

Field类描述了一个类属性相关信息,包括属性名称和类型等,主要功能是修改和查看属性值。

主要方法:

  • String getName() 查询属性名称。
  • Object get(Object obj) 查询传入obj实例对象该Field中的值。
  • void set(Object obj, Object value) 设置obj对象的该Field属性的值为value。

当然还包括了一些基本类型属性的get/set方法。

示例:

Class<Person> clazz = Person.class;
Person p = clazz.newInstance();

// 修改并查看公有属性
Field name = clazz.getField("name");
System.out.println(name.getName());
name.set(p, "xiaomin");// 修改p实例对象的name属性
System.out.println(name.get(p));

// 修改并查看私有属性
Field age = clazz.getDeclaredField("age");
age.setAccessible(true);// 打开权限
age.set(p, 18);// 修改
System.out.println(age.get(p));

综上:getDeclared 获取的是当前类中声明的所有成员变量,包含私有和公有,不包含从父类继承过来的成员。

getFields 获取的是当前类以及父类中所有的公有变量。

2.5 静态加载和动态加载

静态加载:在程序编译时就加载可能用到的所有类,任何一个类出错都无法编译通过,简单来说所有new出来的对象都是静态加载的。

动态加载:在程序运行到某处要用到一个类的时候再加载。

实例:

静态加载将可能用到的类都硬编码到代码中,编译时都会加载。

static void staticDemo(String cmd) {
    // 静态加载对象
    if ("ACar".equals(cmd)) {
        ACar aCar = new ACar();
        aCar.run();
    } else if ("BCar".equals(cmd)) {
        BCar bCar = new BCar();
        bCar.run();
    }
}

动态加载将相同的方法抽象为接口,动态加载要运行的类对象。

static void dynamicDemo(String cmd) {
    try {
        Class clazz = Class.forName(cmd);
        ICar car = (ICar) clazz.newInstance();
        car.run();
    } catch (Exception e) {
        e.printStackTrace();
    }
}


interface ICar {
    void run();
}

class ACar implements ICar {
    @Override
    public void run() {
        System.out.println("ACar run!!!");
    }
}

class BCar implements ICar {
    @Override
    public void run() {
        System.out.println("BCar run!!!");
    }
}

2.6 通过反射获取泛型类型

首先要知道Type 这个类型,Type是所有类型的父接口,有一个实现类Class,以及四个继承Type的接口。Type是Java语言中对类型的抽象。

  1. Class是原始类型,是一个Java类在JVM中的表示。
  2. TypeVariable是泛型类型,你写泛型类时,定义的T 或带边界的 T extends Comparable & Serieal 等最终使用TypeVariable表示的。
public class ConstructorTest<A extends CharSequence, B extends Comparable & Serializable, C> {
    public A a;
    public B b;
    public C c;
    public String d;
    public List<String> stringList;
    public List<? extends String> stringList2;
    public List<? super Number> stringList3;
    public C[] cArray;


    public <E extends Serializable> B showData(E e) throws Exception {
        return b;
    }

    public static void main(String[] args) {
        // 1.查看泛型类中的泛型类型
        // 由于定义泛型类时,定义的泛型类型可能有多个,所以这里用数组
        TypeVariable<Class<ConstructorTest>>[] classGenTypes = ConstructorTest.class.getTypeParameters();
        for (TypeVariable<Class<ConstructorTest>> t : classGenTypes) {
            // 打印定义泛型所用的字符串,这里就是 A,B,C
            System.out.println(t.getName());
            // 获取定义泛型时的边界即 A 继承了哪些类,实现了哪些接口。
            Type[] genBounds = t.getBounds();
            for (Type type : genBounds) {
                System.out.println(type);
            }
            // 打印声明泛型时所在的类,这里就是ConstructorTest
            System.out.println(t.getGenericDeclaration());
        }

        // 2.查看泛型类中成员变量所用的泛型类型
        try {
            Field[] fields = ConstructorTest.class.getDeclaredFields();
            for (Field f : fields) {
                // 由于成员变量的类型有可能是泛型,也有可能是普通对象类型(Class),所以同意用最顶层的接口Type表示
                Type typeVariable = f.getGenericType();
                // 打印对应类型
                System.out.println(typeVariable.getTypeName());
            }
            // 这里我们知道a是泛型类型,可以将Type 强转为其子接口TypeVariable类型
            Field field = ConstructorTest.class.getDeclaredField("a");
            TypeVariable typeVariablea = (TypeVariable) field.getGenericType();
            System.out.println("泛型原始类型信息: " + typeVariablea.getName());
            Class fieldClass = field.getType();
            System.out.println("泛型擦除后的类型信息: " + fieldClass.getName());
            for (Type bound : typeVariablea.getBounds()) {
                System.out.println(bound);
            }
            System.out.println(typeVariablea.getGenericDeclaration());

            // 如果成员变量是泛型容器,定义的时候使用的参数化类型
            Field stringListField = ConstructorTest.class.getDeclaredField("stringList");
            System.out.println(stringListField.getType());
            ParameterizedType stringListParameterizedType = (ParameterizedType) stringListField.getGenericType();
            System.out.println(stringListParameterizedType.getActualTypeArguments()[0]);
            System.out.println(stringListParameterizedType.getOwnerType());
            System.out.println(stringListParameterizedType.getRawType().getTypeName());
            System.out.println(stringListParameterizedType.getTypeName());

            // 如果是通配符类型
            System.out.println("*******************WildcardType*********************");
            Field stringList2Field = ConstructorTest.class.getDeclaredField("stringList2");
            System.out.println(stringList2Field.getType());
            Type type = stringList2Field.getGenericType();
            System.out.println(type.getClass());
            ParameterizedType parameterizedType = (ParameterizedType) type;
            //if (stringList2Field.getGenericType() instanceof ParameterizedType){

                WildcardType wildcardType  = (WildcardType) (parameterizedType).getActualTypeArguments()[0];
                for (Type r:wildcardType.getUpperBounds()){
                    System.out.println(r.getTypeName());
                }
           // }

            System.out.println("****************************************");
            // 如泛型数组
            Field cArrayField = ConstructorTest.class.getField("cArray");
            System.out.println(cArrayField.getType());
            System.out.println(cArrayField.getGenericType());
            if (cArrayField.getGenericType() instanceof GenericArrayType) {
                System.out.println("数组类型" + ((GenericArrayType) cArrayField.getGenericType()).getGenericComponentType());
            }



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


        // 3.查看泛型方法中所声明的泛型类型
        try {
            Method method = ConstructorTest.class.getDeclaredMethod("showData", Serializable.class);
            // 方法的返回值、参数列表、抛出异常列表 中都有可能有泛型类型。
            // 查看返回值中的泛型类型,当然也有可能返回值不是泛型类型,所以用顶层接口Type表示
            Type returnType = method.getGenericReturnType();
            if (returnType instanceof TypeVariable) {
                TypeVariable returnTypeVariable = (TypeVariable) returnType;
                System.out.println(returnTypeVariable.getTypeName());
                System.out.println(returnTypeVariable.getBounds()[0].getTypeName());
            } else {
                System.out.println("返回值不是泛型类型");
                System.out.println(returnType.getTypeName());
            }
            Class returnClass = method.getReturnType();
            System.out.println("泛型方法擦除后的返回值类型:" + returnClass.getName());
            System.out.println("********************");
            // 查看参数列表中的泛型类型,参数有多个,所以是数组
            Type[] paramTypes = method.getGenericParameterTypes();
            for (Type paramType : paramTypes) {
                if (paramType instanceof TypeVariable) {
                    System.out.println(paramType.getTypeName());
                }
            }
            // 查看参数列表泛型擦除后的类型信息。
            Class[] paramTypes1 = method.getParameterTypes();
            for (Class aClass : paramTypes1) {
                System.out.println(aClass.getName());
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

3.反射应用

Java反射机制是指java程序在运行过程中,可以获取任意一个类的相关信息,并且能够调用其方法和属性,这种动态获取信息和动态调用方法的功能叫做Java的反射机制

这里先总结下反射的流程:

(1)先拿到一个类的Class对象。

(2)使用Constructor对象的newInstance方法创建类实例对象。

(3)得到要调用方法的Method对象,利用invoke进行方法调用。

(4)使用Field对象获取或修改实例对象属性。

针对私有成员还需要先setAccessible设置权限。

一句话,反射机制的优点就是可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中它的灵活性就表现的十分明显。比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功能。
它的缺点是对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。

// 后续补充其它应用案例
以下就是通过注解拿到标注的值,通过反射调用方法来对成员变量赋值的工具类。

public class InjectUtil {
    // 1.先拿到上面有@BindView注解的Field
    // 2.通过反射拿到activity中的findViewByID方法
    // 3.通过注解拿到资源ID
    // 4.通过反射调用方法得到view对象,通过field的set方法为其赋值
    public static void injectView(AppCompatActivity activity) {
        if (activity == null) {
            return;
        }
        Class<? extends AppCompatActivity> actClass = activity.getClass();
        try {
            Method find = actClass.getMethod("findViewById", int.class);
            Field[] fields = actClass.getDeclaredFields();
            Field[] fieldss = actClass.getFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(BindView.class)) {
                    BindView bindView = field.getAnnotation(BindView.class);
                    int viewId = bindView.viewId();
                    field.setAccessible(true);
                    field.set(activity, find.invoke(activity, viewId));
                }
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    /**
     * 1.通过activity拿到Intent
     * 2.通过遍历Activity成员变量,获取注解拿到key
     * 3.通过Intent中拿到key对应的值。
     * 4.通过field为其进行赋值
     *
     * @param activity
     */
    public static void injectValue(AppCompatActivity activity) {
        Intent intent = activity.getIntent();
        Bundle bundle = intent.getExtras();
        Class actClass = activity.getClass();
        Field[] fields = actClass.getFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(AutoValue.class)) {
                AutoValue autoValue = field.getAnnotation(AutoValue.class);
                String key = autoValue.value();
                if (bundle.containsKey(key)) {
                    Object value = bundle.get(key);
                    field.setAccessible(true);
                    try {
                        field.set(activity, value);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }
}

参考:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值