3.1.1.Java基础知识

3.1.编程语言

Java:广泛应用于企业级应用、微服务架构,具有强大的生态系统,Spring框架特别流行。

Python:语法简洁,开发效率高,Django和Flask是常用的后端框架。

JavaScript/TypeScript (Node.js):作为全栈开发语言,Node.js使得JavaScript可以在后端使用,适合高并发、I/O密集型应用。

Go (Golang):以高性能、高并发著称,适合构建大规模分布式系统。

Ruby:Ruby on Rails框架非常流行,特别适合快速开发。

PHP:特别适合构建Web应用程序,拥有广泛的支持和生态系统,Laravel是常用的框架。

C#:Microsoft技术栈的一部分,使用.NET Core来构建高效的后端服务。

Kotlin:用于构建后端服务的现代编程语言,特别适合与Spring框架结合使用。

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时,使得 JavaScript 可以在服务器端执行,用于构建高性能的网络应用。

3.1.1.Java

Java 语言技术体系是由 Java 语言本身、相关工具、运行时环境(JVM)、标准库以及各类框架和开发工具等组成的庞大生态系统。Java 语言在不同的层面和领域中有着广泛的应用,特别是在企业级应用、Web 开发、移动开发、分布式系统等方面。

3.1.1.Java 编程语言本身

Java 是一门面向对象的编程语言,具有以下核心概念:

封装:将数据和操作数据的代码封装在一起,隐藏实现细节,提供公共接口。

继承:通过继承机制,可以重用代码并构建层次结构。

多态:允许对象以不同的形式呈现,支持方法重载和方法覆盖。

抽象:通过抽象类和接口隐藏复杂性,暴露必要的操作。

3.1.2.Java数据类型
3.1.2.1.Java的数据类型

Java 支持基本数据类型和引用数据类型:

基本数据类型:整数类型(如 int, long),浮点类型(如 float, double),字符类型(char),布尔类型(boolean)。

引用数据类型:类、接口、数组等。

3.1.2.2.集合

各集合类型之间的对比:

Java 的集合框架提供了多种数据结构的实现,如:

List:有序集合,允许重复元素,常用实现有 ArrayList 和 LinkedList。

Set:无序集合,不允许重复元素,常用实现有 HashSet 和 TreeSet。

Map:键值对集合,常用实现有 HashMap 和 TreeMap。

Queue:队列集合,常用实现有 LinkedList 和 PriorityQueue。

3.1.2.3.Java类型转换原则

在 Java 中,类型转换是指将一个数据类型的值转换为另一个数据类型的过程。Java 中的类型转换可以分为两种主要类型:自动类型转换(隐式转换)和强制类型转换(显式转换)。这两种类型转换遵循一定的原则和规则。

1)自动类型转换(隐式转换)

自动类型转换发生在类型兼容且目标类型能容纳源类型的数据时。常见的自动类型转换包括从小类型到大类型(即从精度小的类型到精度大的类型)。自动类型转换通常由编译器自动完成,不需要显式地使用强制转换。

原则:

从小到大:当数据从精度较小的类型(如 byte, short, char)转换为精度较大的类型(如 int, long, float, double)时,可以进行自动转换。

无损转换:如果目标类型的范围能容纳源类型的所有值,则可以进行自动转换。

|-------------------------------------------------------------------------------------|
| int a = 10; double b = a; // 自动类型转换:int 转为 double System.out.println(b); // 输出 10.0 |

2)强制类型转换(显式转换)

强制类型转换发生在当数据从大类型转换为小类型时,或者数据不兼容时。此时必须显式地使用强制类型转换运算符 (目标类型) 来进行转换。强制类型转换可能会丢失精度或引发 ClassCastException 异常。

原则:

从大到小:当数据从大类型(如 double, long)转换为小类型(如 int, short)时,必须使用强制类型转换。

可能的数据丢失:强制类型转换时,如果目标类型的范围不能完全容纳源类型的值,可能会丢失数据。

对象类型转换:对于对象类型,强制类型转换通常用于在继承体系中将父类类型转换为子类类型(反之也可以),但需要确保类型兼容,否则会引发 ClassCastException。

|--------------------------------------------------------------------------------------------------|
| double a = 10.5; int b = (int) a; // 强制类型转换:double 转为 int,丢失小数部分 System.out.println(b); // 输出 10 |

3)基本数据类型转换(基本数据类型与包装类)

Java 中的基本数据类型(int, char, float 等)与它们的包装类(如 Integer, Character, Double 等)之间也可以进行转换。

自动转换:基本数据类型之间可以进行自动转换,例如 int 转 long,float 转 double。

强制转换:包装类对象和基本数据类型之间通常需要手动转换。

4)对象类型转换(类与接口之间)

Java 中的类和接口可以通过 继承 和 实现 来建立层级关系。如果一个类或接口是另一个类或接口的子类或实现类,则可以进行对象类型转换。通常,这种类型转换分为父类到子类(向下转型)和子类到父类(向上转型)。

向上转型(Upcasting)

子类对象可以赋值给父类变量。向上转型是隐式转换,通常不需要强制转换。

向下转型(Downcasting)

