java学习笔记7 -- 反射

1 概述

1.1 定义

在程序执行期间,通过反射提供的API,可以获取类的内部信息,也可以操作任何对象的属性和方法。

1.2 作用

  1. 在运行时判断一个对象所属的类;
  2. 在运行时动态创建对象;
  3. 在运行时获取类内部的完整结构;
  4. 在运行时操作对象的属性和方法;
  5. 在运行时处理注解;
  6. 在运行时生成动态代理;

1.3 注意事项

一般情况下,使用new来创建对象;但是如果在程序运行过程中才能确定创建的对象,使用反射来创建对象。

1.4 反射与封装性的关系

可以通过反射来获取对象的私有内容,但是这与封装性并不冲突。因为封装性存在的意义是隐藏私有内容,为了给使用者提供更方便、更好用的公共接口。但是调用者应该有权限获取对象中私有的内容。

1.5 反射的应用

        Class clazz = Person.class;
//        1. 通过反射,调用Person类的构造器,创建Person类对象
//        获取Person的构造器
        Constructor constructor = clazz.getConstructor(String.class,int.class);
//      通过构造器创建对象
        Object jack = constructor.newInstance("jack", 20);
        Person p = (Person) jack;
        System.out.println(p.toString());

//        2. 通过反射,调用对象的属性和方法
//        调用属性
        Field age = clazz.getDeclaredField("age");
        age.set(p, 10); // 给属性赋值
        System.out.println(p.toString());

//        调用方法
        Method test = clazz.getDeclaredMethod("test");
        test.invoke(p);

//        通过反射,可以调用类的私有结构. 例如:私有构造器,方法,属性
//        私有构造器
        Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class);
        declaredConstructor.setAccessible(true);
        Person o = (Person) declaredConstructor.newInstance("123");
        System.out.println(o.toString());

//        私有属性
        Field name = clazz.getDeclaredField("name");
        name.setAccessible(true);
        name.set(o, "zs");
        System.out.println(o.toString());

//        私有方法
        Method test1 = clazz.getDeclaredMethod("test1");
        test1.setAccessible(true);
        test1.invoke(o);

2 Class类

2.1 Class类的理解

  1. java源文件经过javac.exe编译之后生成一个或多个字节码文件,然后使用java.exe对某个字节码文件进行解释运行,就相当于将这个字节码文件加载到内存中,这就是类的加载过程。加载到内存中的类,称作运行时类,运行时类是Class类的一个实例,也就是说类也是一个对象。
  2. Class类是描述类的类。
  3. 一个加载的类在 JVM 中只会有一个Class实例,也就是说每个类的运行时类是唯一的,相当于是单例模式。
  4. 作用通过Class对象,可以获取运行时类的完整结构

2.2 获取Class实例的四种方式

在开发中,经常使用方式3来获取Class实例,因为方式3可以传入任意类名,在编译期不会出现异常,更有利于动态性的实现。

//        方式1:通过运行时类的属性.class
        Class<Person> clazz1 = Person.class;
//        方式2:通过类的实例的getClass方法
        Person person = new Person();
        Class<? extends Person> clazz2 = person.getClass();
//        方式3:通过Class类的静态方法forName,需要传入全类名,即包括包名的类名,需要处理异常
        Class<?> clazz3 = Class.forName("包名.Person");
//        方式4:类加载器,此处传入的也是全类名
        ClassLoader classLoader = this.getClass().getClassLoader();
        Class<?> clazz4 = classLoader.loadClass("包名.Person");

        System.out.println(clazz1 == clazz2); // true 同一个对象
        System.out.println(clazz2 == clazz3); // true

2.3 哪些类型可以有Class对象?

除了类(外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类)以外,接口、枚举类、注解、数组、void、基本数据类型都有Class对象。
注意:对于数组来说,只要类型和维度一样,那么就是同一个Class实例。

        int[] a = new int[10];
        int[] b = new int[100];
        Class c10 = a.getClass();
        Class c11 = b.getClass();
        // 只要元素类型与维度一样,就是同一个Class
        System.out.println(c10 == c11); // true

2.4 类的加载过程

在这里插入图片描述
在这里插入图片描述

2.4.1 类加载器

类加载器作用是用来把类(class)装载进内存的。下面三种加载器是父子类,引导类加载器是扩展类加载器的父类,扩展类加载器是系统类加载器的父类。

  1. 引导类加载器:用C++编写的,是JVM自带的类加载器,负责Java平台核心库,用来装载核心类库(例如String),该加载器无法直接获取,即我们不能使用引导类加载器加载自定义类。
  2. 扩展类加载器:负责jre/lib/ext目录下的jar包或 –D java.ext.dirs 指定目录下的jar包装入工作库。
  3. 系统类加载器:负责java –classpath 或 –D java.class.path所指的目录下的类与jar包装入工作 ,是最常用的加载器。自定义类就是使用此加载器进行加载的。

