java基础知识面试题总结

发布于:2025-04-02 ⋅ 阅读:(25) ⋅ 点赞:(0)

Java基础知识面试题

1.重载和重写的区别

重载(Overload):

​ 同一个类中,方法名相同,参数列表不同(个数、类型、顺序)。

class Calculator {
    // 方法1:两个int相加
    public int add(int a, int b) {
        return a + b;
    }
    // 方法2:三个int相加(参数个数不同 → 重载)
    public int add(int a, int b, int c) {
        return a + b + c;
    }
    // 方法3:两个double相加(参数类型不同 → 重载)
    public double add(double a, double b) {
        return a + b;
    }
}

重写(Override):

​ 子类继承父类后,方法名、参数列表、返回类型相同。不能比父类更严格,不能比父类被重写方法声明更宽泛的异常。

class Animal {
    public void eat() {
        System.out.println("动物吃东西");
    }
}

class Dog extends Animal {
    @Override  // 重写父类方法
    public void eat() {
        System.out.println("狗吃骨头");
    }
}
对比项 重载(Overload) 重写(Override)
定义 同一个类中,方法名相同,参数列表不同(个数、类型、顺序)。 子类继承父类后,方法名、参数列表、返回类型相同(JDK5+允许返回子类类型)。
作用范围 同一个类 或父子类(继承关系不影响)。 子类与父类(必须有继承或接口实现关系)。
返回值 可以不同。 必须相同(或为父类方法返回值的子类型)。
访问权限 可以任意修改(public/protected/private)。 不能比父类更严格(如父类public,子类不能private)。
异常抛出 可以抛出任意异常。 不能抛出比父类更宽泛的检查异常(如父类抛IOException,子类不能抛Exception)。
目的 提供同名方法的不同实现方式(功能扩展)。 修改或增强父类方法的行为(多态性)。

2.String 和 StringBuffer、StringBuilder的区别是什么?

String:

​ 内部使用 final char[],修改时会创建新的对象(如 str+=”a“ 会生成新的String)。

String s1 = "Hello";
s1 += " World";  // 创建新对象,原"Hello"成为垃圾
System.out.println(s1);  // "Hello World"

StringBuilder:

​ 内部使用壳扩容的 char[],修改时直接操作原对象。

​ 无同步锁,线程不安全,但性能更高(推荐单线程使用)。

StringBuilder builder = new StringBuilder();
builder.append("Hello").append(" World");  // 单线程推荐
System.out.println(builder);  // "Hello World"

StringBuffer:

​ 内部使用壳扩容的 char[],修改时直接操作原对象。

​ 所有方法使用 synchronized 修饰,保证多线程安全,但性能较低。

StringBuffer buffer = new StringBuffer();
buffer.append("Hello").append(" World");  // 原地修改
System.out.println(buffer);  // "Hello World"
对比项 String StringBuffer StringBuilder
可变性 不可变(final char[]) 可变(动态扩容 char[]) 可变(动态扩容 char[])
线程安全 天然线程安全(不可变) 线程安全(方法加 synchronized 非线程安全(无同步锁)
性能 最低(频繁操作会生成新对象) 中等(同步锁开销) 最高(无锁,适合单线程)
适用场景 字符串常量或少量拼接 多线程环境下的字符串操作 单线程环境下的字符串高效操作
内存开销 高(每次修改生成新对象) 低(原地修改) 低(原地修改)

3.==与equals的区别

==:

​ 比较对象的引用地址是否相同。适用于基本类型(int、double等)和引用类型(对象)。

​ 基本数据类型(如:int,char):直接比较值是否相等。

int a = 10;
int b = 10;
System.out.println(a == b);  // true(值相同)

​ 引用数据类型(如String,Object):比较内存地址是否相同。

String s1 = new String("Java");
String s2 = new String("Java");
System.out.println(s1 == s2);  // false(地址不同)

equals():

​ 比较对象的内容是否逻辑相等。 仅适用于引用类型(对象)。

​ 默认行为(Obejct类实现):与==相同,比较地址。

​ 重写后的行为:(如String、Integer):比较对象内容。

String s1 = new String("Java");
String s2 = new String("Java");
System.out.println(s1.equals(s2));  // true(内容相同)
对比项 == equals()
作用 比较对象的引用地址(内存地址是否相同)。 比较对象的内容是否逻辑相等(可重写)。
适用类型 基本类型(int, double 等)和引用类型(对象)。 仅适用于引用类型(对象)。
默认行为 基本类型:比较值;引用类型:比较地址。 Object 类的默认实现等同于 ==(比较地址),但可被重写(如 StringInteger 等已重写)。
重写影响 无法重写。 可重写以自定义相等逻辑(如 String 比较字符序列)。

4.抽象类和接口的区别是什么?

抽象类:

​ 抽象类是一种特殊的类,它不能被实例化(即不能直接创建对象),主要用于作为其他类的基类(父类)。

abstract class Animal {
    abstract void eat();          // 抽象方法(子类必须实现)
    void sleep() {                // 具体方法(子类可直接继承)
        System.out.println("睡觉");
    }
}
abstract class Vehicle {
    protected int speed;          // 普通变量
    public Vehicle(int speed) {   // 构造方法
        this.speed = speed;
    }
}

接口:

​ 接口(Interface)是一种完全抽象的类,它定义了一组方法(行为规范),但不提供具体实现。任何实现该接口的类都必须提供这些方法的具体体现。

Java8+interface Swimmable {
    void swim();                  // 抽象方法
    default void dive() {         // 默认方法(子类可选重写)
        System.out.println("下潜");
    }
    static void info() {          // 静态方法(直接调用)
        System.out.println("水生生物");
    }
}
interface Flyable {
    int MAX_HEIGHT = 10000;       // 默认是 public static final
    // 不能有构造方法!
}
对比项 抽象类(Abstract Class) 接口(Interface)
定义关键字 abstract class interface
方法实现 可以包含具体方法抽象方法abstract)。 Java 8 前:只能有抽象方法; Java 8+:可包含 defaultstatic 方法。
成员变量 可以是普通变量常量静态变量 默认是 public static final(常量)。
构造方法 有构造方法(但不能实例化,用于子类初始化)。 无构造方法
继承/实现 单继承(一个类只能继承一个抽象类)。 多实现(一个类可实现多个接口)。
设计目的 代码复用(提供部分通用实现)。 定义行为规范(多态、解耦)。
适用场景 多个相关类共享公共逻辑(如模板方法模式)。 定义跨类别的能力(如 ComparableRunnable)。

5.String类的常用方法

1.字符串长度&判空

方法 说明
int length() 返回字符串长度(字符数)。
boolean isEmpty() 判断字符串是否为空(length() == 0)。
boolean isBlank() (Java 11+) 判断字符串是否为空或仅含空白字符(如 " ")。
String str = "Hello";
System.out.println(str.length());  // 5
System.out.println("".isEmpty());  // true
System.out.println("   ".isBlank()); // true

2.字符串比较

方法 说明
boolean equals(Object obj) 比较字符串内容是否相同(区分大小写)。
boolean equalsIgnoreCase(String str) 忽略大小写比较字符串内容。
int compareTo(String str) 按字典顺序比较字符串(返回 0 表示相等)。
int compareToIgnoreCase(String str) 忽略大小写的字典顺序比较。
String s1 = "Java";
String s2 = "java";
System.out.println(s1.equals(s2));  // false
System.out.println(s1.equalsIgnoreCase(s2));  // true
System.out.println(s1.compareTo(s2));  // -32('J' - 'j')

