面试针对复习——反射

Java反射机制概述

反射定义

Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法,加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射

示意图:

动态语言 & 静态语言

动态语言

是一类在运行时可以改变其结构的语言,通俗说就是在运行时代码可以根据某些条件改变自身结构

主要动态语言:Object-CC#JavaScriptPHP

静态语言

与动态语言相对应,在运行时结构不可变的语言就是静态语言

主要静态语言:JavaCC++

注:Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。Java的动态性让编程的时候更加灵活!

反射机制研究及应用

  • 在运行时判断任意一个对象所属的类

  • 在运行时构造任意一个类的对象

  • 在运行时判断任意一个类所具有的成员变量和方法

  • 在运行时获取泛型信息

  • 在运行时调用任意一个对象的成员变量和方法

  • 在运行时处理注解

  • 生成动态代理

反射相关的主要API

  • java.lang.Class: 代表一个类
  • java.lang.reflect.Method: 代表类的方法
  • java.lang.reflect.Field: 代表类的成员变量
  • java.lang.reflect.Constructor: 代表类的构造器

反射的动态性

可以通过反射在编译运行之后在确定需要创建的对象

public class Solution {
    public static void main(String[] args) {
        int num = new Random().nextInt(3);
        String classPath = "";
        switch (num) {
            case 0:
                classPath = "java.lang.Object";
                break;
            case 1:
                classPath = "java.lang.String";
                break;
            case 2:
                classPath = "java.util.Date";
                break;
        }
        Object instance = getInstance(classPath);
        System.out.println(instance);
    }
    public static Object getInstance(String classPath) {
        Object o = null;
        try {
            Class<?> aClass = Class.forName(classPath);
            o = aClass.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            return o;
        }
    }
}

获取Class实例

方式一:调用运行时类的属性

public static void main(String[] args) {
    Class<Persion> clazz = Persion.class;
}

方式二:通过运行时类的对象,调用getClass()方法

public static void main(String[] args) {
    Persion persion = new Persion();
	Class<? extends Persion> aClass = persion.getClass();
}

方式三:调用Class的静态方法:forName()

public static void main(String[] args) throws ClassNotFoundException {
    Class<?> aClass = Class.forName("com.eh.Persion");
}

方式四(不常用):使用类的加载器ClassLoader()

public static void main(String[] args) throws ClassNotFoundException {
    ClassLoader classLoader = Persion.class.getClassLoader();
    Class<?> aClass = classLoader.loadClass("com.eh.Pension");
}

类的加载与ClassLoader的理解

类的加载过程

示意图:

  1. 类的加载

    • 程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。之后使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。将字节码文件加载到内存中的过程称为类的加载。加载到内存中的类。称之为运行时类,此运行时类,就作为Class的一个实例
    • 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)
    • 所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与
  2. 类的链接

    Java类的二进制代码合并到JVM的运行状态之中的过程

  3. 类的初始化

    • 执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)
    • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
    • 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。

类加载器ClassLoader

类加载器的作用是把类(class)装载进内存里

JVM 规范定义了三种类型的加载器:

  • 引导类加载器:用C++编写的,是JVM自带的类加载器,负责Java平台核心库,用来装载核心类库。该加载器无法直接获取
  • 扩展类加载器:负责jre/lib/ext目录下的jar包或
    D java.ext.dirs 指定目录下的jar包装入工作库
  • 系统类加载器:负责java –classpath–D
    java.class.path所指的目录下的类与jar包装入工作 ,是最常用的加载器

代码示例:

public class Solution {
    public static void main(String[] args) {
        // 自定义类,使用的系统类加载器进行加载
        ClassLoader classLoader = Solution.class.getClassLoader();
        System.out.println(classLoader);

        // 调用系统类加载器的getParent():拓展类加载器
        ClassLoader classLoader1 = classLoader.getParent();
        System.out.println(classLoader1);

        // 调用拓展类加载器的getParent():无法获取引导类加载器
        // 引导类加载器主要负责Java的核心库,无法加载自定义类
        ClassLoader classLoader2 = classLoader1.getParent();
        System.out.println(classLoader2);

        // 使用的引导类加载器 但是无法获取到
        ClassLoader classLoader3 = String.class.getClassLoader();
        System.out.println(classLoader3);
    }
}