2.4.2 实例

public class ClassLoadingTest {
public static void main(String[] args) {
System.out.println(A.m); // 100
} }
class A {
静态代码块和静态变量的执行顺序取决于在程序中的出现位置
static { m = 300;}
static int m = 100;
}
//第二步:链接结束后m=0
//第三步:初始化后,m的值由<clinit>()方法执行决定
// 这个A的类构造器<clinit>()方法由类变量的赋值和静态代码块中的语句按照顺序合并
产生,类似于
// <clinit>(){
// m = 300;
// m = 100;
// }

2.4.3 使用ClassLoader加载配置文件

//  	两种方式都是使用Properties进行加载的
        Properties properties = new Properties();

//        方式1:使用IO流读取配置文件
        FileInputStream fis = new FileInputStream("src\\test.properties");
        properties.load(fis);
        
        // 方式2:使用类加载器读取配置文件
        ClassLoader classLoader = this.getClass().getClassLoader();
        InputStream resourceAsStream = classLoader.getResourceAsStream("test.properties");
        properties.load(resourceAsStream);

        String name = properties.getProperty("name");
        String age = properties.getProperty("age");
        System.out.println(name + age);

2.5 反射创建对象

  1. 在开发中,大部分情况下都会使用Class类中的newInstace方法创建运行时类的对象。 因为newInstance方法调用的是运行时类的空参构造器,通用性更强
  2. 如果想要通过有参构造器创建对象,可以使用通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类型的构造器,然后调用形参类型的构造器的newInstance方法来创建对象。
//        通过反射,创建运行时类的对象
        Class<Person> clazz = Person.class;
        /**
         * 在开发中,大部分情况下都会使用Class类中的newInstace方法创建运行时类的对象。
         * 因为newInstance方法调用的是运行时类的空参构造器,通用性更强。
         *
         * 调用newInstance方法需要满足的条件:
         *      1. 运行时类必须要有空参构造器;
         *      2. 空参构造器的访问权限要够,通常设置为public
         */
        Person person = clazz.newInstance();
        System.out.println(person);
//        获取Person的构造器
        Constructor constructor = clazz.getConstructor(String.class,int.class);
//      通过构造器创建对象
        Object jack = constructor.newInstance("jack", 20);
        Person p = (Person) jack;
        System.out.println(p.toString());

2.6 javaBean中要求提供空参构造器的原因

  1. 使用反射创建运行时类的对象时,需要调用空参构造器;
  2. 子类构造器中如果没有使用super显示调用父类中的构造器,那么会默认有一个super()调用父类的空参构造器。

3 通过反射获取运行时类的完整结构

3.1 获取类中的属性

//        获取Person的Class实例
        Class<Person> personClass = Person.class;
//        1. 获取Person类及其父类中所有public的属性
        Field[] fields = personClass.getFields();

//        2. 获取Person类中所有的属性,不受权限修饰符的限制(不能获取父类中的属性)
        Field[] declaredFields = personClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.print(declaredField); //private java.lang.String Person.name
//            获取属性的权限修饰符
            System.out.print(Modifier.toString(declaredField.getModifiers())); //private
//            获取属性的数据类型
            System.out.print(declaredField.getType()); // java.lang.String
//            获取属性的变量名
            System.out.print(declaredField.getName()); //name
            System.out.println();
        }

3.2 获取类中的方法

//        获取Person类以及其父类中的public方法
        Method[] methods = personClass.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
        System.out.println();
//        获取Person类中所有的方法,不受权限修饰符的限制(不能获取父类中的方法)
        Method[] declaredMethods = personClass.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println(declaredMethod);
//            获取权限修饰符
            System.out.println(Modifier.toString(declaredMethod.getModifiers()));
//            获取返回值类型
            System.out.println(declaredMethod.getReturnType());

//            获取方法名
            System.out.println(declaredMethod.getName());
//            获取参数列表
//            getParameterTypes()--参数类型
            Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
            if (!(parameterTypes.length == 0 && parameterTypes == null))
                for (Class<?> parameterType : parameterTypes) {
                    System.out.println(parameterType.getName());
                }
//            获取抛出的异常
            Class<?>[] exceptionTypes = declaredMethod.getExceptionTypes();
             if (exceptionTypes.length > 0) {
                for (Class<?> exceptionType : exceptionTypes) {
                    System.out.println(exceptionType.getName());
                }
            }
            //            获取方法的注解
            Annotation[] annotation = declaredMethod.getAnnotations();
            if(annotation.length > 0){
                for (Annotation annotation1 : annotation) {
                    System.out.println(annotation);
                }
            }

