Java知识

值传递和引用传递

  • 实际上只有值传递,传递值的副本
  • 引用传递是传递地址作为值的副本,所以修改成员会影响原来的对象
  • 基本类型值传递;引用类型(对象,数组等)引用传递

int 和 Integer

int 是基本数据类型,直接计算
Integer 是 Integer 对象,用各种方法
Integer.parseInt(String s)将字符串转为 int(基本类型)
Integer.max(int a, int b)返回较大值
Integer.toString(a)转换成字符串

  • List等泛型集合里面只能放引用类型(对象类型),不能放基本类型
  • 因为 Java 泛型编译后会变成 Object 类型,而 Object 不能接受基本类型
  • 历史原因,Java 设计之初严格区分基本类型和引用类型,泛型是后期才引入的,为了兼容已有的类型系统,选择只支持引用类型

解决办法:用基本类型的包装类就行(Integer,Character, Boolean,Double)

1
2
3
4
5
6
7
8
9
10
int a = 10;         // 基本类型,值直接是 10
Integer b = 10; // 自动装箱(推荐写法)

String s1 = Integer.toString(a); // 转换成字符串
String s2 = b.toString();

Integer i = 100; // 自动装箱:int → Integer
int j = i; // 自动拆箱:Integer → int

Integer.valueOf(100) //返回一个表示数值 100 的 Integer对象

Java 类中的静态变量和静态方法

在 Java 中,静态变量和静态方法是与类本身关联的
而不是与类的实例关联。它们在内存中只存在一份,可以被类的所有实例共享

Java 里面的引用类型

Java 的所有非基本类型都是 引用类型(对象通过引用访问)
Collection 是 List,Set,Queue 等接口的根接口;Map 是 HashMap 和 TreeMap 等类实现的根接口

