当前位置: 首页 > article >正文

java基础100道面试题

一、Java基础概念

1. Java的三大特性是什么?解释其含义。

Java的三大特性是封装、继承和多态

  • 封装(Encapsulation):将数据(属性)和操作数据的方法绑定在一起,形成一个类。通过访问修饰符(如private)限制对某些成员的直接访问,增强安全性。

  • 继承(Inheritance):子类可以继承父类的属性和方法,从而实现代码复用和扩展性。

  • 多态(Polymorphism):同一操作作用于不同的对象时,可以有不同的解释,产生不同的执行结果。主要通过方法重载和方法重写实现。


2. Java中 ==equals() 的区别?
  • ==:用于比较两个变量或对象的引用是否相同。对于基本数据类型,比较的是值;对于引用数据类型,比较的是内存地址。

  • equals():默认情况下与==相同(比较地址),但可以通过重写来比较对象的内容。例如,String类重写了equals()方法,用于比较字符串内容是否相等。


3. String、StringBuilder 和 StringBuffer 的区别?
特性/类名是否可变线程安全性能
String不可变最低
StringBuilder可变较高
StringBuffer可变中等
  • String:不可变对象,每次修改都会生成新的对象,适合少量字符串操作。

  • StringBuilder:可变对象,线程不安全,适合单线程环境下的高效字符串操作。

  • StringBuffer:可变对象,线程安全,适合多线程环境下的字符串操作。


4. Java中的基本数据类型有哪些?对应的包装类是什么?
基本数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

5. 自动装箱(Autoboxing)和拆箱(Unboxing)是什么?
  • 自动装箱(Autoboxing):将基本数据类型自动转换为其对应的包装类。例如:int i = 10; Integer obj = i;

  • 拆箱(Unboxing):将包装类自动转换为对应的基本数据类型。例如:Integer obj = new Integer(10); int i = obj;


6. final关键字的作用(修饰类、方法、变量)?
  • 修饰类:被final修饰的类不能被继承。

  • 修饰方法:被final修饰的方法不能被子类重写。

  • 修饰变量:被final修饰的变量是一个常量,只能赋值一次,且不能修改。


7. static关键字的作用?
  • 静态变量:属于类,所有对象共享同一个静态变量。

  • 静态方法:可以直接通过类名调用,不需要创建对象。

  • 静态代码块:在类加载时执行,用于初始化静态资源。


8. Java是否支持多继承?如何实现类似多继承的功能?
  • Java不支持类的多继承,即一个类只能继承一个父类。

  • 实现类似多继承的功能:通过接口(Interface)实现。一个类可以实现多个接口,从而达到类似多继承的效果。


9. 接口(Interface)和抽象类(Abstract Class)的区别?
特性接口(Interface)抽象类(Abstract Class)
成员变量默认为public static final可以有各种修饰符的变量
方法默认为public abstract可以有具体实现的方法
继承关系类可以实现多个接口类只能继承一个抽象类
构造方法没有构造方法可以有构造方法
使用场景定义规范提供部分实现和模板

10. 什么是不可变对象(Immutable Object)?举例说明。
  • 不可变对象:一旦创建后,其状态(属性值)不能被修改的对象。

  • 特点

    • 所有字段必须是final且不可变。

    • 不提供任何修改对象状态的方法。

    • 如果需要“修改”,则返回一个新的对象。

  • 示例

    • Java中的StringInteger等包装类都是不可变对象。

    • 示例代码:

      String str = "hello";
      str.concat(" world"); // 不会改变str,而是返回一个新的字符串对象
      System.out.println(str); // 输出 "hello"

二、面向对象编程


1. 重载(Overload)和重写(Override)的区别?
特性重载(Overload)重写(Override)
定义位置同一个类中子类中
方法签名参数列表不同参数列表必须相同
返回值类型可以不同必须相同或兼容(协变返回类型)
访问修饰符不影响子类不能更严格
示例void show(int a)void show(String a)父类方法被子类重新实现

2. 构造方法能否被重写?
  • 不能。构造方法没有返回值,也不能使用@Override注解,因此无法被重写。

  • 可以重载:同一个类中可以定义多个构造方法,只要参数列表不同即可。


3. 如何实现对象克隆?深拷贝和浅拷贝的区别?
  • 浅拷贝:只复制对象本身,引用类型的成员变量仍然指向原来的内存地址。

  • 深拷隆:不仅复制对象本身,还递归地复制引用类型的成员变量,确保新对象与原对象完全独立。

实现方式

  • 实现Cloneable接口,并重写clone()方法。

  • 示例代码:

class Person implements Cloneable {
    private String name;
    private int age;
​
    public Object clone() throws CloneNotSupportedException {
        return super.clone(); // 浅拷贝
    }
}
​
// 深拷贝示例
class Student implements Cloneable {
    private String name;
    private Address address; // 引用类型
​
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Student cloned = (Student) super.clone(); // 浅拷贝
        cloned.address = (Address) this.address.clone(); // 手动深拷贝
        return cloned;
    }
}

4. 类的初始化顺序(静态块、实例块、构造方法)?

类的初始化顺序如下:

  1. 加载类时:执行静态变量初始化和静态代码块(按代码出现顺序)。

  2. 创建对象时

    • 先执行实例变量初始化。

    • 再执行实例代码块(按代码出现顺序)。

    • 最后执行构造方法。

示例代码

class Example {
    static {
        System.out.println("静态代码块");
    }
​
    {
        System.out.println("实例代码块");
    }
​
    Example() {
        System.out.println("构造方法");
    }
​
    public static void main(String[] args) {
        new Example();
    }
}