3.3 获取类所在的包

Class<Person> personClass = Person.class;
Package aPackage = personClass.getPackage();

3.4 获取类的构造器

获取构造器中的权限修饰符、返回值类型、构造器名称、参数列表和类的方法一样。

//        获取Person的Class实例
        Class<Person> personClass = Person.class;

//        getConstructors():获取当前运行时类中public修饰的构造器,不能获取父类的构造器
        Constructor<?>[] constructors = personClass.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
        }

//        getDeclaredConstructors():获取当前运行时类中所有的构造器,不受权限修饰符的限制
        Constructor<?>[] declaredConstructors = personClass.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor);
        }

3.5 获取类的父类

3.4.1 获取类的父类(不带泛型)

        Class<Person> personClass = Person.class;
        System.out.println(personClass.getSuperclass());

3.4.2 获取类的父类(带泛型)

        Class<Person> personClass = Person.class;
        System.out.println(personClass.getGenericSuperclass());

3.4.3 获取类的父类的泛型

//        获取Person的Class实例
        Class<Person> personClass = Person.class;
        Type genericSuperclass = personClass.getGenericSuperclass();
        ParameterizedType p = (ParameterizedType) genericSuperclass;
        Type[] typeName = p.getActualTypeArguments();
        for (Type type : typeName) {
//            方法1:调用getTypeName()
            System.out.println(type.getTypeName());
//            方法2:强转为Class,然后调用getName()
            System.out.println(((Class)type).getName());
        }

3.6 获取类的接口

        Class<Person> personClass = Person.class;
        Class<?>[] interfaces = personClass.getInterfaces();
        for (Class<?> anInterface : interfaces) {
            System.out.println(anInterface.getName());
        }

3.7 获取类的注解

        Annotation[] annotations = personClass.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

4 通过反射调用运行时类的指定结构

4.1 属性

注意:需要设置setAccessible(true),如果不设置,操作非public的属性会抛出异常。

//        获取运行时类的Class对象
        Class<Person> personClass = Person.class;
//        创建运行时类Person对象
        Person person = personClass.newInstance();
//        获取运行时类Person的name属性
        Field name = personClass.getDeclaredField("name");
//        设置当前属性是可访问的
        name.setAccessible(true);
//        设置person对象的name属性值
        name.set(person,"zs");
//        获取person对象的name属性值
        Object o = name.get(person);

静态属性的调用
因为gender是静态属性,可以通过类直接调用,又因为gender是通过personClass获取的,所以set中的参数1可以为null,参数2是给gender赋值

//        静态属性的调用
        Field gender = personClass.getDeclaredField("gender");
        gender.setAccessible(true);
//        因为gender是静态属性,可以通过类直接调用,又因为gender是通过personClass获取的,所以set
//        中的参数1可以为null,参数2是给gender赋值
        gender.set(null,12);
        Object o1 = gender.get(null);
        System.out.println(o1);

4.2 方法

注意:需要设置setAccessible(true),如果不设置,操作非public的方法会抛出异常。

//        操作类中的方法
        Class<Person> personClass = Person.class;
//        创建运行时类Person对象
        Person person = personClass.newInstance();
//        获取method方法,参数1是方法名,参数2是参数类型的Class实例
        Method method = personClass.getDeclaredMethod("method",String.class);
//        设置方法为可访问的
        method.setAccessible(true);
//        调用方法,参数1是运行时类的对象,参数2是方法的实参。
//        invoke的返回值是方法method的返回值
        Object invoke = method.invoke(person, "123");
        System.out.println(invoke);

静态方法

        Method test11 = personClass.getDeclaredMethod("test11", int.class);
        test11.setAccessible(true);
        // 参数1是null与静态属性同理
        Object invoke1 = test11.invoke(null, 10);
        System.out.println(invoke1);

4.3 构造器

开发中经常使用的是空参构造器,因为更通用。方法2不经常使用。

  1. 空参构造器
        Class<Person> personClass = Person.class;
        Person person1 = personClass.newInstance();
        System.out.println(person1);
  1. 指定带参构造器
        Class<Person> personClass = Person.class;
//        获取运行时类指定的构造器,参数是构造器的形参列表
        Constructor<Person> declaredConstructor = personClass.getDeclaredConstructor(String.class);
//        设置构造器是可访问的
        declaredConstructor.setAccessible(true);
//        调用此构造器创建对象
        Person person = declaredConstructor.newInstance("1231");
        System.out.println(person);

5 反射的应用:动态代理

5.1 代理模式的原理

使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

5.2 细节

  1. 代理类完成一些通用的功能,被代理类完成核心功能。例如租房,找房和谈价格由房产中介来完成,而付钱和居住是由用户来完成,只有付钱之后才能居住。
  2. 代理类和被代理类需要实现同一接口。

