《互联网大厂Java求职者面试:核心知识大考验》

57 阅读11分钟

面试官:请简要介绍一下Java核心知识中的面向对象编程概念。

王铁牛:面向对象编程有封装、继承、多态。封装就是把数据和操作数据的方法封装在一起;继承是子类继承父类的属性和方法;多态就是同一个行为具有多个不同表现形式。

面试官:不错,回答得很准确。那说说JUC包中常用的工具类有哪些?

王铁牛:有CountDownLatch、CyclicBarrier、Semaphore这些。

面试官:嗯,掌握得还可以。再问个关于JVM的问题,简述一下Java内存区域。

王铁牛:Java内存区域包括程序计数器、虚拟机栈、本地方法栈、堆、方法区。

第一轮结束

面试官:接下来问多线程相关的。如何创建一个线程?

王铁牛:可以通过继承Thread类或者实现Runnable接口。

面试官:那线程池有哪些参数需要关注?

王铁牛:corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler。

面试官:讲讲HashMap的底层数据结构。

王铁牛:HashMap底层是数组+链表+红黑树。

第二轮结束

面试官:说说Spring的核心特性。

王铁牛:依赖注入、面向切面编程。

面试官:Spring Boot有什么优势?

王铁牛:它简化了Spring应用的开发,有自动配置、起步依赖等。

面试官:MyBatis的工作原理是什么?

王铁牛:通过SQL映射文件,把SQL语句和Java代码进行映射。

第三轮结束

面试结束,面试官表示会让王铁牛回家等通知。整体来看,王铁牛对于一些基础和简单的问题回答得还不错,展现出了一定的知识储备。但在面对复杂问题时,回答得不够清晰准确,需要进一步提升对技术细节的理解和掌握程度。

