三天急速通关JAVA基础知识:Day3 基础加强
三天急速通关JAVA基础知识:Day3 基础加强
- 0 文章说明
- 1 接口
- 2 函数式编程
- 2.1 Lambda表达式
- 2.2 方法引用
- 3 异常
- 3.1 异常的定义
- 3.2 异常的分类
- 3.2.1 检查型异常(Checked Exception)
- 3.2.2 非检查型异常(Unchecked Exception)
- 检查型异常与非检查型异常的区别
- 3.3 异常的处理
- 3.3.1 使用`try-catch`捕获异常
- 3.3.2 使用`try-catch-finally`进行清理操作
- 3.3.3 使用`try-with-resources`自动管理资源
- 3.3.4 使用`throws`声明抛出异常
- 4 泛型
- 4.1 泛型的定义
- 4.2 泛型类,接口,方法
- 4.3 通配符与上下限
- 5 集合
- 5.1 集合的定义
- 5.2 顶层集合接口的常见方法
- 6 Stream流
- 6.1 Stream的定义
- 6.2 Stream常见方法
- 7 IO流
- 8 并发
- 8.1 线程
- 8.2 线程池
- 9 网络编程
- 9.1 基础知识
- 9.2 Java网络编程的一些核心类
- 9.3 TCP编程
- 9.4 UDP编程
- 10 注解
- 10.1 注解的定义
- 10.3 常见注解
- 10.4 元注解
- 11 代理
- 11.1 代理的定义
- 11.2 代理的形式(JDK动态代理)
- Final 考试
0 文章说明
本文目的是为了扩展Java的基础知识体系,在后续实践中逐渐掌握加完善,内容由本人已学习的java知识为基础,kimi与gpt辅助完善。本文所提供的信息和内容仅供参考,作者和发布者不保证其准确性和完整性。
1 接口
-
接口的定义
- 使用
interface
关键字定义接口,是抽象方法和常量的集合,方法默认为public abstract
,字段默认为public static final
。// 定义一个接口 public interface Animal { // 抽象方法 默认是public abstract void makeSound(); // 接口中的字段默认是 public static final int LEGS = 4; }
- 使用
-
接口的特性
- 不能被实例化,抽象方法不能有具体实现
- 可以包含抽象方法、默认方法、静态方法和私有方法,一个接口可以继承多个接口
-
接口的实现
- 使用
implements
关键字实现接口,一个类可以实现多个接口的同时继承一个其他类,语法是先继承后实现。 - 实现接口的类必须实现接口中的所有抽象方法(除非该类是抽象类)
// 实现接口的类 public class Dog implements Animal { @Override public void makeSound() { System.out.println("Woof woof!"); } public static void main(String[] args) { Dog dog = new Dog(); // 输出:Woof woof! dog.makeSound(); // 输出:Number of legs: 4 System.out.println("Number of legs: " + Animal.LEGS); } }
- 使用
-
接口中的方法
- 抽象方法
- 方法默认是抽象方法,没有方法体,只有方法签名
- 默认方法
- 使用
default
关键字定义,可以有具体实现 - 解决了接口方法扩展时的兼容性问题
- 使用
- 静态方法
- 使用
static
关键字定义,可以有具体实现,只能通过接口名调用
- 使用
- 私有方法
- 使用
private
关键字定义,只能在接口内部使用 - 用于辅助默认方法或静态方法的实现
// 定义接口 public interface Calculator { // 抽象方法 int add(int a, int b); // 默认方法 default int subtract(int a, int b) { return a - b; } // 静态方法 static int multiply(int a, int b) { return a * b; } // 私有方法(用于辅助默认方法或静态方法) private int addHelper(int a, int b) { return a + b; } }
- 使用
- 抽象方法
-
接口中的字段
- 接口中的字段默认是
public static final
,即常量,必须赋初值,且不能被修改
- 接口中的字段默认是
-
接口的用途
- 定义规范,规定实现类的行为
- 实现多态,通过接口引用调用实现类的方法
- 解耦合,降低类与类之间的依赖关系
- 提供回调机制,用于事件处理等场景
-
接口与抽象类
- 类单继承类多实现接口,接口多继承接口。
- 接口继承自多个接口,存在方法签名冲突时,编译器会报错。
- 类继承自类并实现接口,父类和接口中存在同名方法时,会优先使用父类的方法。为了避免歧义,显式使用
super
来调用父类方法。 - 类实现多个接口,存在同名的默认方法时,需要在实现类中重写该方法以解决冲突。如果要分别调用两个父接口的方法,可以通过接口引用来实现。
interface Foo { default void bar() { System.out.println("Foo.bar"); } } interface Baz { default void bar() { System.out.println("Baz.bar"); } } class Qux implements Foo, Baz { // 重写默认方法 @Override public void bar() { // 通过接口引用分别调用两个接口中的同名方法 Foo.super.bar(); // 调用Foo接口中的bar方法 Baz.super.bar(); // 调用Baz接口中的bar方法 System.out.println("Qux.bar"); } }
2 函数式编程
2.1 Lambda表达式
- Lambda表达式定义
Java 8引入了Lambda表达式,替代匿名内部类用于简化函数式接口(接口中只有一个抽象方法)的实现,Lambda表达式的语法如下:(参数列表) -> 表达式或语句块
- Lambda表达式的简化
- 省略参数类型
如果Lambda表达式的参数类型可以从上下文中推断出来,则可以省略参数类型。// 显式类型 Comparator<Integer> comparator = (Integer a, Integer b) -> a.compareTo(b); // 省略类型 Comparator<Integer> comparator = (a, b) -> a.compareTo(b);
- 省略括号(单个参数)
如果Lambda表达式只有一个参数,可以省略参数的括号。// 显式括号 Consumer<String> consumer = (String s) -> System.out.println(s); // 省略括号 Consumer<String> consumer = s -> System.out.println(s);
- 省略花括号(单个表达式)
如果Lambda表达式体只包含一个表达式,可以省略花括号{}
,并且表达式的结果会自动返回。// 显式花括号 Function<Integer, Integer> function = (Integer x) -> { return x * 2; }; // 省略花括号 Function<Integer, Integer> function = x -> x * 2;
- 省略
return
关键字(单个表达式)
如果Lambda表达式体只包含一个表达式,可以省略return
关键字,表达式的结果会自动作为返回值。// 显式返回 Function<Integer, Integer> function = (Integer x) -> { return x * 2; }; // 省略`return`关键字 Function<Integer, Integer> function = x -> x * 2;
- 省略参数类型
2.2 方法引用
- 方法引用的定义
方法引用是Lambda表达式的简化形式,它使得代码更加简洁、易读,同时保持了功能的完整性。类名::方法名 对象名::方法名 类名::new
- 方法引用的分类
- 静态方法引用
// 定义一个静态方法 public class Utils { public static int add(int a, int b) { return a + b; } } // 使用静态方法引用 BinaryOperator<Integer> adder = Utils::add; System.out.println(adder.apply(3, 4));
- 实例方法引用
// 定义一个实例方法 public class Person { private String name; public Person(String name) { this.name = name; } public void greet() { System.out.println("Hello, my name is " + name); } } // 使用实例方法引用 Person person = new Person("Alice"); Runnable task = person::greet; task.run(); // 输出:Hello, my name is Alice
- 类方法引用
// 使用类方法引用 Comparator<String> comparator = String::compareTo; System.out.println(comparator.compare("apple", "banana")); // 输出:负数
- 构造器引用
// 定义一个类 public class Box<T> { private T value; public Box(T value) { this.value = value; } public T getValue() { return value; } } // 使用构造器引用 Function<String, Box<String>> boxFactory = Box::new; Box<String> box = boxFactory.apply("Hello"); System.out.println(box.getValue()); // 输出:Hello
3 异常
3.1 异常的定义
- 异常是一种特殊的对象,用于表示程序运行时出现的错误或异常情况。当程序运行过程中遇到无法正常执行的代码时,就会抛出一个异常对象。
- 所有异常类都继承自
java.lang.Throwable
类。Throwable
类有两个主要的子类:Exception
:表示程序运行时可能遇到的异常情况,这些异常通常可以通过合理的代码逻辑来避免或处理。Error
:表示程序运行时出现的严重错误,通常是系统级的错误,无法通过代码逻辑来处理,例如内存不足(OutOfMemoryError
)或虚拟机错误(VirtualMachineError
)。
3.2 异常的分类
Java中的异常主要分为两大类:检查型异常(Checked Exception)和非检查型异常(Unchecked Exception)。
3.2.1 检查型异常(Checked Exception)
检查型异常是指那些在编译时会被编译器检查的异常。如果方法中可能抛出检查型异常,那么必须显式地处理这些异常,否则编译器会报错。处理方式有两种:
- 使用
try-catch
语句捕获并处理异常。 - 使用
throws
关键字将异常向上抛出,让调用者处理。
检查型异常通常用于那些可以通过合理逻辑处理的错误情况,例如:
IOException
:表示输入输出操作中可能出现的错误,如文件未找到、磁盘空间不足等。SQLException
:表示数据库操作中可能出现的错误,如SQL语句错误、数据库连接失败等。ClassNotFoundException
:表示类加载器无法找到指定的类。
3.2.2 非检查型异常(Unchecked Exception)
非检查型异常是指那些在编译时不会被编译器检查的异常。这些异常通常是由程序逻辑错误引起的,可以选择性处理。非检查型异常又分为两类:
- 运行时异常(RuntimeException):这些异常通常是由程序逻辑错误引起的,例如:
NullPointerException
:尝试访问空对象的成员时抛出。ArrayIndexOutOfBoundsException
:数组索引越界时抛出。ArithmeticException
:如除以零时抛出。
- 错误(Error):这些异常通常表示系统级的严重错误,程序无法处理,例如:
OutOfMemoryError
:内存不足时抛出。StackOverflowError
:栈溢出时抛出。NoClassDefFoundError
:运行时找不到类定义时抛出。
检查型异常与非检查型异常的区别
特性 | 检查型异常(Checked Exception) | 非检查型异常(Unchecked Exception) |
---|---|---|
编译时检查 | 是,必须处理(捕获或向上抛出) | 否,可以选择性处理 |
常见类型 | IOException , SQLException 等 | NullPointerException , ArrayIndexOutOfBoundsException 等 |
是否可恢复 | 通常可以通过合理逻辑处理 | 通常是程序逻辑错误,难以恢复 |
用途 | 外部环境引起的错误(如文件、网络) | 程序内部逻辑错误或系统级错误 |
3.3 异常的处理
Java提供了多种机制来处理异常,主要包括try-catch
、try-catch-finally
、try-with-resources
、throws
和throw
。
3.3.1 使用try-catch
捕获异常
try-catch
是最基本的异常处理方式。try
块中包含可能抛出异常的代码,catch
块用于捕获并处理异常,捕获不同类型的异常,捕获顺序应从子类到父类。
try {
// 可能抛出多个异常的代码
FileInputStream fis = new FileInputStream("file.txt");
int result = 10 / 0;
} catch (FileNotFoundException e) {
System.out.println("文件未找到:" + e.getMessage());
} catch (ArithmeticException e) {
System.out.println("算术异常:" + e.getMessage());
}
3.3.2 使用try-catch-finally
进行清理操作
finally
块用于在try
和catch
块执行后执行清理操作,无论是否捕获到异常,finally
块中的代码都会执行。finally
块通常用于释放资源,如关闭文件流、释放数据库连接等。
FileInputStream fis = null;
try {
fis = new FileInputStream("file.txt");
// 使用文件流读取文件
} catch (FileNotFoundException e) {
System.out.println("文件未找到:" + e.getMessage());
} finally {
// 清理操作:关闭文件流
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
System.out.println("关闭文件流时发生异常:" + e.getMessage());
}
}
}
- 如果
try
或catch
块中没有return
语句,则finally
块会在方法返回之前执行。 - 如果
try
或catch
块中有return
语句,则finally
块会在return
之前执行,不会改变return
的返回值。 - 如果
try
或catch
块中抛出了异常且未被捕获,则finally
块会在异常向上抛出之前执行。
3.3.3 使用try-with-resources
自动管理资源
Java 7开始,try-with-resources
语句用于自动关闭实现了AutoCloseable
接口的资源(如文件流、数据库连接等)。这种方式可以简化资源管理,避免忘记关闭资源导致的资源泄漏。
try (FileInputStream fis = new FileInputStream("input.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(fis));
FileOutputStream fos = new FileOutputStream("output.txt")) {
String line;
while ((line = reader.readLine()) != null) {
fos.write(line.getBytes());
}
} catch (IOException e) {
e.printStackTrace();
}
try-with-resources
语句会在try
块执行完毕后自动调用资源的close()
方法,即使发生异常也会确保资源被关闭。- 资源必须实现
AutoCloseable
接口或其子接口Closeable
,这些接口定义了close()
方法。 try-with-resources
中声明的资源仅在try
块的作用域内有效。- 如果
try
块中和close()
方法中都抛出了异常,则close()
方法抛出的异常会被抑制(suppressed
),主异常会被抛出。可以通过Throwable.getSuppressed()
方法获取被抑制的异常。
3.3.4 使用throws
声明抛出异常
throws
关键字用于在方法签名中声明该方法可能抛出的异常。如果方法中可能抛出检查型异常(Checked Exception),则必须通过throws
声明抛出异常,否则编译器会报错。对于非检查型异常(Unchecked Exception),throws
声明是可选的。public void processFile() throws IOException, SQLException { // 方法实现 }
- 使用throw手动抛出异常,
throw
关键字用于手动抛出一个异常对象,通常用于业务逻辑校验。抛出的异常可以是系统提供的异常类,也可以是自定义异常类。// 通过继承Exception或其子类来创建自定义异常类。 public class MyCustomException extends Exception { public MyCustomException(String message) { super(message); } } public void checkValue(int value) throws MyCustomException { if (value < 0) { throw new MyCustomException("值不能小于0"); } }
4 泛型
4.1 泛型的定义
- 定义:泛型(Generics)是Java语言中的一种特性,允许在定义类、接口和方法时引入类型参数,从而实现类型安全和代码复用。
- 主要作用:
- 类型安全:在编译时检查类型错误,避免运行时的
ClassCastException
。 - 代码复用:通过泛型可以编写通用的类和方法,适用于多种类型。
- 减少类型转换:避免显式的类型转换,使代码更简洁。
- 类型安全:在编译时检查类型错误,避免运行时的
- 泛型的编译原理:
泛型是通过类型擦除实现的。编译器在编译时会将泛型类型替换为Object类型(或其父类),并在需要时插入类型转换代码。运行时,泛型信息会被擦除,因此泛型的类型检查仅在编译时进行。// 类型参数用尖括号`<>`表示,例如`<T>`、`<E>`等。 // 示例:`ArrayList<T>`,其中`T`是类型参数。 // 需要强制类型转换 List list = new ArrayList(); list.add("Hello"); String s = (String) list.get(0); // 不需要强制类型转换 List<String> list = new ArrayList<>(); list.add("Hello"); String s = list.get(0); ```
4.2 泛型类,接口,方法
- 综合示例:
// 泛型类是在类定义中使用类型参数的类,允许在实例化时指定具体的类型。 class MyClass<T> { private T data; public MyClass(T data) { this.data = data; } public T getData() { return data; } public void setData(T data) { this.data = data; } } MyClass<String> stringClass = new MyClass<>("Hello"); MyClass<Integer> intClass = new MyClass<>(123); // 泛型接口是在接口定义中使用类型参数的接口,允许在实现接口时指定具体的类型。 interface MyInterface<T> { T getData(); void setData(T data); } // 泛型方法是指在方法定义中使用类型参数的方法,允许在调用方法时指定具体的类型。 public <T> T methodName(T param) { // 方法体 } ```
4.3 通配符与上下限
- 定义:通配符(Wildcards)是泛型中用于表示未知类型的符号,通常用于方法参数或泛型类的实例化中。
- 无界通配符(
?
):- (
?
)任意类型,无法添加,只能读取。 - 不能用于泛型类的定义,因为无法在类定义中明确指定具体的类型参数。
List<?> list = new ArrayList<>(); Object obj = list.get(0); // 可以读取,但只能作为Object类型 class MyClass<?> { // 错误:不能在类定义中使用无界通配符 }
- (
- 上限通配符(
<? extends T>
):- 类型参数是
T
的子类或T
本身,无法添加,只能读取。 - 编译器无法确定添加的数据是否符合集合的实际类型,因此不允许添加数据(除了
null
,因为null
可以赋值给任何类型)。import java.util.ArrayList; import java.util.List; public class UpperBoundWildcardExample { public static void main(String[] args) { // 创建一个具体的泛型集合 List<Integer> intList = new ArrayList<>(); intList.add(1); intList.add(2); intList.add(3); // 使用上限通配符 printNumbers(intList); // 正确:intList是Number的子类 } // 方法参数使用上限通配符 public static void printNumbers(List<? extends Number> list) { // 遍历并打印集合中的数据 for (Number num : list) { System.out.println(num); } // 尝试添加数据(会报错) // list.add(4); // 错误:无法确定具体类型 list.add(null); // 正确:null可以添加 } }
- 类型参数是
- 下限通配符(
<? super T>
):- 类型参数是
T
的父类或T
本身,无法读取,只能添加数据,编译器无法确定读取的数据的实际类型,因此只能将读取的数据视为Object类型。import java.util.ArrayList; import java.util.List; public class LowerBoundWildcardExample { public static void main(String[] args) { // 创建一个具体的泛型集合 List<Integer> intList = new ArrayList<>(); intList.add(1); intList.add(2); // 使用下限通配符 addNumbers(intList); // 正确:intList是Integer的父类或自身 // 输出结果 System.out.println(intList); // 输出:[1, 2, 3] } // 方法参数使用下限通配符 public static void addNumbers(List<? super Integer> list) { // 可以向集合中添加Integer及其子类的数据 list.add(3); list.add(4); // 尝试读取数据(只能视为Object类型) Object obj = list.get(0); // 正确:只能将读取的数据视为Object类型 // Integer num = list.get(0); // 错误:无法确定具体类型 } }
- 类型参数是
- 注意事项:
- 使用通配符时,尽量遵循“PECS”原则(Producer Extends, Consumer Super):
- 如果需要从集合中读取数据(生产者),使用
<? extends T>
。 - 如果需要向集合中添加数据(消费者),使用
<? super T>
。
- 如果需要从集合中读取数据(生产者),使用
- 通配符的类型参数在运行时会被擦除(类型擦除)。
- 使用通配符时,尽量遵循“PECS”原则(Producer Extends, Consumer Super):
5 集合
5.1 集合的定义
Java集合是Java语言中用于存储和管理对象的框架,它提供了一系列的接口和实现类,用于存储和操作一组对象。
- 集合(Collection): 存储一组对象,可以是单个元素的集合(截图来自B站黑马教程)
- 映射(Map): 存储键值对(key-value)关系,键唯一,值可以重复。(截图来自B站黑马教程)
5.2 顶层集合接口的常见方法
- Collection:
方法签名 | 描述 |
---|---|
boolean add(E e) | 将指定元素添加到集合中(如果支持此操作)。 |
boolean addAll(Collection<? extends E> c) | 将指定集合中的所有元素添加到此集合中。 |
void clear() | 移除集合中的所有元素(如果支持此操作)。 |
boolean contains(Object o) | 检查集合是否包含指定元素。 |
boolean containsAll(Collection<?> c) | 检查集合是否包含指定集合中的所有元素。 |
boolean isEmpty() | 检查集合是否为空。 |
Iterator<E> iterator() | 返回集合的迭代器。 |
boolean remove(Object o) | 从集合中移除指定元素(如果支持此操作)。 |
boolean removeAll(Collection<?> c) | 移除集合中所有与指定集合相同的元素。 |
boolean retainAll(Collection<?> c) | 仅保留集合中包含在指定集合中的元素。 |
int size() | 返回集合中的元素数量。 |
Object[] toArray() | 返回包含集合中所有元素的数组。 |
<T> T[] toArray(IntFunction<T[]> generator) | 返回包含集合中所有元素的数组,类型由生成器指定。 |
- Map<K,V>:
方法签名 | 描述 |
---|---|
void clear() | 移除 Map 中的所有键值对。 |
boolean containsKey(Object key) | 检查 Map 是否包含指定的键。 |
boolean containsValue(Object value) | 检查 Map 是否包含指定的值。 |
Set<Map.Entry<K, V>> entrySet() | 返回 Map 中所有键值对的集合视图。 |
V get(Object key) | 返回指定键所映射的值,如果键不存在则返回 null 。 |
V getOrDefault(Object key, V defaultValue) | 返回指定键所映射的值,如果键不存在则返回默认值。 |
boolean isEmpty() | 检查 Map 是否为空。 |
Set<K> keySet() | 返回 Map 中所有键的集合视图。 |
V put(K key, V value) | 将指定的键值对插入到 Map 中,如果键已存在,则替换其值。 |
void putAll(Map<? extends K, ? extends V> m) | 将指定 Map 中的所有键值对复制到当前 Map 中。 |
V remove(Object key) | 移除指定键的键值对,并返回其值。 |
void replace(K key, V value) | 替换指定键的值(如果键存在)。 |
int size() | 返回 Map 中键值对的数量。 |
Collection<V> values() | 返回 Map 中所有值的集合视图。 |
default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) | 如果指定键不存在,则使用给定的映射函数计算其值并插入。 |
default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) | 如果指定键存在,则使用给定的函数重新计算其值。 |
default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) | 计算指定键的值,无论键是否存在。 |
default V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) | 如果键不存在,则插入键值对;如果键存在,则使用给定的函数重新计算其值。 |
6 Stream流
6.1 Stream的定义
Stream
是一个来自 java.util.stream
包的接口,它表示一个元素的序列。这个序列可以是有限的,也可以是无限的。Stream
的设计灵感来源于函数式编程语言中的流式操作,它支持一系列操作,如过滤、映射、排序、归并等,这些操作可以组合起来形成复杂的流水线。
Stream
本身不是数据结构,它不会存储数据,而是对数据源(如集合、数组)的抽象表示。Stream
只有在终端操作被调用时,才会真正执行中间操作。Stream
执行了终端操作,就不能再次被使用Stream
的中间操作可以链式调用,终端操作结束List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date"); // 输出:["BANANA", "CHERRY"] List<String> result = fruits.stream() .filter(s -> s.length() > 5) .map(String::toUpperCase) .sorted() .collect(Collectors.toList());
6.2 Stream常见方法
方法名称 | 功能描述 | 示例代码 |
---|---|---|
filter | 筛选满足条件的元素 | java numbers.stream().filter(n -> n % 2 == 0).collect(Collectors.toList()); |
map | 对每个元素应用函数 | java fruits.stream().map(String::length).collect(Collectors.toList()); |
flatMap | 将每个元素映射为流并扁平化 | java nestedList.stream().flatMap(List::stream).collect(Collectors.toList()); |
sorted | 对元素排序 | java numbers.stream().sorted().collect(Collectors.toList()); |
distinct | 去除重复元素 | java numbers.stream().distinct().collect(Collectors.toList()); |
forEach | 对每个元素执行操作 | java numbers.stream().forEach(System.out::println); |
collect | 收集结果到集合 | java numbers.stream().filter(n -> n % 2 == 0).collect(Collectors.toList()); |
reduce | 对元素进行归并操作 | java numbers.stream().reduce(0, Integer::sum); |
count | 返回元素数量 | java numbers.stream().filter(n -> n % 2 == 0).count(); |
min 和 max | 返回最小值或最大值 | java numbers.stream().min(Integer::compare).get(); |
anyMatch 、allMatch 、noneMatch | 检查条件是否满足 | java numbers.stream().anyMatch(n -> n % 2 == 0); |
toArray | 收集到数组 | java numbers.stream().toArray(Integer[]::new); |
groupingBy | 按属性分组 | java fruits.stream().collect(Collectors.groupingBy(String::length)); |
joining | 连接字符串元素 | java fruits.stream().collect(Collectors.joining(", ")); |
7 IO流
- Java的
IO
流是Java语言中用于处理输入输出的核心机制,它提供了一套丰富的类和接口来操作数据的读取和写入。IO
流分为两大类:字节流(InputStream
和OutputStream
)和字符流(Reader
和Writer
) - 常见
IO
流:
流类型 | 继承关系 | 用途 | 特点 |
---|---|---|---|
InputStream | 抽象类 | 字节输入流的基类 | 用于从源读取字节数据,是所有字节输入流的父类。 |
OutputStream | 抽象类 | 字节输出流的基类 | 用于向目标写入字节数据,是所有字节输出流的父类。 |
Reader | 抽象类 | 字符输入流的基类 | 用于从源读取字符数据,支持字符编码,是所有字符输入流的父类。 |
Writer | 抽象类 | 字符输出流的基类 | 用于向目标写入字符数据,支持字符编码,是所有字符输出流的父类。 |
FileInputStream | InputStream | 从文件读取字节数据 | 提供文件的字节输入流,适用于二进制文件。 |
FileOutputStream | OutputStream | 向文件写入字节数据 | 提供文件的字节输出流,适用于二进制文件。 |
FileReader | Reader | 从文件读取字符数据 | 提供文件的字符输入流,适用于文本文件,支持字符编码。 |
FileWriter | Writer | 向文件写入字符数据 | 提供文件的字符输出流,适用于文本文件,支持字符编码。 |
BufferedInputStream | InputStream | 缓冲字节输入流 | 提供缓冲功能,提高字节输入流的读取效率。 |
BufferedOutputStream | OutputStream | 缓冲字节输出流 | 提供缓冲功能,提高字节输出流的写入效率。 |
BufferedReader | Reader | 缓冲字符输入流 | 提供缓冲功能,支持按行读取,提高字符输入流的读取效率。 |
BufferedWriter | Writer | 缓冲字符输出流 | 提供缓冲功能,支持批量写入,提高字符输出流的写入效率。 |
DataInputStream | InputStream | 读取基本数据类型 | 从输入流中读取基本数据类型(如int、double等)。 |
DataOutputStream | OutputStream | 写入基本数据类型 | 向输出流中写入基本数据类型(如int、double等)。 |
ObjectInputStream | InputStream | 反序列化对象 | 从输入流中读取序列化的Java对象。 |
ObjectOutputStream | OutputStream | 序列化对象 | 向输出流中写入可序列化的Java对象。 |
InputStreamReader | Reader | 字节流转字符流 | 将字节输入流转换为字符输入流,支持字符编码。 |
OutputStreamWriter | Writer | 字节流转字符流 | 将字节输出流转换为字符输出流,支持字符编码。 |
ByteArrayInputStream | InputStream | 从字节数组读取数据 | 从内存中的字节数组读取数据,常用于测试。 |
ByteArrayOutputStream | OutputStream | 向字节数组写入数据 | 将数据写入内存中的字节数组,常用于缓冲或数据转换。 |
CharArrayReader | Reader | 从字符数组读取数据 | 从内存中的字符数组读取数据,常用于测试。 |
CharArrayWriter | Writer | 向字符数组写入数据 | 将数据写入内存中的字符数组,常用于缓冲或数据转换。 |
PipedInputStream | InputStream | 管道输入流 | 用于线程间通信,数据从管道输出流流向管道输入流。 |
PipedOutputStream | OutputStream | 管道输出流 | 用于线程间通信,数据从管道输出流流向管道输入流。 |
PipedReader | Reader | 管道字符输入流 | 用于线程间通信,字符数据从管道字符输出流流向管道字符输入流。 |
PipedWriter | Writer | 管道字符输出流 | 用于线程间通信,字符数据从管道字符输出流流向管道字符输入流。 |
SequenceInputStream | InputStream | 合并多个输入流 | 将多个输入流按顺序连接成一个输入流。 |
PrintStream | OutputStream | 格式化输出流 | 提供格式化输出功能,常用于控制台输出(如System.out )。 |
PrintWriter | Writer | 格式化字符输出流 | 提供格式化字符输出功能,支持自动刷新。 |
FilterInputStream | InputStream | 输入流过滤器 | 提供输入流的扩展功能,是其他过滤输入流的基类。 |
FilterOutputStream | OutputStream | 输出流过滤器 | 提供输出流的扩展功能,是其他过滤输出流的基类。 |
FilterReader | Reader | 字符输入流过滤器 | 提供字符输入流的扩展功能,是其他过滤字符输入流的基类。 |
FilterWriter | Writer | 字符输出流过滤器 | 提供字符输出流的扩展功能,是其他过滤字符输出流的基类。 |
- 字节流文件读写:
import java.io.*; public class FileIOExample { public static void main(String[] args) { String filePath = "example.txt"; // 写入文件 try (FileOutputStream fos = new FileOutputStream(filePath)) { String content = "Hello, Java IO!"; fos.write(content.getBytes()); System.out.println("File written successfully."); } catch (IOException e) { e.printStackTrace(); } // 读取文件 try (FileInputStream fis = new FileInputStream(filePath)) { byte[] buffer = new byte[1024]; int length; while ((length = fis.read(buffer)) != -1) { System.out.println(new String(buffer, 0, length)); } } catch (IOException e) { e.printStackTrace(); } } }
- 字符缓冲流
import java.io.*; public class BufferedExample { public static void main(String[] args) { String filePath = "example.txt"; // 写入文件 try (BufferedWriter bw = new BufferedWriter(new FileWriter(filePath))) { bw.write("Hello, Java Buffered IO!"); bw.newLine(); // 写入换行符 bw.write("This is a new line."); System.out.println("File written successfully."); } catch (IOException e) { e.printStackTrace(); } // 读取文件 try (BufferedReader br = new BufferedReader(new FileReader(filePath))) { String line; while ((line = br.readLine()) != null) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } } }
- 数据流
import java.io.*; public class DataStreamExample { public static void main(String[] args) { String filePath = "data.bin"; // 写入数据 try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(filePath))) { dos.writeInt(123); dos.writeDouble(98.6); dos.writeUTF("Hello, Data Stream!"); System.out.println("Data written successfully."); } catch (IOException e) { e.printStackTrace(); } // 读取数据 try (DataInputStream dis = new DataInputStream(new FileInputStream(filePath))) { int i = dis.readInt(); double d = dis.readDouble(); String s = dis.readUTF(); System.out.println("Read int: " + i); System.out.println("Read double: " + d); System.out.println("Read string: " + s); } catch (IOException e) { e.printStackTrace(); } } }
- 对象流
import java.io.*; public class ObjectStreamExample { public static void main(String[] args) { String filePath = "object.bin"; // 序列化对象 try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath))) { Person person = new Person("John Doe", 30); oos.writeObject(person); System.out.println("Object serialized successfully."); } catch (IOException e) { e.printStackTrace(); } // 反序列化对象 try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath))) { Person person = (Person) ois.readObject(); System.out.println("Object deserialized: " + person); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } } // 可序列化的Person类 class Person implements Serializable { private static final long serialVersionUID = 1L; private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person{name='" + name + "', age=" + age + "}"; } }
8 并发
Java
的并发是指在Java
程序中同时执行多个任务的能力。并发编程可以让程序更高效地利用多核处理器的资源,提高程序的性能和响应能力。
8.1 线程
线程是Java
并发编程的基本单位,是程序执行的最小单元。在Java
中,线程的创建和管理由java.lang.Thread
类和java.lang.Runnable
接口支持。
- 创建线程的三种方式:继承Thread,实现Runnable ,实现Callable <T>
// 继承Thread类 // 特点:Java不支持多继承,继承Thread类后无法再继承其他类。 class MyThread extends Thread { @Override public void run() { System.out.println("Running in thread: " + Thread.currentThread().getName()); } } public class ThreadExample { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); // 启动线程 } } // 实现Runnable接口 // 特点:符合面向对象的设计原则,避免了多继承问题。 class MyRunnable implements Runnable { @Override public void run() { System.out.println("Running in thread: " + Thread.currentThread().getName()); } } public class ThreadExample { public static void main(String[] args) { Thread thread = new Thread(new MyRunnable()); thread.start(); // 启动线程 } } // 实现Callable接口 // 特点:Callable接口与Runnable类似,但支持返回值和抛出异常。通常与Future一起使用,适用于需要获取异步任务结果的场景。 import java.util.concurrent.*; class MyCallable implements Callable<Integer> { @Override public Integer call() throws Exception { System.out.println("Running in thread: " + Thread.currentThread().getName()); return 42; // 返回值 } } public class CallableExample { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executor = Executors.newSingleThreadExecutor(); Future<Integer> future = executor.submit(new MyCallable()); System.out.println("Result: " + future.get()); // 获取结果 executor.shutdown(); } }
- 线程的生命周期:
- 新建(New):线程被创建,但尚未调用
start()
方法。 - 运行(Runnable):线程已启动,正在运行或等待CPU时间片。
- 阻塞(Blocked):线程尝试获取对象锁时,锁被其他线程持有。
- 等待(Waiting):线程调用
wait()
、join()
或LockSupport.park()
方法进入等待状态。 - 超时等待(Timed Waiting):线程调用
sleep()
、wait(timeout)
或LockSupport.parkNanos()
等方法进入超时等待状态。 - 终止(Terminated):线程执行完毕或因异常退出。
- 新建(New):线程被创建,但尚未调用
- 线程安全:
当多个线程访问共享资源时,可能会出现数据竞争(多个线程同时修改共享数据,导致结果不确定),线程不安全(有些集合类在多线程环境下可能会出现ConcurrentModificationException
)synchronized
关键字是Java中最基本的同步机制,用于确保同一时间只有一个线程可以访问共享资源。// 同步方法:整个方法加锁。 public synchronized void increment() { count++; } // 同步代码块:对特定对象加锁,更细粒度 public void increment() { synchronized (this) { // 对当前对象加锁 count++; } }
- 锁(
Lock
),java.util.concurrent.locks
包提供了更灵活的锁机制,如ReentrantLock
。import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Counter { private int count = 0; private final Lock lock = new ReentrantLock(); public void increment() { lock.lock(); // 加锁 try { count++; } finally { lock.unlock(); // 释放锁 } } }
8.2 线程池
线程池的主要目的是减少线程创建和销毁的开销,提高程序性能。线程池会预先创建一定数量的线程,并将它们放入一个池中。当有任务需要执行时,线程池会分配一个空闲线程来执行任务,而不是每次都创建新线程。任务执行完成后,线程会返回线程池,等待下一个任务。
java.util.concurrent.Executors
快速创建线程池// 固定大小线程池(newFixedThreadPool) // 固定大小线程池会创建一个固定数量的线程,并且线程数量不会自动扩展。 // 创建4个线程的线程池 ExecutorService executor = Executors.newFixedThreadPool(4); // 单线程线程池(newSingleThreadExecutor) // 单线程线程池确保所有任务都在同一个线程中按顺序执行,适用于需要保证任务顺序的场景。 ExecutorService executor = Executors.newSingleThreadExecutor(); // 缓存线程池(newCachedThreadPool) // 缓存线程池会根据需要创建线程,但空闲线程会在60秒后被回收。适合执行大量短期异步任务。 ExecutorService executor = Executors.newCachedThreadPool(); // 可调度线程池(newScheduledThreadPool) // 可调度线程池支持任务的定时执行和周期性执行。 ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); executor.schedule(() -> System.out.println("Hello"), 5, TimeUnit.SECONDS); // 5秒后执行 executor.scheduleAtFixedRate(() -> System.out.println("Tick"), 0, 2, TimeUnit.SECONDS); // 每2秒执行一次
java.util.concurrent.Executors
创建自定义线程池corePoolSize
:核心线程数。maximumPoolSize
:最大线程数。keepAliveTime
:空闲线程存活时间。unit
:存活时间的单位。workQueue
:任务队列,用于存储等待执行的任务。threadFactory
:线程工厂,用于创建线程。handler
:拒绝策略,当任务过多时的处理方式。
import java.util.concurrent.*; public class CustomThreadPoolExample { public static void main(String[] args) { ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, // 核心线程数 4, // 最大线程数 60, // 空闲线程存活时间 TimeUnit.SECONDS, // 时间单位 new LinkedBlockingQueue<>(10), // 任务队列 Executors.defaultThreadFactory(), // 线程工厂 new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略 ); for (int i = 0; i < 20; i++) { executor.submit(() -> { System.out.println("Task executed by: " + Thread.currentThread().getName()); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } }); } executor.shutdown(); // 关闭线程池 } }
- 线程池的关闭
shutdown()
:尝试关闭线程池,不再接受新任务,但会等待已提交的任务完成。shutdownNow()
:立即关闭线程池,尝试中断正在执行的任务,并返回尚未执行的任务列表。
9 网络编程
Java
网络编程主要涉及基于TCP/IP
和UDP
协议的网络通信,通过Java
的网络API
实现客户端与服务器之间的数据交互。
9.1 基础知识
-
TCP/IP协议栈
- 应用层:如
HTTP
、FTP
、SMTP
等,用于具体的应用程序通信。 - 传输层:如
TCP
和UDP
,负责数据的传输和可靠性。 - 网络层:如
IP
,负责数据包的路由和转发。 - 链路层:负责物理链路上的数据传输。
- 应用层:如
-
TCP协议:面向连接,提供可靠的字节流服务
- 优缺点:可靠性强,数据完整性和顺序有保障,但连接建立和维护成本高,传输效率相对较低。
- 应用场景:Web服务器(
HTTP/HTTPS
),数据库通信(MySQL
、PostgreSQL
),远程桌面(SSH
)
-
UDP协议:不需要建立连接,不保证数据的可靠性和顺序
- 优缺点:传输速度快,延迟低,但不保证数据的可靠性和顺序,可能丢失数据。
- 应用场景:实时音视频通信(
VoIP
、视频会议),游戏(需要低延迟的场景),DNS
查询
-
网络编程模型:
C/S
模型(客户端/服务器模型):客户端主动发起请求,服务器被动监听并响应请求。P2P
模型(对等网络模型):每个节点既可以作为客户端,也可以作为服务器,适用于分布式文件共享等场景。
9.2 Java网络编程的一些核心类
Java的java.net包提供了丰富的类和接口,用于实现网络编程。
-
InetAddress
- 用途:表示IP地址,用于获取主机信息和域名解析。
- 常用方法:
static InetAddress getLocalHost()
:获取本地主机的IP地址。static InetAddress getByName(String host)
:根据主机名获取IP地址。String getHostName()
:获取主机名。byte[] getAddress()
:获取IP地址的字节数组。
-
Socket
- 用途:表示TCP客户端套接字,用于与服务器建立连接并进行通信。
- 常用方法:
Socket(String host, int port)
:连接到指定主机和端口。InputStream getInputStream()
:获取输入流,用于读取服务器发送的数据。OutputStream getOutputStream()
:获取输出流,用于向服务器发送数据。void close()
:关闭套接字。
-
ServerSocket
- 用途:表示TCP服务器套接字,用于监听客户端连接请求。
- 常用方法:
ServerSocket(int port)
:在指定端口监听客户端连接。Socket accept()
:接受客户端连接请求,返回一个与客户端连接的Socket
对象。void close()
:关闭服务器套接字。
-
DatagramSocket
- 用途:表示UDP客户端或服务器套接字,用于发送和接收UDP数据报。
- 常用方法:
DatagramSocket(int port)
:在指定端口监听UDP数据报。void send(DatagramPacket p)
:发送UDP数据报。void receive(DatagramPacket p)
:接收UDP数据报。void close()
:关闭套接字。
-
DatagramPacket
- 用途:表示UDP数据报,封装了发送或接收的数据。
- 常用方法:
DatagramPacket(byte[] buf, int length)
:构造一个用于接收数据报的包。DatagramPacket(byte[] buf, int length, InetAddress address, int port)
:构造一个用于发送数据报的包。byte[] getData()
:获取数据报的内容。int getLength()
:获取数据报的长度。InetAddress getAddress()
:获取发送方或接收方的IP地址。int getPort()
:获取发送方或接收方的端口号。
-
URL
- 用途:表示统一资源定位符,用于访问网络资源。
- 常用方法:
URL(String spec)
:根据URL字符串构造一个URL
对象。URLConnection openConnection()
:打开到该URL的连接。String getProtocol()
:获取协议类型(如http
、https
、ftp
等)。String getHost()
:获取主机名。int getPort()
:获取端口号。String getPath()
:获取资源路径。
-
URLConnection
- 用途:表示与URL的连接,用于读取或写入网络资源。
- 常用方法:
InputStream getInputStream()
:获取输入流,用于读取资源。OutputStream getOutputStream()
:获取输出流,用于写入资源。void connect()
:建立连接。int getContentLength()
:获取资源的内容长度。String getContentType()
:获取资源的内容类型(如text/html
、application/json
等)。
-
HttpURLConnection
- 用途:表示HTTP协议的连接,用于实现HTTP请求和响应。
- 常用方法:
void setRequestMethod(String method)
:设置请求方法(如GET
、POST
、PUT
等)。void setRequestProperty(String key, String value)
:设置请求头。int getResponseCode()
:获取HTTP响应码。String getResponseMessage()
:获取HTTP响应消息。InputStream getInputStream()
:获取响应输入流。OutputStream getOutputStream()
:获取请求输出流。
9.3 TCP编程
- TCP服务端
ServerSocket
:用于监听客户端的连接请求。Socket accept()
:接受客户端连接请求,返回一个与客户端通信的Socket
对象。void close()
:关闭服务器套接字,停止监听。
Socket
:用于与客户端建立连接并进行通信。InputStream getInputStream()
:获取输入流,用于读取客户端发送的数据。OutputStream getOutputStream()
:获取输出流,用于向客户端发送数据。void close()
:关闭与客户端的连接。
import java.io.*; import java.net.*; public class TCPServer { public static void main(String[] args) { int port = 8888; try (ServerSocket serverSocket = new ServerSocket(port)) { System.out.println("Server is running on port " + port); while (true) { // 等待客户端连接 Socket clientSocket = serverSocket.accept(); System.out.println("Client connected: " + clientSocket.getInetAddress()); // 获取输入输出流 try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)) { // 读取客户端消息 String message = in.readLine(); System.out.println("Received from client: " + message); // 向客户端发送响应 out.println("Hello from server!"); } } } catch (IOException e) { e.printStackTrace(); } } } ```
- TCP客户端
Socket
:用于与服务器建立连接并进行通信。InputStream getInputStream()
:获取输入流,用于读取服务器发送的数据。OutputStream getOutputStream()
:获取输出流,用于向服务器发送数据。void close()
:关闭与服务器的连接。
import java.io.*; import java.net.*; public class TCPClient { public static void main(String[] args) { String host = "localhost"; // 服务器地址 int port = 8888; // 服务器端口 try (Socket socket = new Socket(host, port); PrintWriter out = new PrintWriter(socket.getOutputStream(), true); BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) { // 向服务器发送消息 out.println("Hello, server!"); // 读取服务器响应 String response = in.readLine(); System.out.println("Server response: " + response); } catch (IOException e) { e.printStackTrace(); } } }
9.4 UDP编程
- UDP服务端
import java.io.*; import java.net.*; public class UDPServer { public static void main(String[] args) { int port = 8888; try (DatagramSocket socket = new DatagramSocket(port)) { System.out.println("UDP Server is running on port " + port); byte[] buffer = new byte[1024]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length); while (true) { // 接收客户端数据报 socket.receive(packet); String message = new String(packet.getData(), 0, packet.getLength()); System.out.println("Received from client: " + message); // 构造响应数据报 String response = "Hello from UDP server!"; byte[] responseBytes = response.getBytes(); DatagramPacket responsePacket = new DatagramPacket(responseBytes, responseBytes.length, packet.getAddress(), packet.getPort()); socket.send(responsePacket); } } catch (IOException e) { e.printStackTrace(); } } }
- UDP客户端
import java.io.*; import java.net.*; public class UDPClient { public static void main(String[] args) { String host = "localhost"; int port = 8888; try (DatagramSocket socket = new DatagramSocket()) { // 构造发送的数据报 String message = "Hello, UDP server!"; byte[] data = message.getBytes(); DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName(host), port); socket.send(packet); // 接收服务器响应 byte[] buffer = new byte[1024]; DatagramPacket responsePacket = new DatagramPacket(buffer, buffer.length); socket.receive(responsePacket); String response = new String(responsePacket.getData(), 0, responsePacket.getLength()); System.out.println("Server response: " + response); } catch (IOException e) { e.printStackTrace(); } } }
10 注解
10.1 注解的定义
注解(Annotation
)是Java
语言中的一种特殊类型的接口,用于为程序代码提供元数据信息。它并不直接改变代码的逻辑,而是为代码添加额外的说明或指令。注解可以应用于类、方法、字段、参数等程序元素上。
- 注解的语法:注解以
@
符号开头,后跟注解名称。例如:@Override
。 - 注解本质上是一种接口,但它不能被直接实例化。注解的定义使用
@interface
关键字。
10.3 常见注解
注解名称 | 作用范围 | 说明 |
---|---|---|
@Override | 方法 | 表示当前方法覆盖父类中的方法,用于确保方法签名的正确性。 |
@Deprecated | 类、方法、字段 | 标记类、方法或字段已过时,不推荐使用。IDE通常会发出警告。 |
@SuppressWarnings | 类、方法、字段、局部变量 | 抑制编译器警告,常用于忽略特定的编译器警告(如未使用的变量、泛型警告等)。 |
@SafeVarargs | 方法、构造器 | 用于标记使用变长参数的方法或构造器是安全的,避免泛型相关的警告。 |
@FunctionalInterface | 接口 | 标记一个接口为函数式接口,确保接口中只有一个抽象方法。 |
10.4 元注解
- 元注解是用于修饰注解的注解
- 常见元注解:
元注解名称 | 作用范围 | 说明 |
---|---|---|
@Retention | 注解类型 | 定义注解的保留策略(SOURCE 、CLASS 、RUNTIME )。 |
@Target | 注解类型 | 定义注解可以应用的目标范围(如类、方法、字段等)。 |
@Documented | 注解类型 | 表示注解将被包含在JavaDoc文档中。 |
@Inherited | 注解类型 | 表示注解可以被子类继承(仅对类有效)。 |
import java.lang.annotation.*;
// 自定义注解 @MyAnnotation
@Retention(RetentionPolicy.RUNTIME) // 指定注解的保留策略为运行时
@Target({ElementType.TYPE, ElementType.METHOD}) // 指定注解可以应用于类和方法
@Documented // 指定注解将被包含在JavaDoc文档中
@Inherited // 指定注解可以被子类继承
@interface MyAnnotation {
String value() default "default value"; // 注解的元素,提供默认值
}
// 使用自定义注解
@MyAnnotation("This is a class annotation")
class MyClass {
@MyAnnotation("This is a method annotation")
public void myMethod() {
System.out.println("This is my method.");
}
}
// 子类继承父类的注解
class MySubClass extends MyClass {
}
// 测试类
public class AnnotationExample {
public static void main(String[] args) {
// 检查类注解
MyAnnotation classAnnotation = MyClass.class.getAnnotation(MyAnnotation.class);
if (classAnnotation != null) {
System.out.println("Class annotation value: " + classAnnotation.value());
}
// 检查方法注解
MyAnnotation methodAnnotation = MyClass.class.getMethod("myMethod").getAnnotation(MyAnnotation.class);
if (methodAnnotation != null) {
System.out.println("Method annotation value: " + methodAnnotation.value());
}
// 检查子类是否继承了父类的注解
MyAnnotation subClassAnnotation = MySubClass.class.getAnnotation(MyAnnotation.class);
if (subClassAnnotation != null) {
System.out.println("SubClass annotation value: " + subClassAnnotation.value());
}
}
}
11 代理
11.1 代理的定义
代理是一种设计模式,允许通过一个代理对象来控制对目标对象的访问。代理对象作为目标对象的“中间人”,在不改变目标对象代码的情况下,添加额外的功能或行为。
11.2 代理的形式(JDK动态代理)
通过 java.lang.reflect.Proxy
和 InvocationHandler
动态生成代理类。
逻辑:目标类实现接口再生成一个对象,利用Proxy.newProxyInstance
生成代理对象,对象需要三个参数,一个是类加载器,一个是代理类需要实现的接口数组,还有就是调用处理器InvocationHandler
。匿名类的方式重写调用处理器的invoke
方法。
// 定义接口
public interface HelloService {
void sayHello();
}
// 实现接口
public class HelloServiceImpl implements HelloService {
@Override
public void sayHello() {
System.out.println("Hello, world!");
}
}
// 实现 InvocationHandler
import java.lang.reflect.*;
public class MyInvocationHandler implements InvocationHandler {
private final Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method: " + method.getName());
// 调用目标对象的方法
Object result = method.invoke(target, args);
System.out.println("After method: " + method.getName());
return result;
}
}
// 创建代理对象并调用
import java.lang.reflect.Proxy;
public class JDKProxyExample {
public static void main(String[] args) {
// 目标对象
HelloService target = new HelloServiceImpl();
// 创建调用处理器
MyInvocationHandler handler = new MyInvocationHandler(target);
// 创建代理对象
HelloService proxyInstance = (HelloService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),// 类加载器,一般就用target的
new Class<?>[]{HelloService.class},// 接口数组
handler// 调用处理器
);
// 调用代理对象的方法
proxyInstance.sayHello();
}
}
Final 考试
自己练去吧!