3.字符串查找

boolean contains(CharSequence s) 判断是否包含子串。
int indexOf(String str) 返回子串首次出现的索引(-1 表示未找到)。
int lastIndexOf(String str) 返回子串最后一次出现的索引。
boolean startsWith(String prefix) 判断是否以指定字符串开头。
boolean endsWith(String suffix) 判断是否以指定字符串结尾。
String str = "Hello, Java!";
System.out.println(str.contains("Java"));  // true
System.out.println(str.indexOf("Java"));  // 7
System.out.println(str.startsWith("Hello"));  // true

4.字符串截取&分割

方法 说明
String substring(int beginIndex) beginIndex 开始截取子串。
String substring(int begin, int end) 截取 [begin, end) 的子串。
String[] split(String regex) 按正则表达式分割字符串。
String str = "2023-10-01";
String[] parts = str.split("-");  // ["2023", "10", "01"]
System.out.println(str.substring(5));  // "10-01"
System.out.println(str.substring(0, 4));  // "2023"

5.字符串拼接&格式化

方法 说明
String concat(String str) 字符串拼接(等同于 +)。
static String join(CharSequence delimiter, CharSequence... elements) 用分隔符拼接多个字符串。
String format(String format, Object... args) 格式化字符串(类似 printf)。
String s1 = "Hello";
String s2 = "Java";
System.out.println(s1.concat(s2));  // "HelloJava"
System.out.println(String.join(", ", "A", "B", "C"));  // "A, B, C"
System.out.println(String.format("PI=%.2f", 3.14159));  // "PI=3.14"

6.其他常用方法

说明
char charAt(int index) 返回指定索引的字符。
char[] toCharArray() 转为 char[] 数组。
static String valueOf(Object obj) 将对象转为字符串(如 intString)。
String str = "Java";
System.out.println(str.charAt(1));  // 'a'
char[] chars = str.toCharArray();  // ['J', 'a', 'v', 'a']
String numStr = String.valueOf(123);  // "123"

6.Collection 和 Collections 有什么区别?

Collection(接口):

​ 定义集合的基本行为,如增删改查:

​ 主要子接口:

​ List(有序可重复,如 ArrayList、LinkedList)。

​ Set(无序唯一:如 HashSet、TreeSet)。

​ Queue(队列:LinkedList、PriorityQueue)。

public interface Collection<E> extends Iterable<E> {
    boolean add(E e);          // 添加元素
    boolean remove(Object o);  // 删除元素
    int size();               // 返回元素个数
    // 其他方法...
}
Collection<String> coll = new ArrayList<>();
coll.add("A");
coll.add("B");
System.out.println(coll.size());  // 2

Collections(工具类):

​ 提供操作集合的静态方法,如排序、反转、线程安全化:

​ 常用方法:

​ sort():排序(需元素实现Comparable)。

​ binarySearch():二分查找。

​ sychronizedList(): 讲集合转为线程安全版本。

List<Integer> list = Arrays.asList(3, 1, 2);
Collections.sort(list);       // 排序 → [1, 2, 3]
Collections.reverse(list);    // 反转 → [3, 2, 1]
Collections.shuffle(list);    // 随机打乱
List<Integer> numbers = new ArrayList<>(Arrays.asList(5, 2, 8));
Collections.sort(numbers);  // [2, 5, 8]
Collections.fill(numbers, 0);  // [0, 0, 0]
对比项 Collection(接口) Collections(工具类)
类型 Java 集合框架的根接口ListSetQueue 的父接口)。 一个工具类(提供静态方法操作或返回集合)。
作用 定义集合的基本操作(如 add()remove()size())。 提供集合的通用算法(排序、查找、同步化等)。
方法 实例方法(需通过集合对象调用,如 list.add())。 静态方法(直接通过类调用,如 Collections.sort())。
常见子接口/类 List(有序)、Set(无序唯一)、Queue(队列)。 无子类,仅包含静态方法。

7.List、Set、Map 之间的区别是什么?

List(列表):

​ 有序(按插入顺序或索引访问)。允许重复元素,可存储多个null。

​ ArrayList: 基于动态数组,随机访问快(0(1)),但插入/删除慢(0(n))。

​ LinkedList:基于双向联表,插入/删除快(0(1)),但随机访问慢(0(n))。

List<String> list = new ArrayList<>();
list.add("A");  // [A]
list.add("B");  // [A, B]
list.add("A");  // [A, B, A](允许重复)

Set(集合):

​ 无序(HashSet)或按规则排序(TreeSet)。元素唯一(依赖equals()和hashCode())。

​ HashSet: 基于哈希表,查找0(1),无序。

​ TreeSet:基于红黑树,查找0(log n), 按自然顺序或 Comparator 排序。

Set<String> set = new HashSet<>();
set.add("A");  // [A]
set.add("B");  // [A, B](具体顺序可能不同)
set.add("A");  // [A, B](去重)

Map(键值对):

​ key唯一,Value可重复。 HashMap无序,TreeMap 按Key排序。

​ HashMap:基于哈希表,查找 0(1)。

​ TreeMap:基于红黑树,查找0(log n), 按Key 排序。