Java 类型 C++ 对应类型 JAVA 功能描述
String std::string 不可变对象(immutable),通过引用操作
int[] 原生数组或std::vector 数组是对象,带长度属性(arr.length),自动内存管理
List<T> (如 ArrayList) std::vector<T> List是接口,ArrayList是动态数组实现
Map<K,V> (如 HashMap) std::unordered_map<K,V> HashMap基于哈希表,无序
Set<T> (如 HashSet) std::unordered_set<T> HashSet基于哈希表,无序且唯一
自定义类(class Person class Person 对象通过引用访问(类似智能指针)

Java 引用类型的使用
字符串:String name = "Alice";
动态数组:List<Integer> numbers = new ArrayList<>();
哈希表:Map<String, Integer> map = new HashMap<>();
自定义类:Person person = new Person("Alice", 18);

集合的遍历方式

  1. for 循环

  2. 增强 for 循环(for-each 循环)
    for (int i : numbers) {}
    for (var i : numbers) {}// 也可以用 var 相当于 C++的 auto

  3. Iterator 迭代器

    1
    2
    3
    4
    Iterator<Integer> iterator = numbers.iterator();
    while (iterator.hasNext()) {
    int i = iterator.next();
    }
  4. ListIterator 列表迭代器
    是迭代器的子类,可以双向访问列表,在迭代中修改元素

  5. forEach 方法
    list.forEach(element -> System.out.println(element));

  6. Stream API

    1
    2
    3
    list.stream()
    .filter(s -> s.length() > 5)
    .forEach(System.out::println);

接口的使用

面向接口编程
先定义接口,然后实现接口,调用的时候使用接口接收对象
接口可以实现 java 的多态,接口不是“父类”,但你可以用接口类型的变量来引用实现了该接口的类的对象。这是 Java 多态的一种表现形式。

因为 Java 支持 “向上转型”(Upcasting):
当一个类 实现了某个接口,那么它的对象就可以被 自动视为该接口类型的实例。

接口本身不能被实例化(不能 new 接口),
但实现了该接口的类的对象,可以被当作该接口类型的引用使用。

List<String> list = new ArrayList<>();这里发生了什么?
new ArrayList<>() 创建了一个 具体的对象(实例)。
这个对象的实际类型是 ArrayList。
但由于 ArrayList implements List,Java 允许你用 List 类型的引用去“看”这个对象。
这叫 向上转型(Upcasting) —— 对象从“具体类型”被视作“更通用的接口类型”。

Spring 里面依赖注入的时候就发生了向上转型,所以能调用实现类的方法
首先 Spring 扫描到 @Service,创建一个 UserServiceImpl 的实例。
把这个实例注册为 UserService 类型的 Bean(因为 UserServiceImpl implements UserService)。

@Autowired
private UserService userService; // ← 接口类型!

// Spring 自动给字段赋值
this.userService = applicationContext.getBean(UserService.class);
// 内部等价于:
UserService userService = new UserServiceImpl(); // ← 多态:接口引用指向实现类对象

1. 提高代码灵活性

1
2
3
4
5
// 好的做法:使用接口类型声明变量
List<String> list = new ArrayList<>();

// 需要更换实现时,只需修改一处
List<String> list = new LinkedList<>(); // 只需更改这行代码

相比于直接使用具体实现类:

1
2
// 不推荐的做法
ArrayList<String> list = new ArrayList<>();

如果后续需要更换实现,所有使用该变量的地方都可能需要调整。

2. 符合设计原则

  • 里氏替换原则:任何基类(接口)出现的地方,子类(实现类)都可以替换
  • 依赖倒置原则:高层模块(业务代码)不依赖低层模块(具体实现),都依赖于抽象(接口)

3. 促进团队协作与测试

  • 团队协作:前端开发只需查看接口文档了解可用方法,无需等待后端具体实现完成即可开始编写调用逻辑
  • 单元测试:可以轻松使用”模拟对象(Mock)”替代真实的数据库操作等复杂依赖,而 Mock 通常基于接口实现

接口复用代码

Java 只有单继承(extends ),但可以实现(implements) 多个接口

从 Java 8 开始接口可以有:

  • 默认方法(default):提供默认实现
  • 静态方法(static):工具方法
  • 私有方法(private):默认辅助方法

用接口复用代码的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
interface Flyable {
// 默认实现!子类不用重写也可以直接用
default void takeOff() {
System.out.println("Taking off...");
}

// 抽象方法(必须实现)
void fly();
}

class Bird implements Flyable {
@Override
public void fly() {
System.out.println("Bird is flapping wings.");
}
// 注意:takeOff()自动继承!
}

public class Main {
public static void main(String[] args) {
Bird bird = new Bird();
bird.takeOff(); // 直接调用接口的默认方法!
bird.fly();
}
}

lambda 表达式

Java 8 引入了 lambda 表达式
简化了匿名内部类的写法
(a, b) -> a + b
(a, b) -> {a + b; return a + b;}// 多条语句要用{}

stream 的 API

适合集合对象的操作,如过滤,映射,排序,聚合等

问题场景:从一个列表中筛选出所有长度大于 3 的字符串,并收集到一个新的列表中。
没有 Stream 啲做法:

1
2
3
4
5
6
7
8
List<String> originalList = Arrays.asList("apple", "banana", "cherry", "date", "elderberry");
List<String> filteredList = new ArrayList<>();

for (String s : originalList) {
if (s.length() > 3) {
filteredList.add(s);
}
}

使用 Stream API 的做法:

1
2
3
4
List<String> originalList = Arrays.asList("apple", "banana", "cherry", "date", "elderberry");
List<String> filteredList = originalList.stream()
.filter(s -> s.length() > 3)
.collect(Collectors.toList());
  1. originalList.stream():创建一个流,该流包含原始列表中的所有元素。
  2. .filter(s -> s.length() > 3):对流进行过滤,只保留长度大于 3 的字符串。
  3. .collect(Collectors.toList()):将过滤后的结果收集到一个新的列表中。

其他案例:字符串统一排序

  1. map(s -> s.toUpperCase()):将每个字符串转换为大写。
  2. .sorted():对字符串进行排序。
  3. .collect(Collectors.joining(", ")):将排序后的字符串连接成一个字符串,并使用逗号(,)作为分隔符。

计算数字列表的和

  1. numbers.stream():创建一个流,该流包含原始列表中的所有数字。
  2. .mapToInt(Integer::intValue):将数字映射为 int 类型。
  3. .sum():计算数字的和。

cpu 密集的运算用 Stream 并行
I/O 密集的运算用 Stream 串行

终端操作

终端操作是流管道(stream pipeline)中的最后一个操作,是触发流管道执行的操作。
一旦调用,流就被 消费(consumed),不能再被使用。
与中间操作(如 filter, map)不同,终端操作 不返回 Stream,而是返回

所有 Java stream API 的终端操作

类别 方法 功能说明 返回类型 是否短路
遍历 / 副作用 forEach(action) 对每个元素执行操作(并行流中无序) void
forEachOrdered(action) 按照流的原始顺序执行操作(即使并行流也保序) void
收集结果 collect(Collectors.toList())
(典型用法)
将流元素收集到 List(也可 toSet(), joining() 等) R(如 List<T>
toArray() 转为 Object[] 数组 Object[]
toArray(String[]::new)
(示例)
转为指定类型数组(如 String[] T[]
归约 reduce(初始值, 累加器) 从初始值开始,依次合并元素(如字符串拼接、求和) T
reduce(累加器) 无初始值归约,流为空时返回 Optional.empty() Optional<T>
查找与匹配 findFirst() 返回第一个元素(适合有序流) Optional<T> ✅ 是
findAny() 返回任意一个元素(适合并行流,更快) Optional<T> ✅ 是
anyMatch(pred) 是否存在至少一个元素满足条件 boolean ✅ 是
allMatch(pred) 是否所有元素都满足条件 boolean ✅ 是(遇到 false 即停)
noneMatch(pred) 是否没有元素满足条件 boolean ✅ 是(遇到 true 即停)
min(comparator) 返回最小元素(按比较器) Optional<T>
max(comparator) 返回最大元素 Optional<T>
计数 count() 返回流中元素总数 long

中间操作

类别 方法 功能说明 返回类型
过滤 filter(Predicate<? super T> predicate) 保留满足条件的元素 Stream<T>
映射 / 转换 map(Function<? super T, ? extends R> mapper) 将每个元素转换为另一种类型 Stream<R>
flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) 将每个元素映射为一个流,然后扁平化合并为一个流 Stream<R>
mapToInt(ToIntFunction<? super T> mapper) 转为 IntStream(用于基本类型优化) IntStream
mapToLong(...) / mapToDouble(...) 类似,分别转为 LongStream / DoubleStream LongStream / DoubleStream
排序 sorted() 按自然顺序排序(要求元素实现 Comparable Stream<T>
sorted(Comparator<? super T> comparator) 按指定比较器排序 Stream<T>
去重 distinct() 去除重复元素(基于 equals() Stream<T>
截取 limit(long maxSize) 保留前 maxSize 个元素 Stream<T>
skip(long n) 跳过前 n 个元素 Stream<T>
查看 / 调试 peek(Consumer<? super T> action) 对每个元素执行操作(常用于调试),不影响流本身 Stream<T>
并行 / 串行控制 parallel() 将流转换为并行流 Stream<T>
sequential() 将流转换为顺序流 Stream<T>
unordered() 忽略流的顺序性(可能提升并行性能) Stream<T>

责任链模式

一个请求需要多个处理逻辑时的灵活做法
比如各种校验逻辑

先定义一个抽象处理者类

1
2
3
4
5
6
7
8
9
10
// 抽象处理者类
abstract class Handler {
protected Handler next;
// 设置下一个处理节点
public void setNext(Handler next) {
this.next = next;
}
// 抽象处理方法
public abstract boolean handle(Request request);
}

然后每个校验逻辑都继承这个类,比如登录逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 登录态校验节点
class LoginHandler extends Handler {
@Override
public boolean handle(Request request) {
if (request.isLogin()){
System.out.println("登录态校验通过,交给下一个节点");
// 交给下一个节点处理
return next != null ? next.handle(request) : true;
} else {
System.out.println("未登录,返回失败");
// 校验不通过,终止链路
return false;
}
}
}

然后再写其他节点,最后根据需要动态添加节点

1
2
3
4
5
6
7
8
9
10
// 组装链路:登录态校验 -> 权限校验 -> 频率校验
Handler loginHandler = new LoginHandler();
Handler authHandler = new AuthHandler();
Handler rateLimitHandler = new RateLimitHandler();
loginHandler.setNext(authHandler);
authHandler.setNext(rateLimitHandler);

// 创建请求
Request request = new Request(true, "admin", 1);// 已登录,管理员权限,第一次请求
boolean result = loginHandler.handle(request);

这样一来,发起方只需要调用第一个节点,不用关心后面有多少校验步骤
如果某个接口不需要某个校验就直接去掉这个节点就行,非常灵活

List 实现线程安全的方式

不同步的后果
如果两个线程同时执行到第 1 步,它们会读到相同的值(比如都是 100)。然后各自加 1,最后都写回 101。结果应该是 102,但因为两个线程覆盖了彼此的写入,最终只增加了 1。

synchronized 同步方式
使用 synchronized 关键字修饰方法
读写都会加锁,适合读写均衡的场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class SynchronizedCounter {
private int count = 0;

// 使用 synchronized 关键字,保证线程安全
public synchronized void increment() {
count++; // 现在是原子操作
}

public synchronized int getCount() {
return count;
}
}

// 测试类(只需替换Counter的类型)
public class SynchronizedDemo {
public static void main(String[] args) throws InterruptedException {
SynchronizedCounter counter = new SynchronizedCounter(); // 只改这里
Thread[] threads = new Thread[10];

for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.increment();
}
});
threads[i].start();
}

for (Thread thread : threads) {
thread.join();
}

System.out.println("最终计数: " + counter.getCount()); // 结果永远是 10000
}
}