控制台打印结果:

反射的基本操作

获取运行时类的结构

获取属性

获取当前运行时类及其父类中声明为public访问权限的属性

public class Solution {
    public static void main(String[] args) {
        Class<Solution> solutionClass = Solution.class;
        Field[] fields = solutionClass.getFields();
    }
}

获取当前运行时类自己声明的所有属性,不包含父类中声明的属性

public class Solution {
    public static void main(String[] args) {
        Class<Solution> solutionClass = Solution.class;
        Field[] declaredFields = solutionClass.getDeclaredFields();
    }
}

获取属性中的某一个结构部分

public class Solution {
    public static void main(String[] args) {
        Class<Solution> solutionClass = Solution.class;
        Field[] declaredFields = solutionClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            // 获取权限修饰符
            int modifiers = declaredField.getModifiers();
            System.out.println(Modifier.toString(modifiers));
            // 获取数据类型
            Class<?> type = declaredField.getType();
            System.out.println(type);
            // 获取变量名
            String name = declaredField.getName();
            System.out.println(name);
        }
    }
}
获取方法

获取当前运行时类及其父类的中声明为public权限的方法

public class Solution {
    public static void main(String[] args) {
        Class<Solution> solutionClass = Solution.class;
        Method[] methods = solutionClass.getMethods();
    }
}

获取当前运行时类中声明的所有方法(不包含父类中声明的)

public class Solution {
    public static void main(String[] args) {
        Class<Solution> solutionClass = Solution.class;
        Method[] methods = solutionClass.getDeclaredMethods();
    }
}

获取方法中的某一个结构部分

public class Solution {
    public static void main(String[] args) {
        Class<Solution> solutionClass = Solution.class;
        Method[] methods = solutionClass.getDeclaredMethods();
        for (Method method : methods) {
            // 获取方法声明的注解
            Annotation[] annotations = method.getAnnotations();

            // 获取权限修饰符
            int modifiers = method.getModifiers();
            System.out.println(Modifier.toString(modifiers));

            // 获取返回值类型
            String returnType = method.getReturnType().getName();

            // 获取方法名
            String methodName = method.getName();

            // 获取形参列表
            Class<?>[] parameterTypes = method.getParameterTypes();

            // 获取抛出的异常
            Class<?>[] exceptionTypes = method.getExceptionTypes();
        }
    }
}
获取构造器

获取当前运行时类中声明为public的构造器

public class Solution {
    public static void main(String[] args) {
        Class<Solution> solutionClass = Solution.class;
        Constructor<?>[] constructors = solutionClass.getConstructors();
    }
}

获取当前运行时类中声明的所有构造器

public class Solution {
    public static void main(String[] args) {
        Class<Solution> solutionClass = Solution.class;
        Constructor<?>[] constructors = solutionClass.getDeclaredConstructors();
    }
}
获取父类

获取运行时类的父类

public class Solution {
    public static void main(String[] args) {
        Class<Solution> solutionClass = Solution.class;
        Class<? super Solution> superclass = solutionClass.getSuperclass();
    }
}

获取运行时类的带泛型的父类

public class Solution {
    public static void main(String[] args) {
        Class<Solution> solutionClass = Solution.class;
        Type genericSuperclass = solutionClass.getGenericSuperclass();
    }
}
其他获取

获取运行时类实现的接口

public class Solution {
    public static void main(String[] args) {
        Class<Solution> solutionClass = Solution.class;
        Class<?>[] interfaces = solutionClass.getInterfaces();
    }
}

获取运行时类的父类所实现的接口

public class Solution {
    public static void main(String[] args) {
        Class<Solution> solutionClass = Solution.class;
        Class<? super Solution> superclass = solutionClass.getSuperclass();
    }
}