Map<String, Integer> map = new HashMap<>();
map.put("Apple", 1);  // {Apple=1}
map.put("Banana", 2); // {Apple=1, Banana=2}
map.put("Apple", 3);  // {Apple=3, Banana=2}(Key 重复会覆盖)
对比项 List(列表) Set(集合) Map(映射)
接口/实现 List(接口) (ArrayList, LinkedList Set(接口) (HashSet, TreeSet Map(接口) (HashMap, TreeMap
存储结构 有序、可重复(按插入顺序或索引访问)。 无序、唯一(不允许重复元素)。 键值对(Key-Value),Key 唯一,Value 可重复。
允许 null 允许多个 null 值。 最多允许一个 null 值(TreeSet 不允许)。 HashMap:允许一个 null Key 和多个 null Value。 TreeMap:Key 不能为 null
查找性能 ArrayListO(1)(索引访问) LinkedListO(n)(遍历)。 HashSetO(1)(平均) TreeSetO(log n) HashMapO(1)(平均) TreeMapO(log n)
线程安全 默认不安全,可用 Collections.synchronizedListCopyOnWriteArrayList 默认不安全,可用 Collections.synchronizedSetCopyOnWriteArraySet 默认不安全,可用 Collections.synchronizedMapConcurrentHashMap
适用场景 需要保留顺序允许重复(如购物车商品列表)。 需要去重快速查找(如用户 ID 集合)。 需要键值映射(如缓存、字典)。

8.HashMap 和 Hashtable 有什么区别

HashMap:

​ 非线程安全,多线程操作可能导致数据不一致。

​ 替代方案:使用 ConcurrentHashMap(分段锁,性能较高)。

HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("A", 1);
hashMap.put(null, 2);      // 允许 null Key
hashMap.put("B", null);    // 允许 null Value
System.out.println(hashMap);  // {null=2, A=1, B=null}

Hashtable:

​ 线程安全,但所有方法加 synchronized,并发性能差。

Hashtable<String, Integer> hashtable = new Hashtable<>();
hashtable.put("A", 1);
// hashtable.put(null, 2);   // 运行时报 NullPointerException
// hashtable.put("B", null); // 运行时报 NullPointerException
System.out.println(hashtable);  // {A=1}
对比项 HashMap (JDK 1.2+) Hashtable (JDK 1.0)
线程安全 非线程安全(需手动同步,如 ConcurrentHashMap)。 线程安全(方法用 synchronized 修饰,性能较低)。
允许 null 允许 一个 null Key多个 null Value 不允许 null Key 或 Value(会抛 NullPointerException)。
继承体系 继承 AbstractMap,实现 Map 接口。 继承 Dictionary(已过时),实现 Map 接口。
性能 更高(无同步锁,适合单线程)。 较低(同步锁开销,多线程竞争时性能差)。
迭代器 使用 Iterator(快速失败,fail-fast)。 使用 Enumeration(较老的迭代方式)。
扩容机制 默认容量 16,扩容为 2 倍 默认容量 11,扩容为 2 倍 + 1
推荐使用场景 单线程高并发(用 ConcurrentHashMap 遗留代码(现代开发基本被 ConcurrentHashMap 替代)。

9.说一下 HashMap的实现原理

HashMap 是 Java 中最常用的哈希表实现,基于 数组+联表+红黑树 的混合结构,核心目标是实现高效的0(1)时间复杂度(平均情况下)的键值对存取。

1.核心数据结构

​ 数组(桶数组): 默认初始容量为16,存储链表的头节点或红黑树的根节点。数组的每个位置称为一个桶(Bucket),通过哈希函数确定键值对的存储位置。

​ 链表(解决哈希冲突):当多个键的哈希值映射到同一桶时,以链表形式存储(拉链法)。

​ 红黑树(优化长链表):当链表长度超过 8 且桶数组长度 >= 64 时,链表转为红黑树,将查询时间从0(n) 优化为 0(logn)。

2.关键机制
哈希计算: 计算键的 hashCode()(int 类型)。 通过扰动函数 ((h = key.hashCode())^(h>>>16)) 混合高低位,减少哈希冲突。 取模运算 (n-1) & hash 确定桶下标(n 为数组长度,要求是 2的幂)。

// 计算键 "A" 的桶下标
String key = "A";
int hash = key.hashCode() ^ (key.hashCode() >>> 16); // 扰动
int index = (16 - 1) & hash; // 等价于 hash % 16

​ 插入流程:

​ 1.计算键的哈希值和桶下标。

​ 2.若桶为空,直接存入新节点。

​ 3.若桶不为空:

​ 链表:遍历链表,若找到相同键则更新值,否则添加到链表尾部。

​ 红黑树:调用树的插入方法。

​ 4.若链表长度 ≥ 8 且数组长度 ≥ 64,链表转红黑树。

​ 扩容(Rehash):

​ 触发条件:元素数量超过 容量x负载因子(默认负载因子 0.75)。

​ 扩容条件:数组大小扩大为 2倍,重新计算所有键值对位置。

10.ArrayList 与 LinkedList 的区别

ArrayList:

​ 基于动态数组,内存连续,支持快速随机访问。

​ 扩容时需拷贝原数组到新数组(Arrays.copyOf) 。

LinkedList:

​ 基于双向链表,内存不连续,每个节点存储前驱(prev)和后继(next)指针。

​ 无需扩容,插入/删除只需修改相邻节点的指针。

// ArrayList 示例
List<String> arrayList = new ArrayList<>();
arrayList.add("A");  // O(1)
arrayList.get(0);     // O(1)
arrayList.add(0, "B"); // O(n)

// LinkedList 示例
List<String> linkedList = new LinkedList<>();
linkedList.add("A");  // O(1)
linkedList.get(0);    // O(n)
linkedList.addFirst("B"); // O(1)(直接操作头节点)
对比项 ArrayList LinkedList
底层数据结构 动态数组(可自动扩容) 双向链表(节点存储前后指针)
内存占用 更小(仅存储数据,无额外指针) 更大(每个元素需存储前后节点的指针)
随机访问性能 O(1)(通过索引直接定位) O(n)(需从头遍历链表)
插入/删除性能 O(n)(需移动后续元素) O(1)(仅修改指针,但需先定位到操作位置)
适用场景 高频随机访问(如按索引查询) 高频插入/删除(如队列、栈)
扩容机制 初始容量为10,默认扩容 1.5 倍int newCapacity = oldCapacity + (oldCapacity >> 1) 无需扩容(动态增减节点)
线程安全 非线程安全(可用 Collections.synchronizedList 非线程安全(同上)
实现接口 List List + Deque(可作队列或栈使用)

11.Java集合容器有哪些?

一、Collection接口

1**.List(有序、可重复)**

实现类 特点
ArrayList 基于动态数组,查询快(O(1)),增删慢(需移动元素,O(n))。
LinkedList 基于双向链表,增删快(O(1)),查询慢(需遍历,O(n))。支持队列操作。

2.Set(无序、唯一)

实现类 特点
HashSet 基于 HashMap 实现,无序,查询/插入 O(1),允许一个 null 值。
LinkedHashSet 继承 HashSet,内部用链表维护插入顺序。
TreeSet 基于红黑树实现,元素按自然顺序或 Comparator 排序,操作 O(log n)

3.Queue(队列)

实现类 特点
LinkedList 可作双向队列(Deque)。
PriorityQueue 基于堆实现的优先级队列,元素按优先级排序。
ArrayDeque 基于数组的双端队列,高性能(比 LinkedList 更节省内存)。

二、Map接口

实现类 特点
HashMap 基于哈希表(数组+链表+红黑树),线程不安全,允许 null 键/值。
LinkedHashMap 继承 HashMap,用链表维护插入顺序或访问顺序(适合实现 LRU 缓存)。
TreeMap 基于红黑树,键按自然顺序或 Comparator 排序,操作 O(log n)
ConcurrentHashMap 线程安全的 HashMap(分段锁/CAS),高并发场景首选。
List<String> list = new ArrayList<>();  // 常用列表
Set<Integer> set = new HashSet<>();     // 快速去重
Map<String, Integer> map = new HashMap<>();  // 键值存储
Queue<String> queue = new ArrayDeque<>();    // 高效队列

12.哪些集合是线程安全的?

Java提供了多种线程安全的集合类,主要分为两类:

1.早期线程安全集合(性能较低,如Vector、Hashtable)

集合类 特点 问题
Vector 线程安全的 ArrayList(方法用 synchronized 修饰)。 全表锁,并发性能差。
Hashtable 线程安全的 HashMap(方法用 synchronized 修饰)。 全表锁,并发性能差。
Stack 线程安全的栈(继承自 Vector)。 已废弃,推荐用 Deque 替代。

注意:这些类因性能问题,现代开发中已被 JUC(java.util.concurrent)包中的集合取代。

2.现代高并发集合(JUC包中的高效实现,如 ConcurrentHashMap、CopyOnWriteArrayList)

集合类 特点 适用场景
ConcurrentHashMap 分段锁(JDK 7)或 CAS + 红黑树(JDK 8+),高并发下性能优异。 高并发键值存储(如缓存)。
CopyOnWriteArrayList 写时复制(写操作加锁并复制新数组),读操作无锁。 读多写少(如监听器列表)。
CopyOnWriteArraySet 基于 CopyOnWriteArrayList 实现的线程安全 Set 读多写少的去重场景。
ConcurrentLinkedQueue 基于链表的无界线程安全队列(CAS 实现)。 高并发队列(如任务队列)。
ConcurrentSkipListMap 基于跳表实现的线程安全 TreeMap(支持排序)。 高并发有序键值存储。
ConcurrentSkipListSet 基于 ConcurrentSkipListMap 实现的线程安全 TreeSet 高并发有序去重集合。
BlockingQueue 实现类 阻塞队列(如 ArrayBlockingQueueLinkedBlockingQueue)。 生产者-消费者模型。

13.创建线程有哪几种方式

1.继承Thread类

​ 直接继承Thread类并重写 run() 方法。

​ 简单但灵活性差(Java不支持多继承)。

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程运行中(继承Thread)");
    }
}

// 启动线程
MyThread thread = new MyThread();
thread.start();  // 调用start()启动新线程(非run())

2.实现 Runnable 接口

​ 实现 Runnable 接口,重写 run() 方法。

​ 更灵活(可继承其他类或是实现其他接口)。

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程运行中(实现Runnable)");
    }
}

// 启动线程
Thread thread = new Thread(new MyRunnable());
thread.start();

3.实现 Callable 接口 + FutureTask

​ 可返回结果(Callable 的 call() 有返回值)。

​ 支持异常抛出(Runnable 的 run() 不能抛异常)。

import java.util.concurrent.*;

class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "Callable返回结果";
    }
}