父类对象转换为子类对象时,需要强制转换,并且必须确保实际对象类型是子类类型,否则会抛出 ClassCastException。

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| class Animal {} class Dog extends Animal {} public class Test { public static void main(String[] args) { Animal animal = new Dog(); // 向上转型:隐式转换 Dog dog = (Dog) animal; // 向下转型:显式转换 } } |

5)转换原则总结

兼容性:自动类型转换只发生在兼容的类型之间,例如从 int 到 long,或者从父类到子类。

类型范围:数据从小范围类型到大范围类型时自动转换(例如 byte 到 int),从大范围类型到小范围类型时需要强制转换,并且可能丢失精度(例如 double 到 int)。

包装类与基本类型:基本数据类型和其对应的包装类可以通过自动装箱和拆箱来转换,但它们之间的转换如果涉及到不同类型时需要强制转换。

类型检查:进行类型转换时,尤其是对象类型转换,必须保证类型的兼容性,避免 ClassCastException。

3.1.3.异常处理

Java的异常处理机制是Java语言的重要特性之一,它提供了一种系统化的方式来处理程序运行时的错误,保证程序的健壮性和可维护性。以下是Java异常处理机制的详细说明:

3.1.3.1.异常的概念

异常(Exception)是指程序在运行过程中发生的错误或异常事件,可能导致程序终止执行。Java提供了异常处理机制来捕获和处理异常,从而避免程序崩溃。

3.1.3.2.异常的分类

Java的异常分为两大类:

Checked Exception(已检查异常)

编译期必须处理,否则无法通过编译。

例如:IOException、SQLException、FileNotFoundException。

Unchecked Exception(未检查异常)

运行时异常,编译器不会强制要求处理。

例如:NullPointerException、ArrayIndexOutOfBoundsException、ArithmeticException。

Error(错误)

由JVM(Java虚拟机)引起,程序一般无法处理。

例如:StackOverflowError、OutOfMemoryError、VirtualMachineError。

3.1.3.3.异常的处理方式

Java提供了5种主要的异常处理方式:

try-catch 语句(捕获并处理异常)

throws 关键字(声明异常)

throw 关键字(手动抛出异常)

finally 代码块(无论是否发生异常都会执行)

自定义异常(用户自定义异常类)

3.1.3.4.异常的try-catch 语句

用于捕获异常并进行处理,防止程序崩溃。

|-------------------------------------------------------------------------------------------------------------------------------------|
| try { int result = 10 / 0; // 可能发生异常的代码 } catch (ArithmeticException e) { System.out.println("捕获到异常: " + e.getMessage()); // 处理异常 } |

如果多个异常可能被抛出,可以使用多个catch块。

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| try { int[] arr = new int[5]; System.out.println(arr[10]); // 数组越界异常 } catch (ArithmeticException e) { System.out.println("算术异常: " + e.getMessage()); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("数组越界异常: " + e.getMessage()); } |

从Java 7开始,可以用|合并多个异常。

|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| try { int num = Integer.parseInt("abc"); // 可能发生异常 } catch (NumberFormatException | NullPointerException e) { System.out.println("异常: " + e.getMessage()); } |

3.1.3.5.异常的throws 关键字

如果方法不能处理异常,可以使用throws声明异常,让调用者来处理。

|----------------------------------------------------------------------------------------------------------------|
| public void readFile() throws IOException { FileReader file = new FileReader("test.txt"); // 可能抛出IOException } |

调用时需要处理异常:

|------------------------------------------------------------------------------------------------|
| try { readFile(); } catch (IOException e) { System.out.println("文件读取异常: " + e.getMessage()); } |

3.1.3.6.异常的throw 关键字

用于手动抛出异常,必须是Throwable的子类。

|-------------------------------------------------------------------------------------------------------|
| public void checkAge(int age) { if (age < 18) { throw new IllegalArgumentException("年龄必须大于18岁"); } } |

调用时:

|-----------------------------------------------------------------------------------------------------------|
| try { checkAge(15); } catch (IllegalArgumentException e) { System.out.println("异常: " + e.getMessage()); } |

3.1.3.7.finally代码块

无论是否发生异常,finally块中的代码都会执行,常用于释放资源。

|-----------------------------------------------------------------------------------------------------------------------------------------------------------|
| try { FileReader file = new FileReader("test.txt"); } catch (IOException e) { System.out.println("文件读取异常"); } finally { System.out.println("无论如何都会执行"); } |

finally 资源自动关闭(try-with-resources)

从Java 7开始,可以使用try-with-resources自动关闭资源。

|------------------------------------------------------------------------------------------------------------------------|
| try (FileReader file = new FileReader("test.txt")) { // 读取文件 } catch (IOException e) { System.out.println("文件读取异常"); } |

3.1.3.8.自定义异常

可以自定义异常类,继承Exception或RuntimeException。

自定义检查异常:

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 自定义检查异常 class MyException extends Exception { public MyException(String message) { super(message); } } 抛出自定义异常: public void checkNumber(int num) throws MyException { if (num < 0) { throw new MyException("数字不能为负数"); } } |

自定义运行时异常:

|---------------------------------------------------------------------------------------------------------------------|
| class MyRuntimeException extends RuntimeException { public MyRuntimeException(String message) { super(message); } } |

3.1.3.9.异常处理的最佳实践

避免过度捕获异常:只捕获需要处理的异常,避免滥用catch (Exception e) {}。

不要忽略异常:catch块中不能空着,至少要打印日志。

合理使用finally:释放资源,如文件、数据库连接等。

自定义异常:当需要表达特定业务逻辑时,可以定义自定义异常。

不要滥用throws:声明的异常应该是方法调用者能够合理处理的。

3.1.3.10.常见的java异常

|---------------------------------|--------|-----------|
| 异常类 | 缩写 | 中文解释 |
| NullPointerException | NPE | 空指针异常 |
| ArrayIndexOutOfBoundsException | AIOOBE | 数组下标越界异常 |
| ArithmeticException | AE | 算术异常 |
| ClassCastException | CCE | 类转换异常 |
| NumberFormatException | NFE | 数字格式异常 |
| IllegalArgumentException | IAE | 非法参数异常 |
| IllegalStateException | ISE | 非法状态异常 |
| IndexOutOfBoundsException | IOOBE | 索引越界异常 |
| IOException | IOE | 输入输出异常 |
| FileNotFoundException | FNE | 文件未找到异常 |
| EOFException | EOFE | 文件到达末尾异常 |
| InterruptedException | IE | 线程中断异常 |
| NoSuchMethodException | NSME | 没有此方法异常 |
| NoSuchFieldException | NSFE | 没有此字段异常 |
| OutOfMemoryError | OOME | 内存溢出错误 |
| StackOverflowError | SOE | 栈溢出错误 |
| SecurityException | SE | 安全异常 |
| UnsupportedOperationException | UOE | 不支持的操作异常 |
| IllegalAccessException | IAE | 非法访问异常 |
| InstantiationException | IE | 实例化异常 |
| InvocationTargetException | ITE | 调用目标异常 |
| SQLException | SQLE | SQL异常 |
| ClassNotFoundException | CNFE | 类未找到异常 |
| TimeoutException | TE | 超时异常 |
| MissingResourceException | MRE | 缺少资源异常 |
| UnsupportedEncodingException | UEE | 不支持的编码异常 |
| DuplicateFormatFlagsException | DFFE | 重复格式标志异常 |
| RegexSyntaxException | RSE | 正则表达式语法异常 |
| ConcurrentModificationException | CME | 并发修改异常 |
| IllegalMonitorStateException | IMSE | 非法监视器状态异常 |
| FileAlreadyExistsException | FAEE | 文件已存在异常 |
| DirectoryNotEmptyException | DNEE | 目录非空异常 |
| ZipException | ZE | 压缩异常 |

3.1.4.Java序列化

Java序列化技术允许将对象的状态转换为字节流,以便将其传输或保存到磁盘上,然后再通过反序列化恢复为原始的对象。这种技术在Java中非常常见,尤其用于对象的持久化、远程方法调用(RMI)、Web服务等场景。

3.1.4.1.Java序列化基础

1)序列化与反序列化

序列化:将对象转化为字节流的过程,使得对象可以存储到文件、数据库或通过网络传输。

反序列化:将字节流转化为对象的过程,使得对象恢复为原始的状态。

2)实现序列化

在Java中,序列化通过让类实现 java.io.Serializable 接口来启用。如果一个类实现了该接口,Java虚拟机(JVM)就能够将它的实例转换为字节流。

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import java.io.*; public class Person implements Serializable { 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 + "]"; } public static void main(String[] args) throws IOException, ClassNotFoundException { // 序列化对象 Person person = new Person("John", 30); FileOutputStream fileOut = new FileOutputStream("person.ser"); ObjectOutputStream out = new ObjectOutputStream(fileOut); out.writeObject(person); out.close(); fileOut.close(); // 反序列化对象 FileInputStream fileIn = new FileInputStream("person.ser"); ObjectInputStream in = new ObjectInputStream(fileIn); Person deserializedPerson = (Person) in.readObject(); in.close(); fileIn.close(); System.out.println("Deserialized Person: " + deserializedPerson); } } |

Serializable接口是标记接口,不包含任何方法。

通过ObjectOutputStream类将对象写入文件,再通过ObjectInputStream类将对象从文件读取并恢复。

3)序列化与反序列化的相关类

ObjectOutputStream:用于序列化对象并将字节流写入输出流(例如文件、网络流)。

ObjectInputStream:用于从输入流(例如文件、网络流)读取字节流并反序列化成对象。

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("example.ser")); out.writeObject(object); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("example.ser")); Object object = in.readObject(); in.close(); |

3.1.4.2.持久化与Transient修饰符

有时我们不希望某些对象的字段被序列化。可以使用 transient 修饰符来指示JVM跳过该字段的序列化。

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import java.io.*; class Person implements Serializable { private String name; private transient int age; // transient修饰的字段不会被序列化 public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person[name=" + name + ", age=" + age + "]"; } public static void main(String[] args) throws IOException, ClassNotFoundException { Person person = new Person("John", 30); // 序列化对象 FileOutputStream fileOut = new FileOutputStream("person.ser"); ObjectOutputStream out = new ObjectOutputStream(fileOut); out.writeObject(person); out.close(); fileOut.close(); // 反序列化对象 FileInputStream fileIn = new FileInputStream("person.ser"); ObjectInputStream in = new ObjectInputStream(fileIn); Person deserializedPerson = (Person) in.readObject(); in.close(); fileIn.close(); System.out.println("Deserialized Person: " + deserializedPerson); // age将为默认值0 } } |

在上述例子中,age 字段被标记为 transient,所以它不会被序列化,反序列化后 age 的值将恢复为默认值 0。

3.1.4.4.Serializable接口与Externalizable接口

除了 Serializable 接口外,Java还提供了另一个接口 Externalizable,它继承自 Serializable,并添加了两个方法:

writeExternal 和 readExternal。通过 Externalizable 接口,你可以完全控制序列化和反序列化的过程。

Externalizable接口:

1)writeExternal(ObjectOutput out):用于自定义序列化。

2)readExternal(ObjectInput in):用于自定义反序列化。

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import java.io.*; class Person implements Externalizable { private String name; private int age; public Person() { // Externalizable要求提供一个无参构造函数 } public Person(String name, int age) { this.name = name; this.age = age; } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(name); out.writeInt(age); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { name = (String) in.readObject(); age = in.readInt(); } @Override public String toString() { return "Person[name=" + name + ", age=" + age + "]"; } public static void main(String[] args) throws IOException, ClassNotFoundException { Person person = new Person("John", 30); // 序列化对象 FileOutputStream fileOut = new FileOutputStream("person.ext.ser"); ObjectOutputStream out = new ObjectOutputStream(fileOut); person.writeExternal(out); out.close(); fileOut.close(); // 反序列化对象 FileInputStream fileIn = new FileInputStream("person.ext.ser"); ObjectInputStream in = new ObjectInputStream(fileIn); Person deserializedPerson = new Person(); deserializedPerson.readExternal(in); in.close(); fileIn.close(); System.out.println("Deserialized Person: " + deserializedPerson); } } |

在Externalizable接口中,你可以完全控制对象的序列化和反序列化过程。这使得Externalizable相较于Serializable提供了更多的灵活性。

3.1.4.5.序列化UID(serialVersionUID)

serialVersionUID 是一个类的版本控制ID。当序列化和反序列化操作发生时,Java会检查该ID是否匹配。若不匹配,程序将抛出 InvalidClassException。如果类的结构发生变化,建议显式声明****serialVersionUID

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import java.io.*; class Person implements Serializable { private static final long serialVersionUID = 1L; // 显式声明serialVersionUID private String name; private int age; // 其他代码 } |

3.1.4.6.序列化和性能

对象大小:序列化后的对象大小与对象的属性和结构有关。使用 transient 可以减少不必要的字段的序列化。

自定义序列化:通过实现 writeObject 和 readObject 方法可以控制序列化过程的细节,以便更好地控制性能和对象数据。

3.1.4.7.序列化的作用

1)对象的持久化

序列化的一个常见用途是持久化对象数据,将内存中的对象保存到磁盘或数据库中。当系统关闭或程序结束时,序列化使得对象的状态可以被保存下来,稍后可以恢复。

例:在数据库中存储复杂的对象数据时,可以将对象序列化为字节流并保存,之后可以反序列化为对象,继续使用它。

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import java.io.*; public class Person implements Serializable { 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 + "]"; } public static void main(String[] args) throws IOException, ClassNotFoundException { // 创建对象 Person person = new Person("Alice", 25); // 序列化对象保存到文件 try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"))) { out.writeObject(person); } // 反序列化对象从文件读取 try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.ser"))) { Person deserializedPerson = (Person) in.readObject(); System.out.println("Deserialized Person: " + deserializedPerson); } } } |

2)网络通信和远程调用

序列化在网络通信中也起着非常重要的作用。在分布式系统中,数据通常需要跨网络传输。对象序列化后可以通过网络发送到另一台机器,另一台机器收到字节流后通过反序列化将其恢复为原始对象。

远程方法调用(RMI):在Java的远程方法调用(RMI)中,序列化是实现方法调用参数和返回值传输的核心技术。

例:在分布式系统中,如果你需要将对象从一个客户端传输到服务器,序列化可以帮助将对象转化为字节流,通过网络传输,再在另一端反序列化恢复为对象。

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import java.io.*; import java.net.*; public class SerializableExample { public static void main(String[] args) throws IOException, ClassNotFoundException { // 客户端发送对象 Socket socket = new Socket("localhost", 1234); ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); Person person = new Person("Bob", 30); out.writeObject(person); out.flush(); // 关闭连接 out.close(); socket.close(); } } |

3)序列化还常用于缓存机制,特别是在高性能应用中。例如,Web应用会将某些对象(如数据库查询结果)序列化并存储在缓存中。这样,当下次需要这些数据时,可以直接从缓存中读取序列化的对象,而无需重新计算或查询数据库,显著提高性能。

例:将序列化后的java对象存放在redis里

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import redis.clients.jedis.Jedis; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.io.IOException; public class RedisSerializeExample { public static void main(String[] args) { // 创建一个Person对象 Person person = new Person("John Doe", 30); // 创建Jedis客户端并连接到Redis try (Jedis jedis = new Jedis("localhost", 6379)) { // 序列化对象为字节流 byte[] personBytes = serializeObject(person); // 将字节流存储到Redis中,使用字符串类型存储 jedis.set("person:1".getBytes(), personBytes); System.out.println("对象已序列化并存入Redis"); } catch (IOException e) { e.printStackTrace(); } } // 将对象序列化为字节流 public static byte[] serializeObject(Object obj) throws IOException { try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)) { objectOutputStream.writeObject(obj); return byteArrayOutputStream.toByteArray(); } } } |

从Redis读取字节流并反序列化回java对象。

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import redis.clients.jedis.Jedis; import java.io.ByteArrayInputStream; import java.io.ObjectInputStream; import java.io.IOException; public class RedisDeserializeExample { public static void main(String[] args) { // 创建Jedis客户端并连接到Redis try (Jedis jedis = new Jedis("localhost", 6379)) { // 从Redis中获取字节流 byte[] personBytes = jedis.get("person:1".getBytes()); if (personBytes != null) { // 反序列化字节流为对象 Person person = (Person) deserializeObject(personBytes); System.out.println("从Redis读取并反序列化得到的对象: " + person); } else { System.out.println("没有找到对应的对象!"); } } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } // 将字节流反序列化为对象 public static Object deserializeObject(byte[] data) throws IOException, ClassNotFoundException { try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream)) { return objectInputStream.readObject(); } } } |

4)对象复制

序列化和反序列化常常用于对象的深拷贝。当你需要复制一个对象及其所有嵌套对象时,可以通过序列化和反序列化来实现深拷贝。与浅拷贝不同,深拷贝不仅复制对象本身,还复制它引用的所有对象。

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import java.io.*; public class DeepCopyExample { public static void main(String[] args) throws IOException, ClassNotFoundException { // 创建一个对象 Person person1 = new Person("Eve", 22); // 序列化并反序列化以实现深拷贝 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream); out.writeObject(person1); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream in = new ObjectInputStream(byteArrayInputStream); Person person2 = (Person) in.readObject(); // 输出原对象和拷贝对象 System.out.println("Original: " + person1); System.out.println("Copied: " + person2); } } |

5)与JVM、垃圾回收器的结合

序列化技术能够帮助程序持久化对象数据,而不依赖于JVM的生命周期。当对象在内存中不再使用时,它可能被垃圾回收器回收,但通过序列化可以将对象存储在外部存储设备上,从而延长对象的生命周期。

3.1.4.8.序列化和Java对象版本之间的关系

在Java中,序列化与版本之间的关系是非常重要的,尤其是在对象的持久化、网络传输、分布式系统等场景中。随着时间的推移,Java类的结构可能会发生变化,例如添加、删除或修改字段。如果没有妥善处理版本问题,序列化与反序列化时可能会出现错误或不一致的结果,导致程序崩溃或数据丢失。

Java提供了机制来确保序列化过程中的版本控制,主要通过 serialVersionUID 字段来管理版本兼容性。

3.1.4.8.1.serialVersionUID:版本控制标识符

serialVersionUID 是 Java 类在进行序列化和反序列化时用来验证类版本兼容性的标识符。它是一个唯一的长整型(long),用于识别序列化类的版本。如果类的版本发生变化,serialVersionUID 值通常会发生改变,这样在反序列化时,可以通过检查 serialVersionUID 来确保序列化和反序列化过程的兼容性。

定义:

可以通过在类中显式定义 serialVersionUID 来控制版本。若没有显式定义,Java会根据类的结构自动生成一个 serialVersionUID,但这可能会因为类的微小变化(如字段顺序、方法修改等)而变化,从而导致版本不兼容的问题。

作用:

1)当反序列化时,JVM会将 serialVersionUID 用于验证类的版本。

2)如果反序列化过程中发现序列化对象的 serialVersionUID 与当前类的 serialVersionUID 不匹配,JVM会抛出 InvalidClassException 异常,表示类版本不兼容。

3.1.4.8.2.类结构变化对序列化的影响

类在发生结构变化时(如添加字段、删除字段或改变字段类型等),序列化和反序列化的行为可能受到影响。例如:

添加字段:如果类添加了新字段,旧版本序列化的对象在反序列化时可能无法赋予新字段一个默认值,导致 InvalidClassException 或字段缺失。

删除字段:如果删除了某个字段,旧版本序列化的对象在反序列化时会遇到该字段缺失,可能会引发 InvalidClassException。

改变字段类型:如果更改了字段的数据类型,反序列化时会出现类型不匹配的问题。

3.1.4.8.3.序列化版本管理

为了避免版本兼容问题,通常采用以下几种做法:

1)显式声明 serialVersionUID

显式声明 serialVersionUID 可以避免 Java 自动生成并改变 serialVersionUID,从而控制类的序列化版本。

2)向前兼容性

在类中增加新的字段时,可以采取以下措施来确保向后兼容:

a.为新增字段提供默认值或使用 transient 修饰符标记它。

b.使用 readObject 方法自定义反序列化过程,以便给新字段赋一个默认值。

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import java.io.*; public class Person implements Serializable { private static final long serialVersionUID = 2L; // 增加版本号 private String name; private int age; private String address; // 新字段 public Person(String name, int age) { this.name = name; this.age = age; } // 自定义反序列化过程 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); // 保证默认反序列化 if (address == null) { address = "Unknown"; // 给新字段提供默认值 } } @Override public String toString() { return "Person[name=" + name + ", age=" + age + ", address=" + address + "]"; } } |

3)向后兼容性

当删除字段或更改字段时,为了确保反序列化时能够正确处理旧版本的对象,可以使用 transient 关键字或在 readObject 中手动处理字段的缺失。

4)使用 Externalizable 接口

使用 Externalizable 接口提供更高的控制能力,允许开发者在序列化和反序列化时手动指定哪些字段应该被序列化,哪些字段应该忽略。

3.1.4.8.4.serialVersionUID 与序列化兼容性

不同版本类的反序列化:

向后兼容:如果一个类在旧版本中序列化了对象,并且后来添加了字段(向后兼容),则新的类可以反序列化旧的对象,但需要为新增字段提供

如果没有明确指定 serialVersionUID,Java会根据类的结构生成一个版本号,每次类结构发生变化时,都会导致 serialVersionUID 改变,导致反序列化失败。

如果显式声明 serialVersionUID,类的结构变化不会导致反序列化失败,只要字段的变化保持兼容性。默认值。

向前兼容:如果一个类在新版本中序列化了对象,并且旧版本没有新增的字段,那么旧版本的类可能无法处理新版本的对象。

3.1.4.8.5.注意事项

1)Java中的序列化版本控制(serialVersionUID)是确保序列化和反序列化兼容性的关键。它帮助我们在类结构发生变化时,避免不兼容的版本导致程序错误。通过显式声明 serialVersionUID,可以控制不同版本间的兼容性,同时保证向前和向后兼容的策略。

2)在实际应用(比如将Session对象序列化后存放到redis中,使用时再取出来)中,开发人员应当:

在类中显式声明 serialVersionUID。

在修改类结构时,注意保持兼容性,特别是添加、删除字段或修改字段类型时。

如果需要,可以使用 Externalizable 接口来实现更精细的控制。

显式定义和不显式定义的区别 :

|------------|---------------------------------------|--------------------------------------------------|
| 特性 | 显式定义 serialVersionUID | 不显式定义 serialVersionUID |
| 兼容性控制 | 完全控制序列化版本的兼容性,可以自由修改serialVersionUID。 | 每次类的结构发生变化时,自动生成的serialVersionUID会改变,可能导致反序列化失败。 |
| 反序列化失败 | 在类修改时,可以确保反序列化不会因为版本不一致而失败。 | 如果类结构变化,可能会导致InvalidClassException。 |
| 性能 | 编译时已确定serialVersionUID,反序列化时性能更稳定。 | 每次类结构变更时,都会重新计算serialVersionUID,影响性能。 |
| 灵活性 | 可以自定义版本控制逻辑。 | 没有自定义的灵活性,依赖JVM自动计算。 |
| 默认值 | 必须手动定义一个合适的值。 | 默认情况下,JVM会自动计算一个值。 |

3.1.5.注解

在 JDK 17 中,注解(Annotation)是 Java 编程语言的重要特性之一,它为代码提供了元数据,能够影响编译、运行时或构建时的行为。注解本身不会改变代码的逻辑,但它可以用于指定额外的信息、标记或指示编译器、开发工具和运行时环境如何处理某些部分的代码。

3.1.5.1.注解的基本概念

什么是注解?

注解是 Java 代码中的一种元数据,用于提供有关代码的额外信息。它不会影响程序的逻辑执行,但可以被编译器、工具、框架或运行时环境使用。注解通常以 @ 符号标识。

|--------------------------------------------------------------------|
| public @interface MyAnnotation { String value() default "Hello"; } |

注解的定义

注解是通过 @interface 来定义的,类似于接口的定义方式。可以定义成员(方法)来存储注解的参数。

3.1.5.2.注解的常见用途

编译时检查:用于标记代码需要进行特殊处理,或者用来生成警告、错误等,例如 @Override 和 @Deprecated。

运行时行为:在运行时被反射读取,用于动态生成代码、注入依赖等,例如 Spring 框架的 @Autowired 或 @Service。

代码生成:工具可以通过注解生成代码,如 Lombok、AutoValue 等库通过注解生成代码。

3.1.5.3.常见的内置注解
  1. @Override

表示某个方法覆盖了父类的一个方法。如果没有正确覆盖父类的方法,编译器将发出错误。

|----------------------------------------------------------------|
| @Override public String toString() { return "Hello, World!"; } |

  1. @Deprecated

标记某个元素(类、方法、字段等)已过时,不推荐使用。编译器在使用这些过时元素时会给出警告。

|--------------------------------------------------|
| @Deprecated public void oldMethod() { // 旧方法逻辑 } |

  1. @SuppressWarnings

用于指示编译器抑制特定的警告信息。常见的警告类型有 "unchecked"(未经检查的类型)和 "deprecation"(使用了已过时的 API)。

|------------------------------------------------------------------------------------------------------------|
| @SuppressWarnings("deprecation") public void useDeprecatedMethod() { oldMethod(); // 使用了 @Deprecated 的方法 } |

  1. @FunctionalInterface

用于标记接口为函数式接口。一个接口如果有且仅有一个抽象方法,则可以使用此注解标识。

|----------------------------------------------------------------------------------|
| @FunctionalInterface public interface MyFunctionalInterface { void myMethod(); } |

  1. @SafeVarargs

用于标记变长参数(varargs)方法,在调用时避免编译器警告。

|-------------------------------------------------------------------------------------|
| @SafeVarargs public static <T> void printArray(T... elements) { // do something } |

3.1.5.4.自定义注解

你可以定义自己的注解,用来标记特定的代码或提供元数据。

|-------------------------------------------------------------------------------------------------|
| public @interface MyAnnotation { String name() default "default name"; int value() default 0; } |

自定义注解可以带有成员(方法),这些成员可以有默认值,也可以在使用时指定。

3.1.5.5.注解的元素类型

注解的成员方法返回值类型可以是:

基本类型(int, boolean 等)

String

Class

枚举类型

注解类型

这些类型的数组

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| public @interface MyAnnotation { int id(); String name() default "default"; Class<?> type(); MyEnum level() default MyEnum.MEDIUM; String[] tags() default {}; } |

3.1.5.6.注解的应用目标和保留策略

JDK 17 中的注解可以通过两个关键元数据来控制其适用范围和生命周期:@Target 和 @Retention。

  1. @Target

用于指定注解能够应用的目标(类、方法、字段等)。

常见的值:

ElementType.TYPE:类、接口或枚举

ElementType.FIELD:字段

ElementType.METHOD:方法

ElementType.PARAMETER:方法参数

ElementType.LOCAL_VARIABLE:局部变量

例如,下面的注解只能应用到方法上:

|-------------------------------------------------------------------------------------------------------|
| @Target(ElementType.METHOD) public @interface MyMethodAnnotation { String description() default ""; } |

  1. @Retention

用于指定注解的保留策略,表示注解在什么阶段有效:

RetentionPolicy.SOURCE:仅在源码中存在,编译后丢弃。

RetentionPolicy.CLASS:在字节码中存在,编译后保留,但不在运行时可用(默认值)。

RetentionPolicy.RUNTIME:在字节码中存在,且在运行时可以通过反射访问。

例如,下面的注解在运行时仍然可用:

|---------------------------------------------------------------------------------------------------------------------------------------------|
| @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MyRuntimeAnnotation { String value() default "default"; } |

  1. @Inherited

用于指示子类会继承父类的注解,仅适用于类类型的注解。

|--------------------------------------------------------------------------------------------------------------------------------------------------------|
| @Inherited @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface MyInheritedAnnotation { String value() default "default"; } |

3.1.5.7.反射获取注解

Java 反射(Reflection)提供了访问注解的机制,可以在运行时通过 getAnnotation() 方法获取注解信息。

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| public class MyClass { @MyAnnotation(value = "Test") public void myMethod() { } } public class Test { public static void main(String[] args) throws NoSuchMethodException { Method method = MyClass.class.getMethod("myMethod"); MyAnnotation annotation = method.getAnnotation(MyAnnotation.class); System.out.println(annotation.value()); // 输出 "Test" } } |

3.1.5.8.注解处理器

Java 提供了注解处理器(Annotation Processor),可以在编译时扫描、处理注解。常用于代码生成、验证等操作。

编写注解处理器:

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| @SupportedAnnotationTypes("com.example.MyAnnotation") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class MyAnnotationProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { // 处理注解逻辑 return false; // 表示注解没有被处理 } } |

注解处理器通常与 javac 编译器一起使用,可以在编译时生成代码、验证代码等。

3.1.5.9.Java 17 新特性:@FunctionalInterface 的变化

JDK 17 对 @FunctionalInterface 注解的支持没有变化,但值得注意的是,Lambda 表达式和函数式接口的功能得到了持续的改进。特别是 java.util.function 包中的新接口和改进,使得函数式编程在 Java 中更为强大。

3.1.6.抽象类

抽象类是 Java 中非常重要的一部分,它为子类提供了一个模板,并能共享部分实现。

抽象类的核心特性在于它不能直接实例化,它是用来作为其他类的基类,定义了一些共有的方法和属性供子类继承。抽象类能够包含完整实现的方法、抽象方法(没有实现的方法)以及其他类型的成员。

3.1.6.1.抽象类的定义

抽象类是通过 abstract 关键字定义的类。抽象类可以包含抽象方法,也可以包含已经实现的方法。

|-----------------------------------------------------------------------------------------------------------------------------------------|
| abstract class Animal { // 抽象方法 public abstract void sound(); // 已实现的方法 public void breathe() { System.out.println("Breathing..."); } } |

abstract 关键字:用于声明类或方法为抽象类或抽象方法。

抽象类:不能被实例化(即不能创建抽象类的对象),但可以用作其他类的基类。

3.1.6.2.抽象方法

抽象方法是没有方法体的方法。它只能出现在抽象类中,子类必须重写这些抽象方法,才能实例化子类。

|-----------------------------------------------------------------|
| abstract class Animal { // 抽象方法 public abstract void sound(); } |

抽象方法的声明只有方法签名,没有方法体。

子类必须实现所有的抽象方法,除非子类本身是抽象类。

3.1.6.3.抽象类的特性

1)不能实例化

抽象类不能直接创建对象,不能使用 new 操作符来实例化。例如,new Animal() 是非法的。

2)可以有构造方法

抽象类可以有构造方法,子类可以通过调用父类构造方法来初始化抽象类的字段。

|-------------------------------------------------------------------------------------------------|
| abstract class Animal { String name; // 构造方法 public Animal(String name) { this.name = name; } } |

3)可以包含字段和常量

抽象类可以包含成员变量(字段),这些字段可以被子类继承。它也可以包含常量。

|---------------------------------------------------------------------------------------------------------------------------------------|
| abstract class Animal { // 常量 public static final int LEGS = 4; // 字段 String name; public Animal(String name) { this.name = name; } } |

4)可以包含已实现的方法

抽象类可以包含具有实现的方法,这些方法可以在子类中被继承,或者根据需要被覆盖(重写)。

|-----------------------------------------------------------------------------------------------------------------------------------------|
| abstract class Animal { // 已实现的方法 public void breathe() { System.out.println("Breathing..."); } // 抽象方法 public abstract void sound(); } |

5)可以有静态方法

抽象类可以包含静态方法,这些方法属于类本身,而不是对象。

|---------------------------------------------------------------------------------------------------------------------------------|
| abstract class Animal { // 静态方法 public static void staticMethod() { System.out.println("Static method in abstract class."); } } |

6)可以继承抽象类

抽象类可以继承其他类(包括抽象类),并实现或覆盖父类的抽象方法。

|-----------------------------------------------------------------------|
| abstract class Mammal extends Animal { public abstract void feed(); } |

7)抽象类可以实现接口

抽象类可以实现接口,但它可以选择性地实现接口中的方法,也可以选择不实现它们(如果抽象类自身还希望成为抽象类)。

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| interface Swimmable { void swim(); } abstract class Animal implements Swimmable { // 可以选择实现 swim() 方法,或让子类实现 public void swim() { System.out.println("Swimming..."); } } |

3.1.6.4.抽象类与接口的区别
3.1.6.5.抽象类的实例化:通过子类实例化

虽然抽象类不能直接实例化,但可以通过子类来实例化。子类必须实现抽象类中的所有抽象方法,才能创建子类的实例。

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| class Dog extends Animal { public Dog(String name) { super(name); } // 实现抽象方法 public void sound() { System.out.println("Bark"); } } public class Main { public static void main(String[] args) { Animal dog = new Dog("Buddy"); dog.sound(); // 输出:Bark } } |

3.1.6.6.抽象类的继承与多态

抽象类可以通过继承实现多态性。这意味着父类引用可以指向子类对象,进而调用子类的实现。

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| class Cat extends Animal { public Cat(String name) { super(name); } public void sound() { System.out.println("Meow"); } } public class Main { public static void main(String[] args) { Animal animal1 = new Dog("Dog"); Animal animal2 = new Cat("Cat"); animal1.sound(); // 输出:Bark animal2.sound(); // 输出:Meow } } |

3.1.6.7.抽象类的访问控制

方法的访问控制:抽象类中的方法可以有访问修饰符,指定该方法的访问范围。public、protected 和 private 都可以应用于抽象方法。

字段的访问控制:与普通类一样,抽象类中的字段可以有不同的访问权限。通常情况下,字段需要通过 getter 和 setter 方法来访问,以便提供更好的封装性。

3.1.6.8.抽象类的常见应用场景

类继承层次结构:当多个类有一些共同的行为,但又存在差异时,可以使用抽象类来定义共有行为,然后让每个子类实现差异化的行为。

模板方法模式:在某些设计模式中,抽象类用于定义一种方法的框架,并允许子类实现具体的行为(例如,模板方法模式)。

3.1.6.9.Java 17抽象类的新特性

记录类(Record Classes):Java 16 引入了记录类,它本质上是一个简化的类结构。记录类不能是抽象类,但可以与抽象类一起使用以创建更加清晰和简洁的代码。

|--------------------------------|
| record Point(int x, int y) { } |

模式匹配(Pattern Matching):Java 17 引入了增强的模式匹配(例如,instanceof),这使得在抽象类和子类的多态处理中更加便捷。

|------------------------------------------------------------|
| if (obj instanceof String s) { // 对s进行操作,s被自动转换为String类型 } |

3.1.6.10.抽象类的执行顺序

在 Java 中,抽象类的调用顺序通常指的是类的构造顺序,尤其是在涉及到继承和实例化的时候。了解抽象类的调用顺序非常重要,因为它会影响如何初始化对象以及子类如何继承父类的构造过程和方法。通常情况下,抽象类的调用顺序主要涉及到以下几个方面:

3.1.6.10.1.实例化顺序

当实例化一个子类对象时,Java 会按照一定的顺序来初始化父类和子类的部分。抽象类作为父类,其构造方法也会被调用。

1)调用顺序:

父类静态初始化块(如果有的话):父类的静态代码块和静态字段在类加载时被初始化。

子类静态初始化块(如果有的话):子类的静态代码块和静态字段在类加载时被初始化。

父类构造方法(如果父类没有显式的构造方法,Java 会自动调用父类的无参构造方法):父类的构造方法会在子类构造方法之前调用。

子类构造方法:在父类构造方法执行完成之后,子类的构造方法才会执行。

2)代码执行流程

在实例化对象时,构造方法的调用顺序如下:

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| class Animal { // 父类的构造方法 public Animal() { System.out.println("Animal constructor called"); } public void makeSound() { System.out.println("Animal sound"); } } abstract class Dog extends Animal { // 抽象类的构造方法 public Dog() { super(); // 默认调用父类的构造方法 System.out.println("Dog constructor called"); } // 抽象方法 public abstract void bark(); } class Labrador extends Dog { public Labrador() { super(); // 调用抽象类 Dog 的构造方法 System.out.println("Labrador constructor called"); } @Override public void bark() { System.out.println("Woof!"); } } public class Main { public static void main(String[] args) { Labrador lab = new Labrador(); // 创建 Labrador 对象 lab.bark(); // 输出:Woof! } } |

输出顺序:

|------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Animal constructor called // 1. 调用父类的构造方法 Dog constructor called // 2. 调用抽象类的构造方法 Labrador constructor called // 3. 调用子类构造方法 Woof! // 4. 调用子类实现的 bark() 方法 |

解释:

1)父类的构造方法 (Animal 类的构造方法):在实例化子类对象时,首先会执行父类的构造方法。即使父类是抽象类,只要它有构造方法,子类对象在实例化时依然会调用它。

2)抽象类的构造方法 (Dog 类的构造方法):如果抽象类定义了构造方法,那么在子类实例化过程中,也会调用抽象类的构造方法。抽象类的构造方法可以调用父类的构造方法,并且可以执行其他必要的初始化逻辑。

3)子类的构造方法 (Labrador 类的构造方法):最终执行子类的构造方法,完成对象的初始化。需要注意的是,子类的构造方法会显式地调用父类的构造方法(如 super()),即使父类的构造方法是抽象类中的构造方法。

3.1.6.10.2.构造方法中的 super() 和 this()

在 Java 中,super() 用于调用父类的构造方法,this() 用于调用当前类的另一个构造方法。无论是抽象类还是普通类,构造方法中都可以显式地使用 super() 来调用父类的构造方法。

super():显式调用父类构造方法。如果没有显式调用,Java 会自动调用父类的无参构造方法(如果父类有无参构造方法)。

this():如果当前类有多个构造方法,可以通过 this() 来调用同一个类中的其他构造方法。

3.1.6.10.3.抽象方法的调用顺序

抽象方法是没有实现的,它在父类(抽象类)中被声明,在子类中被实现。Java 会按照以下顺序调用抽象方法:

父类的抽象方法:抽象方法本身没有实现,只有声明。在实例化子类对象时,抽象类的抽象方法不会被直接调用,只有子类实现了这些抽象方法,才会被调用。

子类的抽象方法实现:抽象类中的抽象方法必须在子类中实现。实例化子类对象时,父类构造方法调用完毕后,子类中实现的抽象方法才会被调用。

例如:

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| abstract class Animal { // 抽象方法 public abstract void sound(); } class Dog extends Animal { @Override public void sound() { System.out.println("Bark"); } } public class Main { public static void main(String[] args) { Dog dog = new Dog(); dog.sound(); // 调用 Dog 类中实现的 sound() 方法 } } |

输出:

Bark

3.1.6.10.4.继承与重写的调用顺序

Java 中的继承和方法重写(Override)也会影响抽象类的调用顺序。子类重写了父类的抽象方法后,调用顺序如下:

1)父类方法调用顺序:父类中声明的抽象方法会在子类中被重写。在实例化子类对象时,调用的是子类重写后的方法,而不是父类中原本的抽象方法。

2)子类重写的方法调用:即使父类中有方法,子类如果对其进行了重写,那么调用的是子类的版本。

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| abstract class Animal { public abstract void sound(); } class Dog extends Animal { @Override public void sound() { System.out.println("Bark"); } } public class Main { public static void main(String[] args) { Animal dog = new Dog(); dog.sound(); // 调用 Dog 类中实现的 sound() 方法 } } |

输出:

Bark

3.1.7.多线程
3.1.7.1.多线程的概念

多线程(Multithreading)是指在同一个进程中并发执行多个线程,每个线程执行独立的任务,从而提高程序的执行效率。Java 提供了强大的多线程支持,主要涉及:

Thread 类

Runnable 接口

Executor 框架

线程同步机制(锁、volatile、CAS)

线程通信(wait、notify、Condition)

3.1.7.2.Java 创建多线程的方式

1)继承 Thread 类

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " 正在执行:" + i); } } } public class ThreadExample { public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); t1.start(); t2.start(); } } |

特点:

继承 Thread,重写 run() 方法

使用 start() 方法启动线程

缺点: 不能继承其他类(Java 单继承限制)

2)实现 Runnable 接口

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| class MyRunnable implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " 正在执行:" + i); } } } public class RunnableExample { public static void main(String[] args) { Thread t1 = new Thread(new MyRunnable()); Thread t2 = new Thread(new MyRunnable()); t1.start(); t2.start(); } } |