获取当前运行时类所在的包

public class Solution {
    public static void main(String[] args) {
        Class<Solution> solutionClass = Solution.class;
        Package aPackage = solutionClass.getPackage();
    }
}

获取运行时类声明的注解

public class Solution {
    public static void main(String[] args) {
        Class<Solution> solutionClass = Solution.class;
        Annotation[] annotations = solutionClass.getAnnotations();
    }
}

调用运行时类的指定结构

调用运行时类的指定属性

方法一(不常用):

public class Solution {
    public static void main(String[] args) throws Exception {
        Class<Persion> persionClass = Persion.class;
        // 创建运行时类的对象
        Persion persion = persionClass.newInstance();
        
        // 获取指定的属性,要求运行时类声明的该属性为public
        // 通常不采用此方法
        Field id = persionClass.getField("id");
        
        // 设置当前属性的值
        id.set(persion, 123);
        int pid = (int) id.get(persion);
        System.out.println(pid);
    }
}

方法二:

public class Solution {
    public static void main(String[] args) throws Exception {
        Class<Persion> persionClass = Persion.class;
        // 创建运行时类的对象
        Persion persion = persionClass.newInstance();

        // 获取运行时类中指定的变量名的属性(私有的属性也可以获取)
        Field name = persionClass.getDeclaredField("name");

        // 设置当前属性是可以访问的
        name.setAccessible(true);

        name.set(persion, "李四");
        System.out.println(name.get(persion));
    }
}

调用静态属性

public class Solution {
    public static void main(String[] args) throws Exception {
        Class<Persion> persionClass = Persion.class;
        // 创建运行时类的对象
        Persion persion = persionClass.newInstance();

        // 获取运行时类中指定的变量名的属性(私有的属性也可以获取)
        Field name = persionClass.getDeclaredField("name");

        // 设置当前属性的值
        name.setAccessible(true);

        name.set(Persion.class, "李四");
        System.out.println(name.get(Persion.class));
    }
}
调用运行时类的指定方法

调用非静态方法

public String show(String s) {
    System.out.println(s);
    return s;
}
public class Solution {
    public static void main(String[] args) throws Exception {
        Class<Persion> persionClass = Persion.class;
        // 创建运行时类的对象
        Persion persion = persionClass.newInstance();

        // 获取运行时类中指定的变量名的属性(私有的属性也可以获取)
        Method show = persionClass.getDeclaredMethod("show", String.class);
        show.setAccessible(true);
        Object o = show.invoke(persion, "中国");
        System.out.println(o);
    }
}

调用静态方法

public static void showDesc() {
    System.out.println("静态方法");
}
public class Solution {
    public static void main(String[] args) throws Exception {
        Class<Persion> persionClass = Persion.class;
        // 创建运行时类的对象
        Persion persion = persionClass.newInstance();

        // 获取运行时类中指定的变量名的属性(私有的属性也可以获取)
        Method showDesc = persionClass.getDeclaredMethod("showDesc");
        showDesc.setAccessible(true);
        showDesc.invoke(Persion.class);
    }
}
调用运行时类的指定构造器

调用默认构造器

public class Solution {
    public static void main(String[] args) throws Exception {
        Class<Persion> persionClass = Persion.class;
        Persion persion = persionClass.newInstance();
    }
}

调用指定的构造器去创建对象

public class Solution {
    public static void main(String[] args) throws Exception {
        Class<Persion> persionClass = Persion.class;
        // 获取指定的构造器
        Constructor<Persion> declaredConstructor = persionClass.getDeclaredConstructor(String.class);
        // 保证该构造器是可以访问的
        declaredConstructor.setAccessible(true);
        // 通过指定构造器创建对象
        Persion persion = declaredConstructor.newInstance("lisi");
    }
}

反射的应用:动态代理

代理设计模式原理

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

静态代理

静态代理:编译期间,代理类与被代理类就已经确定了

代码实现:

接口:

interface ClothFactory {
    void produceCloth();
}

