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

30 阅读8分钟

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

王铁牛怀揣着紧张又期待的心情,走进了互联网大厂的面试间。严肃的面试官正襟危坐,一场对Java知识的严格考验即将拉开帷幕。

第一轮提问

  • 面试官:我们先从基础的Java核心知识开始。你能说说Java中多态的实现方式有哪些吗?
  • 王铁牛:多态的实现方式主要有两种,一种是方法重载,在一个类中可以有多个方法名相同但参数列表不同的方法;另一种是方法重写,子类重写父类的方法。
  • 面试官:回答得不错。那在Java中,String类为什么被设计成不可变的呢?
  • 王铁牛:因为不可变有很多好处,比如可以保证字符串的安全性,在多线程环境下不用考虑线程安全问题;还可以实现字符串常量池,提高性能。
  • 面试官:很好。那你说说Java中的异常处理机制是怎样的?
  • 王铁牛:Java中异常分为受检查异常和不受检查异常。对于受检查异常,必须进行处理,要么使用try-catch块捕获,要么使用throws关键字声明抛出;对于不受检查异常,不强制要求处理。

第二轮提问

  • 面试官:接下来我们聊聊JUC和多线程相关的内容。你能说下线程的几种状态吗?
  • 王铁牛:线程有新建、就绪、运行、阻塞和死亡这几种状态。新建就是刚创建线程对象;就绪是线程具备了运行条件但还没分配到CPU时间片;运行就是正在执行;阻塞是由于某些原因暂停执行;死亡就是线程执行完毕。
  • 面试官:不错。那在多线程环境下,如何保证数据的一致性呢?
  • 王铁牛:可以使用synchronized关键字来实现同步,它可以修饰方法或者代码块,保证同一时刻只有一个线程可以访问。
  • 面试官:很好。那你对线程池了解多少,线程池有哪些常用的创建方式?
  • 王铁牛:线程池可以使用Executors工具类来创建,比如newFixedThreadPool可以创建固定大小的线程池,newCachedThreadPool可以创建可缓存的线程池。

第三轮提问

  • 面试官:现在我们谈谈框架相关的知识。你在项目中使用过Spring框架,那Spring的核心特性有哪些?
  • 王铁牛:Spring的核心特性有依赖注入和面向切面编程。依赖注入可以降低代码的耦合度,让对象之间的依赖关系由Spring容器来管理;面向切面编程可以在不修改原有代码的基础上增加额外的功能。
  • 面试官:那Spring Boot和Spring有什么区别呢?
  • 王铁牛:嗯……这个……Spring Boot是在Spring基础上发展来的,它简化了Spring的配置,让开发更方便,好像还内置了服务器什么的。
  • 面试官:那MyBatis是如何实现数据库操作的呢?
  • 王铁牛:呃……就是通过一些配置文件,还有映射文件,然后和数据库交互,具体怎么交互我有点说不太清楚。

面试接近尾声,面试官看着王铁牛,严肃地说:“今天的面试就到这里了,你回家等通知吧。在面试中,你对一些基础的Java核心知识和多线程方面的内容回答得不错,展现出了一定的知识储备。不过,在框架相关的一些细节问题上,回答得还不够深入和清晰,后续还需要加强这方面的学习。我们会综合考虑你的表现,之后会给你反馈,请耐心等待。”

问题答案

  1. Java中多态的实现方式有哪些
    • 方法重载:在同一个类中,方法名相同,但参数列表不同(参数的类型、个数、顺序不同)。编译器会根据调用方法时传入的实际参数来决定调用哪个方法。例如:
public class OverloadExample {
    public int add(int a, int b) {
        return a + b;
    }
    public double add(double a, double b) {
        return a + b;
    }
}
- **方法重写**:子类继承父类后,重写父类中的方法。要求方法名、参数列表和返回值类型都相同,访问权限不能比父类的更严格。例如:
class Animal {
    public void makeSound() {
        System.out.println("Animal makes a sound");
    }
}
class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Dog barks");
    }
}
  1. Java中String类为什么被设计成不可变的
    • 安全性:在多线程环境下,不可变的String对象是线程安全的,多个线程可以同时访问同一个String对象,不用担心数据被修改。例如,在网络编程中,URL、文件名等通常使用String类型,如果String是可变的,可能会导致安全问题。
    • 字符串常量池:Java中的字符串常量池是为了提高性能而设计的。当创建一个String对象时,如果常量池中已经存在相同内容的字符串,就会直接引用常量池中的对象,而不是创建新的对象。例如:
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); // true
  1. Java中的异常处理机制是怎样的
    • 异常分类:Java中的异常分为受检查异常(Checked Exception)和不受检查异常(Unchecked Exception)。受检查异常是指在编译时必须进行处理的异常,如IOException、SQLException等;不受检查异常是指在编译时不强制要求处理的异常,如RuntimeException及其子类。
    • 异常处理方式
      • try-catch块:用于捕获和处理异常。例如:
try {
    int result = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println("除数不能为零:" + e.getMessage());
}
    - **throws关键字**:用于声明方法可能抛出的异常,由调用该方法的代码来处理。例如:
public void readFile() throws IOException {
    // 读取文件的代码
}
  1. 线程的几种状态
    • 新建(New):当创建一个Thread对象时,线程处于新建状态。例如:
Thread t = new Thread();
- **就绪(Runnable)**:调用线程的start()方法后,线程进入就绪状态,等待获取CPU时间片。例如:
t.start();
- **运行(Running)**:线程获得CPU时间片后,开始执行run()方法,进入运行状态。
- **阻塞(Blocked)**:线程由于某些原因暂停执行,进入阻塞状态。例如,线程调用了sleep()方法、等待输入输出等。
- **死亡(Terminated)**:线程执行完run()方法或者因为异常退出,进入死亡状态。

5. 在多线程环境下,如何保证数据的一致性 - synchronized关键字:可以修饰方法或者代码块,保证同一时刻只有一个线程可以访问被修饰的方法或代码块。例如:

public class SynchronizedExample {
    private int count = 0;
    public synchronized void increment() {
        count++;
    }
}
- **Lock接口**:Java提供了Lock接口及其实现类(如ReentrantLock)来实现同步。与synchronized关键字相比,Lock接口提供了更灵活的锁机制。例如:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockExample {
    private int count = 0;
    private Lock lock = new ReentrantLock();
    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
}
  1. 线程池有哪些常用的创建方式
    • newFixedThreadPool:创建一个固定大小的线程池。例如:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FixedThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            executor.execute(new Task());
        }
        executor.shutdown();
    }
    static class Task implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " is running");
        }
    }
}
- **newCachedThreadPool**:创建一个可缓存的线程池,如果线程池中的线程空闲时间过长,会被回收;如果有新的任务提交,会创建新的线程。例如:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachedThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            executor.execute(new Task());
        }
        executor.shutdown();
    }
    static class Task implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " is running");
        }
    }
}
  1. Spring的核心特性有哪些
    • 依赖注入(Dependency Injection,DI):是指对象之间的依赖关系由Spring容器来管理,而不是由对象自己创建。通过依赖注入,可以降低代码的耦合度,提高代码的可维护性和可测试性。例如:
public class UserService {
    private UserDao userDao;
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }
    public void addUser() {
        userDao.addUser();
    }
}
- **面向切面编程(Aspect-Oriented Programming,AOP)**:是指在不修改原有代码的基础上,通过切面来增加额外的功能,如日志记录、事务管理等。例如,使用Spring AOP可以在方法执行前后添加日志记录:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {
    @After("execution(* com.example.service.*.*(..))")
    public void logAfter(JoinPoint joinPoint) {
        System.out.println("Method " + joinPoint.getSignature().getName() + " executed");
    }
}
  1. Spring Boot和Spring有什么区别
    • 配置简化:Spring需要大量的XML配置文件或者Java配置类来进行配置,而Spring Boot通过自动配置和约定优于配置的原则,大大简化了配置过程。例如,在Spring Boot中,只需要添加相关的依赖,就可以自动配置好数据库连接、Web服务器等。
    • 内置服务器:Spring Boot内置了Tomcat、Jetty等服务器,不需要额外的服务器配置,直接运行应用程序即可。而Spring应用通常需要部署到外部服务器上。
    • 快速开发:Spring Boot提供了一系列的starter依赖,通过添加这些依赖,可以快速集成各种功能,提高开发效率。
  2. MyBatis是如何实现数据库操作的
    • 配置文件:MyBatis需要配置核心配置文件(如mybatis-config.xml)和映射文件(如UserMapper.xml)。核心配置文件主要配置数据库连接信息、插件等;映射文件主要配置SQL语句和Java对象之间的映射关系。
    • SqlSessionFactory:通过SqlSessionFactoryBuilder读取核心配置文件,创建SqlSessionFactory对象。例如:
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;

public class MyBatisUtil {
    private static SqlSessionFactory sqlSessionFactory;
    static {
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static SqlSessionFactory getSqlSessionFactory() {
        return sqlSessionFactory;
    }
}
- **SqlSession**:通过SqlSessionFactory创建SqlSession对象,SqlSession对象可以执行SQL语句。例如:
import org.apache.ibatis.session.SqlSession;

public class UserDao {
    public void addUser() {
        SqlSession session = MyBatisUtil.getSqlSessionFactory().openSession();
        try {
            session.insert("UserMapper.addUser");
            session.commit();
        } finally {
            session.close();
        }
    }
}