// 启动线程
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.start();

// 获取结果(会阻塞直到线程完成)
String result = futureTask.get();  
System.out.println(result);

4.使用线程池

​ 避免频繁创建/销毁线程的开销,提高系统资源利用率。

​ 通过 ExecutorService 管理线程生命周期。

import java.util.concurrent.*;

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 创建线程池(固定大小)
        ExecutorService executor = Executors.newFixedThreadPool(3);

        // 提交任务(支持Runnable或Callable)
        executor.execute(() -> System.out.println("线程池执行Runnable任务"));
        Future<String> future = executor.submit(() -> "线程池执行Callable任务");

        // 获取Callable结果
        try {
            System.out.println(future.get());
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 关闭线程池
        executor.shutdown();
    }
}
方式 优点 缺点 适用场景
继承 Thread 简单直接 无法继承其他类 简单任务
实现 Runnable 灵活性高(可继承其他类) 无返回值 多线程资源共享
实现 Callable 可返回结果、支持异常 需配合 FutureTask 使用 需要返回结果的异步任务
线程池 资源复用、管理便捷 需配置参数(如核心线程数) 高并发、长期运行任务

14.线程有哪些状态

Java线程的生命周期分为 6 种状态,可通过 Thread.getState() 查看。

1.NEW(新建)

​ 线程被创建但尚未启动(未调用 start())。

​ 触发条件:新建线程。

2.RUNNABLE(可运行)

​ 线程正在JVM种执行或等待系统资源(如CPU)。

​ 触发条件:调用start()后进入 RUNNABLE。 被阻塞的线程(如I/O)恢复后也回到RUNNABLE。

3.BLOCKED(阻塞)

​ 线程等待获取监视器锁(sychronized)时被阻塞。

​ 触发条件:竞争 sychronized 锁失败(锁被其他线程持有)。

4.WAITING(无限等待)

​ 线程主动进入等待,除非被唤醒,否则一直等待。

​ 触发条件:调用Object.wait()、Thread.join() 或 LockSupport 。park()。

5.TIMED_WAITING(超时等待)

​ 线程在指定时间内等待,超时后自行唤醒。

​ 触发条件:Thread.sleep(long)、Object.wait(long)、Thread.join(long)等带超时参数的方法。

6.TERMINATED(终止)

​ 线程执行完毕(run() 方法结束)或异常退出。

​ 触发条件:thread.start(); // 执行完 run() 后状态为 TERMINATED 。

15.sleep() 和 wait() 有什么区别?

1.锁的行为不同

​ sleep():睡眠期间不释放锁,其他线程无法进入同步块。

​ wait():释放锁,其他线程可获取锁并执行。

2.调用前提不同

​ wait() 必须在 sychronized 代码块中调用,否则抛 IllegalMonitorStateException。

​ sleep() 可以在任何地方调用。

3.设计目的不同

​ sleep():用于延迟执行(如定时任务)。

​ wait():用于线程间协作(如生产者-消费者模型)。

sleep() 实例

public class SleepDemo {
    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (SleepDemo.class) {
                System.out.println("线程A: 持有锁并睡眠2秒");
                try {
                    Thread.sleep(2000); // 不释放锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程A: 唤醒后继续执行");
            }
        }).start();

        new Thread(() -> {
            synchronized (SleepDemo.class) {
                System.out.println("线程B: 获取到锁"); // 必须等待线程A释放锁
            }
        }).start();
    }
}

执行结果:

线程A: 持有锁并睡眠2秒  
(等待2秒)  
线程A: 唤醒后继续执行  
线程B: 获取到锁

wait()示例:

public class WaitDemo {
    public static void main(String[] args) {
        Object lock = new Object();
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程A: 持有锁并等待");
                try {
                    lock.wait(); // 释放锁,线程B可获取锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程A: 被唤醒后重新获得锁");
            }
        }).start();

        new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程B: 获取到锁并唤醒线程A");
                lock.notify(); // 唤醒线程A(但线程B仍持有锁,直到同步块结束)
                try {
                    Thread.sleep(1000); // 模拟耗时操作
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程B: 释放锁");
            }
        }).start();
    }
}

执行结果:

线程A: 持有锁并等待  
线程B: 获取到锁并唤醒线程A  
线程B: 释放锁  
线程A: 被唤醒后重新获得锁
对比项 Thread.sleep() Object.wait()
所属类 Thread 类的静态方法 Object 类的实例方法
锁的释放 不释放锁(即使占用 synchronized 锁) 释放锁(调用前必须持有 synchronized 锁)
使用场景 单纯让线程暂停指定时间 线程间通信(需配合 notify()/notifyAll()
唤醒条件 时间到期后自动唤醒 必须由其他线程显式唤醒(或超时,若调用 wait(long timeout)
异常处理 需捕获 InterruptedException 需捕获 InterruptedException
状态变化 进入 TIMED_WAITING 状态 进入 WAITINGTIMED_WAITING 状态

16.notify() 和 notifyAll() 有什么区别?

1.唤醒机制

​ notify():从等待池(WAITING 状态的线程)中随机选择一个线程唤醒,其他线程仍保持等待。

​ notifyAll():唤醒等待池中所有线程,这些线程需要重新竞争锁。

2.锁竞争流程

​ notify():被选中的线程从 WAITING 变为 BLOCKED,尝试获取锁。其他未被唤醒的线程仍在 WAITING 状态。

​ notifyAll():所有等待线程从 WAITING 变为 BLOCKED,一起竞争锁。最终只有一个线程能获取锁,其余线程继续阻塞。

对比项 notify() notifyAll()
唤醒范围 随机唤醒 一个 正在等待(wait())的线程。 唤醒 所有 正在等待(wait())的线程。
锁竞争 被唤醒的线程直接竞争锁,其他线程继续等待。 所有被唤醒的线程一起竞争锁。
使用场景 明确知道只需唤醒一个线程(如单生产者-单消费者)。 需要唤醒所有线程(如资源释放后所有消费者竞争)。
性能影响 更高效(减少不必要的线程竞争)。 可能引起“惊群效应”(大量线程竞争资源)。
公平性 不保证公平性(依赖 JVM 实现)。 不保证公平性,但所有线程有机会竞争。

17.说一说几种常见的线程池及适用场景?

Java 通过 Executors 工具类提供了几种标准线程池实现,均基于 ThreadPoolExecutor 封装。

  1. FixedThreadPool(固定大小线程池)

    特点:

    ​ 核心线程数 = 最大线程数 (corePoolSize = maximumPoolSize),线程数固定。

    ​ 使用无界队列(LinkedBlockingQueue),任务堆积可能导致OOM。

    适用场景:

    ​ 适用于负载稳定的场景(如已知并发量可控的Web服务)。

    缺点:

    ​ 无界队列可能导致内存溢出(如提交速度远高于提交速度)。

    创建方式:

    ExecutorService executor = Executors.newFixedThreadPool(5); // 固定5个线程
    
  2. CachedThreadPool(可缓存线程池)

    特点:

    ​ 核心线程数为0,最大线程数为 Integer.MAX_VALUE(理论上无限创建线程)。

    ​ 使用 SynchronousQueue(不存储任务,直接移交线程执行)。

    ​ 空闲线程超时60秒后回收。

    适用场景:

    ​ 适用于短时异步任务且任务量波动大的场景(如突发流量请求)。

    缺点:

    ​ 线程数无上限,可能耗尽系统资源(如CPU或内存)。

    创建方式:

    ExecutorService executor = Executors.newCachedThreadPool();
    

    3.SingleThreadExecutor(单线程线程池)

    特点:

    ​ 核心线程数 = 最大线程数 = 1 (保证任务按提交顺序串执行)。

    ​ 使用无界队列(LinkedBlockingQueue),任务堆积可能导致OOM。

    适用场景:

    ​ 需要任务顺序执行的场景 (如日志顺序写入、单线程任务调度)。

    缺点:

    ​ 无界队列风险同 FixedThreadPool。

    创建方式:

    ExecutorService executor = Executors.newSingleThreadExecutor();
    

    4.ScheduledThreadPool(定时任务线程池)

    特点:

    ​ 支持延迟执行和周期性执行任务。

    ​ 使用 DelayedWorkQueue(按任务触发时间排序)。

    适用场景:

    ​ 定时任务(如每隔10秒统计一次数据)。

    ​ 延迟任务(如5分钟后执行回调)。

    创建方式:

    ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
    

18.创建线程池的核心参数有哪些?

1. 核心参数列表

参数名 作用 推荐设置规则
corePoolSize 核心线程数(长期存活的线程)。 CPU密集型:CPU核心数 + 1 IO密集型:CPU核心数 × 2
maximumPoolSize 最大线程数(临时线程 = maximumPoolSize - corePoolSize)。 建议 corePoolSize × 2~3(根据任务阻塞时间调整)
keepAliveTime 临时线程空闲存活时间(超时后销毁)。 默认 60秒(IO密集型可缩短,CPU密集型可延长)
unit keepAliveTime 的时间单位(秒、毫秒等)。 通常用 TimeUnit.SECONDS
workQueue 任务队列(存储未执行的Runnable任务)。 根据场景选择: - ArrayBlockingQueue(有界) - LinkedBlockingQueue(无界)
threadFactory 线程工厂(自定义线程命名、优先级等)。 建议自定义命名(如MyThreadFactory),便于排查问题
handler 拒绝策略(当队列和线程池全满时的处理方式)。 默认AbortPolicy(抛异常),生产建议用CallerRunsPolicy或自定义日志记录

2.参数详解

(1)核心线程 vs 最大线程

​ 核心线程:长期存活,即使空闲也不销毁(除非allowCoreThreadTimeOut=true)。

​ 临时线程:当任务数 > corePoolSize 且队列满时创建,空闲超时后销毁。

(2)任务队列选择

队列类型 特点 适用场景
ArrayBlockingQueue 有界队列,固定大小。 需要控制任务积压,避免OOM。
LinkedBlockingQueue 无界队列(默认Integer.MAX_VALUE)。 任务量不可预测,但可能引发OOM。
SynchronousQueue 不存储任务,直接移交线程。 高并发且快速响应(如CachedThreadPool)。
PriorityBlockingQueue 优先级队列。 任务需要按优先级执行。

(3)拒绝策略(RejectedExecutionHandler)**

策略 行为
AbortPolicy(默认) 直接抛出 RejectedExecutionException
CallerRunsPolicy 由提交任务的线程自己执行任务(降低提交速度)。
DiscardPolicy 静默丢弃被拒绝的任务(不通知)。
DiscardOldestPolicy 丢弃队列中最旧的任务,然后重试提交。

19.线程池中submit() 和 execute() 方法有什么区别?

在 Java 线程池中,submit() 和 execute() 是提交任务的两种核心方法,二者在返回值、异常处理和使用场景上有显著差异。

使用示例:

​ execute() 方法

​ 特点:异常需通过 Thread.setUncaughtExceptionHandler 捕获,否则会丢失。无法获取任务执行结果。

ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(() -> {
    System.out.println("任务执行中");
    // 异常会直接抛出到线程未捕获异常处理器
    if (true) throw new RuntimeException("execute()异常");
});

​ submit() 方法

​ 特点:通过Future.get() 获得返回值或异常。

​ 支持Callable(有返回值)和 Runnable(返回null)。

Future<String> future = executor.submit(() -> {
    System.out.println("Callable任务执行中");
    return "success";
});

// 获取结果(会阻塞)
try {
    String result = future.get(); // 捕获ExecutionException(封装任务异常)
    System.out.println(result);
} catch (ExecutionException e) {
    System.out.println("捕获到任务异常: " + e.getCause());
}
对比项 execute() submit()
返回值 无返回值(void 返回 Future 对象,可获取任务结果或异常。
异常处理 异常直接抛出(需自行捕获) 异常封装在 Future 中(需调用 get() 时捕获)。
适用任务类型 仅支持 Runnable 支持 RunnableCallable
方法来源 定义在 Executor 接口。 定义在 ExecutorService 子接口。

20.在Java程序中怎么保证多线程的运行安全?

多线程安全问题的本质是共享资源的并发访问冲突。Java提供了多种机制来保证线程安全。

1.不可变对象(Immutable Objects)

​ 原理:对象创建后状态不可修改,天然线程安全。

​ 适用场景:配置信息,常量数据。

// final 修饰类和字段,不提供setter方法
public final class ImmutableConfig {
    private final String serverUrl;
    public ImmutableConfig(String url) { this.serverUrl = url; }
    public String getServerUrl() { return serverUrl; }
}

2.线程封闭(Thread Confinement)

​ 原理:将对象限制在单个线程内访问,避免共享。

​ 实现方式:

​ 局部变量(栈封闭):每个线程都有自己的栈帧。

​ ThreadLocal:为每个线程保留独立副本。

private static ThreadLocal<SimpleDateFormat> dateFormat = 
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

3.同步锁(Synchronization)

​ (1) synchronized 关键字

// 同步方法(锁当前对象)
public synchronized void increment() { counter++; }

// 同步代码块(锁指定对象)
public void update() {
    synchronized (lockObject) {
        // 临界区代码
    }
}

​ (2) ReentrantLock(显示锁)

private final ReentrantLock lock = new ReentrantLock();
public void doTask() {
    lock.lock();
    try {
        // 临界区代码
    } finally {
        lock.unlock(); // 必须手动释放
    }
}

4.并发容器(Thread-Safe Collections)

​ 原理:基于 CAS 或分段锁实现的高效线程安全容器。

​ 常用类:

​ ConcurrentHashMap:替代 HashMap

​ CopyOnWriteArrayList:替代 ArrayList(读多写少场景)

​ BlockingQueue:生产者-消费者模型(如 ArrayBlockingQueue)

Map<String, String> concurrentMap = new ConcurrentHashMap<>();
List<String> copyOnWriteList = new CopyOnWriteArrayList<>();

5.原子类(Atomic Classes)

​ 原理:通过CAS(Compare-And-Swap)实现无锁线程安全操作。

​ 适用场景:计数器、状态标志。

private AtomicInteger counter = new AtomicInteger(0);
public void safeIncrement() {
    counter.incrementAndGet(); // 原子操作
}

6.线程池(ThreadPool)

​ 原理:统一管理线程生命周期,避免频繁创建/销毁线程。

ExecutorService executor = new ThreadPoolExecutor(
    4,                              // corePoolSize
    8,                              // maximumPoolSize
    30, TimeUnit.SECONDS,           // keepAliveTime
    new ArrayBlockingQueue<>(100),   // workQueue
    new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);

21.什么是死锁?

1.死锁的定义

​ 死锁是指两个或多个线程在执行过程中,因争夺资源而陷入互相等待的状态,导致所有线程都无法继续执行。此时系统处于僵持状态,除非外部干预,否则程序无法恢复。

2.死锁的四个必要条件

必须同时满足以下条件才会发生死锁:

​ 1.互斥条件:资源一次只能被一个线程占用(如锁)。

​ 2.占有并等待:线程持有至少一个资源,同时等待获取其他被占用的资源。

​ 3.不可抢占:已分配给线程的资源不能被其他线程强行夺取,必须由线程自行释放。

​ 4.循环等待:存在一个线程的循环等待链,每个线程都在等待下一个线程锁占用的资源。

22.怎么防止死锁

1.固定锁顺序(破坏循环等待)

​ 原理:所有线程按全局统一的顺序获取锁。

​ 适用场景:多锁嵌套场景。

​ 优点:简单有效,适用于已知所有锁的场景。

// 定义全局锁顺序规则:lock1 → lock2
public void transfer(Account from, Account to, int amount) {
    Object firstLock = from.getId() < to.getId() ? from : to;
    Object secondLock = from.getId() < to.getId() ? to : from;

    synchronized (firstLock) {
        synchronized (secondLock) {
            from.withdraw(amount);
            to.deposit(amount);
        }
    }
}

2.超时机制(破坏不可抢占)

​ 原理:通过 tryLock设置超时,放弃等待并释放已有锁。

​ 适用场景:高并发竞争环境。

​ 优点:避免无限等待,系统可自恢复。

private final Lock lock1 = new ReentrantLock();
private final Lock lock2 = new ReentrantLock();

public void doTask() throws InterruptedException {
    while (true) {
        if (lock1.tryLock(1, TimeUnit.SECONDS)) {
            try {
                if (lock2.tryLock(1, TimeUnit.SECONDS)) {
                    try {
                        // 成功获取两把锁
                        break;
                    } finally {
                        lock2.unlock();
                    }
                }
            } finally {
                lock1.unlock();
            }
        }
        // 随机休眠避免活锁
        Thread.sleep((long) (Math.random() * 100));
    }
}

3.一次性申请所有资源(破坏占有并等待)

​ 原理:通过更高层次的锁(如门面锁)保证原子性获取所有资源。

​ 适用场景:需要多个资源的操作。

​ 缺点:可能降低并发度。

public class ResourceManager {
    private final Object globalLock = new Object();

    public void useResources(A a, B b) {
        synchronized (globalLock) {
            // 安全使用资源a和b
        }
    }
}

4.使用无锁数据结构(破坏互斥条件)

​ 原理:通过CAS(Compare-And-Swap)实现无锁编程。

​ 使用场景:计算器、状态标志等简单操作。

​ 优点:高性能,无死锁风险。

private AtomicInteger counter = new AtomicInteger(0);

public void safeIncrement() {
    counter.incrementAndGet(); // 原子操作,无需锁
}

5.减少锁粒度(降低死锁概率)

​ 原理:缩小同步代码块范围,缩短锁持有时间。

public void process() {
    readFile(); // 无锁操作
    synchronized (this) {
        updateDB(); // 最小化锁范围
    }
}

6.设计模式规避

​ 银行家算法:预分配资源前检查系统安全性(适合资源调度系统)。。

​ 哲学家就餐问题解决方案: 限制最多 n-1 人同时拿筷子。仅当左右筷子都可用时才拿起。

// 使用Semaphore限制并发拿筷子的哲学家数量
Semaphore table = new Semaphore(4); // 5个哲学家最多4人同时拿

public void takeForks() throws InterruptedException {
    table.acquire();
    synchronized (leftFork) {
        synchronized (rightFork) {
            eat();
        }
    }
    table.release();
}

23.synchronized 和 lock 的区别

synchronized:

​ 特点:

​ 自动加锁和释放锁(进入代码块加锁,退出时释放锁)。

​ 不可中断(线程必须等待锁释放)。

public class SynchronizedExample {
    private int count = 0;
    
    public synchronized void increment() {  // 方法同步
        count++;
    }
    
    public void doSomething() {
        synchronized (this) {  // 代码块同步
            count--;
        }
    }
}

Lock:

​ 特点:

​ 必须手动释放锁(否则可能导致死锁)。

​ 支持尝试获取锁(tryLock())

​ 可中断(lockInterruptibly)

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockExample {
    private final Lock lock = new ReentrantLock();
    private int count = 0;
    
    public void increment() {
        lock.lock();  // 手动加锁
        try {
            count++;
        } finally {
            lock.unlock();  // 必须手动释放锁
        }
    }
    
    public void tryLockExample() {
        if (lock.tryLock()) {  // 尝试获取锁(非阻塞)
            try {
                count--;
            } finally {
                lock.unlock();
            }
        } else {
            System.out.println("获取锁失败");
        }
    }
}
对比项 synchronized (Java 内置锁) Lock (如 ReentrantLock)
锁的获取方式 自动获取和释放(进入同步代码块获取,退出时释放) 手动获取和释放(必须显式调用 lock()unlock()
锁的可中断性 不可中断(线程阻塞后只能等待) 可中断lockInterruptibly() 可响应中断)
公平锁支持 非公平锁(默认) 可配置公平锁new ReentrantLock(true)
条件变量 wait()/notify()(依赖 Object 监视器) 支持多个条件变量Condition 接口,更灵活)
性能 JVM 优化,适用于低竞争场景 高竞争场景下性能更好(如 ReentrantLock 的 CAS 机制)
锁的尝试获取 不支持(必须阻塞等待) 支持tryLock() 可尝试获取锁,避免死锁)
适用场景 简单的同步控制(如单方法同步) 复杂同步需求(如超时、可中断、公平锁等)

24.java的八大基本数据类型,分别占多少字节?

数据类型 关键字 字节数 取值范围 默认值 包装类
字节型 byte 1字节 -128 ~ 127 0 Byte
短整型 short 2字节 -32,768 ~ 32,767 0 Short
整型 int 4字节 -2³¹ ~ 2³¹-1 (-2,147,483,648 ~ 2,147,483,647) 0 Integer
长整型 long 8字节 -2⁶³ ~ 2⁶³-1 0L Long
单精度浮点型 float 4字节 ±3.4E+38 (约6-7位有效数字) 0.0f Float
双精度浮点型 double 8字节 ±1.7E+308 (约15位有效数字) 0.0d Double
字符型 char 2字节 ‘\u0000’ ~ ‘\uffff’ (0 ~ 65,535) ‘\u0000’ Character
布尔型 boolean 未明确定义(通常按1字节处理) true / false false Boolean

关键细节:

​ 1.boolean的特殊性

​ JVM规范未严格定义 boolean 的大小,不同JVM实现可能不同:

​ 编译时:通常用int(4字节)或 byte(1字节)表示

​ 数组时:通常每个元素占1字节。

​ 实际开发中按1字节估算即可。

​ 2.浮点数经度问题

float f = 0.1f + 0.2f;  // 结果可能是 0.30000004
double d = 0.1 + 0.2;   // 结果可能是 0.30000000000000004

​ 3.类型转换规则

​ 小类型 -> 大类型:自动转换(如 byte -> int )

​ 大类型 -> 小类型: 需强制转换(可能丢失精度)

int i = 100;
byte b = (byte)i;  // 必须显式强制转换

25.JDK8有哪些新特性?

1.语言核心增强

​ 1.Lambda表达式

​ 作用:简化匿名内部类,实现函数式编程。

// 旧写法
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
}).start();