特点:

实现 Runnable 接口,不影响继承其他类

线程对象通过 Thread 类包装 Runnable 对象

推荐使用,更加灵活

3)使用 Lambda 表达式

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| public class LambdaThreadExample { public static void main(String[] args) { Thread t1 = new Thread(() -> { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " 执行:" + i); } }); t1.start(); } } |

特点:

代码更简洁,推荐使用

适用于 Java 8 及以上版本

4)使用 Executor 框架

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ExecutorExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); // 创建线程池 for (int i = 0; i < 5; i++) { executor.execute(() -> { System.out.println(Thread.currentThread().getName() + " 处理任务"); }); } executor.shutdown(); // 关闭线程池 } } |

特点:

线程池管理,提高性能

适用于高并发场景(Web 服务器、数据库连接池)

3.1.7.3.线程同步与并发控制

多个线程访问共享资源时,可能会发生线程安全问题,常见的解决方案:

synchronized 关键字

ReentrantLock(可重入锁)

volatile 关键字

CAS(无锁机制)

ThreadLocal(线程本地变量)

1) synchronized(同步锁)

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } } public class SynchronizedExample { public static void main(String[] args) { Counter counter = new Counter(); Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; i++) counter.increment(); }); Thread t2 = new Thread(() -> { for (int i = 0; i < 1000; i++) counter.increment(); }); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("最终计数:" + counter.getCount()); } } |