CopyOnWrite 同步方式
读无锁,写会复制新的数组,适合读多写少的场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.concurrent.locks.ReentrantLock;

public class CopyOnWriteCounter {
private volatile int[] countArray = {0}; // 用数组包装计数器,volatile 保证可见性
private final ReentrantLock lock = new ReentrantLock(); // 写操作专用锁

// 写操作:每次 increment 都复制数组
public void increment() {
lock.lock(); // 加锁,确保写操作互斥
try {
int[] oldArray = countArray;
int[] newArray = new int[oldArray.length]; // 复制数组
newArray[0] = oldArray[0] + 1; // 修改新数组
countArray = newArray; // 原子性地替换数组引用
} finally {
lock.unlock(); // 解锁
}
}

// 读操作:无锁,直接读取当前数组值
public int getCount() {
return countArray[0];
}
}

Map 的遍历

for-each 循环和 entrySet()方法
for (Map.Entry<K, V> entry : map.entrySet()) { entry.getKey(); entry.getValue(); }
for-each 循环和 keySet()方法
for (String key : map.keySet()) { key; map.get(key); }
用迭代器遍历

1
2
3
4
5
Iterator<Entry<K, V>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Entry<K, V> entry = iterator.next();
entry.getKey(); entry.getValue();
}

用 lambda 表达式和 forEach()方法遍历
map.forEach((key, value) -> { key; value; });
使用 stream 流,还可以进行过滤和映射等
map.entrySet().stream().forEach(entry -> { entry.getKey(); entry.getValue(); });