// Lambda 写法
new Thread(() -> System.out.println("Hello")).start();

​ 2.函数式接口(Functional Interface)

​ 定义:只有一个抽象方法的接口

​ 注解:@FunctionalInterface

​ 内置四大核心接口:

接口 方法 用途
Consumer<T> void accept(T) 消费型(接收参数处理)
Supplier<T> T get() 供给型(生成数据)
Function<T,R> R apply(T) 函数型(转换数据)
Predicate<T> boolean test(T) 断言型(条件判断)

​ 3.方法引用(Method Reference)

// 1. 静态方法引用
Function<String, Integer> parser = Integer::parseInt;

// 2. 实例方法引用
Consumer<String> printer = System.out::println;

// 3. 对象方法引用
Function<String, Integer> length = String::length;

// 4. 构造器引用
Supplier<List<String>> listSupplier = ArrayList::new;

2.集合框架增强

​ 1.Stream API

List<String> names = Arrays.asList("Tom", "Jerry", "Bob");

// 中间操作
names.stream()
     .filter(name -> name.length() > 3)
     .map(String::toUpperCase)
     .sorted()
     // 终止操作
     .forEach(System.out::println);

​ 2.默认方法(Default Methods)

​ 接口中可定义实现方法:

interface Vehicle {
    default void print() {
        System.out.println("我是一辆车");
    }
}