输出

静态代码块
实例代码块
构造方法

5. 什么是多态?如何通过代码体现?
  • 多态:同一操作作用于不同的对象时,产生不同的执行结果。

  • 实现方式:通过继承和方法重写实现。

  • 代码示例

class Animal {
    void sound() {
        System.out.println("动物发出声音");
    }
}
​
class Dog extends Animal {
    @Override
    void sound() {
        System.out.println("汪汪");
    }
}
​
class Cat extends Animal {
    @Override
    void sound() {
        System.out.println("喵喵");
    }
}
​
public class Test {
    public static void main(String[] args) {
        Animal a1 = new Dog(); // 多态
        a1.sound(); // 输出 "汪汪"
​
        Animal a2 = new Cat(); // 多态
        a2.sound(); // 输出 "喵喵"
    }
}

6. 解释Java中的访问修饰符(public、protected、default、private)。
修饰符同一包内子类(不同包)不同包且非子类
public
protected×
default××
private×××

7. 如何防止类被继承?
  • 使用final关键字修饰类,例如:

public final class MyClass {
    // 类不能被继承
}

8. 内部类有哪些类型?各自的特点是什么?
  • 成员内部类:定义在类内部,作为外部类的成员,可以访问外部类的所有成员。

  • 静态内部类:使用static修饰,属于外部类本身,不依赖于外部类实例。

  • 局部内部类:定义在方法或代码块中,只能在定义它的方法或代码块中使用。

  • 匿名内部类:没有名字的内部类,通常用于简化代码,常用于事件监听器等场景。


9. 匿名内部类能否访问外部类的非final变量?
  • 不能直接访问外部类的非final局部变量。

  • 原因:匿名内部类的对象可能比外部方法的生命周期更长,为了保证安全性,JVM要求匿名内部类只能访问finaleffectively final(从Java 8开始支持)的局部变量。


10. 什么是单例模式?写出线程安全的实现方式。
  • 单例模式:确保一个类只有一个实例,并提供全局访问点。

线程安全的实现方式

  1. 饿汉式(线程安全,但可能会浪费资源):

public class Singleton {
    private static final Singleton instance = new Singleton();
​
    private Singleton() {}
​
    public static Singleton getInstance() {
        return instance;
    }
}
  1. 懒汉式 + 双重检查锁定(线程安全且延迟加载):