5.3 静态代理

5.2.1 缺点

代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。

5.2.2 实例1

多线程中实现Runnable接口来创建线程:

两者都实现了Runnable接口
class RunnableTest implement Runnable{} 被代理类
class Thread implement Runnable{} 代理类
main(){
Thread t = new Thread(new RunnableTest());
t.start();
}

5.2.3 实例2

public class ProxyTest {
    public static void main(String[] args) {
//        创建被代理类的对象
        AntaFactory antaFactory = new AntaFactory();
//        创建代理类的对象
        ProxyFactory p = new ProxyFactory(antaFactory);
//        调用代理类中的方法
        p.produce();
    }
}

/*
接口,被代理类和代理类都需要实现这个接口
 */
interface ClothFactory{
    void produce();
}
/*
被代理类
 */
class AntaFactory implements ClothFactory{
    @Override
    public void produce() {
        System.out.println("生产运动鞋");
    }
}

/*
代理类
 */
class ProxyFactory implements ClothFactory{
    private AntaFactory antaFactory; // 被代理类的对象
    public ProxyFactory(AntaFactory antaFactory){
        this.antaFactory = antaFactory;
    }
    @Override
    public void produce() {
        System.out.println("准备");
        antaFactory.produce();
        System.out.println("善后");
    }
}

5.4 动态代理

5.4.1 定义

动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象

5.4.2 优点

在运行期创建代理类,更加灵活,而且创建代理类的类是通用的。

5.4.3 步骤

  1. 创建接口和被代理类
/*
接口,被代理类和代理类都需要实现这个接口
 */
interface ClothFactory {
    void produce(String type);

    String design();
}

/*
被代理类
 */
class AntaFactory implements ClothFactory {
    @Override
    public void produce(String type) {
        System.out.println("生产" + type);
    }

    @Override
    public String design() {
        System.out.println("设计");
        return "";
    }
}
  1. 创建一个实现接口InvocationHandler的类,它必须实现invoke方法,以完成代理的具体操作。invoke方法主要的作用是当通过代理类的对象调用方法a时,动态的去调用被代理类中的同名方法a。
class MyInvocationHandler implements InvocationHandler {
    private Object obj; // 被代理类的对象

     public void bind(Object obj){
        this.obj = obj; // 给被代理类的对象赋值
    }

    /*
    proxy:代理类
    method:代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
    args:方法参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    	 System.out.println("调用方法前添加代码");
        // obj是被代理类的对象,returnValue是方法的返回值
        Object returnValue = method.invoke(obj, args);
        System.out.println("调用方法后添加代码");
        return returnValue;
    }

}
  1. 通过Proxy的静态方法newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)动态创建代理类,并调用方法
public class ProxyTest {
    public static void main(String[] args) {
//        创建被代理对象
        AntaFactory antaFactory = new AntaFactory();
//        将被代理对象绑定到handler,用于方法的调用
        MyInvocationHandler handler = new MyInvocationHandler();
        handler.bind(antaFactory);
//    通过Proxy的静态方法newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)动态创建代理类
//        参数传入代理类加载器,接口,用于方法调用的handler
        ClothFactory o = (ClothFactory)Proxy.newProxyInstance(antaFactory.getClass().getClassLoader(), antaFactory.getClass().getInterfaces(), handler);
//        通过代理类调用方法
        String design = o.design();
        o.produce("shoes");
    }
}

也可以采用匿名内部类的方式创建InvocationHandler的实现类对象

        ClothFactory o = (ClothFactory)Proxy.newProxyInstance(antaFactory.getClass().getClassLoader(), antaFactory.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            	插入通用处理
                Object invoke = method.invoke(antaFactory, args);
                插入通用处理
                return invoke;
            }
        });

5.5 动态代理与AOP(Aspect Orient Programming)

  1. 使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有太大的意义。通常都是为指定的目标对象(被代理类)生成动态代理。
  2. 这种动态代理在AOP中被称为AOP代理,AOP代理可代替目标对象(被代理类),AOP代理包含了目标对象(被代理类)的全部方法。但AOP代理中的方法与目标对象(被代理类)的方法存在差异:AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理。
class MyInvocationHandler implements InvocationHandler {
    private Object obj; // 被代理类的对象
     public void bind(Object obj){
        this.obj = obj; // 给被代理类的对象赋值
    }
    /*
    proxy:代理类
    method:代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
    args:方法参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    	 System.out.println("调用方法前添加代码"); // 通用处理
        // obj是被代理类的对象,returnValue是方法的返回值
        Object returnValue = method.invoke(obj, args);
        System.out.println("调用方法后添加代码"); // 通用处理
        return returnValue;
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值