​ 3.新日期时间API(java.time)

用途
LocalDate 日期(年月日)
LocalTime 时间(时分秒)
LocalDateTime 日期+时间
Instant 时间戳(机器时间)
Duration/Period 时间间隔
LocalDate today = LocalDate.now();
LocalDate birthday = LocalDate.of(1990, Month.JANUARY, 1);
long days = ChronoUnit.DAYS.between(birthday, today);

3.JVM与工具增强

原空间(Metaspace)

​ 取代永久代(PermGen):

​ 使用本地内存,避免 OutOfMemoryError:PerGen space

​ 默认无大小限制 (可通过 -XX: MetaspaceSize 调整)

4.其他重要特性

​ 1.Optional 类

​ 解决 NPE 问题:

Optional<String> opt = Optional.ofNullable(getName());
String name = opt.orElse("default");

​ 2.重复注解

​ 同意注解多次使用:

@Target(ElementType.TYPE)
@Repeatable(Filters.class)
public @interface Filter {
    String value();
}

@Filter("filter1")
@Filter("filter2")
public class MyClass {}

​ 3**.类型注解**

​ 注解可用在任何类型前:

List<@NonNull String> list = new ArrayList<>();

26.Stream流中的常用方法

Stream API 是 JDK8 引入的函数式数据处理工具,提供了丰富的链式操作方法,主要分为中间操作和终端操作两大类。

一、中间操作(Intermediate Operations)

中间操作放回新的 Stream,支持链式调用(惰性求值,遇到终止操作才执行)。

​ 1.过滤与去重

