二十、断言(Assertion)
20.1 断言概述
断言是一种调试工具,用于在代码中插入一些检查点,确保程序在执行到这些点时某些条件为真。如果断言条件为假,会抛出 AssertionError
异常。断言主要用于开发和测试阶段,帮助程序员快速定位错误。
20.2 断言的语法
Java 中的断言使用 assert
关键字,有两种形式:
- 简单形式:
assert condition;
int num = 10;
assert num > 5; // 如果 num <= 5,会抛出 AssertionError
- 带消息形式:
assert condition : message;
int value = -1;
assert value >= 0 : "Value must be non - negative";
20.3 启用和禁用断言
默认情况下,Java 虚拟机是禁用断言的。要启用断言,可以在运行 Java 程序时使用 -ea
(-enableassertions
的缩写)选项。例如:
收起
sh
java -ea MyClass
要禁用断言,可以使用 -da
(-disableassertions
的缩写)选项。
二十一、包(Package)
21.1 包的概念
包是 Java 中组织类和接口的一种方式,它可以避免命名冲突,提高代码的可维护性和可管理性。包可以包含类、接口、子包等。
21.2 包的声明和使用
- 声明包:在 Java 源文件的第一行使用
package
关键字声明包名。
package com.example.myapp;public class MyClass {// 类的定义
}
- 使用包中的类:可以使用全限定名或
import
语句来使用包中的类。
// 使用全限定名
com.example.myapp.MyClass obj = new com.example.myapp.MyClass();// 使用 import 语句
import com.example.myapp.MyClass;
MyClass obj2 = new MyClass();
21.3 包的访问权限
包可以影响类和成员的访问权限。例如,默认(没有任何访问修饰符)的类和成员只能被同一个包中的其他类访问。
二十二、包访问权限和访问控制总结
22.1 访问修饰符回顾
Java 中有四种访问修饰符:private
、default
(默认,无关键字)、protected
和 public
,它们对类、成员变量和方法的访问权限限制如下:
访问修饰符 | 本类 | 同一个包中的类 | 不同包中的子类 | 不同包中的非子类 |
---|---|---|---|---|
private |
可以访问 | 不可以访问 | 不可以访问 | 不可以访问 |
default |
可以访问 | 可以访问 | 不可以访问 | 不可以访问 |
protected |
可以访问 | 可以访问 | 可以访问 | 不可以访问 |
public |
可以访问 | 可以访问 | 可以访问 | 可以访问 |
22.2 类的访问权限
类可以使用 public
或默认访问修饰符。如果一个类被声明为 public
,则该类可以被任何包中的其他类访问;如果没有指定访问修饰符(默认),则该类只能被同一个包中的其他类访问。
22.3 成员的访问权限
成员变量和方法可以使用四种访问修饰符中的任何一种。private
成员只能在本类中访问;default
成员可以在同一个包中的类中访问;protected
成员除了可以在同一个包中的类访问外,还可以在不同包的子类中访问;public
成员可以被任何类访问。
二十三、Java 中的命令行参数
23.1 命令行参数的概念
命令行参数是在运行 Java 程序时传递给主方法(main
方法)的参数。main
方法的签名为 public static void main(String[] args)
,其中 args
是一个字符串数组,用于接收命令行参数。
23.2 使用命令行参数
public class CommandLineArgsExample {public static void main(String[] args) {if (args.length > 0) {System.out.println("Command line arguments:");for (String arg : args) {System.out.println(arg);}} else {System.out.println("No command line arguments provided.");}}
}
要运行这个程序并传递命令行参数,可以在命令行中这样做:
收起
sh
java CommandLineArgsExample arg1 arg2 arg3
二十四、Java 中的命令行编译和运行
24.1 编译 Java 程序
使用 javac
命令来编译 Java 源文件。例如,要编译一个名为 MyClass.java
的源文件,可以在命令行中输入:
收起
sh
javac MyClass.java
这会在当前目录下生成一个名为 MyClass.class
的字节码文件。
24.2 运行 Java 程序
使用 java
命令来运行编译好的 Java 程序。例如,要运行 MyClass
类的 main
方法,可以在命令行中输入:
收起
sh
java MyClass
如果程序需要命令行参数,可以在类名后面跟上参数,如:
收起
sh
java MyClass arg1 arg2
24.3 编译和运行带包的 Java 程序
如果 Java 程序使用了包,编译和运行时需要注意目录结构和类路径。假设 MyClass
类位于 com.example
包中,源文件的目录结构应该是 com/example/MyClass.java
。编译时可以使用以下命令:
收起
sh
javac com/example/MyClass.java
运行时需要指定完整的类名(包括包名):
收起
sh
java com.example.MyClass
二十五、Java 中的注解处理器 API
25.1 注解处理器概述
注解处理器 API(javax.annotation.processing
)允许开发者在编译时处理注解。注解处理器可以读取、修改和生成 Java 源代码,常用于生成代码、验证代码等场景。
25.2 自定义注解处理器
要创建自定义注解处理器,需要继承 AbstractProcessor
类,并实现 process
方法。以下是一个简单的示例:
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import java.util.Set;@SupportedAnnotationTypes("com.example.MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {for (TypeElement annotation : annotations) {System.out.println("Processing annotation: " + annotation.getSimpleName());}return true;}
}
要使用这个注解处理器,需要将其注册到 Java 编译器中。可以通过创建 META - INF/services/javax.annotation.processing.Processor
文件,并在文件中指定注解处理器的全限定名来实现。
二十六、Java 中的 Lambda 表达式和函数式接口
26.1 Lambda 表达式概述
Lambda 表达式是 Java 8 引入的一种简洁的语法,用于表示匿名函数。它可以作为参数传递给方法,使代码更加简洁和易读。
26.2 Lambda 表达式的语法
Lambda 表达式的基本语法为:(parameters) -> expression
或 (parameters) -> { statements; }
// 无参数的 Lambda 表达式
Runnable runnable = () -> System.out.println("Running...");
new Thread(runnable).start();// 有参数的 Lambda 表达式
java.util.List<Integer> numbers = java.util.Arrays.asList(1, 2, 3, 4, 5);
numbers.forEach((Integer num) -> System.out.println(num));
26.3 函数式接口
函数式接口是只包含一个抽象方法的接口。Lambda 表达式可以用于实现函数式接口。Java 提供了一些内置的函数式接口,如 Predicate
、Consumer
、Function
等。
import java.util.function.Predicate;Predicate<Integer> isEven = (num) -> num % 2 == 0;
System.out.println(isEven.test(4)); // 输出 true
二十七、Java 中的 Stream API
27.1 Stream API 概述
Stream API 是 Java 8 引入的一种处理集合数据的新方式,它提供了一种声明式的操作方式,使得代码更加简洁和易读。Stream 可以对集合中的元素进行过滤、映射、排序等操作。
27.2 创建 Stream
可以通过集合、数组等创建 Stream。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = numbers.stream();
27.3 Stream 操作
- 中间操作:如
filter
、map
、sorted
等,中间操作会返回一个新的 Stream。
Stream<Integer> evenNumbers = stream.filter(num -> num % 2 == 0);
- 终端操作:如
forEach
、collect
、count
等,终端操作会触发 Stream 的执行并产生结果。
evenNumbers.forEach(System.out::println);
以上进一步丰富了 Java 基础知识的内容,涵盖了从断言、包管理、命令行操作到 Java 8 新特性(Lambda 表达式、Stream API)等多个方面,这些知识是构建更复杂 Java 应用的基石。
二十八、方法引用
28.1 方法引用概述
方法引用是 Java 8 引入的一种简化 Lambda 表达式的语法糖,它允许直接引用已有的方法或构造函数,使代码更加简洁易读。方法引用可以看作是 Lambda 表达式的一种特殊形式,当 Lambda 表达式只是简单地调用一个已存在的方法时,就可以使用方法引用。
28.2 方法引用的类型
静态方法引用
语法:类名::静态方法名
import java.util.Arrays;
import java.util.List;class MathUtils {public static int square(int num) {return num * num;}
}public class StaticMethodReferenceExample {public static void main(String[] args) {List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);numbers.stream().map(MathUtils::square).forEach(System.out::println);}
}
实例方法引用
语法:对象名::实例方法名
import java.util.Arrays;
import java.util.List;class StringUtils {public boolean isUpperCase(String str) {return str.equals(str.toUpperCase());}
}public class InstanceMethodReferenceExample {public static void main(String[] args) {StringUtils utils = new StringUtils();List<String> words = Arrays.asList("HELLO", "world", "JAVA");words.stream().filter(utils::isUpperCase).forEach(System.out::println);}
}
特定类型的实例方法引用
语法:类名::实例方法名
import java.util.Arrays;
import java.util.List;public class SpecificTypeInstanceMethodReferenceExample {public static void main(String[] args) {List<String> words = Arrays.asList("apple", "banana", "cherry");words.stream().map(String::length).forEach(System.out::println);}
}
构造方法引用
语法:类名::new
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;class Person {private String name;public Person(String name) {this.name = name;}public String getName() {return name;}
}public class ConstructorMethodReferenceExample {public static void main(String[] args) {List<String> names = Arrays.asList("Alice", "Bob", "Charlie");List<Person> people = names.stream().map(Person::new).collect(Collectors.toList());people.forEach(person -> System.out.println(person.getName()));}
}
二十九、Optional 类
29.1 Optional 类概述
Optional
类是 Java 8 引入的一个容器类,用于表示一个值可能存在也可能不存在的情况。它可以避免 NullPointerException
,使代码更加健壮。
29.2 创建 Optional 对象
import java.util.Optional;// 创建一个包含值的 Optional 对象
Optional<String> optionalWithValue = Optional.of("Hello");// 创建一个可能为空的 Optional 对象
Optional<String> optionalNullable = Optional.ofNullable(null);// 创建一个空的 Optional 对象
Optional<String> emptyOptional = Optional.empty();
29.3 Optional 的常用方法
import java.util.Optional;public class OptionalExample {public static void main(String[] args) {Optional<String> optional = Optional.of("Hello");// 判断 Optional 是否包含值boolean isPresent = optional.isPresent();System.out.println("Is present: " + isPresent);// 获取 Optional 中的值,如果值不存在会抛出 NoSuchElementExceptionString value = optional.get();System.out.println("Value: " + value);// 如果值存在则执行操作optional.ifPresent(str -> System.out.println("Value is: " + str));// 如果值不存在则返回指定的默认值String defaultValue = optional.orElse("Default");System.out.println("Default value: " + defaultValue);// 如果值不存在则执行指定的操作optional.orElseGet(() -> {System.out.println("Value is absent, doing something...");return "Generated value";});}
}
三十、CompletableFuture 类
30.1 CompletableFuture 类概述
CompletableFuture
是 Java 8 引入的一个强大的异步编程工具,它实现了 Future
接口,同时提供了丰富的方法来处理异步任务的完成、组合和异常处理。
30.2 创建 CompletableFuture 对象
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;public class CompletableFutureCreationExample {public static void main(String[] args) throws ExecutionException, InterruptedException {// 创建一个已经完成的 CompletableFutureCompletableFuture<String> completedFuture = CompletableFuture.completedFuture("Result");System.out.println(completedFuture.get());// 异步执行任务CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}return "Async result";});System.out.println(future.get());}
}
30.3 CompletableFuture 的组合和链式调用
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;public class CompletableFutureCombinationExample {public static void main(String[] args) throws ExecutionException, InterruptedException {CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");// 组合两个 CompletableFutureCompletableFuture<String> combinedFuture = future1.thenCombine(future2, (s1, s2) -> s1 + " " + s2);System.out.println(combinedFuture.get());// 链式调用CompletableFuture<String> chainedFuture = future1.thenApply(s -> s + "!").thenApply(String::toUpperCase);System.out.println(chainedFuture.get());}
}
30.4 CompletableFuture 的异常处理
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;public class CompletableFutureExceptionHandlingExample {public static void main(String[] args) throws ExecutionException, InterruptedException {CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {if (Math.random() < 0.5) {throw new RuntimeException("Something went wrong");}return "Success";});CompletableFuture<String> handledFuture = future.exceptionally(ex -> {System.out.println("Exception: " + ex.getMessage());return "Recovered result";});System.out.println(handledFuture.get());}
}
三十一、Java 中的模块化系统(Java 9+)
31.1 模块化系统概述
Java 9 引入了模块化系统(Project Jigsaw),旨在解决大型项目中类路径的复杂性、依赖管理困难等问题。模块化系统允许将代码组织成模块,每个模块有明确的依赖关系和导出的包。
31.2 模块的定义
在 Java 9 及以后的版本中,可以通过 module-info.java
文件来定义模块。例如:
// module-info.java
module mymodule {// 导出包,其他模块可以访问这些包中的类exports com.example.mypackage;// 依赖其他模块requires othermodule;
}
31.3 模块化编译和运行
编译模块化代码
收起
sh
javac --module-source-path src -d mods $(find src -name "*.java")
运行模块化程序
收起
sh
java --module-path mods -m mymodule/com.example.mypackage.Main
三十二、Java 中的密封类和接口(Java 15+)
32.1 密封类和接口概述
Java 15 引入了密封类和接口,用于限制哪些类可以继承该类或实现该接口。通过使用 sealed
关键字,可以明确指定允许的子类或实现类,增强了代码的安全性和可维护性。
32.2 密封类的定义和使用
// 定义密封类
public sealed class Shape permits Circle, Rectangle {// 类的定义
}// 定义允许的子类
final class Circle extends Shape {// 子类的定义
}non-sealed class Rectangle extends Shape {// 子类的定义
}
在上述示例中,Shape
是一个密封类,只有 Circle
和 Rectangle
可以继承它。Circle
被声明为 final
,表示它不能再被其他类继承;Rectangle
被声明为 non-sealed
,表示它的子类不受限制。
32.3 密封接口的定义和使用
// 定义密封接口
public sealed interface Animal permits Dog, Cat {void makeSound();
}// 定义允许的实现类
final class Dog implements Animal {@Overridepublic void makeSound() {System.out.println("Woof!");}
}non-sealed class Cat implements Animal {@Overridepublic void makeSound() {System.out.println("Meow!");}
}
这里,Animal
是一个密封接口,只有 Dog
和 Cat
可以实现它。同样,Dog
是 final
类,Cat
是 non-sealed
类。
这些内容进一步丰富了 Java 基础知识体系,涵盖了 Java 8 及以后版本引入的一些重要特性,对于理解和运用现代 Java 编程至关重要。
三十三、文本块(Java 13+)
33.1 文本块概述
文本块是 Java 13 引入的新特性,它提供了一种更方便的方式来处理多行字符串,避免了传统字符串拼接中繁琐的换行符和转义字符。
33.2 文本块的语法
文本块使用三个双引号("""
)作为开始和结束标记,并且可以包含换行符、制表符等,无需进行额外的转义。
public class TextBlockExample {public static void main(String[] args) {String html = """<html><body><h1>Hello, World!</h1></body></html>""";System.out.println(html);}
}
33.3 文本块的处理
文本块在编译时会进行一些处理,例如去除每行的前导空格,并且可以使用 \s
来保留一个空格。
String poem = """Twinkle, twinkle, little star,How I wonder what you are.Up above the world so high,Like a diamond in the sky.""";
// 输出时会按照预期的格式输出,去除每行前面多余的空格
System.out.println(poem);
三十四、记录类(Java 14+)
34.1 记录类概述
记录类(Record)是 Java 14 引入的一种特殊类,用于表示不可变的数据载体。它可以自动生成构造方法、getter
方法、equals()
、hashCode()
和 toString()
方法,减少了样板代码。
34.2 记录类的定义和使用
// 定义记录类
record Person(String name, int age) {// 可以添加额外的方法public String getNameAndAge() {return name + " is " + age + " years old.";}
}public class RecordExample {public static void main(String[] args) {Person person = new Person("Alice", 25);System.out.println(person.name()); // 自动生成的 getter 方法System.out.println(person.getNameAndAge());}
}
34.3 记录类的特点
- 不可变性:记录类的属性是不可变的,一旦创建,其值不能被修改。
- 紧凑构造方法:可以定义紧凑构造方法来进行参数验证等操作。
record Point(int x, int y) {// 紧凑构造方法public Point {if (x < 0 || y < 0) {throw new IllegalArgumentException("Coordinates cannot be negative.");}}
}
三十五、模式匹配(Java 14+)
35.1 模式匹配概述
模式匹配是 Java 14 开始逐步引入的特性,它可以简化类型检查和类型转换的代码,使代码更加简洁易读。目前主要包括 instanceof
的模式匹配和 switch
表达式的模式匹配。
35.2 instanceof
的模式匹配
在传统的 Java 中,进行类型检查和类型转换需要多行代码,而模式匹配可以将其简化为一行。
public class InstanceOfPatternMatchingExample {public static void main(String[] args) {Object obj = "Hello";if (obj instanceof String str) {// 直接使用 str 变量,无需显式类型转换System.out.println(str.toUpperCase());}}
}
35.3 switch
表达式的模式匹配(Java 17+)
switch
表达式的模式匹配允许在 switch
语句中使用模式来匹配不同的情况。
sealed interface Shape permits Circle, Rectangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}public class SwitchPatternMatchingExample {public static double area(Shape shape) {return switch (shape) {case Circle c -> Math.PI * c.radius() * c.radius();case Rectangle r -> r.width() * r.height();};}public static void main(String[] args) {Shape circle = new Circle(5);System.out.println("Circle area: " + area(circle));}
}
三十六、协变返回类型(Java 5+)
36.1 协变返回类型概述
协变返回类型是 Java 5 引入的特性,它允许子类重写父类的方法时,返回类型可以是父类方法返回类型的子类。这使得方法重写更加灵活。
36.2 协变返回类型的示例
class Animal {Animal create() {return new Animal();}
}class Dog extends Animal {// 重写方法,返回类型为 Dog,是 Animal 的子类@OverrideDog create() {return new Dog();}
}
三十七、菱形运算符(Java 7+)
37.1 菱形运算符概述
菱形运算符(<>
)是 Java 7 引入的特性,用于简化泛型类的实例化。在创建泛型类的对象时,可以省略泛型类型参数,编译器会根据上下文自动推断。
37.2 菱形运算符的使用
import java.util.ArrayList;
import java.util.List;public class DiamondOperatorExample {public static void main(String[] args) {// Java 7 之前的写法List<String> list1 = new ArrayList<String>();// 使用菱形运算符List<String> list2 = new ArrayList<>();}
}
三十八、自动装箱和拆箱(Java 5+)
38.1 自动装箱和拆箱概述
自动装箱和拆箱是 Java 5 引入的特性,它允许基本数据类型和对应的包装类之间自动进行转换,提高了代码的简洁性。
38.2 自动装箱和拆箱的示例
public class AutoboxingUnboxingExample {public static void main(String[] args) {// 自动装箱:将基本数据类型转换为包装类Integer num1 = 10; // 自动拆箱:将包装类转换为基本数据类型int num2 = num1; System.out.println("Autoboxed value: " + num1);System.out.println("Unboxed value: " + num2);}
}
三十九、可变参数(Java 5+)
39.1 可变参数概述
可变参数(Varargs)是 Java 5 引入的特性,它允许方法接受可变数量的参数,使用 ...
语法来表示。
39.2 可变参数的使用
public class VarargsExample {public static int sum(int... numbers) {int total = 0;for (int num : numbers) {total += num;}return total;}public static void main(String[] args) {int result1 = sum(1, 2, 3);int result2 = sum(4, 5, 6, 7);System.out.println("Sum 1: " + result1);System.out.println("Sum 2: " + result2);}
}
这些 Java 基础知识涵盖了从早期版本到较新版本的多个重要特性,理解和掌握这些内容有助于更好地进行 Java 编程。
四十、序列化控制
40.1 自定义序列化过程
虽然实现 Serializable
接口能让对象自动序列化,但有时候需要自定义序列化过程。可以通过在类中定义 writeObject
和 readObject
方法来实现。
import java.io.*;class CustomSerializable implements Serializable {private int value;public CustomSerializable(int value) {this.value = value;}// 自定义序列化方法private void writeObject(ObjectOutputStream out) throws IOException {// 可以在此添加额外的处理逻辑out.defaultWriteObject();out.writeInt(value * 2); // 对值进行一些处理后再写入}// 自定义反序列化方法private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {in.defaultReadObject();this.value = in.readInt() / 2; // 恢复处理后的值}public int getValue() {return value;}
}public class CustomSerializationExample {public static void main(String[] args) {CustomSerializable obj = new CustomSerializable(10);try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("custom.ser"));ObjectInputStream ois = new ObjectInputStream(new FileInputStream("custom.ser"))) {oos.writeObject(obj);CustomSerializable deserializedObj = (CustomSerializable) ois.readObject();System.out.println(deserializedObj.getValue());} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}}
}
40.2 序列化版本号(serialVersionUID
)
serialVersionUID
是一个序列化版本的标识符。当对象序列化和反序列化时,JVM 会比较 serialVersionUID
的值,如果不一致会抛出 InvalidClassException
。手动指定 serialVersionUID
可以避免在类结构发生微小变化时导致反序列化失败。
import java.io.Serializable;class VersionedClass implements Serializable {private static final long serialVersionUID = 123456789L;private String data;public VersionedClass(String data) {this.data = data;}public String getData() {return data;}
}
四十一、反射的更多应用
41.1 动态代理
动态代理是 Java 反射机制的一个重要应用,它允许在运行时创建代理类和对象。Java 提供了两种动态代理方式:基于接口的动态代理(java.lang.reflect.Proxy
)和基于类的动态代理(CGLIB)。这里介绍基于接口的动态代理。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 定义接口
interface Subject {void request();
}// 实现接口的真实类
class RealSubject implements Subject {@Overridepublic void request() {System.out.println("RealSubject: Handling request.");}
}// 实现 InvocationHandler 接口
class ProxyHandler implements InvocationHandler {private Object target;public ProxyHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("Before method call.");Object result = method.invoke(target, args);System.out.println("After method call.");return result;}
}public class DynamicProxyExample {public static void main(String[] args) {RealSubject realSubject = new RealSubject();ProxyHandler handler = new ProxyHandler(realSubject);Subject proxySubject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),new Class<?>[]{Subject.class},handler);proxySubject.request();}
}
41.2 反射获取和修改注解
通过反射可以获取类、方法、字段等元素上的注解信息,并且可以根据需要进行处理。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;// 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface MyAnnotation {String value();
}class AnnotatedClass {@MyAnnotation("Test Annotation")public void annotatedMethod() {// 方法体}
}public class ReflectionAnnotationExample {public static void main(String[] args) throws NoSuchMethodException {Class<?> clazz = AnnotatedClass.class;Method method = clazz.getMethod("annotatedMethod");if (method.isAnnotationPresent(MyAnnotation.class)) {MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);System.out.println("Annotation value: " + annotation.value());}}
}
四十二、Java 中的信号量(Semaphore
)
42.1 信号量概述
Semaphore
是 Java 并发包(java.util.concurrent
)中的一个类,用于控制同时访问某个资源的线程数量。它维护了一组许可证,线程在访问资源前需要获取许可证,访问结束后释放许可证。
42.2 信号量的使用示例
import java.util.concurrent.Semaphore;class Resource {private final Semaphore semaphore = new Semaphore(2); // 允许最多 2 个线程同时访问public void accessResource() {try {semaphore.acquire();System.out.println(Thread.currentThread().getName() + " is accessing the resource.");Thread.sleep(2000); // 模拟资源访问时间System.out.println(Thread.currentThread().getName() + " has finished accessing the resource.");} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {semaphore.release();}}
}public class SemaphoreExample {public static void main(String[] args) {Resource resource = new Resource();for (int i = 0; i < 5; i++) {new Thread(() -> resource.accessResource()).start();}}
}
四十三、Java 中的屏障(CyclicBarrier
)
43.1 屏障概述
CyclicBarrier
是 Java 并发包中的另一个同步工具,它允许一组线程相互等待,直到所有线程都到达某个屏障点,然后所有线程再继续执行。CyclicBarrier
可以重复使用。
43.2 屏障的使用示例
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;class Worker implements Runnable {private final CyclicBarrier barrier;public Worker(CyclicBarrier barrier) {this.barrier = barrier;}@Overridepublic void run() {try {System.out.println(Thread.currentThread().getName() + " is doing some work.");barrier.await(); // 等待其他线程到达屏障点System.out.println(Thread.currentThread().getName() + " has passed the barrier.");} catch (InterruptedException | BrokenBarrierException e) {Thread.currentThread().interrupt();}}
}public class CyclicBarrierExample {public static void main(String[] args) {int numThreads = 3;CyclicBarrier barrier = new CyclicBarrier(numThreads, () -> System.out.println("All threads have reached the barrier."));for (int i = 0; i < numThreads; i++) {new Thread(new Worker(barrier)).start();}}
}
四十四、Java 中的交换器(Exchanger
)
44.1 交换器概述
Exchanger
是 Java 并发包中的一个工具类,它允许两个线程在某个同步点交换数据。当两个线程都到达交换点时,它们会交换各自持有的数据。
44.2 交换器的使用示例
import java.util.concurrent.Exchanger;class ExchangerTask implements Runnable {private final Exchanger<String> exchanger;private String data;public ExchangerTask(Exchanger<String> exchanger, String data) {this.exchanger = exchanger;this.data = data;}@Overridepublic void run() {try {System.out.println(Thread.currentThread().getName() + " before exchange: " + data);data = exchanger.exchange(data);System.out.println(Thread.currentThread().getName() + " after exchange: " + data);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}
}public class ExchangerExample {public static void main(String[] args) {Exchanger<String> exchanger = new Exchanger<>();new Thread(new ExchangerTask(exchanger, "Data from Thread 1")).start();new Thread(new ExchangerTask(exchanger, "Data from Thread 2")).start();}
}
这些 Java 基础知识进一步拓展了 Java 的使用场景,涉及到序列化的深入控制、反射的高级应用以及并发编程中的多种同步工具,有助于你更全面地掌握 Java 编程。
四十五、Java 中的锁机制补充
45.1 读写锁(ReentrantReadWriteLock
)
概述
ReentrantReadWriteLock
是 Java 并发包中提供的一种读写分离的锁机制。它允许多个线程同时进行读操作,但在写操作时会独占锁,以保证数据的一致性。读写锁适用于读多写少的场景。
示例代码
import java.util.concurrent.locks.ReentrantReadWriteLock;class ReadWriteLockExample {private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();private int data;public int readData() {readLock.lock();try {return data;} finally {readLock.unlock();}}public void writeData(int newData) {writeLock.lock();try {data = newData;} finally {writeLock.unlock();}}
}
45.2 StampedLock
概述
StampedLock
是 Java 8 引入的一种新的锁机制,它支持三种模式:写锁、读锁和乐观读。乐观读模式下不会阻塞写操作,能在一定程度上提高并发性能。
示例代码
import java.util.concurrent.locks.StampedLock;class StampedLockExample {private final StampedLock stampedLock = new StampedLock();private int data;public int optimisticReadData() {long stamp = stampedLock.tryOptimisticRead();int currentData = data;if (!stampedLock.validate(stamp)) {stamp = stampedLock.readLock();try {currentData = data;} finally {stampedLock.unlockRead(stamp);}}return currentData;}public void writeData(int newData) {long stamp = stampedLock.writeLock();try {data = newData;} finally {stampedLock.unlockWrite(stamp);}}
}
四十六、Java 中的并发集合
46.1 ConcurrentHashMap
概述
ConcurrentHashMap
是线程安全的哈希表实现,它在多线程环境下能高效地进行并发操作。与 HashTable
相比,ConcurrentHashMap
采用分段锁或 CAS(Compare - And - Swap)等机制,减少了锁的粒度,提高了并发性能。
示例代码
import java.util.concurrent.ConcurrentHashMap;public class ConcurrentHashMapExample {public static void main(String[] args) {ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();map.put("key1", 1);map.put("key2", 2);Integer value = map.get("key1");System.out.println(value);}
}
46.2 CopyOnWriteArrayList
概述
CopyOnWriteArrayList
是线程安全的 List
实现,它在进行写操作(如 add
、remove
)时会创建一个原数组的副本,在副本上进行修改,修改完成后将原数组引用指向新的副本。这种方式保证了读操作不需要加锁,适用于读多写少的场景。
示例代码
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;public class CopyOnWriteArrayListExample {public static void main(String[] args) {CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();list.add("element1");list.add("element2");Iterator<String> iterator = list.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());}}
}
46.3 ConcurrentLinkedQueue
概述
ConcurrentLinkedQueue
是线程安全的无界非阻塞队列,基于链表实现。它采用 CAS 操作来保证并发环境下的线程安全,适合在多线程环境下进行高效的队列操作。
示例代码
import java.util.concurrent.ConcurrentLinkedQueue;public class ConcurrentLinkedQueueExample {public static void main(String[] args) {ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();queue.offer("item1");queue.offer("item2");String item = queue.poll();System.out.println(item);}
}
四十七、Java 中的类加载机制
47.1 类加载器的层次结构
Java 中的类加载器分为以下几种,它们构成了一个层次结构:
- 启动类加载器(Bootstrap ClassLoader):负责加载 Java 的核心类库,如
java.lang
包下的类,它是用 C++ 实现的,在 Java 代码中无法直接引用。 - 扩展类加载器(Extension ClassLoader):负责加载 Java 的扩展类库,通常是
jre/lib/ext
目录下的类。 - 应用类加载器(Application ClassLoader):负责加载用户类路径(
classpath
)下的类,它是ClassLoader
类的子类。
47.2 双亲委派模型
概述
双亲委派模型是 Java 类加载器的工作模式。当一个类加载器收到类加载请求时,它首先不会自己去尝试加载这个类,而是把请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都会传送到顶层的启动类加载器。只有当父类加载器反馈自己无法完成该加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
示例代码
public class ClassLoaderExample {public static void main(String[] args) {ClassLoader classLoader = ClassLoaderExample.class.getClassLoader();System.out.println("Current class loader: " + classLoader);System.out.println("Parent class loader: " + classLoader.getParent());System.out.println("Grandparent class loader: " + classLoader.getParent().getParent());}
}
47.3 自定义类加载器
概述
在某些情况下,需要自定义类加载器,例如从网络、数据库等非标准位置加载类。自定义类加载器需要继承 ClassLoader
类,并重写 findClass
方法。
示例代码
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;public class CustomClassLoader extends ClassLoader {private String classPath;public CustomClassLoader(String classPath) {this.classPath = classPath;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {byte[] classData = loadClassData(name);if (classData == null) {throw new ClassNotFoundException();}return defineClass(name, classData, 0, classData.length);}private byte[] loadClassData(String name) {String filePath = classPath + File.separator + name.replace('.', File.separatorChar) + ".class";try (FileInputStream fis = new FileInputStream(filePath);ByteArrayOutputStream bos = new ByteArrayOutputStream()) {byte[] buffer = new byte[1024];int bytesRead;while ((bytesRead = fis.read(buffer)) != -1) {bos.write(buffer, 0, bytesRead);}return bos.toByteArray();} catch (IOException e) {return null;}}
}
四十八、Java 中的枚举的高级用法
48.1 枚举实现单例模式
概述
使用枚举实现单例模式是一种简洁且安全的方式,它能自动处理序列化和反序列化问题,防止反射攻击。
示例代码
enum SingletonEnum {INSTANCE;public void doSomething() {System.out.println("Doing something...");}
}public class EnumSingletonExample {public static void main(String[] args) {SingletonEnum singleton = SingletonEnum.INSTANCE;singleton.doSomething();}
}
48.2 枚举与策略模式结合
概述
可以将枚举与策略模式结合,每个枚举常量代表一种策略,通过不同的枚举常量调用不同的策略方法。
示例代码
enum PaymentStrategy {CREDIT_CARD {@Overridepublic void pay(double amount) {System.out.println("Paying " + amount + " using credit card.");}},PAYPAL {@Overridepublic void pay(double amount) {System.out.println("Paying " + amount + " using PayPal.");}};public abstract void pay(double amount);
}public class EnumStrategyExample {public static void main(String[] args) {PaymentStrategy strategy = PaymentStrategy.CREDIT_CARD;strategy.pay(100.0);}
}
这些 Java 基础知识涵盖了锁机制、并发集合、类加载机制以及枚举的高级用法等方面,进一步丰富了 Java 编程的知识体系。
四十九、Java 中的弱引用、软引用和虚引用
49.1 引用类型概述
在 Java 中,除了强引用,还有弱引用(WeakReference
)、软引用(SoftReference
)和虚引用(PhantomReference
),它们的强度依次减弱,主要用于更灵活地管理对象的生命周期,帮助垃圾回收器进行内存管理。
49.2 弱引用(WeakReference
)
概述
弱引用所引用的对象在垃圾回收时,无论当前内存是否充足,都会被回收。常用于实现一些缓存机制,避免内存泄漏。
示例代码
import java.lang.ref.WeakReference;public class WeakReferenceExample {public static void main(String[] args) {Object obj = new Object();WeakReference<Object> weakRef = new WeakReference<>(obj);obj = null; // 去除强引用System.gc(); // 手动触发垃圾回收Object retrievedObj = weakRef.get();if (retrievedObj == null) {System.out.println("Object has been garbage - collected.");} else {System.out.println("Object is still alive.");}}
}
49.3 软引用(SoftReference
)
概述
软引用所引用的对象在内存充足时不会被回收,但当内存不足时,会被垃圾回收器回收。常用于实现内存敏感的缓存。
示例代码
import java.lang.ref.SoftReference;public class SoftReferenceExample {public static void main(String[] args) {Object obj = new Object();SoftReference<Object> softRef = new SoftReference<>(obj);obj = null; // 去除强引用try {// 模拟内存不足,创建大量对象byte[] bigArray = new byte[1024 * 1024 * 10];} catch (OutOfMemoryError e) {System.out.println("Out of memory error occurred.");}Object retrievedObj = softRef.get();if (retrievedObj == null) {System.out.println("Object has been garbage - collected due to memory shortage.");} else {System.out.println("Object is still alive.");}}
}
49.4 虚引用(PhantomReference
)
概述
虚引用是最弱的一种引用类型,它的主要作用是在对象被垃圾回收时收到一个系统通知。虚引用必须和引用队列(ReferenceQueue
)联合使用。
示例代码
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;public class PhantomReferenceExample {public static void main(String[] args) {Object obj = new Object();ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();PhantomReference<Object> phantomRef = new PhantomReference<>(obj, referenceQueue);obj = null; // 去除强引用System.gc(); // 手动触发垃圾回收if (referenceQueue.poll() != null) {System.out.println("Object has been garbage - collected and detected by phantom reference.");} else {System.out.println("Object may still be alive.");}}
}