特点:

synchronized 保证方法执行的原子性

缺点: 影响性能,阻塞其他线程

2) ReentrantLock(可重入锁)

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import java.util.concurrent.locks.ReentrantLock; class SafeCounter { private int count = 0; private final ReentrantLock lock = new ReentrantLock(); public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } } public int getCount() { return count; } } public class LockExample { public static void main(String[] args) { SafeCounter counter = new SafeCounter(); Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; i++) counter.increment(); }); Thread t2 = new Thread(() -> { for (int i = 0; i < 1000; i++) counter.increment(); }); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("最终计数:" + counter.getCount()); } } |

特点:

ReentrantLock 提供更高级的锁控制

可中断、支持公平锁

需要手动 unlock(),避免死锁

3)volatile 关键字

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| class VolatileExample { private volatile boolean running = true; public void stop() { running = false; } public void run() { while (running) { System.out.println("线程运行中..."); } System.out.println("线程停止"); } public static void main(String[] args) throws InterruptedException { VolatileExample example = new VolatileExample(); Thread t = new Thread(example::run); t.start(); Thread.sleep(1000); example.stop(); } } |

特点:

volatile 保证变量可见性

适用于标志位控制,不适用于计数累加(不保证原子性)

4)线程通信(wait / notify)

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| class SharedResource { private boolean flag = false; public synchronized void produce() throws InterruptedException { while (flag) wait(); System.out.println("生产数据"); flag = true; notify(); } public synchronized void consume() throws InterruptedException { while (!flag) wait(); System.out.println("消费数据"); flag = false; notify(); } } public class WaitNotifyExample { public static void main(String[] args) { SharedResource resource = new SharedResource(); new Thread(() -> { try { while (true) resource.produce(); } catch (InterruptedException e) { } }).start(); new Thread(() -> { try { while (true) resource.consume(); } catch (InterruptedException e) { } }).start(); } } |