方法 说明 示例
filter(Predicate) 过滤元素 stream.filter(s -> s.length() > 3)
distinct() 去重(依赖equals() stream.distinct()
limit(long) 限制元素数量 stream.limit(5)
skip(long) 跳过前N个元素 stream.skip(2)

​ 2.映射转换

方法 说明 示例
map(Function) 元素转换 stream.map(String::toUpperCase)
flatMap(Function) 扁平化转换(合并流) stream.flatMap(list -> list.stream())
mapToInt/Double/Long() 转为数值流 stream.mapToInt(Integer::parseInt)

​ 3.排序与窥视

方法 说明 示例
sorted() 自然排序 stream.sorted()
sorted(Comparator) 自定义排序 stream.sorted(Comparator.reverseOrder())
peek(Consumer) 窥视元素(调试用) stream.peek(System.out::println)

二、终止操作(Terminal Operations)

终止操作会触发实际计算,返回非 Stream 结果。

​ 1.遍历与匹配

方法 说明 示例
forEach(Consumer) 遍历元素 stream.forEach(System.out::println)
allMatch(Predicate) 所有元素匹配 stream.allMatch(s -> s.startsWith("A"))
anyMatch(Predicate) 任一元素匹配 stream.anyMatch(s -> s.contains("java"))
noneMatch(Predicate) 无元素匹配 stream.noneMatch(s -> s.isEmpty())
findFirst() 返回第一个元素 stream.findFirst()
findAny() 返回任意元素(并行流适用) stream.findAny()

​ 2.归约与收集

方法 说明 示例
reduce(BinaryOperator) 归约计算 stream.reduce((a, b) -> a + b)
collect(Collector) 转换为集合/其他结构 stream.collect(Collectors.toList())
count() 统计元素数量 stream.count()
min(Comparator) 返回最小值 stream.min(Integer::compare)
max(Comparator) 返回最大值 stream.max(String::length)

三、并行流(Parallel Stream)

通过 parallel() 或直接创建并行流,利用多核加速处理。

List<String> result = list.parallelStream()
    .filter(s -> s.length() > 3)
    .map(String::toUpperCase)
    .collect(Collectors.toList());

注意事项:

​ 1.确保操作是线程安全的。

​ 2.数据量小可能反而更慢(线程切换开销)

​ 3.避免在并行流中使用有状态操作(如sorted())

27.什么是自动装箱和自动拆箱?

1.基本概念

术语 定义 示例
自动装箱 基本数据类型自动转换为对应的包装类对象 Integer i = 10; 等价于 Integer i = Integer.valueOf(10);
自动拆箱 包装类对象自动转换为对应的基本数据类型 int num = i; 等价于 int num = i.intValue();

2.发生场景*

​ (1) 自动装箱场景

// 赋值操作
Integer a = 100;    // 基本类型 → 包装类

// 方法参数传递
List<Integer> list = new ArrayList<>();
list.add(1);       // 基本类型 → 包装类

// 三元运算符
boolean flag = true;
Integer result = flag ? 1 : 0;  // 两边都会装箱

​ (2)自动拆箱场景

// 赋值操作
Integer b = Integer.valueOf(200);
int num = b;       // 包装类 → 基本类型

// 运算操作
Integer x = 10, y = 20;
int sum = x + y;   // 先拆箱再计算

// 比较操作
if (x > 5) {       // 拆箱后比较
    System.out.println("大于5");
}

3.注意事项

​ (1) 性能问题

​ 频繁装箱/拆箱会导致额外对象创建(如循环内操作)

// 低效写法(每次循环都装箱)
Long sum = 0L;
for (long i = 0; i < 10000; i++) {
    sum += i;  // 隐含拆箱和重新装箱
}

​ (2) NPE风险

​ 拆箱时包装类为null会抛NPE:

Integer nullInt = null;
int num = nullInt;  // 抛出 NullPointerException

​ (3) 缓存机制

​ 部分包装类缓存常用值(如 Integer缓存 -128~127)

Integer a = 127, b = 127;
System.out.println(a == b);  // true (使用缓存对象)

Integer c = 128, d = 128;
System.out.println(c == d);  // false (新建对象)

28.java中创建对象的方式有哪些?

1.使用new关键字(最常用)

​ 特点:

​ 直接触发类加载(如果类未加载)

​ 调用构造函数初始化对象

​ 对象分配在堆内存中

// 调用构造方法创建对象
Person person = new Person("张三", 25);

2.反射机制(Class.newInstance() 或 Constructor.newInstance())

​ 特点:

​ 动态创建对象(框架常用,如 Spring IOC)

​ 可突破 private 构造方法的限制(通过setAccessible(true))

​ 性能比 new 关键字差

// 方式1:Class.newInstance()(JDK9已废弃)
Person p1 = Person.class.newInstance();

// 方式2:Constructor.newInstance()(推荐)
Constructor<Person> constructor = Person.class.getConstructor(String.class, int.class);
Person p2 = constructor.newInstance("李四", 30);

3.克隆(Cloneable) 接口

​ 特点:

​ 浅拷贝(对象内部的引用类型成员仍指向原对象)

​ 需实现 Cloneable 接口(否则抛 CloneNotSupportedException)

​ 不调用构造方法

class Person implements Cloneable {
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

Person original = new Person("王五", 35);
Person cloned = (Person) original.clone();

4.反序列化(ObjectInputStream)

​ 特点:

​ 对象必须实现 Serializable 接口

​ 不调用构造方法

​ 常用于网络传输或持久化存储

// 序列化对象到文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.bin"));
oos.writeObject(new Person("赵六", 40));
oos.close();

// 反序列化创建对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.bin"));
Person deserialized = (Person) ois.readObject();

5.工厂模式/静态工作方法

​ 特点:

​ 封装对象创建逻辑

​ 可返回子类对象(隐藏实现细节)

​ 例如:Integer.valueOf()就是静态工厂方法

// 工厂类
class PersonFactory {
    public static Person createPerson(String name, int age) {
        return new Person(name, age);
    }
}

// 使用工厂方法创建对象
Person p = PersonFactory.createPerson("田七", 45);
方式 是否调用构造方法 适用场景 性能
new 关键字 常规对象创建 ⚡️ 最快
反射 动态加载(如框架) ⏳ 较慢
克隆 复制已有对象 ⚡️ 快
反序列化 网络传输/持久化 ⏳ 慢
工厂方法 复杂对象创建 ⚡️ 快

29.ArrayList的扩容机制

1.核心扩容原理

ArrayList 基于动态数据实现,当容量不足时会自动扩容,关键参数:

​ 默认初始容量:10(无参构造时)

​ 扩容公式:新容量 = 旧容量 + 旧容量 >> 1(及1.5倍)

​ 最大容量:Integer.MAX_VALUE - 8 (部分JVM需要数组头信息)

2.扩容流程

​ 步骤说明:

​ 1.检查容量:当调用add() 方法时,先检查当前元素数量 size + 1 是否超过数组长度 elementData.length

​ 2.触发扩容:如果容量不足,调用 grow() 方法扩容。

​ 3.数据迁移:使用 Arrays.copyOf() 创建新数组并复制原数据

​ 示例:

ArrayList<Integer> list = new ArrayList<>(); // 初始容量=10
for (int i = 0; i < 15; i++) {
    list.add(i); 
    // 第11次add时触发扩容:10 -> 15
    // 第16次add时再次扩容:15 -> 22
}

3.性能优化建议

​ 1.预分配容量:如果已知数据量,构造时指定初始容量避免多次扩容。

​ 2.避免频繁扩容:批量添加数据时使用 addAll() 比 循环 add() 更高效。

​ 3.空间换时间:对大集合考虑使用 LinkedList 或 手动管理数组

30.说说常见的设计模式

设计模式是软件设计中针对常见问题的可重用解决方案。

一、创建型模式(5种)

​ 1.单例模式(Singleton)

​ 作用:确保一个类只有一个实例,并提供全局访问点

​ 应用场景:配置管理、线程池、数据库连接池

​ 2.工厂方法模式(Factory Method)

​ 作用:定义创建对象的接口,让子类决定实例化哪个类

​ 应用场景:日志记录器、数据库访问

​ 3.抽象工厂模式(Abstract Factory)

​ 作用:创建相关或依赖对象的家族,而不需指定具体类

​ 特点:比工厂方法模式更高层次的抽象

​ 4.建造者模式(Builder)

​ 作用:将一个复杂对象的构建与其表示分离

​ 应用场景:创建复杂对象(如XML解析器)

​ 5.原型模式(Prototype)

​ 作用:通过复制现有实例来创建新对象

​ 关键方法:clone()

二、解构型模式(7种)

​ 6.适配器模式(Adapter)

​ 作用:将不兼容接口转换为可兼容接口

​ 类型:

​ 类适配器(继承)

​ 对象适配器(组合)

​ 7.桥接模式(Bridge)

​ 作用:将抽象与实现分离,使它们可以独立变化

​ 8.组合模式(Composite)

​ 作用:将对象组合成树形结构以表示“部分-整体”层次结构

​ 9.装饰器模式(Decorator)

​ 作用:动态地给对象添加额外职责

​ 10.外观模式(Facade)

​ 作用:为子系统提供统一的高层接口

​ 11.享元模式(Flyweight)

​ 作用:通过共享技术有效支持大量细粒度对象

​ 关键:区分内部状态和外部状态

​ 12.代理模式(Proxy)

​ 类型:远程代理、虚拟代理、保护代理、智能引用代理。

三、行为型模式(11种)

​ 13.责任链模式(Chain of Responsibility)

​ 作用:让多个对象都有机会处理请求

​ 14.命令模式(Command)

​ 应用场景:事务处理、任务队列

​ 15.解释器模式(Interpreter)

​ 作用:定义语言的文法表示、并解释执行

​ 16.迭代器模式(Iterator)

​ 作用:定义语言的文法表示,并解释执行

​ 17.中介者模式(Mediator)

​ 作用:用一个中介对象封装一系列对象交互

​ 18.备忘录模式(Memento)

​ 作用:捕获对象内部状态并在之后恢复

​ 19.观察者模式(Observer)

​ 应用场景:事件处理系统

​ 20.状态模式(State)

​ 作用:允许对象在内部状态改变时改变行为

​ 21.策略模式(Strategy)

​ 作用:定义算法族,使它们可以互相替换。

​ 22.模板方式模式(Template Method)

​ 作用:定义算法骨架,将某些步骤延迟到子类

​ 23.访问者模式(Visitor)

​ 作用:在不修改类的前提下为类添加新操作


网站公告

今日签到

点亮在社区的每一天
去签到