Map 实现线程安全

  • HashMap 线程不安全,效率高一点
  • ConcurrentHashMap 是 java 中一个线程安全的哈希表实现类,适合多线程并发地读写操作,实现原理是分段锁和 CAS(Compare And Swap 比较并替换),将整个哈希表分成了多个 Segment(段),每段都有自己独立的锁,读不需要锁,写锁定对应的 Segment 大大提高并发性能
  • HashTable 基本被淘汰了,他线程安全,通过将内部方法都加 synchronized 关键字,读写都加锁,同一时刻只能有一个线程访问,效率较低

Java 的线程安全

  • volatile 关键字修饰成员变量,静态变量,可以确保变量可见性
    即当一个线程修改了 volatile 变量的值,其他线程能够立即看到这个修改
  • synchronized 关键字修饰实例方法,静态方法,代码块,可以确保原子性和有序性
    提供同步机制,确保同一时刻只有一个线程能执行被 synchronized 修饰的代码块或方法,从而保证操作的原子性和有序性。

concurrent 是并发的意思
juc 就是 java.util.concurrent 包

线程池相关

  • ThreadPoolExecutor: 最核心的线程池类
  • Executors: 线程池工厂类

并发集合类

  • ConcurrentHashMap: 线程安全的哈希表,采用分段锁机制
  • CopyOnWriteArrayList: 线程安全的列表,适合读多写少场景

同步工具类

  • CountDownLatch: 倒计时锁存器,用于等待多个线程完成各自任务后再继续执行
  • CyclicBarrier: 循环屏障,让一组线程互相等待直到都到达屏障点,可重复使用
  • Semaphore: 信号量,用于控制同时访问特定资源的线程数量

