Java基础知识三
五十、Java 中的并发工具类 CountDownLatch
50.1 概述
CountDownLatch 是 Java 并发包中的一个同步工具类,它允许一个或多个线程等待其他线程完成操作后再继续执行。CountDownLatch 内部维护一个计数器,初始化时指定计数器的值,当计数器的值减为 0 时,等待的线程将被唤醒。
50.2 示例代码
import java.util.concurrent.CountDownLatch;
class Worker implements Runnable {
private final CountDownLatch latch;
public Worker(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " is working.");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " has finished working.");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown(); // 计数器减 1
}
}
}
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int workerCount = 3;
CountDownLatch latch = new CountDownLatch(workerCount);
for (int i = 0; i < workerCount; i++) {
new Thread(new Worker(latch)).start();
}
latch.await(); // 等待所有线程完成
System.out.println("All workers have finished, main thread can continue.");
}
}
五十一、Java 中的 ThreadLocal
51.1 概述
ThreadLocal 是 Java 提供的一个线程局部变量工具类,它为每个使用该变量的线程都提供一个独立的变量副本,每个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。
51.2 示例代码
public class ThreadLocalExample {
private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
int value = threadLocal.get();
threadLocal.set(value + 1);
System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
int value = threadLocal.get();
threadLocal.set(value + 1);
System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
}
});
t1.start();
t2.start();
}
}
五十二、Java 中的异步编程补充:CompletableFuture 组合与异常处理进阶
52.1 CompletableFuture 的组合操作进阶
thenCompose 方法
thenCompose 方法用于将多个 CompletableFuture 串联起来,前一个 CompletableFuture 的结果作为后一个 CompletableFuture 的输入。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureThenComposeExample {
public static CompletableFuture<Integer> asyncTask1() {
return CompletableFuture.supplyAsync(() -> 10);
}
public static CompletableFuture<Integer> asyncTask2(int input) {
return CompletableFuture.supplyAsync(() -> input * 2);
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> combinedFuture = asyncTask1().thenCompose(CompletableFutureThenComposeExample::asyncTask2);
System.out.println(combinedFuture.get());
}
}
52.2 CompletableFuture 的异常处理进阶
handle 方法
handle 方法可以处理 CompletableFuture 执行过程中出现的异常,无论是否出现异常,该方法都会被调用。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureHandleExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
if (Math.random() < 0.5) {
throw new RuntimeException("Something went wrong");
}
return 10;
}).handle((result, ex) -> {
if (ex != null) {
System.out.println("Exception: " + ex.getMessage());
return 0;
}
return result;
});
System.out.println(future.get());
}
}
这些 Java 基础知识进一步深入了内存管理、并发编程等方面,有助于你更全面地理解和运用 Java 语言。
五十三、Java 中的 Fork/Join 框架
53.1 概述
Fork/Join 框架是 Java 7 引入的一个用于并行执行任务的框架,它基于分治算法,将一个大任务拆分成多个小任务,并行执行这些小任务,最后将结果合并。该框架使用 ForkJoinPool 来管理线程池,RecursiveTask 和 RecursiveAction 是实现具体任务的两个抽象类,前者有返回值,后者无返回值。
53.2 示例:使用 RecursiveTask 计算数组元素之和
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
class ArraySumTask extends RecursiveTask<Integer> {
private static final int THRESHOLD = 10;
private final int[] array;
private final int start;
private final int end;
public ArraySumTask(int[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if (end - start <= THRESHOLD) {
int sum = 0;
for (int i = start; i < end; i++) {
sum += array[i];
}
return sum;
} else {
int mid = (start + end) / 2;
ArraySumTask leftTask = new ArraySumTask(array, start, mid);
ArraySumTask rightTask = new ArraySumTask(array, mid, end);
leftTask.fork();
int rightResult = rightTask.compute();
int leftResult = leftTask.join();
return leftResult + rightResult;
}
}
}
public class ForkJoinExample {
public static void main(String[] args) {
int[] array = new int[100];
for (int i = 0; i < 100; i++) {
array[i] = i + 1;
}
ForkJoinPool forkJoinPool = new ForkJoinPool();
ArraySumTask task = new ArraySumTask(array, 0, array.length);
int result = forkJoinPool.invoke(task);
System.out.println("Sum of array elements: " + result);
}
}
五十四、Java 中的 Atomic 类
54.1 概述
Atomic 类位于 java.util.concurrent.atomic 包下,提供了一些可以进行原子操作的类,例如 AtomicInteger、AtomicLong、AtomicBoolean 等。原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何线程上下文切换。使用 Atomic 类可以在不使用锁的情况下实现线程安全的操作。
54.2 示例:使用 AtomicInteger 实现计数器
import java.util.concurrent.atomic.AtomicInteger;
class AtomicCounter {
private final AtomicInteger counter = new AtomicInteger(0);
public void increment() {
counter.incrementAndGet();
}
public int getCount() {
return counter.get();
}
}
public class AtomicExample {
public static void main(String[] args) throws InterruptedException {
AtomicCounter atomicCounter = new AtomicCounter();
int threadCount = 10;
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
atomicCounter.increment();
}
});
threads[i].start();
}
for (Thread thread : threads) {
thread.join();
}
System.out.println("Final count: " + atomicCounter.getCount());
}
}
五十五、Java 中的 CompletableFuture 与 ExecutorService 结合
55.1 概述
CompletableFuture 可以和 ExecutorService 结合使用,通过自定义线程池来执行异步任务,这样可以更好地控制线程资源,避免使用默认线程池可能带来的性能问题。
55.2 示例:使用自定义线程池执行 CompletableFuture 任务
import java.util.concurrent.*;
public class CompletableFutureWithExecutorExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(2);
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "Task completed";
}, executor);
System.out.println(future.get());
executor.shutdown();
}
}
五十六、Java 中的注解元注解
56.1 概述
元注解是用于注解其他注解的注解,Java 提供了几种元注解,用于控制注解的使用范围、生命周期等。常见的元注解有 @Retention、@Target、@Documented、@Inherited 等。
56.2 常见元注解介绍及示例
@Retention
指定注解的保留策略,有 RetentionPolicy.SOURCE(只在源文件中保留,编译时丢弃)、RetentionPolicy.CLASS(在编译后的字节码文件中保留,但运行时不可用)、RetentionPolicy.RUNTIME(在运行时可用)三种取值。
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@interface MyRuntimeAnnotation {
String value();
}
@Target
指定注解可以应用的目标元素类型,例如类、方法、字段等。
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.FIELD})
@interface MyTargetAnnotation {
String value();
}
@Documented
表示该注解会被包含在 JavaDoc 文档中。
import java.lang.annotation.Documented;
@Documented
@interface MyDocumentedAnnotation {
String value();
}
@Inherited
表示该注解可以被继承,如果一个使用了 @Inherited 注解的注解应用在一个类上,那么该类的子类也会继承这个注解。
import java.lang.annotation.Inherited;
@Inherited
@interface MyInheritedAnnotation {
String value();
}
@MyInheritedAnnotation("Test")
class ParentClass {}
class ChildClass extends ParentClass {}
五十七、Java 中的泛型通配符的更多使用场景
57.1 通配符上限和下限结合使用
在某些情况下,需要同时使用通配符的上限和下限来限制泛型类型。例如,在一个方法中,需要接收一个集合,该集合的元素类型必须是某个类的子类,并且可以添加该类或其子类的元素。
import java.util.ArrayList;
import java.util.List;
class Animal {}
class Dog extends Animal {}
class Labrador extends Dog {}
public class WildcardCombinationExample {
public static void addDogs(List<? super Dog> dogList) {
dogList.add(new Dog());
dogList.add(new Labrador());
}
public static void printAnimals(List<? extends Animal> animalList) {
for (Animal animal : animalList) {
System.out.println(animal);
}
}
public static void main(String[] args) {
List<Dog> dogList = new ArrayList<>();
addDogs(dogList);
printAnimals(dogList);
}
}
57.2 通配符在方法参数中的使用
通配符可以使方法更加灵活,能够处理不同类型的泛型集合。
import java.util.ArrayList;
import java.util.List;
public class WildcardInMethodExample {
public static double sum(List<? extends Number> numbers) {
double total = 0;
for (Number num : numbers) {
total += num.doubleValue();
}
return total;
}
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
intList.add(3);
double result = sum(intList);
System.out.println("Sum: " + result);
}
}
这些 Java 基础知识进一步拓展了并发编程、泛型、注解等方面的内容,对于深入理解和运用 Java 语言有很大帮助。
五十八、Java 中的垃圾回收算法
58.1 标记 - 清除算法(Mark - Sweep)
概述
标记 - 清除算法是最基础的垃圾回收算法,分为两个阶段:标记阶段和清除阶段。在标记阶段,垃圾回收器会从根对象开始遍历,标记所有可达的对象;在清除阶段,会清除所有未被标记的对象。
缺点
该算法会产生大量的内存碎片,随着时间的推移,可能会导致无法为大对象分配连续的内存空间。
58.2 标记 - 整理算法(Mark - Compact)
概述
标记 - 整理算法在标记 - 清除算法的基础上进行了改进。同样先进行标记阶段,标记出所有可达对象,然后将这些存活的对象向一端移动,最后清除边界以外的内存空间。
优点
解决了标记 - 清除算法产生内存碎片的问题,使得内存空间更加连续。
58.3 复制算法(Copying)
概述
复制算法将可用内存划分为大小相等的两块,每次只使用其中一块。当这一块内存用完后,就将还存活的对象复制到另一块上,然后把已使用过的内存空间一次清理掉。
优点
实现简单,运行高效,不会产生内存碎片。
缺点
可用内存空间减少为原来的一半。
58.4 分代收集算法(Generational Collection)
概述
分代收集算法是目前大多数 Java 虚拟机采用的垃圾回收算法。它根据对象的存活周期将内存划分为不同的区域,一般分为新生代和老年代。新生代中对象的存活时间较短,采用复制算法进行垃圾回收;老年代中对象的存活时间较长,采用标记 - 清除或标记 - 整理算法进行回收。
五十九、Java 中的垃圾回收器
59.1 Serial 垃圾回收器
概述
Serial 垃圾回收器是最古老的垃圾回收器,它是单线程的,在进行垃圾回收时会暂停所有的用户线程(Stop - The - World)。适用于单 CPU 环境下的小型应用。
启用方式
在 JVM 启动时添加 -XX:+UseSerialGC 参数。
59.2 Parallel 垃圾回收器
概述
Parallel 垃圾回收器也称为吞吐量优先的垃圾回收器,它使用多线程进行垃圾回收,能够充分利用多核 CPU 的优势,提高垃圾回收的效率。同样会产生 Stop - The - World 现象。
启用方式
新生代使用 Parallel Scavenge 回收器,老年代使用 Parallel Old 回收器,可通过 -XX:+UseParallelGC 或 -XX:+UseParallelOldGC 参数启用。
59.3 CMS 垃圾回收器
概述
CMS(Concurrent Mark Sweep)垃圾回收器是一种以获取最短回收停顿时间为目标的回收器,它在大部分时间内可以与用户线程并发执行,减少了 Stop - The - World 的时间。主要应用于对响应时间要求较高的场景。
启用方式
通过 -XX:+UseConcMarkSweepGC 参数启用。
59.4 G1 垃圾回收器
概述
G1(Garbage - First)垃圾回收器是一种面向服务端应用的垃圾回收器,它将整个堆划分为多个大小相等的 Region,跟踪各个 Region 里的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,优先回收垃圾最多的 Region。
启用方式
通过 -XX:+UseG1GC 参数启用。
六十、Java 中的异常链
60.1 概述
异常链是指在捕获一个异常后,抛出另一个新的异常,并将原始异常作为新异常的原因。这样可以保留原始异常的信息,方便后续的调试和问题定位。
60.2 示例代码
class CustomException extends Exception {
public CustomException(String message, Throwable cause) {
super(message, cause);
}
}
public class ExceptionChainExample {
public static void method1() throws Exception {
try {
int result = 1 / 0;
} catch (ArithmeticException e) {
throw new CustomException("An error occurred in method1", e);
}
}
public static void main(String[] args) {
try {
method1();
} catch (Exception e) {
System.out.println("Caught exception: " + e.getMessage());
System.out.println("Original exception: " + e.getCause());
}
}
}
六十一、Java 中的守护线程
61.1 概述
守护线程(Daemon Thread)是一种特殊的线程,它的作用是为其他线程提供服务。当所有的非守护线程结束时,守护线程会自动终止,即使它的任务还没有完成。常见的守护线程如垃圾回收线程。
61.2 示例代码
class MyDaemonThread extends Thread {
@Override
public void run() {
while (true) {
try {
System.out.println("Daemon thread is running...");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class DaemonThreadExample {
public static void main(String[] args) {
MyDaemonThread daemonThread = new MyDaemonThread();
daemonThread.setDaemon(true); // 设置为守护线程
daemonThread.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main thread is exiting.");
}
}
六十二、Java 中的对象克隆
62.1 浅克隆
概述
浅克隆是指创建一个新对象,新对象的属性和原对象相同,但对于引用类型的属性,新对象和原对象引用的是同一个对象。在 Java 中,可以通过实现 Cloneable 接口并重写 clone() 方法来实现浅克隆。
示例代码
class Person implements Cloneable {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class ShallowCloneExample {
public static void main(String[] args) throws CloneNotSupportedException {
Person p1 = new Person("Alice", 20);
Person p2 = (Person) p1.clone();
System.out.println(p2.name + " " + p2.age);
}
}
62.2 深克隆
概述
深克隆是指创建一个新对象,新对象的属性和原对象相同,对于引用类型的属性,也会创建一个新的对象,而不是引用同一个对象。可以通过序列化和反序列化来实现深克隆。
示例代码
import java.io.*;
class Address implements Serializable {
String city;
public Address(String city) {
this.city = city;
}
}
class Employee implements Serializable {
String name;
Address address;
public Employee(String name, Address address) {
this.name = name;
this.address = address;
}
public Employee deepClone() throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Employee) ois.readObject();
}
}
public class DeepCloneExample {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Address address = new Address("New York");
Employee emp1 = new Employee("Bob", address);
Employee emp2 = emp1.deepClone();
System.out.println(emp2.name + " " + emp2.address.city);
}
}
这些内容涵盖了 Java 垃圾回收、异常处理、线程和对象克隆等方面的基础知识,有助于你更深入地理解 Java 语言的底层机制和编程技巧。
六十三、Java 中的同步器 Phaser
63.1 概述
Phaser 是 Java 7 引入的一个可重用的同步屏障,类似于 CyclicBarrier 和 CountDownLatch,但功能更强大。Phaser 可以动态地调整参与线程的数量,并且支持分阶段执行任务。每个阶段称为一个 “相位(Phase)”,当所有注册的线程都到达某个相位的屏障点时,该相位结束,进入下一个相位。
63.2 示例代码
import java.util.concurrent.Phaser;
class Task implements Runnable {
private final Phaser phaser;
public Task(Phaser phaser) {
this.phaser = phaser;
phaser.register(); // 注册当前线程到 Phaser
}
@Override
public void run() {
for (int phase = 0; phase < 3; phase++) {
System.out.println(Thread.currentThread().getName() + " arrived at phase " + phase);
phaser.arriveAndAwaitAdvance(); // 到达当前相位并等待其他线程
}
phaser.arriveAndDeregister(); // 完成所有阶段,注销线程
}
}
public class PhaserExample {
public static void main(String[] args) {
Phaser phaser = new Phaser(1); // 初始注册一个线程
for (int i = 0; i < 3; i++) {
new Thread(new Task(phaser)).start();
}
// 主线程参与 Phaser
for (int phase = 0; phase < 3; phase++) {
System.out.println("Main thread arrived at phase " + phase);
phaser.arriveAndAwaitAdvance();
}
phaser.arriveAndDeregister();
}
}
六十四、Java 中的 ScheduledExecutorService
64.1 概述
ScheduledExecutorService 是 Java 提供的一个用于执行定时任务和周期性任务的接口,它继承自 ExecutorService。通过 ScheduledExecutorService 可以方便地安排任务在未来某个时间点执行,或者按照一定的周期重复执行。
64.2 示例代码
延迟执行任务
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorDelayExample {
public static void main(String[] args) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Runnable task = () -> System.out.println("Task executed after delay.");
executor.schedule(task, 2, TimeUnit.SECONDS); // 延迟 2 秒执行任务
executor.shutdown();
}
}
周期性执行任务
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorPeriodicExample {
public static void main(String[] args) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Runnable task = () -> System.out.println("Periodic task executed.");
executor.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS); // 延迟 1 秒后,每 2 秒执行一次任务
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
executor.shutdown();
}
}
六十五、Java 中的反射调用构造方法
65.1 概述
通过反射可以在运行时动态地调用类的构造方法来创建对象。可以获取类的构造方法对象,然后使用该对象的 newInstance() 方法来创建实例。
65.2 示例代码
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
class MyClass {
private String message;
public MyClass() {
this.message = "Default message";
}
public MyClass(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
public class ReflectionConstructorExample {
public static void main(String[] args) {
try {
// 获取无参构造方法并创建对象
Constructor<MyClass> constructor1 = MyClass.class.getConstructor();
MyClass obj1 = constructor1.newInstance();
System.out.println(obj1.getMessage());
// 获取有参构造方法并创建对象
Constructor<MyClass> constructor2 = MyClass.class.getConstructor(String.class);
MyClass obj2 = constructor2.newInstance("Custom message");
System.out.println(obj2.getMessage());
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
六十六、Java 中的 try-with-resources 语句的深入理解
66.1 概述
try-with-resources 语句是 Java 7 引入的一种语法糖,用于自动关闭实现了 AutoCloseable 接口的资源。在 try 语句块结束时,会自动调用资源的 close() 方法,无论是否发生异常。
66.2 示例代码
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class TryWithResourcesExample {
public static void main(String[] args) {
try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
66.3 多个资源的处理
try-with-resources 语句可以同时管理多个资源,资源会按照声明的相反顺序关闭。
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class MultipleResourcesExample {
public static void main(String[] args) {
try (BufferedReader br = new BufferedReader(new FileReader("input.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
六十七、Java 中的字符串拼接性能比较
67.1 不同拼接方式概述
在 Java 中,常见的字符串拼接方式有 + 运算符、StringBuilder、StringBuffer 和 String.join() 方法。不同的拼接方式在性能上有差异,尤其是在大量拼接操作时。
67.2 性能比较示例代码
public class StringConcatenationPerformance {
public static void main(String[] args) {
int loopCount = 10000;
// 使用 + 运算符拼接
long startTime1 = System.currentTimeMillis();
String result1 = "";
for (int i = 0; i < loopCount; i++) {
result1 += "a";
}
long endTime1 = System.currentTimeMillis();
System.out.println("Using + operator: " + (endTime1 - startTime1) + " ms");
// 使用 StringBuilder 拼接
long startTime2 = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < loopCount; i++) {
sb.append("a");
}
String result2 = sb.toString();
long endTime2 = System.currentTimeMillis();
System.out.println("Using StringBuilder: " + (endTime2 - startTime2) + " ms");
// 使用 StringBuffer 拼接
long startTime3 = System.currentTimeMillis();
StringBuffer sbf = new StringBuffer();
for (int i = 0; i < loopCount; i++) {
sbf.append("a");
}
String result3 = sbf.toString();
long endTime3 = System.currentTimeMillis();
System.out.println("Using StringBuffer: " + (endTime3 - startTime3) + " ms");
// 使用 String.join() 方法拼接
long startTime4 = System.currentTimeMillis();
String[] array = new String[loopCount];
for (int i = 0; i < loopCount; i++) {
array[i] = "a";
}
String result4 = String.join("", array);
long endTime4 = System.currentTimeMillis();
System.out.println("Using String.join(): " + (endTime4 - startTime4) + " ms");
}
}
一般来说,StringBuilder 的性能最好,适合单线程环境下的大量拼接操作;StringBuffer 是线程安全的,但性能相对较低;+ 运算符在少量拼接时方便,但大量拼接时性能较差;String.join() 方法在拼接数组元素时较为方便。
这些内容进一步丰富了 Java 基础知识体系,涵盖了并发同步器、定时任务、反射、资源管理和字符串操作等方面,有助于你更全面地掌握 Java 编程。
六十八、Java 中的契约式设计(Design by Contract)
68.1 概述
契约式设计是一种编程范式,强调在软件设计中明确定义模块之间的契约。在 Java 里,通常通过前置条件、后置条件和不变式来实现。前置条件是调用方法前必须满足的条件;后置条件是方法执行完毕后必须满足的条件;不变式是在对象的整个生命周期内都必须满足的条件。虽然 Java 本身没有直接的语法支持契约式设计,但可以通过断言或自定义检查逻辑来模拟。
68.2 示例代码
class BankAccount {
private double balance;
public BankAccount(double initialBalance) {
// 前置条件:初始余额不能为负
assert initialBalance >= 0 : "Initial balance cannot be negative";
this.balance = initialBalance;
}
public void deposit(double amount) {
// 前置条件:存款金额必须为正
assert amount > 0 : "Deposit amount must be positive";
double oldBalance = balance;
balance += amount;
// 后置条件:新余额等于旧余额加上存款金额
assert balance == oldBalance + amount : "Deposit operation failed";
}
public void withdraw(double amount) {
// 前置条件:取款金额必须为正且不能超过余额
assert amount > 0 && amount <= balance : "Invalid withdrawal amount";
double oldBalance = balance;
balance -= amount;
// 后置条件:新余额等于旧余额减去取款金额
assert balance == oldBalance - amount : "Withdrawal operation failed";
}
// 不变式:余额始终不能为负
public boolean invariant() {
return balance >= 0;
}
}
public class DesignByContractExample {
public static void main(String[] args) {
BankAccount account = new BankAccount(1000);
account.deposit(500);
account.withdraw(200);
assert account.invariant() : "Invariant violated";
}
}
六十九、Java 中的 Locale 类和国际化(i18n)
69.1 Locale 类概述
Locale 类用于表示特定的地理、政治或文化区域。在 Java 中,它被广泛应用于国际化和本地化(i18n 和 l10n)。通过 Locale 对象,可以根据不同的地区和语言环境显示不同的文本、日期、数字等信息。
69.2 示例代码
获取默认 Locale
import java.util.Locale;
public class LocaleExample {
public static void main(String[] args) {
Locale defaultLocale = Locale.getDefault();
System.out.println("Default Locale: " + defaultLocale);
}
}
使用 Locale 格式化日期和数字
import java.text.DateFormat;
import java.text.NumberFormat;
import java.util.Date;
import java.util.Locale;
public class LocaleFormattingExample {
public static void main(String[] args) {
Date currentDate = new Date();
double number = 12345.67;
// 美国地区
Locale usLocale = Locale.US;
DateFormat usDateFormat = DateFormat.getDateInstance(DateFormat.FULL, usLocale);
NumberFormat usNumberFormat = NumberFormat.getNumberInstance(usLocale);
System.out.println("US Date: " + usDateFormat.format(currentDate));
System.out.println("US Number: " + usNumberFormat.format(number));
// 法国地区
Locale frLocale = Locale.FRANCE;
DateFormat frDateFormat = DateFormat.getDateInstance(DateFormat.FULL, frLocale);
NumberFormat frNumberFormat = NumberFormat.getNumberInstance(frLocale);
System.out.println("French Date: " + frDateFormat.format(currentDate));
System.out.println("French Number: " + frNumberFormat.format(number));
}
}
七十、Java 中的方法句柄(Method Handles)
70.1 概述
方法句柄是 Java 7 引入的一种新的反射机制,它提供了一种更轻量级、更高效的方式来调用方法、构造函数和字段访问。与传统的反射相比,方法句柄的性能更好,并且可以在运行时动态绑定方法。
70.2 示例代码
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
class MyClass {
public void printMessage(String message) {
System.out.println(message);
}
}
public class MethodHandleExample {
public static void main(String[] args) throws Throwable {
MyClass obj = new MyClass();
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType methodType = MethodType.methodType(void.class, String.class);
MethodHandle methodHandle = lookup.findVirtual(MyClass.class, "printMessage", methodType);
methodHandle.invoke(obj, "Hello, Method Handles!");
}
}
七十一、Java 中的 VarHandle
71.1 概述
VarHandle 是 Java 9 引入的一个新特性,用于在 Java 中进行变量的低级别访问和操作。它提供了一种统一的方式来访问和修改字段、数组元素和其他变量,支持原子操作和内存排序语义。
71.2 示例代码
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
class Counter {
private int value;
private static final VarHandle VALUE;
static {
try {
MethodHandles.Lookup lookup = MethodHandles.lookup();
VALUE = lookup.findVarHandle(Counter.class, "value", int.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
}
public void increment() {
VALUE.getAndAdd(this, 1);
}
public int getValue() {
return (int) VALUE.get(this);
}
}
public class VarHandleExample {
public static void main(String[] args) {
Counter counter = new Counter();
counter.increment();
System.out.println("Counter value: " + counter.getValue());
}
}
七十二、Java 中的 Lambda 表达式和方法引用的作用域
72.1 Lambda 表达式的作用域
Lambda 表达式可以访问外部的局部变量、实例变量和静态变量。但对于局部变量,必须是 final 或事实上的 final 变量(即变量赋值后不再改变)。
72.2 示例代码
import java.util.function.Consumer;
public class LambdaScopeExample {
private int instanceVar = 10;
private static int staticVar = 20;
public void testLambdaScope() {
int localVar = 30; // 事实上的 final 变量
Consumer<Integer> consumer = (num) -> {
System.out.println("Instance variable: " + instanceVar);
System.out.println("Static variable: " + staticVar);
System.out.println("Local variable: " + localVar);
System.out.println("Parameter: " + num);
};
consumer.accept(40);
}
public static void main(String[] args) {
LambdaScopeExample example = new LambdaScopeExample();
example.testLambdaScope();
}
}
72.3 方法引用的作用域
方法引用的作用域规则与 Lambda 表达式类似,同样遵循对局部变量的 final 或事实上 final 的要求。
这些 Java 基础知识涵盖了编程范式、国际化、高级反射机制以及 Lambda 作用域等方面,能帮助你进一步深入理解和运用 Java 语言。
七十三、Java 中的 Comparator 和 Comparable 接口
73.1 Comparable 接口
概述
Comparable 接口位于 java.lang 包下,它只有一个抽象方法 compareTo(T o),用于定义对象之间的自然排序规则。实现该接口的类的对象可以进行比较和排序,像 String、Integer 等类都实现了 Comparable 接口。
示例代码
class Student implements Comparable<Student> {
private int id;
private String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public int compareTo(Student other) {
return Integer.compare(this.id, other.id);
}
@Override
public String toString() {
return "Student{id=" + id + ", name='" + name + "'}";
}
}
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ComparableExample {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student(3, "Alice"));
students.add(new Student(1, "Bob"));
students.add(new Student(2, "Charlie"));
Collections.sort(students);
for (Student student : students) {
System.out.println(student);
}
}
}
73.2 Comparator 接口
概述
Comparator 接口位于 java.util 包下,用于定义对象的自定义排序规则。当类没有实现 Comparable 接口,或者需要多种不同的排序方式时,可以使用 Comparator 接口。
示例代码
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class Book {
private String title;
private int price;
public Book(String title, int price) {
this.title = title;
this.price = price;
}
public String getTitle() {
return title;
}
public int getPrice() {
return price;
}
@Override
public String toString() {
return "Book{title='" + title + "', price=" + price + "}";
}
}
public class ComparatorExample {
public static void main(String[] args) {
List<Book> books = new ArrayList<>();
books.add(new Book("Java Programming", 50));
books.add(new Book("Python Basics", 30));
books.add(new Book("C++ Advanced", 80));
// 按价格排序
Comparator<Book> priceComparator = Comparator.comparingInt(Book::getPrice);
Collections.sort(books, priceComparator);
System.out.println("Sorted by price:");
for (Book book : books) {
System.out.println(book);
}
// 按标题排序
Comparator<Book> titleComparator = Comparator.comparing(Book::getTitle);
Collections.sort(books, titleComparator);
System.out.println("Sorted by title:");
for (Book book : books) {
System.out.println(book);
}
}
}
七十四、Java 中的反射与类加载
74.1 通过反射获取类的注解信息
概述
利用反射可以在运行时获取类、方法、字段等元素上的注解信息,从而根据注解进行相应的处理。
示例代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface MyAnnotation {
String value();
}
@MyAnnotation("Test Class")
class MyAnnotatedClass {
// 类的内容
}
public class ReflectionAnnotationRetrieval {
public static void main(String[] args) {
Class<MyAnnotatedClass> clazz = MyAnnotatedClass.class;
if (clazz.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
System.out.println("Annotation value: " + annotation.value());
}
}
}
74.2 动态加载类
概述
通过 Class.forName() 方法可以在运行时动态加载类,这在很多框架和工具中都有应用,比如 JDBC 驱动的加载。
示例代码
public class DynamicClassLoading {
public static void main(String[] args) {
try {
Class<?> clazz = Class.forName("java.util.ArrayList");
System.out.println("Class loaded: " + clazz.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
七十五、Java 中的异常分类与自定义异常
75.1 异常分类
概述
Java 中的异常分为受检查异常(Checked Exception)和非受检查异常(Unchecked Exception)。受检查异常必须在方法签名中声明或者进行捕获处理,而非受检查异常(如 RuntimeException 及其子类)则不需要。
示例代码
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class ExceptionClassification {
public static void main(String[] args) {
// 受检查异常示例
try {
FileInputStream fis = new FileInputStream("nonexistent.txt");
} catch (FileNotFoundException e) {
System.out.println("File not found: " + e.getMessage());
}
// 非受检查异常示例
int[] arr = {1, 2, 3};
try {
System.out.println(arr[5]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Array index out of bounds: " + e.getMessage());
}
}
}
75.2 自定义异常
概述
在实际开发中,有时需要根据业务需求自定义异常类,自定义异常类通常继承自 Exception 或 RuntimeException。
示例代码
class CustomException extends Exception {
public CustomException(String message) {
super(message);
}
}
class CustomRuntimeException extends RuntimeException {
public CustomRuntimeException(String message) {
super(message);
}
}
public class CustomExceptionExample {
public static void main(String[] args) {
try {
throwCustomCheckedException();
} catch (CustomException e) {
System.out.println("Custom checked exception: " + e.getMessage());
}
try {
throwCustomRuntimeException();
} catch (CustomRuntimeException e) {
System.out.println("Custom runtime exception: " + e.getMessage());
}
}
public static void throwCustomCheckedException() throws CustomException {
throw new CustomException("This is a custom checked exception.");
}
public static void throwCustomRuntimeException() {
throw new CustomRuntimeException("This is a custom runtime exception.");
}
}
七十六、Java 中的流操作进阶
76.1 流的并行处理
概述
Java 流支持并行处理,通过 parallelStream() 方法可以将顺序流转换为并行流,利用多核处理器提高处理效率,但要注意并行处理可能带来的线程安全问题。
示例代码
import java.util.Arrays;
import java.util.List;
public class ParallelStreamExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 并行流处理
long sum = numbers.parallelStream()
.mapToInt(Integer::intValue)
.sum();
System.out.println("Sum: " + sum);
}
}
76.2 流的收集器高级用法
概述
Collectors 类提供了很多强大的收集器,除了常见的 toList()、toSet() 等,还可以进行分组、分区、统计等操作。
示例代码
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
public class AdvancedCollectorsExample {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Charlie", 25)
);
// 按年龄分组
Map<Integer, List<Person>> groupedByAge = people.stream()
.collect(Collectors.groupingBy(Person::getAge));
System.out.println("Grouped by age: " + groupedByAge);
// 按年龄分区(是否大于 28 岁)
Map<Boolean, List<Person>> partitionedByAge = people.stream()
.collect(Collectors.partitioningBy(person -> person.getAge() > 28));
System.out.println("Partitioned by age > 28: " + partitionedByAge);
}
}
这些内容进一步丰富了 Java 基础知识体系,涵盖了排序、反射、异常处理和流操作等重要方面,有助于你更全面深入地掌握 Java 编程。
七十七、Java 中的内存模型(JMM)基础
77.1 概述
Java 内存模型(Java Memory Model,JMM)是 Java 虚拟机规范中定义的一种抽象概念,用于屏蔽不同硬件和操作系统的内存访问差异,保证 Java 程序在各种平台下都能达到一致的内存访问效果。它主要定义了线程和主内存之间的抽象关系,以及多线程环境下的可见性、原子性和有序性问题。
77.2 主内存与工作内存
- 主内存:是所有线程共享的内存区域,存储了对象的实例、静态变量等数据。
- 工作内存:每个线程都有自己的工作内存,它是主内存的一个副本,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接操作主内存中的变量。不同线程之间也无法直接访问对方的工作内存。
77.3 可见性问题
在多线程环境下,如果一个线程修改了某个共享变量的值,其他线程可能无法立即看到这个修改,这就是可见性问题。可以使用 volatile 关键字来解决部分可见性问题。
class VisibilityExample {
private static volatile boolean flag = false;
public static void main(String[] args) {
Thread writer = new Thread(() -> {
flag = true;
System.out.println("Writer thread set flag to true");
});
Thread reader = new Thread(() -> {
while (!flag) {
// 等待 flag 变为 true
}
System.out.println("Reader thread detected flag is true");
});
reader.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
writer.start();
}
}
77.4 原子性问题
原子性是指一个操作是不可中断的,要么全部执行成功,要么全部不执行。Java 中的基本数据类型的读写操作通常是原子性的,但像 i++ 这种复合操作不是原子性的。可以使用 Atomic 类或 synchronized 关键字来保证原子性。
import java.util.concurrent.atomic.AtomicInteger;
class AtomicityExample {
private static AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
int threadCount = 10;
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.incrementAndGet();
}
});
threads[i].start();
}
for (Thread thread : threads) {
thread.join();
}
System.out.println("Final counter value: " + counter.get());
}
}
77.5 有序性问题
在 Java 中,编译器和处理器为了提高性能,可能会对指令进行重排序。但在单线程环境下,重排序不会影响程序的执行结果;在多线程环境下,重排序可能会导致程序出现错误。可以使用 volatile 关键字和 synchronized 关键字来保证有序性。
七十八、Java 中的序列化版本控制的最佳实践
78.1 手动指定 serialVersionUID
在实现 Serializable 接口的类中,手动指定 serialVersionUID 可以避免在类结构发生微小变化时导致反序列化失败。
import java.io.Serializable;
class MySerializableClass implements Serializable {
private static final long serialVersionUID = 123456789L;
private String data;
public MySerializableClass(String data) {
this.data = data;
}
public String getData() {
return data;
}
}
78.2 处理类结构变化
- 添加字段:如果在类中添加了新的字段,并且希望旧版本的序列化对象仍然可以被反序列化,可以为新字段提供默认值。
import java.io.Serializable;
class UpdatedSerializableClass implements Serializable {
private static final long serialVersionUID = 123456789L;
private String data;
private int newField = 0; // 新增字段并提供默认值
public UpdatedSerializableClass(String data) {
this.data = data;
}
public String getData() {
return data;
}
public int getNewField() {
return newField;
}
}
- 删除字段:删除字段时要谨慎,因为旧版本的序列化对象中可能包含该字段的数据,反序列化时可能会出现问题。可以考虑使用
transient关键字将字段标记为临时字段,而不是直接删除。 - 修改字段类型:修改字段类型可能会导致反序列化失败,尽量避免这种操作。如果必须修改,可以考虑提供自定义的
readObject和writeObject方法来处理。
七十九、Java 中的集合排序的更多方式
79.1 使用 TreeSet 和 TreeMap 进行自然排序和定制排序
TreeSet
TreeSet 是一个有序的集合,它会根据元素的自然顺序或指定的比较器进行排序。
import java.util.TreeSet;
class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Person other) {
return Integer.compare(this.age, other.age);
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
public class TreeSetSortingExample {
public static void main(String[] args) {
TreeSet<Person> people = new TreeSet<>();
people.add(new Person("Alice", 25));
people.add(new Person("Bob", 20));
people.add(new Person("Charlie", 30));
for (Person person : people) {
System.out.println(person);
}
}
}
TreeMap
TreeMap 是一个有序的映射,它会根据键的自然顺序或指定的比较器进行排序。
import java.util.TreeMap;
class CustomKey implements Comparable<CustomKey> {
private int id;
public CustomKey(int id) {
this.id = id;
}
@Override
public int compareTo(CustomKey other) {
return Integer.compare(this.id, other.id);
}
@Override
public String toString() {
return "CustomKey{id=" + id + "}";
}
}
public class TreeMapSortingExample {
public static void main(String[] args) {
TreeMap<CustomKey, String> map = new TreeMap<>();
map.put(new CustomKey(3), "Value 3");
map.put(new CustomKey(1), "Value 1");
map.put(new CustomKey(2), "Value 2");
for (CustomKey key : map.keySet()) {
System.out.println(key + ": " + map.get(key));
}
}
}
79.2 使用 Collections.sort() 进行自定义排序
除了使用 Comparator 接口,还可以使用 Collections.sort() 方法结合 Lambda 表达式进行自定义排序。
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
class Product {
private String name;
private double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
@Override
public String toString() {
return "Product{name='" + name + "', price=" + price + "}";
}
}
public class CollectionsSortingExample {
public static void main(String[] args) {
List<Product> products = new ArrayList<>();
products.add(new Product("Laptop", 1000.0));
products.add(new Product("Mouse", 20.0));
products.add(new Product("Keyboard", 50.0));
// 按价格排序
Collections.sort(products, (p1, p2) -> Double.compare(p1.getPrice(), p2.getPrice()));
for (Product product : products) {
System.out.println(product);
}
}
}
八十、Java 中的 Lambda 表达式与函数式接口的高级应用
80.1 函数式接口的组合
可以将多个函数式接口组合成一个新的函数式接口,以实现更复杂的逻辑。
import java.util.function.Function;
public class FunctionalInterfaceComposition {
public static void main(String[] args) {
Function<Integer, Integer> multiplyByTwo = num -> num * 2;
Function<Integer, Integer> addThree = num -> num + 3;
// 组合函数
Function<Integer, Integer> combined = multiplyByTwo.andThen(addThree);
int result = combined.apply(5);
System.out.println("Result: " + result);
}
}
80.2 方法引用与 Lambda 表达式的结合
方法引用可以和 Lambda 表达式结合使用,使代码更加简洁。
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
class Printer {
public static void print(String message) {
System.out.println(message);
}
}
public class MethodReferenceWithLambda {
public static void main(String[] args) {
List<String> messages = Arrays.asList("Hello", "World", "Java");
// 使用方法引用和 Lambda 表达式
Consumer<String> printer = Printer::print;
messages.forEach(printer);
}
}
80.3 延迟执行与惰性求值
Lambda 表达式可以实现延迟执行和惰性求值,只有在需要结果时才进行计算。
import java.util.function.Supplier;
public class LazyEvaluation {
public static void main(String[] args) {
Supplier<Integer> lazyValue = () -> {
System.out.println("Calculating value...");
return 10 + 20;
};
// 只有调用 get() 方法时才会执行 Lambda 表达式
System.out.println("Before getting value");
int value = lazyValue.get();
System.out.println("Value: " + value);
}
}
这些内容进一步拓展了 Java 基础知识的深度和广度,涉及内存模型、序列化、集合排序以及 Lambda 表达式等多个重要方面,有助于你更全面地掌握 Java 编程的核心要点。
本文来自博客园,作者:练剑哥,转载请注明原文链接:https://www.cnblogs.com/qiaozc/p/18710826

浙公网安备 33010602011771号