特点:

wait() 和 notify() 实现线程间通信

避免死锁,适用于生产者-消费者模型

3.1.8.线程池
3.1.8.1.为什么需要线程池

1)减少线程创建销毁的开销:创建和销毁线程是有成本的,频繁创建线程会影响性能。

2)控制线程并发数量:防止线程过多导致 CPU 负载过高或内存溢出。

3)提高系统稳定性:使用线程复用和任务队列来管理任务,避免资源耗尽。

3.1.8.2.Java 线程池的核心类

Java 提供了 Executor 框架 来管理线程池:

ExecutorService(接口):线程池的核心接口。

Executors(工具类):用于创建不同类型的线程池(newFixedThreadPool()、newCachedThreadPool())。

ThreadPoolExecutor(底层实现类):自定义线程池的核心类。

3.1.8.3.创建线程池的方式

1)使用 Executors 创建线程池

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class FixedThreadPoolExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); // 创建固定大小的线程池 for (int i = 0; i < 5; i++) { final int taskId = i; executor.execute(() -> { System.out.println(Thread.currentThread().getName() + " 执行任务 " + taskId); try { Thread.sleep(1000); } catch (InterruptedException ignored) {} }); } executor.shutdown(); // 关闭线程池 } } |

特点:

固定线程数(3 个线程),超出的任务会进入任务队列等待执行。

