《互联网大厂 Java 面试:核心知识、框架与中间件大考验》

77 阅读9分钟

互联网大厂 Java 面试:核心知识、框架与中间件大考验

王铁牛怀揣着对互联网大厂的向往,走进了面试室。严肃的面试官早已坐在那里,一场考验 Java 核心能力的面试即将拉开帷幕。

第一轮面试 面试官:“首先问几个 Java 核心知识的问题。Java 中基本数据类型有哪些?” 王铁牛:“有 byte、short、int、long、float、double、char、boolean。” 面试官:“回答得不错。那 String 是基本数据类型吗?” 王铁牛:“不是,String 是引用数据类型。” 面试官:“很好。那说说 Java 中多态的实现方式有哪些?” 王铁牛:“主要有继承和接口两种方式。通过继承父类,子类可以重写父类的方法实现多态;通过实现接口,不同的类可以实现相同的接口方法,也能体现多态。” 面试官:“非常棒,看来你对 Java 核心知识掌握得很扎实。”

第二轮面试 面试官:“接下来聊聊 JUC、JVM 和多线程相关的。JUC 包是什么?有什么作用?” 王铁牛:“JUC 是 java.util.concurrent 包,它提供了在并发编程中常用的工具类,能帮助我们更方便地进行多线程编程。” 面试官:“不错。那 JVM 的内存区域是如何划分的?” 王铁牛:“主要分为堆、栈、方法区、程序计数器和本地方法栈。堆是存放对象实例的地方;栈主要存储局部变量和方法调用信息;方法区存储类的信息、常量、静态变量等;程序计数器记录当前线程执行的字节码行号;本地方法栈为本地方法服务。” 面试官:“回答得挺全面。那在多线程编程中,如何保证线程安全?” 王铁牛:“可以使用 synchronized 关键字和 Lock 接口来实现同步,也可以使用线程安全的类,比如 ConcurrentHashMap 等。” 面试官:“很好,对这些知识理解得很清晰。”

第三轮面试 面试官:“现在谈谈一些框架和中间件。Spring 框架的核心特性有哪些?” 王铁牛:“Spring 的核心特性有 IoC(控制反转)和 AOP(面向切面编程)。IoC 是将对象的创建和依赖关系的管理交给 Spring 容器;AOP 可以在不修改原有代码的情况下,对程序进行增强。” 面试官:“不错。那 Spring Boot 相对于 Spring 有什么优势?” 王铁牛:“Spring Boot 简化了 Spring 应用的开发,它提供了自动配置,能快速搭建项目,减少了大量的配置文件。” 面试官:“那 MyBatis 是如何实现数据库操作的?” 王铁牛:“MyBatis 通过 XML 文件或者注解来配置 SQL 语句,然后通过 Mapper 接口来调用这些 SQL 语句,实现对数据库的增删改查操作。” 面试官:“看来你对框架方面也有一定的了解。那 Dubbo、RabbitMq、xxl - job 和 Redis 你能简单说一下它们的用途吗?” 王铁牛:“Dubbo 是一个分布式服务框架,用于实现服务的注册、发现和调用;RabbitMq 是消息队列,用于实现异步通信和系统解耦;xxl - job 是分布式任务调度平台,能方便地进行任务调度;Redis 是一个高性能的键值对数据库,常用于缓存、分布式锁等场景。” 面试官:“整体回答得还可以。不过在一些细节上还可以再深入研究。你先回家等通知吧,后续有消息我们会及时联系你。”

答案详解

  1. Java 基本数据类型:Java 中有 8 种基本数据类型,byte 占 1 个字节,范围是 - 128 到 127;short 占 2 个字节;int 占 4 个字节;long 占 8 个字节;float 占 4 个字节,用于表示单精度浮点数;double 占 8 个字节,用于表示双精度浮点数;char 占 2 个字节,用于表示单个字符;boolean 只有两个值 true 和 false,在 JVM 中没有明确规定其占用空间大小。
  2. String 不是基本数据类型:基本数据类型是 Java 语言中最基础的数据类型,而 String 是一个类,属于引用数据类型。它存储的是对象的引用,而不是具体的值。
  3. Java 多态的实现方式
    • 继承:子类继承父类后,可以重写父类的方法。当通过父类引用指向子类对象时,调用重写的方法会执行子类的实现,从而实现多态。例如:
class Animal {
    public void sound() {
        System.out.println("Animal makes a sound");
    }
}

class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("Dog barks");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog();
        animal.sound(); // 输出 Dog barks
    }
}
- **接口**:一个类可以实现多个接口,不同的类实现相同的接口方法可以有不同的实现逻辑。例如:
interface Shape {
    double area();
}

class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

class Rectangle implements Shape {
    private double length;
    private double width;

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override
    public double area() {
        return length * width;
    }
}

