Java基础常见面试题(四)

news2024/12/24 8:01:09

反射

什么是反射?

反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。

反射的应用场景了解么?

像咱们平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。但是!这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。

这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。

比如下面是通过 JDK 实现动态代理的示例代码,其中就使用了反射类 Method 来调用指定的方法。

public class DebugInvocationHandler implements InvocationHandler {
    /**
     * 代理类中的真实对象
     */
    private final Object target;

    public DebugInvocationHandler(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        System.out.println("before method " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("after method " + method.getName());
        return result;
    }
}

另外,像 Java 中的一大利器 注解 的实现也用到了反射。为什么你使用 Spring 的时候 ,一个@Component注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 @Value注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。

反射机制的优缺点有哪些?

优点:能够运行时动态获取类的实例,提高灵活性;可与动态编译结合 Class.forName(‘com.mysql.jdbc.Driver.class’),加载MySQL的驱动类。

缺点:使用反射性能较低,需要解析字节码,将内存中的对象进行解析。其解决方案是:通过 setAccessible(true) 关闭JDK的安全检查来提升反射速度;多次创建一个类的实例时,有缓存会快很多;ReflflectASM工具类,通过字节码生成的方式加快反射速度。

如何获取反射中的Class对象?

  1. Class.forName(“类的路径”);当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。
Class clz = Class.forName("java.lang.String");
  1. 类名.class。这种方法只适合在编译前就知道操作的 Class。
Class clz = String.class;
  1. 对象名.getClass()。
String str = new String("Hello");
Class clz = str.getClass();
  1. 如果是基本类型的包装类,可以调用包装类的Type属性来获得该包装类的Class对象。

Java反射API有几类?

反射 API 用来生成 JVM 中的类、接口或则对象的信息。

  1. Class 类:反射的核心类,可以获取类的属性,方法等信息。

  2. Field 类:Java.lang.reflect 包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。

  3. Method 类:Java.lang.reflect 包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。

  4. Constructor 类:Java.lang.reflect 包中的类,表示类的构造方法。

反射使用的步骤?

  1. 获取想要操作的类的Class对象,这是反射的核心,通过Class对象我们可以任意调用类的方法。

  2. 调用 Class 类中的方法,既就是反射的使用阶段。

  3. 使用反射 API 来操作这些信息。

具体可以看下面的例子:

public class Student {

    private Integer age;

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public static void main(String[] args) {

        //正常调用
        Student student = new Student();
        student.setAge(16);
        System.out.println("Student Age : " + student.getAge());

        //使用反射调用
        try {
            Class<?> aClass = Class.forName("com.lzl.javaSE.test8.Student");
            Method setAge = aClass.getMethod("setAge", Integer.class);
            Constructor<?> studentConstructor = aClass.getConstructor();
            Object studentObject = studentConstructor.newInstance();
            setAge.invoke(studentObject, 18);
            Method getAge = aClass.getMethod("getAge");
            System.out.println("Student Age : " + getAge.invoke(studentObject));

        } catch (ClassNotFoundException | InstantiationException | InvocationTargetException | NoSuchMethodException |
                 IllegalAccessException e) {
            throw new RuntimeException(e);
        }


    }
}

从代码中可以看到我们使用反射调用了 setAge方法,并传递了 16 的值。之后使用反射调用了 getAge 方法,输出其年龄。上面的代码整个的输出结果是:

Student Age : 16
Student Age : 18

从这个简单的例子可以看出,一般情况下我们使用反射获取一个对象的步骤:

  • 获取类的 Class 对象实例
Class<?> aClass = Class.forName("com.lzl.javaSE.test8.Student");
  • 根据 Class 对象实例获取 Constructor 对象
Constructor<?> studentConstructor = aClass.getConstructor();
  • 使用 Constructor 对象的 newInstance 方法获取反射类对象
Object studentObject = studentConstructor.newInstance();

而如果要调用某一个方法,则需要经过下面的步骤:

  • 获取方法的 Method 对象
Method setAge = aClass.getMethod("setAge", Integer.class);
  • 利用 invoke 方法调用方法
setAge.invoke(studentObject, 18);

为什么引入反射概念?反射机制的应用有哪些?

我们来看一下 Oracle 官方文档中对反射的描述:

从 Oracle 官方文档中可以看出,反射主要应用在以下几方面:

  • 反射让开发人员可以通过外部类的全路径名创建对象,并使用这些类,实现一些扩展的功能。
  • 反射让开发人员可以枚举出类的全部成员,包括构造函数、属性、方法。以帮助开发者写出正确的代码。
  • 测试时可以利用反射 API 访问类的私有成员,以保证测试代码覆盖率。

也就是说,Oracle 希望开发者将反射作为一个工具,用来帮助程序员实现本不可能实现的功能。

举两个最常见使用反射的例子,来说明反射机制的强大之处:

第一种:JDBC 的数据库的连接

在JDBC 的操作中,如果要想进行数据库的连接,则必须按照以上的几步完成

  1. 通过Class.forName()加载数据库的驱动程序 (通过反射加载,前提是引入相关了Jar包);
  2. 通过 DriverManager 类进行数据库的连接,连接的时候要输入数据库的连接地址、用户名、密码;
  3. 通过Connection 接口接收连接。
public class ConnectionJDBC {  
  
    /** 
     * @param args 
     */  
    //驱动程序就是之前在classpath中配置的JDBC的驱动程序的JAR 包中  
    public static final String DBDRIVER = "com.mysql.jdbc.Driver";  
    //连接地址是由各个数据库生产商单独提供的,所以需要单独记住  
    public static final String DBURL = "jdbc:mysql://localhost:3306/test";  
    //连接数据库的用户名  
    public static final String DBUSER = "root";  
    //连接数据库的密码  
    public static final String DBPASS = "root";  
      
      
    public static void main(String[] args) throws Exception {  
        Connection con = null; //表示数据库的连接对象  
        Class.forName(DBDRIVER); //1、使用CLASS 类加载驱动程序 ,反射机制的体现 
        con = DriverManager.getConnection(DBURL,DBUSER,DBPASS); //2、连接数据库  
        System.out.println(con);  
        con.close(); // 3、关闭数据库  
    }  

第二种:Spring 框架的使用,最经典的就是xml的配置模式。

Spring 通过 XML 配置模式装载 Bean 的过程:

  1. 将程序内所有 XML 或 Properties 配置文件加载入内存中;
  2. Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息;
  3. 使用反射机制,根据这个字符串获得某个类的Class实例;
  4. 动态配置实例的属性。

Spring这样做的好处是:

  1. 不用每一次都要在代码里面去new或者做其他的事情;
  2. 以后要改的话直接改配置文件,代码维护起来就很方便了;
  3. 有时为了适应某些需求,Java类里面不一定能直接调用另外的方法,可以通过反射机制来实现。

模拟 Spring 加载 XML 配置文件:

public class BeanFactory {
       private Map<String, Object> beanMap = new HashMap<String, Object>();
       /**
       * bean工厂的初始化.
       * @param xml xml配置文件
       */
       public void init(String xml)  {
              try {
                     //读取指定的配置文件
                     SAXReader reader = new SAXReader();
                     ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
                     //从class目录下获取指定的xml文件
                     InputStream ins = classLoader.getResourceAsStream(xml);
                     Document doc = reader.read(ins);
                     Element root = doc.getRootElement();  
                     Element foo;
                    
                     //遍历bean
                     for (Iterator i = root.elementIterator("bean"); i.hasNext();) {  
                            foo = (Element) i.next();
                            //获取bean的属性id和class
                            Attribute id = foo.attribute("id");  
                            Attribute cls = foo.attribute("class");
                           
                            //利用Java反射机制,通过class的名称获取Class对象
                            Class bean = Class.forName(cls.getText());
                           
                            //获取对应class的信息
                            java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(bean);
                            //获取其属性描述
                            java.beans.PropertyDescriptor pd[] = info.getPropertyDescriptors();
                            //设置值的方法
                            Method mSet = null;
                            //创建一个对象
                            Object obj = bean.newInstance();
                           
                            //遍历该bean的property属性
                            for (Iterator ite = foo.elementIterator("property"); ite.hasNext();) {  
                                   Element foo2 = (Element) ite.next();
                                   //获取该property的name属性
                                   Attribute name = foo2.attribute("name");
                                   String value = null;
                                  
                                   //获取该property的子元素value的值
                                   for(Iterator ite1 = foo2.elementIterator("value"); ite1.hasNext();) {
                                          Element node = (Element) ite1.next();
                                          value = node.getText();
                                          break;
                                   }
                                  
                                   for (int k = 0; k < pd.length; k++) {
                                          if (pd[k].getName().equalsIgnoreCase(name.getText())) {
                                                 mSet = pd[k].getWriteMethod();
                                                 //利用Java的反射极致调用对象的某个set方法,并将值设置进去
                                                 mSet.invoke(obj, value);
                                          }
                                   }
                            }
                           
                            //将对象放入beanMap中,其中key为id值,value为对象
                            beanMap.put(id.getText(), obj);
                     }
              } catch (Exception e) {
                     System.out.println(e.toString());
              }
       }
      