public class Singleton {
    private static volatile Singleton instance = null;
​
    private Singleton() {}
​
    public static Singleton getInstance() {
        if (instance == null) { // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) { // 第二次检查
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
  1. 静态内部类(推荐,线程安全且延迟加载):

public class Singleton {
    private Singleton() {}
​
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
​
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

三、集合框架


1. ArrayList 和 LinkedList 的区别及适用场景?
特性ArrayListLinkedList
底层实现基于动态数组基于双向链表
随机访问效率高(通过索引直接访问)低(需要遍历节点)
插入/删除效率低(可能涉及大量元素移动)高(只需修改指针)
内存占用较小较大(每个节点包含额外的指针)
适用场景需要频繁随机访问的场景需要频繁插入和删除操作的场景

2. HashMap 的工作原理?如何解决哈希冲突?
  • 工作原理

    • HashMap 使用哈希表存储键值对。

    • 根据键的 hashCode() 计算哈希值,确定存储位置。

    • 如果发生哈希冲突(多个键映射到同一个桶),则使用链表或红黑树存储这些键值对。

  • 解决哈希冲突

    • 默认情况下,使用链表解决冲突。

    • 当链表长度超过阈值(默认为8)且当前桶数量大于64时,链表会转换为红黑树以提高查找效率。


3. HashMap 和 Hashtable 的区别?
特性HashMapHashtable
线程安全不是线程安全的是线程安全的
允许 null 键值允许一个 null 键和多个 null 值不允许 null 键和 null 值
性能更高(非同步)较低(同步)
继承关系实现 Map 接口继承 Dictionary 类

4. ConcurrentHashMap 如何保证线程安全?
  • 分段锁机制(Java 7):将整个哈希表分为多个段(Segment),每个段独立加锁,从而减少锁竞争。

  • CAS + synchronized(Java 8):使用 CAS 操作和 synchronized 锁来保证线程安全,避免了分段锁的设计,提升了性能。


5. HashSet 的底层实现是什么?
  • HashSet 底层基于 HashMap 实现。

  • 每个元素作为 HashMap 的键,而值是一个固定的对象(PRESENT)。

  • 因此,HashSet 的特性与 HashMap 的键一致:无序、不允许重复。


6. Comparable 和 Comparator 的区别?
特性ComparableComparator
定义方式在类中实现 Comparable 接口作为独立的比较器类实现
自然排序提供自然排序规则可以提供多种自定义排序规则
灵活性单一排序规则多种排序规则
使用场景对象本身具有明确的排序规则时需要多种排序规则时

7. Iterator 和 ListIterator 的区别?
特性IteratorListIterator
遍历方向只能单向遍历(向前)可以双向遍历(向前和向后)
功能只能删除元素支持添加、删除和替换元素
适用范围所有集合仅适用于 List 集合

8. fail-fast 和 fail-safe 机制的区别?
特性fail-fastfail-safe
定义快速失败机制,当检测到集合被修改时抛出异常安全失败机制,允许在并发修改时正常运行
实现方式使用 modCount 检测修改次数使用副本机制(如 CopyOnWriteArrayList
适用场景单线程环境多线程环境

9. 如何实现一个不可变的集合?
  • 使用 Collections.unmodifiableXXX() 方法包装集合:

List<String> list = new ArrayList<>();
list.add("A");
List<String> unmodifiableList = Collections.unmodifiableList(list);
  • 自定义实现:确保集合内部状态不可修改,并且不暴露任何可修改的方法。


10. ArrayList 的默认初始容量是多少?扩容机制是什么?
  • 默认初始容量:10。

  • 扩容机制

    • 当容量不足时,扩容为原容量的 1.5 倍newCapacity = oldCapacity + (oldCapacity >> 1))。

    • 创建一个新的数组,并将原有元素复制到新数组中。

四、异常处理


1. Java 异常分类(Error 和 Exception 的区别)?
  • Error

    • 表示 JVM 系统级错误或资源耗尽等严重问题,程序无法处理。

    • 示例:OutOfMemoryErrorStackOverflowError

  • Exception

    • 表示程序运行时发生的异常情况,可以通过代码捕获和处理。

    • 分为 受检异常(Checked Exception)非受检异常(Unchecked Exception)

      • 受检异常:必须在代码中显式处理(如 IOException)。

      • 非受检异常:无需强制处理(如 RuntimeException 及其子类)。


2. try-catch-finally 中,finally 是否一定会执行?
  • 通常情况下会执行

    • finally 块无论是否发生异常都会执行。

  • 例外情况

    • 程序提前终止(如调用 System.exit())。

    • finally 块中发生死循环或抛出其他异常。


3. throw 和 throws 的区别?
特性throwthrows
用途用于抛出具体的异常对象用于声明方法可能抛出的异常类型
使用位置方法体内方法签名后
示例throw new NullPointerException();public void method() throws IOException {}

4. 什么是自定义异常?如何实现?
  • 自定义异常:根据业务需求定义的异常类,继承自 ExceptionRuntimeException

  • 实现方式

// 自定义受检异常
class MyException extends Exception {
    public MyException(String message) {
        super(message);
    }
}
​
// 自定义非受检异常
class MyRuntimeException extends RuntimeException {
    public MyRuntimeException(String message) {
        super(message);
    }
}

5. try-with-resources 的作用及使用场景?
  • 作用:自动关闭实现了 AutoCloseable 接口的对象(如文件流、数据库连接等),避免手动调用 close() 方法。

  • 使用场景

try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
    String line = br.readLine();
} catch (IOException e) {
    e.printStackTrace();
}

6. OutOfMemoryError 和 StackOverflowError 的区别?
特性OutOfMemoryErrorStackOverflowError
原因内存不足栈溢出(递归过深或栈帧过大)
触发场景创建大对象、加载大量数据等无限递归调用

7. 列举常见的 RuntimeException?
  • NullPointerException

  • ArrayIndexOutOfBoundsException

  • ClassCastException

  • IllegalArgumentException

  • ArithmeticException

  • NumberFormatException

  • IllegalStateException


8. 为什么推荐捕获具体异常而非 Exception?
  • 捕获具体异常可以更精确地处理不同类型的异常,避免掩盖潜在问题。

  • 示例:

try {
    // 可能抛出多种异常的代码
} catch (FileNotFoundException e) {
    // 处理文件未找到的情况
} catch (IOException e) {
    // 处理其他 I/O 错误
}

9. final、finally 和 finalize() 的区别?
特性finalfinallyfinalize()
作用修饰变量、方法或类,表示不可修改异常处理机制,确保代码块执行对象销毁前的清理操作
使用场景定义常量、防止覆盖等确保资源释放已被废弃,不推荐使用

10. 异常处理对性能的影响有哪些?
  • 影响性能的原因

    • 异常处理机制涉及栈的展开和恢复,开销较大。

    • 频繁抛出异常会显著降低程序性能。

  • 优化建议

    • 避免在正常逻辑中频繁使用异常。

    • 使用条件判断代替异常捕获来处理常见错误。

五、多线程与并发


1. 线程的创建方式有哪些?
  • 继承 Thread

    class MyThread extends Thread {
        public void run() {
            System.out.println("Thread running");
        }
    }
    new MyThread().start();
  • 实现 Runnable 接口

    class MyRunnable implements Runnable {
        public void run() {
            System.out.println("Runnable running");
        }
    }
    new Thread(new MyRunnable()).start();
  • 实现 Callable 接口(支持返回值和抛出异常):

    Callable<Integer> task = () -> {
        return 42;
    };
    FutureTask<Integer> futureTask = new FutureTask<>(task);
    new Thread(futureTask).start();

2. Runnable 和 Callable 的区别?
特性RunnableCallable
返回值无返回值支持返回值
异常处理不能抛出受检异常可以抛出受检异常
使用场景适合简单任务适合需要返回结果的任务

3. 线程的生命周期有哪些状态?
  • 新建(New):线程对象被创建,但尚未启动。

  • 可运行(Runnable):线程已启动,等待 CPU 调度。

  • 阻塞(Blocked):线程因竞争锁而被阻塞。

  • 等待(Waiting/ Timed Waiting):线程调用 wait()sleep() 等方法进入等待状态。

  • 终止(Terminated):线程执行完毕或异常退出。


4. sleep() 和 wait() 的区别?
特性sleep()wait()
是否释放锁不释放释放
调用位置可在任何地方调用必须在同步代码块中调用
用途暂停线程一段时间让线程等待其他线程的通知

5. synchronized 关键字的用法(修饰方法、代码块)?
  • 修饰方法

    • 对实例方法加锁:锁为当前对象(this)。

    • 对静态方法加锁:锁为类的 Class 对象。

  • 修饰代码块

    synchronized (lockObject) {
        // 同步代码块
    }

6. volatile 关键字的作用?
  • 作用

    • 保证变量的可见性(线程间共享变量的修改能及时更新到主内存)。

    • 禁止指令重排序。

  • 局限性

    • 不保证原子性,适用于单个变量的读写操作。


7. 什么是线程安全?如何保证线程安全?
  • 线程安全:多个线程访问共享资源时,程序的行为符合预期。

  • 实现方式

    • 使用同步机制(如 synchronizedReentrantLock)。

    • 使用不可变对象。

    • 使用线程本地存储(ThreadLocal)。

    • 使用并发集合(如 ConcurrentHashMap)。


8. ReentrantLock 和 synchronized 的区别?
特性ReentrantLocksynchronized
功能扩展支持公平锁、中断等待等功能有限
性能更灵活,可能更高简单高效
使用复杂度需要显式加锁和解锁自动管理锁

9. 线程池的核心参数有哪些?工作原理是什么?
  • 核心参数

    • corePoolSize:核心线程数。

    • maximumPoolSize:最大线程数。

    • keepAliveTime:空闲线程存活时间。

    • workQueue:任务队列。

    • RejectedExecutionHandler:拒绝策略。

  • 工作原理

    • 当提交任务时,如果线程数小于核心线程数,则创建新线程执行任务。

    • 如果线程数达到核心线程数且队列未满,则将任务放入队列。

    • 如果队列已满且线程数小于最大线程数,则创建新线程。

    • 如果无法处理任务,则触发拒绝策略。


10. ThreadLocal 的作用及可能的内存泄漏问题?
  • 作用:为每个线程提供独立的变量副本,避免线程间数据共享。

  • 内存泄漏问题

    • ThreadLocal 使用弱引用存储键值对,但值是强引用。

    • 如果不手动清理 ThreadLocal,可能导致线程池中的线程持有无效的引用,造成内存泄漏。


11. 什么是死锁?如何避免死锁?
  • 死锁:两个或多个线程互相等待对方释放资源,导致所有线程都无法继续执行。

  • 避免死锁

    • 按固定顺序加锁。

    • 使用超时机制。

    • 减少锁的粒度。


12. ConcurrentHashMap 如何实现高并发?
  • 分段锁机制(Java 7):将哈希表分为多个段,每个段独立加锁。

  • CAS + synchronized(Java 8):使用 CAS 操作和细粒度锁,减少锁竞争。


13. AtomicInteger 的实现原理?
  • 基于 CAS(Compare-And-Swap)操作实现线程安全的原子操作。

  • 内部维护一个 volatile 变量,确保可见性。


14. CountDownLatch 和 CyclicBarrier 的区别?
特性CountDownLatchCyclicBarrier
重用性不可重用可重用
用途等待多个线程完成等待多个线程到达屏障点

15. 如何实现生产者-消费者模式?
  • 基于阻塞队列

    BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);
    ​
    class Producer implements Runnable {
        public void run() {
            try {
                while (true) {
                    queue.put(produce());
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    ​
        private Integer produce() {
            return 1;
        }
    }
    ​
    class Consumer implements Runnable {
        public void run() {
            try {
                while (true) {
                    consume(queue.take());
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    ​
        private void consume(Integer item) {
            System.out.println("Consumed: " + item);
        }
    }

六、IO与NIO


1. Java IO流的分类(字节流、字符流)?
  • 字节流

    • 基本单位是字节(byte),适用于处理二进制数据。

    • 输入流:InputStream,输出流:OutputStream

  • 字符流

    • 基本单位是字符(char),适用于处理文本数据。

    • 输入流:Reader,输出流:Writer


2. BIO、NIO 和 AIO 的区别?
特性BIONIOAIO
模型阻塞式 I/O非阻塞式 I/O异步 I/O
多路复用每个连接一个线程单线程管理多个连接系统负责调度
适用场景小规模连接大规模连接高并发场景

3. FileInputStream 和 FileReader 的区别?
特性FileInputStreamFileReader
数据类型字节流字符流
用途读取二进制文件读取文本文件
编码问题不涉及编码可能涉及编码转换

4. 什么是 NIO 中的 Channel、Buffer 和 Selector?
  • Channel

    • 类似于传统的流,但支持双向读写操作。

    • 常见实现:FileChannelSocketChannel

  • Buffer

    • 数据容器,用于存储和操作数据。

    • 常见类型:ByteBufferCharBufferIntBuffer

  • Selector

    • 用于监听多个通道的事件(如连接请求、数据到达等),实现多路复用。


5. 序列化和反序列化的作用?如何实现?
  • 作用

    • 序列化:将对象转换为字节流,便于存储或传输。

    • 反序列化:将字节流还原为对象。

  • 实现方式

    • 实现 Serializable 接口:

      class Person implements Serializable {
          private static final long serialVersionUID = 1L;
          private String name;
          private int age;
      
          // Getters and setters
      }
      
      // 序列化
      try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
          oos.writeObject(new Person("Alice", 25));
      }
      
      // 反序列化
      try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) {
          Person person = (Person) ois.readObject();
      }

6. transient 关键字的作用?
  • 作用:标记某个字段不参与序列化过程。

  • 示例

    class Person implements Serializable {
        private String name;
        private transient String password; // 不会被序列化
    }

7. 什么是零拷贝(Zero Copy)?
  • 零拷贝:减少数据在内核态和用户态之间的拷贝次数,提高 I/O 性能。

  • 实现机制

    • 使用 sendfile()mmap() 等系统调用。

    • 数据直接从磁盘缓冲区发送到网络接口,无需经过用户态。


8. 如何读取大文件(如 10GB)而不内存溢出?
  • 方法

    • 使用缓冲流(BufferedReaderBufferedInputStream)分块读取。

    • 使用 NIO 的 MappedByteBuffer 映射文件到内存。

  • 示例代码

    // 使用 BufferedReader 分块读取
    try (BufferedReader br = new BufferedReader(new FileReader("large_file.txt"))) {
        String line;
        while ((line = br.readLine()) != null) {
            processLine(line); // 处理每一行
        }
    }
    ​
    // 使用 MappedByteBuffer
    try (FileChannel channel = FileChannel.open(Paths.get("large_file.bin"), StandardOpenOption.READ)) {
        MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
        byte[] data = new byte[buffer.limit()];
        buffer.get(data);
        processData(data); // 处理数据
    }

七、JVM与内存管理


1. JVM 内存区域划分(堆、栈、方法区等)?
  • 程序计数器:记录当前线程执行的字节码指令地址。

  • 虚拟机栈:存储局部变量表、操作数栈、动态链接等信息,每个线程独占。

  • 本地方法栈:为 JNI(Native 方法)服务。

  • :存放对象实例和数组,所有线程共享。

  • 方法区:存储类信息、常量池、静态变量等,也称元空间(Metaspace)。

  • 直接内存:非 JVM 内部内存,用于 NIO 的 ByteBuffer 等。


2. 对象创建的过程是什么?
  • 类加载检查:检查对象对应的 .class 文件是否已加载。

  • 分配内存:在堆中分配内存(可能涉及垃圾回收或线程同步)。

  • 初始化零值:将对象字段初始化为默认值。

  • 执行构造方法:调用 <init> 方法完成对象初始化。


3. 垃圾回收算法有哪些?解释标记-清除、复制、标记-整理。
  • 标记-清除

    • 标记所有存活对象,然后清除未被标记的对象。

    • 缺点:会产生内存碎片。

  • 复制

    • 将存活对象复制到另一个区域,清理原区域。

    • 缺点:需要两倍内存空间。

  • 标记-整理

    • 标记存活对象,并将它们移动到一端,清理尾部空间。

    • 优点:避免内存碎片。


4. 什么是 GC Roots?哪些对象可以作为 GC Roots?
  • GC Roots:垃圾回收的起点,从这些对象开始遍历引用链。

  • 常见的 GC Roots

    • 虚拟机栈中的局部变量。

    • 方法区中的类静态属性。

    • 方法区中的常量。

    • 本地方法栈中的 JNI 引用。


5. 强引用、软引用、弱引用、虚引用的区别?
引用类型特性
强引用普通引用,对象不会被回收。
软引用内存不足时会被回收,适用于缓存场景。
弱引用GC 时一定会被回收,适用于临时对象。
虚引用不会阻止对象被回收,仅用于跟踪对象的回收状态。

6. 如何判断对象是否可被回收?
  • 如果对象无法通过任何引用链到达 GC Roots,则认为该对象可被回收。

  • 判断过程分为两步:

    1. 引用可达性分析:检查是否有引用指向该对象。

    2. finalize() 方法:如果对象实现了 finalize() 方法且未被调用,会在回收前尝试复活。


7. 常见的垃圾收集器有哪些(如 CMS、G1)?
收集器名称特点
Serial单线程,适合单核 CPU 和小内存环境。
ParNewSerial 的多线程版本。
Parallel Scavenge注重吞吐量,适合科学计算等场景。
CMS低延迟,适合交互式应用,但会产生内存碎片。
G1分代收集,注重平衡吞吐量和延迟,适合大内存环境。
ZGC高并发、低延迟,适合超大内存环境。
Shenandoah类似 ZGC,提供更低的暂停时间。

8. 什么是类加载机制?类加载过程有哪些阶段?
  • 类加载机制:将 .class 文件加载到 JVM 中并生成对应的 Class 对象。

  • 类加载过程

    1. 加载:从文件系统或网络中读取字节码。

    2. 验证:确保字节码格式正确且符合 JVM 规范。

    3. 准备:为类的静态变量分配内存并设置默认值。

    4. 解析:将符号引用替换为直接引用。

    5. 初始化:执行静态代码块和静态变量初始化。


9. 双亲委派模型是什么?如何打破它?
  • 双亲委派模型

    • 加载类时优先由父类加载器加载,只有父类加载器无法加载时才由子类加载器加载。

    • 目的是保证类的唯一性和安全性。

  • 打破方式

    • 使用自定义类加载器覆盖 loadClass() 方法,改变加载顺序。


10. OutOfMemoryError 的可能原因及解决方案?
错误类型原因解决方案
Java Heap Space堆内存不足增加 -Xmx 参数
PermGen/Metaspace方法区或元空间不足增加 -XX:MaxMetaspaceSize 参数
Direct Memory直接内存不足增加 -XX:MaxDirectMemorySize 参数
StackOverflowError栈深度过大增加 -Xss 参数

11. JVM 调优的常用参数有哪些?
  • 堆内存设置

    • -Xms:初始堆大小。

    • -Xmx:最大堆大小。

  • 新生代设置

    • -Xmn:新生代大小。

  • 垃圾收集器选择

    • -XX:+UseG1GC:启用 G1 收集器。

    • -XX:+UseConcMarkSweepGC:启用 CMS 收集器。

  • 其他参数

    • -XX:MaxMetaspaceSize:元空间最大大小。

    • -XX:+PrintGCDetails:打印垃圾回收详细信息。

八、JDBC与数据库


1. JDBC 操作数据库的基本步骤?
  • 加载驱动:使用 Class.forName() 加载数据库驱动。

  • 建立连接:通过 DriverManager.getConnection() 获取数据库连接。

  • 创建语句对象:使用 Connection 创建 StatementPreparedStatement

  • 执行 SQL:调用 executeQuery()executeUpdate() 执行查询或更新操作。

  • 处理结果集:遍历 ResultSet 获取查询结果。

  • 关闭资源:关闭 ResultSetStatementConnection

示例代码

try {
    Class.forName("com.mysql.cj.jdbc.Driver");
    Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password");
    Statement stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery("SELECT * FROM users");
​
    while (rs.next()) {
        System.out.println(rs.getString("name"));
    }
​
    rs.close();
    stmt.close();
    conn.close();
} catch (Exception e) {
    e.printStackTrace();
}

2. Statement 和 PreparedStatement 的区别?
特性StatementPreparedStatement
SQL 注入防护容易受到 SQL 注入攻击自动防止 SQL 注入
性能每次执行都需要编译 SQL预编译 SQL,性能更高
参数化支持不支持参数化支持参数化查询
适用场景简单的静态 SQL动态 SQL 或频繁执行的 SQL

3. 什么是数据库连接池?为什么使用它?
  • 数据库连接池:预先创建一组数据库连接并管理它们的生命周期,避免频繁创建和销毁连接。

  • 优点

    • 提高性能:减少连接创建和关闭的开销。

    • 控制资源:限制最大连接数,防止资源耗尽。

    • 简化管理:统一管理和复用连接。


4. 事务的 ACID 特性是什么?
  • Atomicity(原子性):事务中的所有操作要么全部成功,要么全部失败。

  • Consistency(一致性):事务执行前后,数据库状态保持一致。

  • Isolation(隔离性):多个事务并发执行时,彼此隔离,互不干扰。

  • Durability(持久性):事务提交后,其结果永久保存。


5. 什么是脏读、不可重复读、幻读?
问题描述
脏读一个事务读取了另一个未提交事务的数据。
不可重复读同一事务中多次读取同一数据,结果不一致。
幻读同一事务中多次查询同一条件,结果集发生变化。

6. 如何通过 JDBC 实现事务管理?
  • 步骤

    1. 禁用自动提交:conn.setAutoCommit(false);

    2. 执行一系列 SQL 操作。

    3. 如果所有操作成功,调用 conn.commit(); 提交事务。

    4. 如果发生异常,调用 conn.rollback(); 回滚事务。

    5. 最后恢复自动提交:conn.setAutoCommit(true);

示例代码

try {
    Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password");
    conn.setAutoCommit(false);
​
    String sql1 = "UPDATE accounts SET balance = balance - 100 WHERE id = 1";
    String sql2 = "UPDATE accounts SET balance = balance + 100 WHERE id = 2";
​
    Statement stmt = conn.createStatement();
    stmt.executeUpdate(sql1);
    stmt.executeUpdate(sql2);
​
    conn.commit();
    System.out.println("Transaction committed successfully.");
} catch (SQLException e) {
    e.printStackTrace();
    try {
        conn.rollback();
        System.out.println("Transaction rolled back.");
    } catch (SQLException ex) {
        ex.printStackTrace();
    }
} finally {
    try {
        conn.setAutoCommit(true);
        conn.close();
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

九、Java 8+ 新特性


1. Lambda 表达式的语法及使用场景?
  • 语法

    (参数列表) -> { 函数体 }
  • 示例

    // 无参数,无返回值
    () -> System.out.println("Hello");
    ​
    // 单个参数,无返回值
    (x) -> System.out.println(x);
    ​
    // 多个参数,有返回值
    (x, y) -> { return x + y; };
  • 使用场景

    • 简化函数式接口的实现。

    • 结合 Stream API 进行集合操作。

    • 实现事件监听器等回调机制。


2. 函数式接口(Functional Interface)是什么?
  • 定义:只包含一个抽象方法的接口。

  • 注解:可以用 @FunctionalInterface 标记。

  • 示例

    @FunctionalInterface
    interface MyFunctionalInterface {
        void doSomething();
    }
    ​
    MyFunctionalInterface func = () -> System.out.println("Doing something");
    func.doSomething();

3. Stream API 的核心操作有哪些?
  • 创建 Stream

    • 集合类的 stream() 方法。

    • 数组通过 Arrays.stream()

  • 中间操作(惰性求值):

    • filter:过滤元素。

    • map:转换元素。

    • sorted:排序。

    • distinct:去重。

  • 终端操作(触发执行):

    • forEach:遍历。

    • collect:收集结果。

    • reduce:聚合操作。

    • count:统计数量。

示例代码

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
     .filter(name -> name.startsWith("A"))
     .map(String::toUpperCase)
     .forEach(System.out::println);

4. Optional 类的作用及使用方法?
  • 作用:避免空指针异常,提供更安全的空值处理方式。

  • 常用方法

    • of(T value):创建非空的 Optional

    • ofNullable(T value):创建可能为空的 Optional

    • isPresent():判断是否有值。

    • orElse(T other):如果没有值,返回默认值。

    • ifPresent(Consumer<? super T> consumer):如果有值,执行指定操作。

示例代码

Optional<String> optional = Optional.ofNullable("Hello");
optional.ifPresent(System.out::println);

String result = optional.orElse("Default Value");
System.out.println(result);

5. 接口的默认方法和静态方法是什么?
  • 默认方法

    • 使用 default 关键字定义,默认方法可以有实现。

    • 解决接口扩展问题,避免破坏现有实现类。

  • 静态方法

    • 使用 static 关键字定义,可以直接通过接口调用。

示例代码

interface MyInterface {
    default void defaultMethod() {
        System.out.println("Default method");
    }
​
    static void staticMethod() {
        System.out.println("Static method");
    }
}
​
MyInterface.staticMethod(); // 调用静态方法
MyInterface obj = new MyInterface() {};
obj.defaultMethod(); // 调用默认方法

6. 方法引用(Method Reference)的四种形式?
  • 对象::实例方法

    user::getName
  • 类::静态方法

    String::valueOf
  • 类::实例方法

    String::toLowerCase
  • 构造器引用

    MyClass::new

示例代码

List<String> names = Arrays.asList("Alice", "Bob");
names.stream().map(String::toUpperCase).forEach(System.out::println);

7. 什么是 CompletableFuture?如何实现异步编程?
  • CompletableFuture:用于异步编程,支持链式调用和组合操作。

  • 核心方法

    • supplyAsync:异步执行任务并返回结果。

    • thenApply:对结果进行转换。

    • thenAccept:消费结果。

    • exceptionally:处理异常。

    • join:阻塞等待结果。

示例代码

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    return 42; // 模拟耗时任务
});
​
future.thenAccept(result -> {
    System.out.println("Result: " + result);
}).exceptionally(ex -> {
    System.err.println("Error: " + ex.getMessage());
    return null;
});
​
// 阻塞等待结果
int result = future.join();
System.out.println("Final Result: " + result);

十、设计模式


1. 列举常见的设计模式(至少5种)。
  • 创建型模式

    • 单例模式(Singleton):确保一个类只有一个实例。

    • 工厂模式(Factory):提供创建对象的接口,延迟实例化。

    • 抽象工厂模式(Abstract Factory):提供一组相关或依赖对象的创建接口。

  • 结构型模式

    • 适配器模式(Adapter):将一个类的接口转换成客户端期望的另一个接口。

    • 代理模式(Proxy):为其他对象提供一种代理以控制对这个对象的访问。

  • 行为型模式

    • 观察者模式(Observer):定义对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会收到通知。


2. 手写单例模式的双重检查锁(Double-Check Locking)实现。
public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {
        // 私有构造函数,防止外部实例化
    }

    public static Singleton getInstance() {
        if (instance == null) { // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) { // 第二次检查
                    instance = new Singleton(); // 创建实例
                }
            }
        }
        return instance;
    }
}
  • volatile 关键字:保证 instance 的可见性,防止指令重排序。


3. 工厂模式的作用及实现方式?
  • 作用

    • 将对象的创建过程封装起来,降低耦合度。

    • 提供统一的接口,隐藏具体的实现细节。

  • 实现方式

    • 简单工厂模式

      public class CarFactory {
          public static Car createCar(String type) {
              if ("BMW".equals(type)) {
                  return new BMW();
              } else if ("Mercedes".equals(type)) {
                  return new Mercedes();
              }
              return null;
          }
      }
    • 工厂方法模式

      public interface CarFactory {
          Car createCar();
      }
      ​
      public class BMWFactory implements CarFactory {
          @Override
          public Car createCar() {
              return new BMW();
          }
      }

4. 观察者模式如何实现?
  • 定义:定义对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会收到通知。

  • 实现方式

    • 示例代码

      import java.util.ArrayList;
      import java.util.List;
      ​
      // 观察者接口
      interface Observer {
          void update(String message);
      }
      ​
      // 被观察者
      class Subject {
          private List<Observer> observers = new ArrayList<>();
      ​
          public void addObserver(Observer observer) {
              observers.add(observer);
          }
      ​
          public void removeObserver(Observer observer) {
              observers.remove(observer);
          }
      ​
          public void notifyObservers(String message) {
              for (Observer observer : observers) {
                  observer.update(message);
              }
          }
      }
      ​
      // 具体观察者
      class ConcreteObserver implements Observer {
          @Override
          public void update(String message) {
              System.out.println("Received: " + message);
          }
      }
      ​
      // 使用示例
      public class Main {
          public static void main(String[] args) {
              Subject subject = new Subject();
              Observer observer = new ConcreteObserver();
      ​
              subject.addObserver(observer);
              subject.notifyObservers("Hello Observers!");
          }
      }

5. 适配器模式和代理模式的区别?
特性适配器模式代理模式
目的将一个类的接口转换成客户端期望的接口为其他对象提供代理,控制访问
适用场景不兼容的接口之间进行适配增强功能或延迟加载
实现方式包装目标对象,暴露适配后的接口实现与目标对象相同的接口

示例对比

  • 适配器模式

    class Adapter implements Target {
        private Adaptee adaptee;
    ​
        public Adapter(Adaptee adaptee) {
            this.adaptee = adaptee;
        }
    ​
        @Override
        public void request() {
            adaptee.specificRequest();
        }
    }
  • 代理模式

    class Proxy implements Subject {
        private RealSubject realSubject;
    ​
        @Override
        public void request() {
            if (realSubject == null) {
                realSubject = new RealSubject();
            }
            realSubject.request();
        }
    }

十一、综合问题


1. String s = new String("abc"); 创建了几个对象?
  • 答案:创建了 两个对象

  • 解释

    1. 字面量 "abc" 会先在字符串常量池中查找,如果不存在则创建一个对象。

    2. new String("abc") 明确通过 new 关键字创建了一个新的 String 对象。


2. Integer a = 127; Integer b = 127;a == b 的结果?为什么?
  • 结果true

  • 原因

    • Java 中 -128 ~ 127 范围内的 Integer 对象会被缓存(IntegerCache)。

    • 因此,ab 引用的是同一个缓存对象,== 比较的是引用地址,结果为 true

  • 注意:超出范围时(如 Integer a = 128; Integer b = 128;),a == b 结果为 false,因为会创建不同的对象。


3. 如何实现 LRU 缓存?
  • 思路

    • 使用 LinkedHashMap 实现,重写 removeEldestEntry() 方法。

    • 当缓存达到容量限制时,移除最久未使用的元素。

  • 示例代码

    import java.util.LinkedHashMap;
    import java.util.Map;
    ​
    public class LRUCache<K, V> extends LinkedHashMap<K, V> {
        private final int capacity;
    ​
        public LRUCache(int capacity) {
            super(capacity, 0.75f, true); // accessOrder=true 表示按访问顺序排序
            this.capacity = capacity;
        }
    ​
        @Override
        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
            return size() > capacity; // 超过容量时移除最老的元素
        }
    }

4. String 为什么是不可变的?
  • 原因

    • String 的值存储在 final char[] value 中,无法修改。

    • 不可变性保证了线程安全。

    • 提高性能:字符串常量池可以复用相同的实例。

    • 安全性:防止敏感信息(如密码)被篡改。


5. 什么是反射?如何通过反射创建对象?
  • 反射:在运行时动态获取类的信息并操作类的行为。

  • 创建对象

    Class<?> clazz = Class.forName("com.example.MyClass");
    Object obj = clazz.getDeclaredConstructor().newInstance();

6. 泛型擦除是什么?如何绕过泛型擦除?
  • 泛型擦除:编译时,泛型类型信息会被擦除,替换为原始类型(如 List<String> 变为 List)。

  • 绕过方法

    • 使用 Class 对象保存类型信息。

    • 示例:

      public class GenericType<T> {
          private Class<T> type;
      ​
          public GenericType(Class<T> type) {
              this.type = type;
          }
      ​
          public boolean isTypeOf(Object obj) {
              return type.isInstance(obj);
          }
      }
      ​
      GenericType<String> stringType = new GenericType<>(String.class);
      System.out.println(stringType.isTypeOf("Hello")); // true

7. 如何实现深拷贝(Deep Copy)?
  • 方式

    • 手动复制:递归复制对象及其所有子对象。

    • 序列化:将对象序列化为字节流后再反序列化。

  • 示例代码

    import java.io.*;
    ​
    public class DeepCopy implements Serializable {
        private static <T extends Serializable> T deepCopy(T obj) throws IOException, ClassNotFoundException {
            try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
                 ObjectOutputStream oos = new ObjectOutputStream(bos)) {
                oos.writeObject(obj);
    ​
                try (ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
                     ObjectInputStream ois = new ObjectInputStream(bis)) {
                    return (T) ois.readObject();
                }
            }
        }
    }

8. 如何用 Java 实现一个简单的阻塞队列?
  • 思路