适用于任务数量较多但线程数量可控的场景(如 Web 服务器)。

2)使用 ThreadPoolExecutor 自定义线程池

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import java.util.concurrent.*; public class CustomThreadPoolExample { public static void main(String[] args) { ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, // 核心线程数 4, // 最大线程数 60, // 线程最大空闲时间 TimeUnit.SECONDS, // 时间单位 new LinkedBlockingQueue<>(2), // 任务队列(最大 2 个) Executors.defaultThreadFactory(), // 线程工厂 new ThreadPoolExecutor.AbortPolicy() // 拒绝策略 ); for (int i = 0; i < 6; i++) { final int taskId = i; executor.execute(() -> { System.out.println(Thread.currentThread().getName() + " 执行任务 " + taskId); try { Thread.sleep(2000); } catch (InterruptedException ignored) {} }); } executor.shutdown(); } } |

线程池参数解析

参数 作用

corePoolSize 核心线程数(不会被销毁)

maximumPoolSize 最大线程数

keepAliveTime 线程空闲的存活时间

TimeUnit 时间单位,如 SECONDS

workQueue 任务队列,如 LinkedBlockingQueue<>(2)

threadFactory 线程工厂(创建新线程)

handler 拒绝策略

3.1.8.4.线程池的四种常见类型

1)newFixedThreadPool(固定大小线程池)

ExecutorService executor = Executors.newFixedThreadPool(3);

特点:

固定线程数,超出的任务会放入队列。

适用场景:适用于服务器、数据库连接池。

2)newCachedThreadPool(可缓存线程池)

ExecutorService executor = Executors.newCachedThreadPool();

特点:

线程数不固定,会根据需求动态创建或回收线程。

适用于短时间大量请求,比如高并发场景。

适用场景:高并发、大量短任务,如爬虫、批量任务处理。

3)newSingleThreadExecutor(单线程池)

ExecutorService executor = Executors.newSingleThreadExecutor();

特点:

只有一个线程,所有任务顺序执行。

适用于任务必须按顺序执行的场景,如日志写入。

适用场景:单线程任务处理,如订单支付。

4)newScheduledThreadPool(定时任务线程池)

ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

executor.schedule(() -> System.out.println("延迟执行任务"), 3, TimeUnit.SECONDS);

executor.shutdown();

特点:

支持定时任务和周期性任务。

适用场景:定时任务,如定时备份、定时发送邮件。

3.1.8.5.线程池的拒绝策略

当线程池满了且任务队列也满了时,如何处理新的任务?

拒绝策略 说明

AbortPolicy 抛出异常(默认策略)

CallerRunsPolicy 让调用线程(如 main 线程)自己执行任务

DiscardPolicy 丢弃新任务,不抛异常

DiscardOldestPolicy 丢弃最旧的任务,然后尝试执行新任务

示例:使用 CallerRunsPolicy

|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, 4, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2), new ThreadPoolExecutor.CallerRunsPolicy() ); |

特点:当任务无法提交时,主线程自己执行任务,不会丢任务。

3.1.8.6.线程池的正确关闭

线程池关闭有两种方式:

|---------------------------------------------------------------------------------|
| executor.shutdown(); // 允许已提交任务执行完后关闭 executor.shutdownNow(); // 立即关闭线程池,取消所有任务 |

shutdown():等待所有任务执行完成后关闭(推荐)。

shutdownNow():尝试中断正在执行的任务,不推荐。

3.1.8.7.线程池的使用场景

线程池类型 适用场景

FixedThreadPool 适用于并发任务量较大,但线程数固定的场景,如 Web 服务器

CachedThreadPool 适用于短时间大量任务,如爬虫、并发请求

SingleThreadExecutor 适用于顺序执行任务,如日志写入

ScheduledThreadPool 适用于定时任务,如自动备份

3.1.9.Java 运行时环境 (JVM)

1)JVM 结构

Java 虚拟机(JVM)是 Java 代码的执行引擎,负责加载字节码并进行解释执行或 JIT 编译。JVM 的结构包括:

类加载器(ClassLoader):加载 Java 类文件,将其转换为 JVM 可以理解的格式。

内存管理:JVM 通过堆(Heap)和栈(Stack)等内存区域来管理内存。

堆:存放动态分配的对象。

栈:每个线程都有自己的栈,存储局部变量和方法调用。

方法区:存储类的元数据、常量池等。

垃圾回收(Garbage Collection):自动管理内存,回收不再使用的对象。

2) JVM 参数调优

JVM 启动时可以配置多种参数,以优化性能,常用参数包括:

堆大小:-Xms 和 -Xmx 用于设置堆的初始大小和最大大小。

垃圾回收器选择:-XX:+UseG1GC、-XX:+UseParallelGC 等。

JVM 日志输出:-XX:+PrintGCDetails、-Xloggc 用于日志输出。

3) JIT 编译

JIT(Just-In-Time)编译器在运行时将字节码编译为本地机器代码,以提高性能。

3.1.10.Java标准库

Java 标准库(JDK API)提供了丰富的功能支持,主要分为以下几部分:

1)Java 核心类库(java.lang、java.util)

这些是 Java 运行环境中最基本的类,无需导入即可使用。

java.lang(核心基础类)

Object:所有类的父类

String:字符串操作类

Math:数学计算工具类

System:系统相关方法(如 System.out.println())

Thread:线程管理

Runtime:运行时环境

java.util(常用工具类)

ArrayList、LinkedList:集合框架(List)

HashMap、TreeMap:键值存储(Map)

Collections:集合工具类

Random:随机数生成

Date、Calendar、LocalDateTime:日期时间处理

Optional:避免 NullPointerException

2)输入/输出(java.io、java.nio)

用于处理文件、流、网络等输入输出操作。

java.io(传统 IO)

File:文件操作

InputStream、OutputStream:字节流

Reader、Writer:字符流

BufferedReader、BufferedWriter:带缓冲区的 IO

ObjectInputStream、ObjectOutputStream:对象序列化

java.nio(新 IO)

ByteBuffer、CharBuffer:缓冲区

FileChannel、SocketChannel:高效 IO 通信

Selector:多路复用

3)并发编程(java.util.concurrent、java.lang.Thread)

支持多线程与并发编程。

java.lang.Thread:线程基础类

java.util.concurrent(JUC 包)

ExecutorService:线程池

Future:异步任务

CountDownLatch、CyclicBarrier:线程同步工具

ReentrantLock:可重入锁

AtomicInteger、AtomicLong:原子操作

ForkJoinPool:并行计算框架

4)网络编程(java.net

用于处理 TCP/IP、HTTP、UDP 等网络通信。

Socket、ServerSocket:TCP 连接

DatagramSocket、DatagramPacket:UDP 连接

URL、HttpURLConnection:HTTP 请求

InetAddress:IP 地址解析

5)数据库编程(java.sql、javax.persistence)

支持数据库操作和持久化框架。

java.sql(JDBC 规范)

Connection:数据库连接

Statement、PreparedStatement:SQL 语句执行

ResultSet:结果集处理

DataSource:数据库连接池

javax.persistence(JPA 规范)

EntityManager:持久化管理

Query:JPQL 查询

Transaction:事务管理

6)反射与动态代理(java.lang.reflect、java.lang.invoke)

用于运行时操作类和方法。

java.lang.reflect

Class:表示类的结构

Method、Field、Constructor:类的成员信息

Proxy:动态代理

java.lang.invoke

MethodHandle:高效方法调用

LambdaMetafactory:动态生成 Lambda 表达式

7) Java 安全(java.security)

提供加密、认证、权限控制等功能。

MessageDigest:哈希计算(如 MD5、SHA-256)

Cipher:加解密(AES、DES)

KeyPairGenerator、Signature:数字签名

SecureRandom:安全随机数

8) Java 8+ 新特性(java.time、java.util.function)

Java 8 及以上版本引入了许多增强功能。

Lambda 表达式(函数式编程)

Function、Consumer、Supplier、Predicate

Stream API(流式操作数据)

java.time(新的日期时间 API)

LocalDate、LocalTime、LocalDateTime

Duration、Period

DateTimeFormatter

9) XML 和 JSON 处理(java.xml、javax.json、com.fasterxml.jackson)

用于解析和生成 XML、JSON 数据。

XML 解析:

DocumentBuilder(DOM 解析)

SAXParser(SAX 解析)

JSON 处理:

javax.json(JSON-P 处理 JSON)

Jackson、Gson(第三方库)

10)GUI 开发(java.awt、javax.swing、javafx)

用于创建桌面应用程序。

3.1.11.Java 架构与框架
3.1.11.1.Spring Framework

Spring 是 Java 最流行的开源框架,提供了全面的基础设施支持:

Spring Core:包括 IOC(控制反转)和 AOP(面向切面编程)。

Spring MVC:轻量级的 Web 框架,支持 RESTful 风格的 Web 服务。

Spring Boot:简化 Spring 应用的配置和部署,支持快速启动和开发。

Spring Data:简化数据库操作,支持 JPA、MongoDB、Cassandra 等数据存储。

Spring Security:为应用提供认证和授权功能。

Spring Cloud:支持微服务架构的开发,提供服务发现、负载均衡等功能。

3.1.11.2.Hibernate

Hibernate 是一种 ORM(对象关系映射)框架,简化了数据库操作,将 Java 对象和数据库表之间建立映射关系。

3.1.11.3.Apache Kafka

Apache Kafka 是一个分布式流处理平台,广泛用于高吞吐量的消息队列系统。

3.1.11.4.Java EE / Jakarta EE

Java 企业级应用开发标准(现为 Jakarta EE),包含多个重要的 API 和框架:

Servlet 和 JSP:用于开发 Web 应用。

JPA:Java 持久化 API,简化数据库操作。

JMS:Java 消息服务,用于消息队列。

EJB:企业级 Bean,用于事务处理、远程调用等。

3.1.12.集合
3.1.12.1.集合框架

Java 提供了强大的集合框架,它是 Java 的核心组成部分之一。主要包括以下内容:

接口:集合框架的核心接口包括:

Collection:集合的根接口。

List:有序集合,允许元素重复,常见实现有 ArrayList、LinkedList。

Set:不允许重复的集合,常见实现有 HashSet、LinkedHashSet、TreeSet。

Queue:队列接口,常见实现有 LinkedList、PriorityQueue。

Map:映射接口,键值对存储,常见实现有 HashMap、TreeMap、LinkedHashMap、Hashtable。

实现类:集合框架的具体实现类,例如:

ArrayList、LinkedList(List 接口实现类)

HashSet、TreeSet、LinkedHashSet(Set 接口实现类)

HashMap、TreeMap、LinkedHashMap(Map 接口实现类)

PriorityQueue(Queue 接口实现类)

工具类:例如:

Collections:提供了对集合的操作(如排序、查找、反转等)的静态方法。

Arrays:用于操作数组的工具类,可以将数组转换为集合等。

3.1.12.2.Java 8 Stream API(流式操作)

Java 8 引入了 Stream API,使得集合的操作更加灵活和函数化。流式操作可以简化集合的遍历和处理,并可以进行复杂的数据转换和计算。

Stream 接口:主要有 Stream、IntStream、LongStream 和 DoubleStream 用于处理集合数据。

常用操作:filter、map、reduce、collect、forEach、flatMap 等方法。

并行流:parallelStream() 用于并行处理集合,提高性能。

3.1.12.3.并发集合

Java 提供了一些线程安全的集合实现,适用于多线程环境中的集合操作:

ConcurrentHashMap:一个线程安全的哈希表实现,适用于高并发场景。

CopyOnWriteArrayList、CopyOnWriteArraySet:用于处理读多写少的并发场景。

BlockingQueue:线程安全的队列接口,常见实现有 ArrayBlockingQueue、LinkedBlockingQueue。

ConcurrentLinkedQueue、ConcurrentLinkedDeque:适用于高并发环境下的队列和双端队列实现。

3.1.12.4.Guava 库