       //other codes
}

反射机制的原理是什么?

Class actionClass=Class.forName(MyClass);
Object action=actionClass.newInstance();
Method method = actionClass.getMethod(“myMethod”,null);
method.invoke(action,null);

上面就是最常见的反射使用的例子,前两行实现了类的装载、链接和初始化(newInstance方法实际上也是使用反射调用了方法),后两行实现了从class对象中获取到method对象然后执行反射调用。

反射原理简要描述如下:

  1. 反射获取类实例 Class.forName(),并没有将实现留给了java,而是交给了jvm去加载!主要是先获取 ClassLoader, 然后调用 native 方法,获取信息,加载类则是回调 java.lang.ClassLoader。最后,jvm又会回调 ClassLoader 进类加载!

  2. newInstance() 主要做了三件事:

    • 权限检测,如果不通过直接抛出异常;
    • 查找无参构造器,并将其缓存起来;
    • 调用具体方法的无参构造方法,生成实例并返回。
  3. 获取Method对象

在这里插入图片描述
上面的Class对象是在加载类时由JVM构造的,JVM为每个类管理一个独一无二的Class对象,这份Class对象里维护着该类的所有Method,Field,Constructor的cache,这份cache也可以被称作根对象。

每次getMethod获取到的Method对象都持有对根对象的引用,因为一些重量级的Method的成员变量(主要是MethodAccessor),我们不希望每次创建Method对象都要重新初始化,于是所有代表同一个方法的Method对象都共享着根对象的MethodAccessor,每一次创建都会调用根对象的copy方法复制一份:

 Method copy() { 

        Method res = new Method(clazz, name, parameterTypes, returnType,

                                exceptionTypes, modifiers, slot, signature,

                                annotations, parameterAnnotations, annotationDefault);

        res.root = this;

        res.methodAccessor = methodAccessor;

        return res;

    }
  1. 调用invoke()方法。调用invoke方法的流程如下:

在这里插入图片描述
调用Method.invoke之后,会直接去调MethodAccessor.invoke。MethodAccessor就是上面提到的所有同名method共享的一个实例,由ReflectionFactory创建。

创建机制采用了一种名为inflation的方式(JDK1.4之后):如果该方法的累计调用次数<=15,会创建出NativeMethodAccessorImpl,它的实现就是直接调用native方法实现反射;如果该方法的累计调用次数>15,会由java代码创建出字节码组装而成的MethodAccessorImpl。(是否采用inflation和15这个数字都可以在jvm参数中调整) 以调用MyClass.myMethod(String s)为例,生成出的MethodAccessorImpl字节码翻译成Java代码大致如下:

public class GeneratedMethodAccessor1 extends MethodAccessorImpl {    
    public Object invoke(Object obj, Object[] args)  throws Exception {
        try {
            MyClass target = (MyClass) obj;
            String arg0 = (String) args[0];
            target.myMethod(arg0);
        } catch (Throwable t) {
            throw new InvocationTargetException(t);
        }
    }
}

如果想要详细了解反射的原理,可以参考这篇文章——深入理解java反射原理。

注解

何谓注解?

Annotation (注解) 是 Java5 开始引入的新特性,可以看作是一种特殊的注释,主要用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用。

注解本质是一个继承了Annotation 的特殊接口:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {

}

public interface Override extends Annotation{

}

JDK 提供了很多内置的注解(比如 @Override 、@Deprecated),同时,我们还可以自定义注解。

注解的解析方法有哪几种?

注解只有被解析之后才会生效,常见的解析方法有两种:

  • 编译期直接扫描 : 编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用@Override 注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。

  • 运行期通过反射处理 : 像框架中自带的注解(比如 Spring 框架的 @Value 、@Component)都是通过反射来进行处理的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/344215.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大数据技术架构(组件)34——Spark:Spark SQL--Optimize

2.2.3、Optimize2.2.3.1、SQL3.3.1.1、RB1、Join选择在Hadoop中&#xff0c;MR使用DistributedCache来实现mapJoin。即将小文件存放到DistributedCache中&#xff0c;然后分发到各个Task上&#xff0c;并加载到内存中&#xff0c;类似于Map结构&#xff0c;然后借助于Mapper的迭…

【半监督医学图像分割 2021 CVPR】CVRL

文章目录【半监督医学图像分割 2021 CVPR】CVRL摘要1. 介绍1.1 总览1.2 无监督对比学习2. 实验3. 总结【半监督医学图像分割 2021 CVPR】CVRL 论文题目&#xff1a;Momentum Contrastive Voxel-wise Representation Learning for Semi-supervised Volumetric Medical Image Seg…

ThinkPHP多语言模块文件包含RCE复现详细教程

免责声明 本文章只用于技术交流&#xff0c;若使用本文章提供的技术信息进行非法操作&#xff0c;后果均由使用者本人负责。 漏洞描述&#xff1a; ThinkPHP在开启多语言功能的情况下存在文件包含漏洞&#xff0c;攻击者可以通过get、header、cookie等位置传入参数&#xff…

Transformer机制学习笔记

学习自https://www.bilibili.com/video/BV1J441137V6 RNN&#xff0c;CNN网络的缺点 难以平行化处理&#xff0c;比如我们要算b4b^4b4&#xff0c;我们需要一次将a1a^1a1~a4a^4a4依次进行放入网络中进行计算。 于是有人提出用CNN代替RNN 三角形表示输入&#xff0c;b1b^1b1的…

【数据结构】算法的复杂度分析:让你拥有未卜先知的能力

&#x1f451;专栏内容&#xff1a;数据结构⛪个人主页&#xff1a;子夜的星的主页&#x1f495;座右铭&#xff1a;日拱一卒&#xff0c;功不唐捐 文章目录一、前言二、时间复杂度1、定义2、大O的渐进表示法3、常见的时间复杂度三、空间复杂度1、定义2、常见的空间复杂度一、前…

微信小程序删除list指定列表项

一、删除效果展示&#xff1a; // 重要代码片段async deleteListItem(e) {const sureResult await wx.showModal({title: "提示",content: "确定要删除这项吗",});if (sureResult.confirm) {const { index } e.currentTarget.dataset;setTimeout(()>{…

Centos7安装kvm WEB管理工具kimchi

参考&#xff1a;虚拟化技术之kvm WEB管理工具kimchi一、kimchi安装1.1 下载wok安装包和kimchi安装包# 1. wok下载地址&#xff1a; wget https://github.com/kimchi-project/kimchi/releases/download/2.5.0/wok-2.5.0-0.el7.centos.noarch.rpm # 2. kimchi下载地址&#xff1…

Springmvc补充配置

Controller配置总结 控制器通常通过接口定义或注解定义两种方法实现 在用接口定义写控制器时&#xff0c;需要去Spring配置文件中注册请求的bean;name对应请求路径&#xff0c;class对应处理请求的类。 <bean id"/hello" class"com.demo.Controller.HelloCo…

【Spark分布式内存计算框架——Spark SQL】2. SparkSQL 概述(上)

第二章 SparkSQL 概述 Spark SQL允许开发人员直接处理RDD&#xff0c;同时可以查询在Hive上存储的外部数据。Spark SQL的一个重要特点就是能够统一处理关系表和RDD&#xff0c;使得开发人员可以轻松的使用SQL命令进行外部查询&#xff0c;同时进行更加复杂的数据分析。 2.1 前…

蓝桥杯模块学习16——PCF8591(深夜学习——单片机)

一、硬件电路&#xff1a;1、蓝桥杯板子上的电路&#xff1a;&#xff08;1&#xff09;AIN0-3&#xff1a;四种模拟量的输入口&#xff0c;AIN1为光敏电阻控制电压输入&#xff0c;AIN3为电位器控制电压输入&#xff08;2&#xff09;A0-2&#xff1a;决定设备的地址码&#x…

SSTI漏洞原理及渗透测试

模板引擎&#xff08;Web开发中&#xff09; 是为了使 用户界面 和 业务数据&#xff08;内容&#xff09;分离而产生的&#xff0c;它可以生成特定格式的文档&#xff0c; 利用模板引擎来生成前端的HTML代码&#xff0c;模板引擎会提供一套生成HTML代码的程序&#xff0c;之后…

TensorRT的Python接口解析

TensorRT的Python接口解析 文章目录TensorRT的Python接口解析4.1. The Build Phase4.1.1. Creating a Network Definition in Python4.1.2. Importing a Model using the ONNX Parser4.1.3. Building an Engine4.2. Deserializing a Plan4.3. Performing Inference点此链接加入…

科技巨头争相入局,卫星通信领域将迎来怎样的发展?

近年来&#xff0c;全球卫星通信产业进入了一个高速发展的阶段。与卫星通信相关的新技术和新应用不断出现&#xff0c;成为了媒体报道的热点&#xff0c;也引起了公众的广泛关注。尤其是刚刚过去的2022年&#xff0c;华为和苹果公司分别发布了搭载卫星通信技术的手机&#xff0…

万丈高楼平地起:Linux常用命令

目录 系统管理命令 man命令 ls命令 cd命令 useradd命令 passwd命令 free命令 whoami命令 ps命令 date命令 pwd命令 shutdown命令 文件目录管理命令 touch命令 cat命令 mkdir命令 rm命令 cp命令 mv命令 find命令 more指令 less指令 head指令 tail指令 …

基于HTML实现浪漫情人节表白代码(附源代码)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

【spark】第三章——SparkSQL

文章目录1. SparkSQL 概述1.1 SparkSQL 是什么1.2 Hive and SparkSQL1.3 SparkSQL 特点1.3.1 易整合1.3.2 统一的数据访问1.3.3 兼容 Hive1.3.4 标准数据连接1.4 DataFrame 是什么1.5 DataSet 是什么2. SparkSQL 核心编程2.1 新的起点2.2 DataFrame2.2.1 创建 DataFrame2.2.2 S…

MVC架构 —— 理解 Dao 层和 Service 层

MVC 框架念叨了千百遍&#xff0c;但是对于它的理解还是停留在概念上。 作为一种经典架构设计典范&#xff0c;MVC 在日新月异的软件行业却能常青数十年&#xff0c;一定有其独特的魅力。 一、Dao 层和 Service 层的概念 Dao 是 Data Access Object &#xff08;数据访问对象&…

FPGA纯verilog实现任意分辨率视频输出显示,高度贴近真实项目,提供工程源码和技术支持

目录1、前言2、视频显示的VESA协议3、VESA协议的bug4、FPGA实现任意分辨率视频输出显示5、FDMA实现数据缓存6、vivado工程详解7、上板调试验证并演示8、福利&#xff1a;工程代码的获取1、前言 本设计使用纯Verilog代码实现&#xff0c;重点在于基于AXI协议的DDR控制器的运用&…

SpringBoot 整合EasyExcel详解

一、概述 Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存&#xff0c;poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题&#xff0c;但POI还是有一些缺陷&#xff0c;比如07版Excel解压缩以及解压后存储都是在内…

内网渗透-src挖掘-互联网打点到内网渗透-2023年2月

1、通过信息搜集&#xff0c;发现目标有一个互联网访问的骑士cms 2、发现该系统骑士cms版本为6.0.20&#xff0c;通过搜索&#xff0c;发现骑士cms < 6.0.48存在任意文件包含漏洞 /Application/Common/Controller/BaseController.class.php 该文件的assign_resume_tpl函数…