答案:

  1. 面向对象编程概念
    • 封装:是指将数据和操作数据的方法绑定在一起,通过访问修饰符控制外部对内部数据和方法的访问。这样可以提高数据的安全性和程序的可维护性。例如,在一个类中定义私有成员变量和公共的访问方法,外部只能通过这些公共方法来操作内部数据。
    • 继承:允许一个类继承另一个类的属性和方法。被继承的类称为父类或超类,继承的类称为子类。子类可以扩展父类的功能,实现代码复用。比如,定义一个父类“动物”,子类“狗”可以继承“动物”的属性(如颜色、体重等)和方法(如呼吸、进食等),同时还可以有自己特有的方法(如叫)。
    • 多态:指同一个行为具有多个不同表现形式。在Java中,多态通过方法重写和接口实现来体现。比如,定义一个接口“动物行为”,有“奔跑”方法,“狗”类和“猫”类都实现这个接口,当调用“奔跑”方法时,根据对象实际的类型(是狗还是猫)来执行不同的奔跑方式,这就是多态的体现。
  2. JUC包中常用工具类
    • CountDownLatch:一个同步辅助类,允许一个或多个线程等待其他线程完成操作。它通过一个计数器来实现,初始化时设置一个计数值,调用await()方法的线程会被阻塞,直到计数值变为0。例如,在一个多线程任务中,主线程需要等待多个子线程都完成任务后再继续执行,可以使用CountDownLatch。子线程执行完任务后调用countDown()方法减少计数值,当计数值为0时,主线程的await()方法不再阻塞。
    • CyclicBarrier:它允许一组线程互相等待,直到到达某个公共屏障点。与CountDownLatch不同的是,CyclicBarrier可以重用。例如,有多个线程需要共同完成一个复杂任务的不同阶段,当每个线程完成自己负责的阶段后,调用await()方法等待,直到所有线程都到达屏障点,然后再一起继续执行后续任务。
    • Semaphore:一个计数信号量,用于控制对共享资源的访问。它维护了一个许可集,许可的数量可以在构造时指定。线程可以通过acquire()方法获取许可,通过release()方法释放许可。例如,在一个系统中,有多个线程需要访问一个有限数量的共享资源(如数据库连接池),可以使用Semaphore来控制线程对资源的访问,确保资源不会被过度使用。
  3. Java内存区域
    • 程序计数器:是一块较小的内存空间,它记录着当前线程执行的字节码指令的地址。每个线程都有自己独立的程序计数器,它是线程私有的。在多线程环境下,程序计数器可以保证每个线程都能独立地按照自己的执行逻辑执行代码。
    • 虚拟机栈:也是线程私有的,它描述的是Java方法执行的内存模型。每个方法在执行时会创建一个栈帧,栈帧中包含局部变量表、操作数栈、动态链接、方法出口等信息。局部变量表用于存储方法中的局部变量,操作数栈用于执行方法中的指令操作。当方法执行结束,对应的栈帧会出栈。
    • 本地方法栈:与虚拟机栈类似,它用于执行本地方法(用C或C++实现的方法)。
    • :是Java内存中最大的一块区域,被所有线程共享。它用于存放对象实例和数组。堆是垃圾回收的主要区域,垃圾回收器会定期回收堆中不再使用的对象,以释放内存空间。
    • 方法区:它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区也是被所有线程共享的。在Java 8及以后,方法区被移到了元空间(Metaspace),元空间使用本地内存,而不是像之前在堆中分配空间。
  4. 创建线程的方式
    • 继承Thread类:定义一个类继承Thread类,并重写Thread类的run()方法。然后创建该类的对象,调用start()方法启动线程。例如:
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("This is a thread created by extending Thread class");
    }
}
public class Main {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}
  • 实现Runnable接口:定义一个类实现Runnable接口,实现其中的run()方法。然后创建Thread类的对象,并将实现了Runnable接口的类的对象作为参数传递给Thread类的构造函数,最后调用start()方法启动线程。例如:
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("This is a thread created by implementing Runnable interface");
    }
}
public class Main {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
}
  1. 线程池的参数
    • corePoolSize:线程池的核心线程数。当提交的任务数小于corePoolSize时,线程池会创建新的线程来执行任务。
    • maximumPoolSize:线程池允许的最大线程数。当提交的任务数大于corePoolSize时,会将任务放入workQueue中,如果workQueue已满且线程数小于maximumPoolSize,则会创建新的线程来执行任务。
    • keepAliveTime:线程池中的线程在空闲时的存活时间。当线程空闲时间超过keepAliveTime时,线程会被销毁,除非线程数小于corePoolSize。
    • unit:keepAliveTime的时间单位。
    • workQueue:任务队列,用于存放提交的任务。当线程池中的线程都在忙碌时,新提交的任务会被放入workQueue中等待执行。
    • threadFactory:线程工厂,用于创建线程。可以通过自定义threadFactory来设置线程的名称、优先级等属性。
    • handler:拒绝策略,当线程池无法接收新任务时(如线程数达到maximumPoolSize且workQueue已满),会调用handler来处理新任务。常见的拒绝策略有AbortPolicy(直接抛出异常)、CallerRunsPolicy(调用者线程执行任务)、DiscardPolicy(丢弃新任务)、DiscardOldestPolicy(丢弃队列中最旧的任务)。
  2. HashMap的底层数据结构: HashMap底层是数组+链表+红黑树。初始时,HashMap创建一个长度为16的数组。当向HashMap中插入键值对时,会通过计算键的哈希值,然后根据哈希值对数组长度取模得到数组的索引位置。如果该位置为空,则直接插入新的节点;如果不为空,则会发生哈希冲突。此时会将新节点插入到链表的头部(JDK 1.8之前)或通过红黑树来优化插入(JDK 1.8之后,当链表长度达到8且数组长度大于等于64时,链表会转换为红黑树)。这样在查找时,先根据哈希值找到对应的数组位置,如果是链表或红黑树,则在其中进行遍历查找键值对。当红黑树节点数小于6时,又会转换回链表结构。
  3. Spring的核心特性
    • 依赖注入:通过控制反转(IoC)容器,将对象之间的依赖关系由程序代码直接控制转换为由容器来管理。例如,在一个类中需要依赖另一个类的对象时,不再通过new关键字在本类中创建,而是由容器将该依赖对象注入到本类中。这样可以降低类之间的耦合度,提高代码的可维护性和可测试性。比如,在一个用户服务类中需要使用数据库连接对象,通过依赖注入,容器可以根据配置将不同的数据库连接实现注入到用户服务类中,而用户服务类无需关心具体的连接实现是如何创建的。
    • 面向切面编程(AOP):它允许把一些通用功能(如日志记录、事务管理等)从业务逻辑中分离出来,以一种可插拔的方式动态地织入到目标对象的方法执行过程中。例如,在多个业务方法执行前记录日志,使用AOP可以通过定义切面和切点,将日志记录的逻辑织入到目标方法执行之前,而不需要在每个业务方法中都重复编写日志记录代码。
  4. Spring Boot的优势
    • 自动配置:Spring Boot通过自动配置功能,根据项目中引入的依赖自动配置Spring应用的各种组件和功能。例如,当引入了Spring Data JPA依赖时,Spring Boot会自动配置好数据库连接、实体管理器等相关组件,大大简化了开发过程,开发者无需手动编写大量的配置代码。
    • 起步依赖:提供了一种方便的方式来引入一组相关的依赖。比如,想要使用Spring Boot搭建一个Web应用,只需要引入spring-boot-starter-web起步依赖,它会自动包含Spring MVC、Tomcat等相关的依赖,避免了手动逐个添加依赖的繁琐。
    • 简化部署:Spring Boot应用可以很方便地打包成一个可执行的JAR包,内置了Tomcat等服务器,无需像传统的Web应用那样部署到外部的Web容器中。可以直接通过命令行运行JAR包来启动应用,部署过程更加简单快捷。
  5. MyBatis的工作原理: MyBatis通过SQL映射文件,把SQL语句和Java代码进行映射。首先,MyBatis会读取SQL映射文件,解析其中的SQL语句。当执行Java方法时,如果该方法被配置了SQL映射,MyBatis会根据传入的参数动态地生成SQL语句。然后,MyBatis会通过SqlSessionFactory创建SqlSession对象,SqlSession用于执行SQL语句并与数据库进行交互。在执行SQL语句时,MyBatis会将参数设置到SQL语句中,执行查询时会将查询结果封装成Java对象返回。例如,在一个查询用户信息的方法中,通过SQL映射文件定义好SQL语句,在方法中传入用户ID参数,MyBatis会将该参数替换到SQL语句中,执行查询后将结果封装成User对象返回给调用者。