面试官:请简要介绍一下Java核心知识中面向对象的三大特性。
王铁牛:嗯,这个我知道,有封装、继承、多态。封装就是把对象的属性和方法封装起来,对外提供统一的接口;继承就是子类继承父类的属性和方法;多态就是同一个方法可以根据对象的不同类型而表现出不同的行为。
面试官:不错,回答得很准确。那再问你,JUC包下常用的类有哪些?
王铁牛:这个嘛,有CountDownLatch、CyclicBarrier、Semaphore这些。
面试官:很好,看来你对这部分知识掌握得还可以。接下来,说说JVM的内存结构。
王铁牛:呃,JVM内存结构啊,有堆、栈、方法区,还有本地方法栈和程序计数器。
面试官:第一轮面试结束,整体表现不错,基础知识掌握得比较扎实。下面进入第二轮面试。首先问你,多线程中如何实现线程安全?
王铁牛:可以用synchronized关键字,还有Lock接口。
面试官:那线程池的核心参数有哪些?
王铁牛:有corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler。
面试官:回答得还行。再问你,HashMap的底层实现原理。
王铁牛:嗯……它是基于数组和链表实现的,当链表长度超过一定阈值时会转换为红黑树。
面试官:第二轮面试结束,对一些关键知识点有一定了解,但回答的深度还可以再加强。现在进入第三轮面试。说说Spring框架中IoC和AOP的概念。
王铁牛:IoC就是控制反转,把对象的创建和依赖注入交给Spring容器;AOP就是面向切面编程,在不修改原有代码的基础上增强功能。
面试官:那Spring Boot的自动配置原理是什么?
王铁牛:这个……不太清楚。
面试官:最后问你,MyBatis的动态SQL有哪些?
王铁牛:好像有if、where、foreach这些。
面试官:三轮面试结束了,你对基础知识有一定掌握,但在一些复杂问题上回答得不是很理想。回去等通知吧。
答案:
- Java面向对象三大特性:
- 封装:将对象的属性和方法包装起来,通过访问修饰符(如private、public等)控制外部对其的访问。这样可以隐藏对象的内部实现细节,提高数据的安全性和程序的可维护性。例如,一个类中的私有属性,外部类不能直接访问和修改,只能通过该类提供的公共方法来间接操作。
- 继承:子类继承父类的属性和方法,实现代码的复用。子类可以扩展父类的功能,同时避免重复编写相同的代码。比如,定义一个父类“Animal”,子类“Dog”和“Cat”可以继承“Animal”的一些通用属性(如名字、年龄等)和方法(如吃东西的方法),然后根据自身特点进行扩展。
- 多态:同一个方法可以根据对象的不同类型而表现出不同的行为。这使得程序具有更好的扩展性和灵活性。例如,定义一个父类“Shape”,有一个方法“draw”,子类“Circle”和“Rectangle”继承“Shape”并重写“draw”方法,在使用时可以通过父类引用指向子类对象,调用“draw”方法时会根据实际对象类型调用相应的子类实现。
- JUC包下常用类:
- CountDownLatch:一个同步辅助类,允许一个或多个线程等待其他线程完成操作。它通过一个计数器来实现,当计数器的值为0时,等待的线程可以继续执行。比如,有多个线程需要等待某个初始化操作完成后再执行,可以使用CountDownLatch,初始化操作完成后调用countDown方法减少计数器的值,其他线程调用await方法等待计数器变为0。
- CyclicBarrier:它允许一组线程互相等待,直到到达某个公共屏障点。与CountDownLatch不同的是,CyclicBarrier可以重用。例如,在一个多线程计算任务中,每个线程计算一部分结果,当所有线程都计算完成后,再进行汇总操作,这时就可以使用CyclicBarrier让线程等待所有线程计算完毕。
- Semaphore:信号量,用于控制对共享资源的访问。它维护了一个许可集,线程可以通过acquire方法获取许可,通过release方法释放许可。比如,有一个资源池,限制同时只能有一定数量的线程访问该资源,就可以使用Semaphore来控制线程的并发访问数量。
- JVM内存结构:
- 堆:是JVM中最大的一块内存区域,用于存放对象实例。所有对象都在堆中分配内存。堆又可以分为新生代、老年代和永久代(在Java 8及以后,永久代被元空间取代)。新生代主要存放新创建的对象,老年代存放经过多次垃圾回收后仍然存活的对象,永久代(元空间)存放类信息、常量等。
- 栈:每个线程都有自己独立的栈空间,用于存储局部变量、方法调用等。栈中的数据是线程私有的,并且遵循先进后出的原则。当一个方法被调用时,会在栈中创建一个栈帧,用于存储该方法的局部变量和操作数等。
- 方法区:存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在Java 8及以后,方法区被元空间取代,元空间使用本地内存,而不是像永久代那样使用JVM的堆内存。
- 本地方法栈:与栈类似,用于执行本地方法(用C或C++实现的方法)。
- 程序计数器:是一块较小的内存区域,它记录了当前线程所执行的字节码的行号指示器。字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令。
- 多线程实现线程安全:
- synchronized关键字:可以修饰方法或代码块。当修饰方法时,该方法在同一时刻只能被一个线程访问;当修饰代码块时,只有获取到该对象锁的线程才能执行代码块中的内容。例如,在一个卖票的场景中,多个线程同时卖票可能会出现票数错误的问题,使用synchronized修饰卖票的方法,就可以保证同一时间只有一个线程在卖票,从而保证线程安全。
- Lock接口:相比于synchronized,它提供了更灵活的锁控制。比如ReentrantLock类实现了Lock接口,它可以通过tryLock方法尝试获取锁,避免死锁的发生,还可以通过lockInterruptibly方法在获取锁的过程中响应中断。
- 线程池核心参数:
- corePoolSize:线程池的核心线程数。当提交的任务数小于corePoolSize时,线程池会创建新的线程来执行任务。
- maximumPoolSize:线程池允许的最大线程数。当提交的任务数大于corePoolSize且任务队列已满时,会创建新的线程,直到线程数达到maximumPoolSize。
- keepAliveTime:线程池中的线程在空闲时的存活时间。当线程空闲时间超过keepAliveTime时,线程会被销毁,以减少资源消耗。
- unit:keepAliveTime的时间单位。
- workQueue:任务队列,用于存放提交的任务。当提交的任务数大于corePoolSize时,任务会被放入workQueue中等待执行。
- threadFactory:线程工厂,用于创建线程。可以通过自定义线程工厂来设置线程的名称、优先级等属性。
- handler:任务拒绝策略。当线程池中的线程数达到maximumPoolSize且任务队列已满时,会调用handler来处理新提交的任务。常见的任务拒绝策略有AbortPolicy(直接抛出异常)、CallerRunsPolicy(由调用线程执行任务)、DiscardPolicy(直接丢弃任务)、DiscardOldestPolicy(丢弃队列中最老的任务)。
- HashMap底层实现原理:
- HashMap基于数组和链表(JDK 8及以后引入红黑树)实现。它有一个初始容量为16的数组,当向HashMap中插入键值对时,会根据键的哈希值计算出在数组中的索引位置。
- 如果该位置为空,则直接插入新的键值对。
- 如果该位置不为空,就会比较键的hash值和equals方法,如果相等则覆盖原来的值;如果不相等,则将新的键值对插入到链表(或红黑树)中。
- 当链表长度超过一定阈值(默认8)时,链表会转换为红黑树,以提高查询效率。当红黑树节点数小于等于6时,又会转换回链表。
- Spring框架中IoC和AOP的概念:
- IoC(控制反转):把对象的创建和依赖注入交给Spring容器。传统方式下,对象之间的依赖关系由程序员在代码中手动创建和管理,而IoC容器会根据配置文件或注解来自动创建对象并注入它们之间的依赖关系。这样可以降低对象之间的耦合度,提高代码的可维护性和可测试性。例如,一个类需要依赖另一个类的对象,通过IoC容器,只需要在配置文件中声明这种依赖关系,容器会自动创建并注入依赖对象。
- AOP(面向切面编程):在不修改原有代码的基础上增强功能。它将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,以切面的形式织入到目标对象中。比如,在多个业务方法中都需要进行日志记录,使用AOP可以通过切面统一实现日志记录功能,而不需要在每个业务方法中都重复编写日志记录代码。
- Spring Boot自动配置原理:
- Spring Boot的自动配置是基于条件注解实现的。它会根据项目中引入的依赖和配置信息,自动配置相应的组件和功能。
- Spring Boot通过@Conditional注解来判断是否满足特定条件,如果满足条件则自动配置相应的Bean。例如,当项目中引入了Spring Data JPA的依赖时,Spring Boot会自动配置JPA相关的数据源、EntityManager等Bean。
- 自动配置类会根据不同的条件加载不同的配置类,这些配置类会注册各种Bean到Spring容器中,从而实现项目的自动配置。
- MyBatis的动态SQL:
- if:根据条件判断决定是否拼接SQL语句。例如:
这里根据传入的参数id和name是否为空来决定是否拼接相应的查询条件。<select id="selectUser" resultType="User"> SELECT * FROM user <where> <if test="id!= null"> AND id = #{id} </if> <if test="name!= null"> AND name = #{name} </if> </where> </select>- where:智能地处理SQL语句中的WHERE子句。它会自动去除多余的AND或OR关键字。比如上述例子中,如果没有传入任何条件,最终生成的SQL语句会自动去掉WHERE关键字,避免语法错误。
- foreach:用于循环遍历集合,生成对应的SQL语句。例如:
这里通过foreach遍历集合ids,生成对应的IN子句,用于批量查询。<select id="selectUsers" resultType="User"> SELECT * FROM user WHERE id IN <foreach collection="ids" item="id" open="(" separator="," close=")"> #{id} </foreach> </select>