    • 使用 synchronizedLock 控制并发。

    • 使用 wait()notify() 实现生产者和消费者之间的通信。

  • 示例代码

    import java.util.LinkedList;
    import java.util.Queue;
    ​
    public class BlockingQueue<T> {
        private final Queue<T> queue = new LinkedList<>();
        private final int capacity;
    ​
        public BlockingQueue(int capacity) {
            this.capacity = capacity;
        }
    ​
        public synchronized void put(T item) throws InterruptedException {
            while (queue.size() >= capacity) {
                wait(); // 队列满时等待
            }
            queue.add(item);
            notifyAll(); // 唤醒消费者
        }
    ​
        public synchronized T take() throws InterruptedException {
            while (queue.isEmpty()) {
                wait(); // 队列空时等待
            }
            T item = queue.poll();
            notifyAll(); // 唤醒生产者
            return item;
        }
    }

http://www.kler.cn/a/573189.html

相关文章:

  • ​Unity插件-Mirror使用方法(八)组件介绍(​Network Behaviour)
  • 人工智能之数学基础:矩阵的初等行变换
  • CMake学习笔记(一):工程的新建和如何将源文件生成二进制文件
  • 详细介绍 conda 的常用命令和使用方式
  • pdfplumber 解析 PDF 表格的原理
  • NUMA架构介绍
  • 50.xilinx fir滤波器系数重加载如何控制
  • K8S学习之基础十三:k8s中ReplicaSet的用法
  • 【单片机】嵌入式系统的硬件与软件特性
  • ios使用swift调用deepseek或SiliconFlow接口
  • 网络编程——UDP
  • Java 8 新特性
  • PCA(主成分分析)核心原理
  • Git 2.48.1 官方安装与配置全流程指南(Windows平台)
  • Libgdx游戏开发系列教程(6)——游戏暂停
  • 人工智能直通车系列02【Python 基础与数学基础】(控制流线性代数:向量基本概念)
  • 基于SpringBoot的在线骑行网站的设计与实现(源码+SQL脚本+LW+部署讲解等)
  • SpringMvc与Struts2
  • Switch开关的防抖监听器
  • libcoap在Ubuntu下的编译(基于CMake)