原子类

  • AtomicInteger: 原子整数类,提供对整数类型的原子操作
  • AtomicReference: 原子引用类,提供对对象引用进行原子操作

Thread.sleep() vs Object.wait()区别

特性 Thread.sleep() Object.wait()
所属类 Thread 类(静态方法) Object 类(实例方法)
锁释放 不释放锁 会释放锁
使用前提 任意位置调用 必须在同步块内(持有锁)
唤醒机制 超时自动恢复 notify() / notifyAll() 或超时
设计用途 暂停线程执行,不涉及锁协作 线程间协调,释放锁让其他线程工作

notify()是唤醒一个进程,被唤醒的进程如果结束时没调用 notify()方法,那其他线程就没人唤醒了
notifyAll()是唤醒所有进程,然后他们竞争一个锁,一个幸运儿获得锁

synchronized 简单易用,自动加锁释放锁
ReentrantLock 更复杂,有很多高级功能

CopyOnWriteArrayList

这是一个线程安全的 ArrayList 实现,适用于读多写少的场景
这个原理是:读不加锁,写的时候加锁,写会复制一个新的数组,修改新数组
所以其他线程读的时候,要么是读新的快照,要么是旧的快照(都是线程安全的)
不会出现读到撕裂值和中间状态

最终一致性
不要求“立刻看到最新数据”,但保证“迟早会看到”。
只有允许最终一致的场景,才适合使用 CopyOnWrite(COW)这类机制。

count++ 是读-改-写的复合操作,需要原子性
int temp = count; // 读
temp = temp + 1; // 改
count = temp; // 写
count 这种是要求读最新数据的所以 CopyOnWrite 不适用

一个 COW 的应用场景:事件监听器列表(Event Listener List)
场景描述:
在很多框架中(如 GUI 编程、Spring 事件机制、Netty、日志系统等),都有这样的需求:

  • 系统运行过程中,偶尔会有组件注册或注销事件监听器(写操作很少);
  • 但频繁会触发事件,需要遍历所有监听器并通知它们(读操作极多);
  • 遍历时不能因为有人动态增删监听器而崩溃(比如抛 ConcurrentModificationException);
  • 允许“本次事件通知不包含刚刚注册的监听器”(即可以接受短暂的旧快照)。

事件监听器就是在这里就就是用户登录的时候会触发的事件,每个用户登录都会读这个,所以读非常多,但是写很少,而且可以允许登录的时候用旧快照,因为旧快照也是合法的,也就是会少触发个事件,大体不影响,以后还是会用新快照(最终一致性)

一些其他应用场景:系统配置缓存,白名单/黑名单

你提供的关于 Java 乐观锁实现方式的描述是正确的,但确实不够规范和清晰。以下是规范化后的表述:

Java 中实现乐观锁的方式

1. CAS (Compare and Swap) 操作

  • CAS 是乐观锁的核心机制

  • Java 提供了java.util.concurrent.atomic包,包含各种原子类(如AtomicIntegerAtomicLong等)

  • 这些原子类利用 CAS 操作实现线程安全的原子操作,是乐观锁的基础实现方式

  • CAS 操作本身是一次性的原子比较和交换,但使用中通常结合循环来实现自旋等待

  • CAS 有 ABA 的问题,就是如果读的时候是 A,修改时还是 A,那 CAS 就会认为变量没被修改过开始更新,但可能 A 是被改成 B 后又被改回 A 了

2. 版本号控制

  • 在数据中增加版本号字段,用于记录数据的更新版本
  • 每次更新数据时递增版本号
  • 更新时比较当前版本号与获取时的版本号,若一致则更新成功,否则更新失败

3. 时间戳机制

  • 使用时间戳记录数据的最后更新时间
  • 更新数据时比较时间戳
  • 若当前时间戳与数据记录的时间戳不一致,则说明数据已被其他线程更新,本次更新失败

这些方式都体现了乐观锁的核心思想:假设数据冲突较少,先进行操作,而在提交时检查是否有冲突发生。


Java知识
http://www.981928.xyz/2025/11/14/Java知识/
作者
981928
发布于
2025年11月14日
许可协议