public class Main {
    public static void main(String[] args) {
        Shape circle = new Circle(5);
        Shape rectangle = new Rectangle(3, 4);
        System.out.println(circle.area());
        System.out.println(rectangle.area());
    }
}
  1. JUC 包:java.util.concurrent 包是 Java 为了方便开发者进行并发编程而提供的工具包。它包含了很多实用的类和接口,比如线程池相关的类(ThreadPoolExecutor、Executors 等)、锁相关的类(ReentrantLock、ReadWriteLock 等)、并发集合类(ConcurrentHashMap、CopyOnWriteArrayList 等),可以帮助开发者更高效地实现多线程程序。
  2. JVM 内存区域划分
    • :是 JVM 中最大的一块内存区域,所有的对象实例和数组都在这里分配内存。堆是线程共享的,会进行垃圾回收。
    • :每个线程都有自己的栈,栈中存储局部变量、方法调用信息等。每个方法在执行时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
    • 方法区:存储类的信息、常量、静态变量等。在 JDK 1.8 之前,方法区也被称为永久代,JDK 1.8 之后使用元空间代替了永久代。
    • 程序计数器:是一个较小的内存区域,它记录当前线程执行的字节码行号。每个线程都有自己独立的程序计数器,是线程私有的。
    • 本地方法栈:与栈类似,不过它是为本地方法服务的,本地方法是使用非 Java 语言(如 C、C++)实现的方法。
  3. 多线程编程中保证线程安全的方法
    • synchronized 关键字:可以修饰方法或代码块。当修饰方法时,整个方法在同一时间只能被一个线程访问;当修饰代码块时,只有该代码块在同一时间只能被一个线程访问。例如:
class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}
- **Lock 接口**:常用的实现类是 ReentrantLock。它提供了比 synchronized 更灵活的锁机制,例如可以实现公平锁、可中断锁等。例如:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Counter {
    private int count = 0;
    private Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public int getCount() {
        return count;
    }
}
- **使用线程安全的类**:例如 ConcurrentHashMap 是线程安全的哈希表,CopyOnWriteArrayList 是线程安全的列表,它们内部使用了各种并发控制机制来保证线程安全。

7. Spring 框架的核心特性 - IoC(控制反转):传统的程序中,对象的创建和依赖关系的管理由程序本身负责,而在 Spring 中,这些工作交给了 Spring 容器。Spring 容器通过 XML 配置文件或者注解来创建和管理对象,将对象之间的依赖关系注入到对象中。例如:

public class UserService {
    private UserDao userDao;

    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    public void addUser() {
        userDao.addUser();
    }
}

public class UserDao {
    public void addUser() {
        System.out.println("Add user to database");
    }
}

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.addUser();
    }
}
- **AOP(面向切面编程)**:AOP 可以在不修改原有代码的情况下,对程序进行增强。例如,可以在方法执行前后添加日志记录、事务管理等功能。Spring AOP 主要基于代理模式实现。例如:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println("Before method: " + joinPoint.getSignature().getName());
    }

    @After("execution(* com.example.service.*.*(..))")
    public void afterAdvice(JoinPoint joinPoint) {
        System.out.println("After method: " + joinPoint.getSignature().getName());
    }
}
  1. Spring Boot 相对于 Spring 的优势
    • 自动配置:Spring Boot 提供了大量的自动配置类,根据项目中引入的依赖自动进行配置,减少了开发者手动配置的工作量。例如,引入 Spring Boot 的 Web 依赖后,它会自动配置嵌入式的 Tomcat 服务器和 Spring MVC。
    • 起步依赖:Spring Boot 提供了一系列的起步依赖,开发者只需要添加相应的依赖,就可以快速搭建项目。例如,添加 spring - boot - starter - data - jpa 依赖,就可以快速使用 JPA 进行数据库操作。
    • 内置服务器:Spring Boot 内置了 Tomcat、Jetty 等服务器,不需要单独部署服务器,直接运行项目即可。
  2. MyBatis 实现数据库操作的方式
    • XML 配置方式:编写 XML 文件,在文件中配置 SQL 语句。例如:
<mapper namespace="com.example.dao.UserDao">
    <select id="getUserById" parameterType="int" resultType="com.example.entity.User">
        SELECT * FROM users WHERE id = #{id}
    </select>
</mapper>
- **注解方式**:在 Mapper 接口的方法上使用注解来配置 SQL 语句。例如:
import org.apache.ibatis.annotations.Select;

public interface UserDao {
    @Select("SELECT * FROM users WHERE id = #{id}")
    User getUserById(int id);
}
- **Mapper 接口调用**:通过 MyBatis 的 SqlSession 或者 Spring 集成的方式获取 Mapper 接口的实例,然后调用接口中的方法来执行 SQL 语句。例如:
SqlSession session = sqlSessionFactory.openSession();
UserDao userDao = session.getMapper(UserDao.class);
User user = userDao.getUserById(1);
  1. Dubbo、RabbitMq、xxl - job 和 Redis 的用途
    • Dubbo:是一个分布式服务框架,用于实现服务的注册、发现和调用。在分布式系统中,各个服务之间可以通过 Dubbo 进行远程调用,提高系统的可扩展性和可维护性。Dubbo 支持多种注册中心(如 Zookeeper、Nacos 等),可以方便地管理服务的注册和发现。
    • RabbitMq:是一个消息队列,用于实现异步通信和系统解耦。生产者将消息发送到 RabbitMq 的队列中,消费者从队列中获取消息进行处理。这样可以提高系统的性能和可靠性,例如在电商系统中,用户下单后可以将订单消息发送到队列中,由其他服务异步处理订单。
    • xxl - job:是一个分布式任务调度平台,用于实现任务的定时执行和分布式调度。可以在不同的服务器上部署任务,通过 xxl - job 进行统一管理和调度,方便开发者进行任务的监控和管理。
    • Redis:是一个高性能的键值对数据库,常用于缓存、分布式锁、消息队列等场景。由于 Redis 数据存储在内存中,读写速度非常快,所以可以作为缓存来减轻数据库的压力。同时,Redis 提供了原子操作和分布式锁的实现,可以用于解决分布式系统中的并发问题。例如,在电商系统中,可以使用 Redis 缓存商品信息,提高系统的响应速度。