Google 的 Guava 库提供了对集合操作的丰富扩展,包含许多额外的集合类型和工具类,增强了 Java 的集合功能:

Immutable Collections:不可变集合,提供 ImmutableList、ImmutableSet、ImmutableMap 等实现。

Multiset:允许相同元素出现多次的集合类型。

Multimap:每个键可以对应多个值的映射类型,类似于 Map<K, List<V>>。

BiMap:双向映射,允许反向查找。

Table:用于表示二维表格数据,类似于 Map<RowKey, Map<ColumnKey, Value>>。

3.1.13.文件处理

在 JDK 17 中,Java 提供了多种文件处理技术,涵盖了传统的 java.io 包、现代化的 NIO (New I/O) 包、以及一些辅助工具库。以下是一个详细的文件处理技术体系,涵盖了各个相关类和接口。

3.1.13.1.传统 I/O(java.io 包)

java.io 包是 Java 早期提供的文件操作类,适用于文件的基本读写、流操作等。它主要以字节流和字符流的方式处理文件。

字节流类:

FileInputStream:用于读取文件的字节流。

FileOutputStream:用于写入字节流到文件。

BufferedInputStream:提供字节流的缓冲支持,提高读取性能。

BufferedOutputStream:提供字节流的缓冲支持,提高写入性能。

字符流类:

FileReader:用于读取字符流,适合文本文件。

FileWriter:用于写入字符流,适合文本文件。

BufferedReader:提供字符流的缓冲支持,支持按行读取。

BufferedWriter:提供字符流的缓冲支持,支持按行写入。

打印流:

PrintWriter:便于打印格式化文本输出,支持自动刷新等功能。

随机访问文件:

RandomAccessFile:允许对文件的随机访问,适合在文件任意位置读写数据。

文件和目录:

File:表示文件和目录,可以检查文件是否存在、创建文件和目录、删除文件等。

示例(使用 BufferedReader 读取文件):

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class FileReaderExample { public static void main(String[] args) { try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) { String line; while ((line = br.readLine()) != null) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } } } |

3.1.13.2.NIO(New I/O)------java.nio 包

NIO 是 Java 1.4 引入的用于提高 I/O 性能的 API,提供了更强大的文件操作能力。自 Java 7 起,NIO 的文件操作变得更加简便,特别是通过 java.nio.file 包。

核心类和功能:

Path:代表文件路径,替代了 File 类。它是 NIO 文件系统 API 的基础。

Files:包含了许多静态方法,简化了文件读写操作、文件管理等。

Files.readAllBytes(Path):读取文件内容为字节数组。

Files.readAllLines(Path):按行读取文件内容。

Files.write(Path, byte[]):写字节数组到文件。

Files.copy(Path, Path):复制文件。

Files.move(Path, Path):移动文件。

Files.delete(Path):删除文件。

Files.exists(Path):检查文件是否存在。

Files.isDirectory(Path):检查路径是否为目录。

Files.createDirectory(Path):创建目录。

FileSystems:用于获取文件系统的实例,支持不同的文件系统(如默认文件系统、虚拟文件系统等)。

WatchService:提供文件系统监控,可以监控文件或目录的变化,适用于文件系统事件(如文件创建、修改、删除)监听。

示例(使用 Files 读取文件内容):

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import java.nio.file.*; import java.io.IOException; import java.util.List; public class FilesExample { public static void main(String[] args) { Path path = Paths.get("file.txt"); try { List<String> lines = Files.readAllLines(path); for (String line : lines) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } } } |

示例(使用 Files 写入文件):

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import java.nio.file.*; import java.io.IOException; public class FilesWriteExample { public static void main(String[] args) { Path path = Paths.get("output.txt"); String content = "Hello, NIO!"; try { Files.write(path, content.getBytes()); System.out.println("文件已写入"); } catch (IOException e) { e.printStackTrace(); } } } |

示例(使用 WatchService 监听文件变化):

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import java.nio.file.*; import java.io.IOException; public class FileWatcherExample { public static void main(String[] args) throws IOException, InterruptedException { Path dir = Paths.get("watched_dir"); WatchService watchService = FileSystems.getDefault().newWatchService(); dir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE); while (true) { WatchKey key = watchService.take(); for (WatchEvent<?> event : key.pollEvents()) { System.out.println("事件类型: " + event.kind() + ", 文件: " + event.context()); } if (!key.reset()) { break; } } } } |

3.1.13.3.java.nio.file 包中的其他重要类

Paths:用于将字符串路径转换为 Path 对象。

DirectoryStream:用于遍历目录中的文件。

FileVisitOption 和 FileVisitor:用于递归访问文件系统,适合遍历整个目录树。

OpenOption:定义打开文件时的行为选项(如 StandardOpenOption.CREATE)。

示例(递归遍历目录):

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.io.IOException; public class FileVisitorExample { public static void main(String[] args) throws IOException { Path startPath = Paths.get("root_directory"); Files.walkFileTree(startPath, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { System.out.println("文件: " + file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { System.err.println("访问失败: " + file); return FileVisitResult.CONTINUE; } }); } } |

java.util.zip 包:

对于压缩文件的处理,java.util.zip 包提供了对 ZIP 和 GZIP 格式的支持。

ZipInputStream 和 ZipOutputStream:用于读取和写入 ZIP 文件。

GZIPInputStream 和 GZIPOutputStream:用于处理 GZIP 格式的压缩文件。

java.nio.charset

Java 通过 Charset 类和相关的工具类提供字符编码的支持,适用于文件的字符编码转换。

3.1.14.java属性加载顺序

命令行参数:-D 参数优先级最高,直接影响全局系统属性。

==>

环境变量:系统环境变量紧随其后。

==>

系统属性:通过 System.setProperty() 设置的系统属性。

==>

类路径中的属性文件:如 application.properties 等,在类加载时读取。

==>

父类的静态变量:父类的静态变量在父类类加载时初始化。

==>

子类的静态变量:子类的静态变量在子类类加载时初始化。

==>

父类的实例变量:父类的实例变量在对象实例化时初始化。

==>

子类的实例变量:子类的实例变量在对象实例化时初始化,会覆盖或隐藏父类的实例变量。

==>

硬编码的默认值:如果没有其他方式提供值,类中的硬编码默认值会生效。

3.1.100.常用处理
3.1.100.1.计算一个文件夹下所有文件的字数

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import java.io.*; import java.util.*; public class FileWordCount { public static void main(String[] args) { // 指定要计算字数的文件夹路径 String folderPath = "C:/path/to/your/folder"; // 请替换为你实际的文件夹路径 File folder = new File(folderPath); // 计算文件夹中的所有文件字数 long totalWordCount = countWordsInFolder(folder); System.out.println("文件夹中所有文件的总字数: " + totalWordCount); } // 计算文件夹下所有文件的字数 public static long countWordsInFolder(File folder) { long wordCount = 0; // 确保 folder 是一个文件夹且存在 if (folder.exists() && folder.isDirectory()) { // 获取文件夹下的所有文件 File[] files = folder.listFiles(); if (files != null) { for (File file : files) { if (file.isDirectory()) { // 如果是子文件夹,则递归计算 wordCount += countWordsInFolder(file); } else if (file.isFile()) { // 如果是文件,计算该文件的字数 wordCount += countWordsInFile(file); } } } } else { System.out.println("指定的路径不是有效的文件夹!"); } return wordCount; } // 计算单个文件的字数 public static long countWordsInFile(File file) { long wordCount = 0; try (BufferedReader br = new BufferedReader(new FileReader(file))) { String line; while ((line = br.readLine()) != null) { // 使用正则表达式匹配单词并计算 String[] words = line.split("\\s+"); wordCount += words.length; } } catch (IOException e) { e.printStackTrace(); } return wordCount; } } |

3.1.101.推荐资料

Java自主学习系统.zip

链接: https://pan.baidu.com/s/1fePXHawtaXO64_E25_zL5Q?pwd=0000 提取码: 0000

相关推荐
froginwe117 小时前
CSS3 框大小:深入解析与优化技巧
开发语言
脸大是真的好~7 小时前
黑马JAVA+AI 加强03-集合-Collection-List和Set集合-迭代器(Iterator)遍历-并发修改异常
java
C_Liu_7 小时前
12.C++:模版进阶
开发语言·c++
cj6341181508 小时前
DBeaver连接本地MySQL、创建数据库表的基础操作
java·后端
sali-tec8 小时前
C# 基于halcon的视觉工作流-章54-N点标定
开发语言·图像处理·算法·计算机视觉·c#
娇娇yyyyyy8 小时前
C++11新特性基础知识点汇总
开发语言·c++·算法
CILMY238 小时前
【一问专栏】Python中is和==的区别详解
开发语言·python·is·==
书院门前细致的苹果8 小时前
深入理解 Java 多线程与线程池 —— 从原理到实战
java·开发语言
烟花落o8 小时前
指针深入第二弹--字符指针、数组指针、函数指针、函数指针数组、转移表的理解加运用
c语言·开发语言·笔记·vscode·算法