代理类:

package com.eh.test;

// 代理类
public class ProxyClothFactory implements ClothFactory {

    private ClothFactory factory;

    public ProxyClothFactory(ClothFactory factory) {
        this.factory = factory;
    }

    @Override
    public void produceCloth() {
        System.out.println("代理工厂做了一些准备工作");
        factory.produceCloth();
        System.out.println("代理工厂做了一些后续工作");
    }
}

被代理类:

public class NikeClothFactory implements ClothFactory {
    @Override
    public void produceCloth() {
        System.out.println("Nike生产了一批运动服");
    }
}

测试类:

package com.eh.test;

public class StaticProxyTest {
    public static void main(String[] args) {
        // 创建被代理类对象
        NikeClothFactory nikeClothFactory = new NikeClothFactory();
        // 创建代理类对象
        ProxyClothFactory proxyClothFactory = new ProxyClothFactory(nikeClothFactory);
        proxyClothFactory.produceCloth();
    }
}

动态代理

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

代码实现(JDK动态代理)

接口:

package com.eh.test;

interface Human {
    String getBelief();

    void eat(String food);
}

被代理类

// 被代理类
public class SuperMan implements Human {

    @Override
    public String getBelief() {
        return "I believe I can fly";
    }

    @Override
    public void eat(String food) {
        System.out.println("我喜欢吃" + food);
    }
}

代理类

public class ProxyFactory {
    public static Object getProxyInstance(Object obj) {
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
        myInvocationHandler.bind(obj);
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), myInvocationHandler);
    }
}

class MyInvocationHandler implements InvocationHandler {

    // 定义被代理类的对象
    private Object object;

    public void bind(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 该 method 为代理类对象的方法,也可以作为被代理的方法调用
        Object invokeValue = method.invoke(object, args);
        // 上述方法(被代理类对象的方法)的返回值
        return invokeValue;
    }
}

测试类

public class Test {
    public static void main(String[] args) {
        SuperMan superMan = new SuperMan();
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        String belief = proxyInstance.getBelief();
        proxyInstance.eat("食物");
    }
}

AOP切面编程就充分的体现出动态代理的优势

原理:只需在invoke方法中编辑自己想要添加的方法

反射常见的面试问题

  1. 通过new的方式或反射的方式都可以调用公共并的结构,开发中应该用哪个?

    一般情况下建议使用new的方式,当设计到创建的对象的动态性的时候建议使用反射

  2. 反射机制与面向对象中的封装性是不是矛盾,如何看待这两个技术?

    答:不矛盾,面向对象的封装性是是为了建议并提示开发者不去直接使用私有的方法属性及构造器,尽量去调用其设计者设计的public公有方法;而反射机制可以处理一些极端情况,有些开发者可能就要使用用封装好的类里面的私有方法属性或者构造器,反射机制就为这类人提供了这样操作的可能,而且反射还有更多其他的应用,比如常见的Spring框架就是以反射为基础

  3. 写一个ArrayList的动态代理类

public static void main(String[] args) {
        final ArrayList<String> list = new ArrayList<>();
        List<String> proxyInstance = (List<String>) Proxy.newProxyInstance(list.getClass().getClassLoader(), list.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("pre-------------");
                return method.invoke(list, args);
            }
        });
        proxyInstance.add("hello");
        System.out.println(list);
    }
  1. 动静态代理的区别,什么场景使用?

    静态代理:

    静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类,静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。

    动态代理:

    • 动态代理是实现 JDK 里的 InvocationHandler 接口的 invoke 方法,但注意的是代理的是接口,也就是业务类必须要实现接口,通过 Proxy 里的 newProxyInstance 得到代理对象
    • 还有一种动态代理 CGLIB,代理的是类,不需要业务类继承接口,通过派生的子类来实现代理。通过在运行时,动态修改字节码达到修改类的目的

    AOP 编程就是基于动态代理实现的,比如著名的 Spring 框架、Hibernate 框架等等都是动态代理的使用例子

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值