Java基础知识二
二十、断言(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 {
@Override
public 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 中的值,如果值不存在会抛出 NoSuchElementException
String 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 {
// 创建一个已经完成的 CompletableFuture
CompletableFuture<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");
// 组合两个 CompletableFuture
CompletableFuture<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 {
@Override
public void makeSound() {
System.out.println("Woof!");
}
}
non-sealed class Cat implements Animal {
@Override
public 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 的子类
@Override
Dog 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 {
@Override
public void request() {
System.out.println("RealSubject: Handling request.");
}
}
// 实现 InvocationHandler 接口
class ProxyHandler implements InvocationHandler {
private Object target;
public ProxyHandler(Object target) {
this.target = target;
}
@Override
public 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;
}
@Override
public 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;
}
@Override
public 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;
}
@Override
protected 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 {
@Override
public void pay(double amount) {
System.out.println("Paying " + amount + " using credit card.");
}
},
PAYPAL {
@Override
public 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.");
}
}
}
本文来自博客园,作者:练剑哥,转载请注明原文链接:https://www.cnblogs.com/qiaozc/p/18710825

浙公网安备 33010602011771号