互联网大厂面试:Java核心知识、JUC、JVM等技术大考验
在互联网大厂宽敞明亮的面试室内,严肃的面试官坐在桌前,面前放着求职者的简历,而王铁牛则略显紧张地坐在对面。面试正式开始。
第一轮面试 面试官:“我们先从Java核心知识开始。请简要介绍一下Java中的面向对象编程的四大特性。” 王铁牛:“嗯,面向对象编程的四大特性是封装、继承、多态和抽象。封装就是把数据和操作数据的方法绑定在一起,隐藏内部实现细节;继承是子类继承父类的属性和方法;多态就是同一个方法可以根据对象的不同类型表现出不同的行为;抽象就是定义抽象类和抽象方法,让子类去实现。” 面试官:“回答得不错。那在Java中,接口和抽象类有什么区别呢?” 王铁牛:“接口里的方法默认是抽象的,不能有方法体,一个类可以实现多个接口;抽象类里可以有抽象方法也可以有非抽象方法,一个类只能继承一个抽象类。” 面试官:“很好。那说说Java中的异常处理机制,try-catch-finally的执行顺序是怎样的?” 王铁牛:“首先执行try块里的代码,如果try块里抛出异常,就会跳到对应的catch块去处理异常,不管是否发生异常,finally块里的代码都会执行。” 面试官:“非常棒,你的基础很扎实。”
第二轮面试 面试官:“接下来聊聊JUC和多线程相关的内容。什么是线程安全,如何保证线程安全?” 王铁牛:“线程安全就是在多线程环境下,对共享资源的访问不会产生数据不一致等问题。可以使用synchronized关键字或者Lock接口来保证线程安全。” 面试官:“那synchronized和Lock有什么区别呢?” 王铁牛:“synchronized是Java的关键字,是隐式锁,会自动释放;Lock是一个接口,是显式锁,需要手动释放。” 面试官:“再问一个,线程池的核心参数有哪些,分别代表什么含义?” 王铁牛:“线程池的核心参数有corePoolSize,它是核心线程数;maximumPoolSize,最大线程数;keepAliveTime,线程空闲时的存活时间;TimeUnit,时间单位;BlockingQueue,任务队列;ThreadFactory,线程工厂;RejectedExecutionHandler,拒绝策略。” 面试官:“你对这部分知识掌握得很清晰,很不错。”
第三轮面试 面试官:“现在我们来谈谈一些框架相关的问题。Spring的核心特性有哪些?” 王铁牛:“Spring的核心特性有IoC(控制反转)和AOP(面向切面编程)。IoC就是把对象的创建和管理交给Spring容器,AOP就是在不修改原有代码的基础上,对程序进行增强。” 面试官:“那Spring Boot和Spring有什么关系,Spring Boot有什么优势?” 王铁牛:“Spring Boot是基于Spring的,它简化了Spring应用的开发,提供了自动配置,能快速搭建项目,减少了很多配置文件。” 面试官:“MyBatis中#{}和{}的区别是什么?” **王铁牛**:“这个……#{}是预编译处理,能防止SQL注入,{}是直接替换,可能会有SQL注入风险。” 面试官:“看来你对框架也有一定的了解。不过在一些项目中,我们可能会用到分布式相关的技术,比如Dubbo和RabbitMQ,你能说说Dubbo的工作原理吗?” 王铁牛:“Dubbo嘛,就是……嗯……它好像是个分布式服务框架,能进行远程调用,具体原理我有点说不清楚。” 面试官:“那RabbitMQ的应用场景有哪些呢?” 王铁牛:“RabbitMQ可以用于消息队列,能实现异步处理、解耦和流量削峰,但是具体怎么用我不太清楚。”
面试接近尾声,面试官合上手中的记录,说道:“今天的面试就到这里。你在一些基础的Java知识和常见框架的简单概念上回答得不错,展现出了一定的知识储备。不过在分布式技术等方面,你的理解还不够深入。我们会综合考虑这次面试的情况,你先回家等通知吧。”
问题答案详解
- 面向对象编程的四大特性
- 封装:将数据(属性)和操作数据的方法绑定在一起,形成一个独立的单元。通过访问控制修饰符(如private、protected、public)来隐藏内部实现细节,只对外提供必要的接口,提高了代码的安全性和可维护性。例如,一个类的属性可以设置为private,通过公有的getter和setter方法来访问和修改这些属性。
- 继承:子类可以继承父类的属性和方法,从而实现代码的复用。子类可以在父类的基础上进行扩展,添加新的属性和方法,或者重写父类的方法。Java中使用extends关键字来实现继承,一个类只能继承一个父类(单继承)。
- 多态:同一个方法可以根据对象的不同类型表现出不同的行为。多态的实现方式有两种:方法重载和方法重写。方法重载是指在一个类中定义多个同名但参数列表不同的方法;方法重写是指子类重写父类的方法,在运行时根据对象的实际类型来调用相应的方法。
- 抽象:抽象是指将一类对象的共同特征提取出来,形成抽象类或接口。抽象类不能实例化,它可以包含抽象方法和非抽象方法,抽象方法只有声明没有实现,需要子类去实现。接口是一种特殊的抽象类,接口中的方法都是抽象方法,一个类可以实现多个接口。
- 接口和抽象类的区别
- 方法实现:接口中的方法默认是抽象的,不能有方法体(Java 8及以后可以有默认方法和静态方法);抽象类中可以有抽象方法,也可以有非抽象方法。
- 继承和实现:一个类可以实现多个接口,但只能继承一个抽象类。
- 访问修饰符:接口中的成员变量默认是public static final类型,方法默认是public abstract类型;抽象类中的成员变量和方法可以使用各种访问修饰符。
- 设计目的:接口主要用于定义一组规范,实现类需要遵循这些规范;抽象类主要用于代码复用和提供公共的实现。
- try-catch-finally的执行顺序
- 首先执行try块中的代码。
- 如果try块中没有抛出异常,那么执行完try块后,会跳过catch块,直接执行finally块。
- 如果try块中抛出了异常,会根据异常的类型找到对应的catch块进行处理,处理完catch块后,再执行finally块。
- 无论try块中是否抛出异常,finally块中的代码都会执行(除非在try或catch块中执行了System.exit(0))。
- 线程安全及保证线程安全的方法
- 线程安全:在多线程环境下,对共享资源的访问不会产生数据不一致、数据丢失等问题。例如,多个线程同时对一个共享变量进行读写操作,如果没有进行同步控制,就可能会出现线程安全问题。
- 保证线程安全的方法
- synchronized关键字:可以修饰方法或代码块,它会为对象或类加锁,同一时间只有一个线程可以访问被synchronized修饰的代码。当一个线程进入synchronized代码块时,会获取锁,其他线程需要等待该线程释放锁后才能进入。
- Lock接口:Lock是一个接口,常见的实现类有ReentrantLock。使用Lock需要手动获取锁和释放锁,通过lock()方法获取锁,通过unlock()方法释放锁,通常在finally块中释放锁,以确保锁一定会被释放。
- synchronized和Lock的区别
- 语法层面:synchronized是Java的关键字,是隐式锁,会自动释放锁;Lock是一个接口,是显式锁,需要手动获取和释放锁。
- 锁的获取和释放:synchronized在代码执行完或抛出异常时会自动释放锁;Lock需要在finally块中手动调用unlock()方法释放锁。
- 锁的特性:Lock提供了更多的锁特性,如可中断锁、公平锁等。synchronized是非公平锁,而Lock可以通过构造函数指定是否为公平锁。
- 性能:在竞争不激烈的情况下,synchronized的性能较好;在竞争激烈的情况下,Lock的性能可能会更好。
- 线程池的核心参数及含义
- corePoolSize:核心线程数,线程池创建后,会一直保持的线程数量。当有新任务提交时,如果线程池中的线程数量小于corePoolSize,会创建新的线程来执行任务。
- maximumPoolSize:最大线程数,线程池允许的最大线程数量。当任务队列满了,并且线程池中的线程数量小于maximumPoolSize时,会创建新的线程来执行任务。
- keepAliveTime:线程空闲时的存活时间。当线程池中的线程数量超过corePoolSize,并且这些线程在keepAliveTime时间内没有任务可执行,它们会被销毁,直到线程数量回到corePoolSize。
- TimeUnit:keepAliveTime的时间单位,如TimeUnit.SECONDS表示秒。
- BlockingQueue:任务队列,用于存储提交的任务。常见的任务队列有ArrayBlockingQueue、LinkedBlockingQueue等。
- ThreadFactory:线程工厂,用于创建线程。可以通过自定义线程工厂来设置线程的名称、优先级等。
- RejectedExecutionHandler:拒绝策略,当任务队列满了,并且线程池中的线程数量达到了maximumPoolSize时,新提交的任务会被拒绝。常见的拒绝策略有AbortPolicy(直接抛出异常)、CallerRunsPolicy(让提交任务的线程来执行任务)等。
- Spring的核心特性
- IoC(控制反转):也称为依赖注入(DI),是指将对象的创建和管理交给Spring容器,而不是由对象自己来创建。通过IoC,对象之间的依赖关系由容器来管理,降低了对象之间的耦合度。例如,一个类需要依赖另一个类的对象,只需要在类中声明依赖,Spring容器会自动将依赖的对象注入到该类中。
- AOP(面向切面编程):在不修改原有代码的基础上,对程序进行增强。AOP将程序中的一些通用功能(如日志记录、事务管理等)提取出来,形成切面,在需要的地方进行切入。通过AOP,可以提高代码的可维护性和复用性。
- Spring Boot和Spring的关系及Spring Boot的优势
- 关系:Spring Boot是基于Spring的,它简化了Spring应用的开发过程。Spring Boot提供了自动配置功能,能够根据项目的依赖和配置自动配置Spring应用,减少了大量的配置文件。
- 优势
- 快速搭建项目:通过Spring Initializr可以快速创建Spring Boot项目,减少了项目初始化的时间。
- 自动配置:Spring Boot根据项目的依赖自动配置Spring应用,开发者只需要关注业务逻辑。
- 嵌入式服务器:Spring Boot内置了Tomcat、Jetty等嵌入式服务器,不需要额外配置服务器。
- 监控和管理:Spring Boot Actuator提供了对应用的监控和管理功能,如健康检查、性能监控等。
- MyBatis中#{}和${}的区别
- #{}:是预编译处理,MyBatis会将#{}替换为占位符?,然后使用PreparedStatement来执行SQL语句。这样可以防止SQL注入攻击,因为占位符会对输入的参数进行转义处理。
- **{}直接替换为参数的值。这种方式可能会导致SQL注入攻击,因为参数的值会直接拼接到SQL语句中,没有进行转义处理。一般情况下,建议使用#{},只有在需要动态指定表名、列名等情况下才使用${}。
- Dubbo的工作原理
- 服务注册:服务提供者在启动时,会将自己提供的服务信息(如服务接口、服务地址等)注册到注册中心(如Zookeeper)。
- 服务发现:服务消费者在启动时,会从注册中心获取服务提供者的信息,并缓存到本地。
- 远程调用:服务消费者通过代理对象调用服务提供者的服务,代理对象会将调用信息封装成请求,通过网络发送给服务提供者。
- 服务调用:服务提供者接收到请求后,会根据请求信息调用相应的服务方法,并将结果返回给服务消费者。
- RabbitMQ的应用场景
- 异步处理:将一些耗时的操作(如文件上传、短信发送等)放入消息队列中,由专门的消费者进行处理,提高系统的响应速度。
- 解耦:系统中的各个模块通过消息队列进行通信,降低了模块之间的耦合度。例如,一个订单系统和一个库存系统,订单系统生成订单后,将消息发送到消息队列,库存系统从消息队列中获取消息并处理库存。
- 流量削峰:在高并发场景下,将请求放入消息队列中,消费者按照一定的速度从消息队列中获取请求进行处理,避免系统因瞬间高流量而崩溃。例如,电商系统的秒杀活动,可以将用户的请求放入消息队列中,控制处理速度。