本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:Java API是Java编程语言的核心类库,为开发者提供了丰富的功能支持,包括集合、IO流、多线程、网络编程、反射等常用模块。”Java API中文网页版2”是专为中国开发者打造的本地化文档资源,以网页形式呈现,支持离线查阅,涵盖Java SE平台所有公共类和接口。文档详细说明了每个类的方法、构造函数、异常处理及示例代码,极大提升了开发效率与学习体验。无论是初学者还是资深开发者,该文档都是不可或缺的参考工具,适合用于日常编程查询与技术学习。
javaAPI 中文网页版2

1. Java API概述与核心类库介绍

Java API(Application Programming Interface)是Java平台提供的标准类库集合,涵盖了从基础数据类型封装到高级网络通信等广泛功能。它是Java语言强大生态的基础支撑,使开发者能够高效构建稳定、可扩展的应用程序。

Java API主要模块包括但不限于:
- java.lang :提供Java语言基础类,如 Object String System 等;
- java.util :集合框架、日期时间处理、随机数生成等实用工具;
- java.io :支持文件与流式数据的输入输出操作;
- java.net :实现网络通信相关功能,如Socket和HTTP访问;
- java.nio :提供更高效的非阻塞IO操作支持。

通过合理使用这些核心类库,开发者可以避免重复造轮子,专注于业务逻辑的实现与优化。

2. 集合框架(List、Set、Map)详解与使用

集合框架是Java中处理数据结构的核心工具,它不仅提供了灵活的存储结构,还具备良好的扩展性和性能优化机制。本章将从集合框架的基本概念入手,逐步深入到List、Set和Map接口的使用方法,并结合实际代码进行演示。通过本章的学习,读者将能够熟练掌握Java集合框架的设计思想与应用场景,为开发高效、可维护的Java程序打下坚实基础。

2.1 集合框架概述

Java集合框架(Java Collections Framework,JCF)是一组接口和类的集合,它们提供了一种统一的方式用于操作和处理数据集合。这些类和接口位于 java.util 包中,包括 List Set Map 等常用接口以及它们的实现类。集合框架的设计充分体现了面向对象的封装、继承和多态特性,同时也支持泛型编程,提升了代码的类型安全性。

2.1.1 集合框架的结构与接口继承关系

Java集合框架的核心接口主要包括 Collection List Set Map 等,它们之间的继承与实现关系如下图所示:

classDiagram
    Collection <|-- List
    Collection <|-- Set
    List <|-- ArrayList
    List <|-- LinkedList
    Set <|-- HashSet
    Set <|-- TreeSet
    Set <|-- LinkedHashSet
    Map <|-- HashMap
    Map <|-- TreeMap
    Map <|-- ConcurrentHashMap
    Map <|-- LinkedHashMap
  • Collection 是集合框架的根接口,它定义了集合的基本操作,如添加、删除、遍历等。
  • List 是有序的、可重复的集合接口,常见实现类包括 ArrayList LinkedList
  • Set 是无序的、不可重复的集合接口,常见的实现类有 HashSet TreeSet LinkedHashSet
  • Map 是键值对的集合,其接口定义了键值对的增删改查操作,常见实现类有 HashMap TreeMap ConcurrentHashMap 等。

说明 Map 并不继承自 Collection 接口,但通常也被归为集合框架的一部分,因为其用途广泛且与集合操作密切相关。

2.1.2 集合与数组的区别

特性 数组 集合
类型 静态 动态
大小 固定 可变
类型安全 通过泛型实现 支持泛型
操作 增删改查需要手动实现 提供丰富API
数据类型 支持基本类型和对象 仅支持对象(通过泛型)
线程安全 不具备线程安全机制 部分实现类支持线程安全

代码示例:数组与集合的初始化对比

// 数组初始化
int[] numbers = new int[5];
numbers[0] = 10;

// List集合初始化
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");

逻辑分析与参数说明

  • int[] numbers = new int[5]; :定义了一个长度为5的整型数组,初始值为0。
  • List<String> list = new ArrayList<>(); :使用泛型创建了一个字符串类型的 ArrayList 集合,初始容量为10(默认)。
  • list.add("Java"); :向集合中添加元素,自动扩容,无需手动管理容量。

2.2 List接口及实现类

List 是Java集合框架中最常用的接口之一,它允许存储重复元素,并保持插入顺序。常见的实现类包括 ArrayList LinkedList ,它们在性能和适用场景上各有特点。

2.2.1 ArrayList和LinkedList的特性与适用场景

特性 ArrayList LinkedList
数据结构 动态数组 双向链表
随机访问 O(1) O(n)
插入/删除 O(n)(中间) O(1)(首尾)O(n)(中间)
内存占用 较低 较高(节点开销)
线程安全
适用场景 查询多、增删少 频繁插入/删除

代码示例:ArrayList与LinkedList的初始化与访问

// 使用ArrayList
List<String> arrayList = new ArrayList<>();
arrayList.add("A");
arrayList.add("B");
System.out.println(arrayList.get(0));  // 输出:A

// 使用LinkedList
List<String> linkedList = new LinkedList<>();
linkedList.add("X");
linkedList.add("Y");
System.out.println(linkedList.get(0));  // 输出:X

逐行分析与逻辑说明

  • List<String> arrayList = new ArrayList<>(); :声明一个字符串类型的 ArrayList 实例。
  • arrayList.add("A"); :添加元素到列表末尾,内部自动扩容。
  • arrayList.get(0) :通过索引快速访问元素,时间复杂度为O(1)。
  • List<String> linkedList = new LinkedList<>(); :声明一个字符串类型的 LinkedList 实例。
  • linkedList.add("X"); :添加元素,底层通过链表结构进行操作。
  • linkedList.get(0) :虽然也能访问,但链表访问中间元素效率较低,需从头节点开始遍历。

2.2.2 List的常用操作:增删改查与遍历

常用操作示例代码

List<Integer> numbers = new ArrayList<>();
numbers.add(10);            // 添加元素
numbers.add(1, 20);         // 在索引1位置插入20
numbers.set(0, 30);         // 修改索引0的值为30
numbers.remove(1);          // 删除索引1的元素

// 遍历方式一:增强for循环
for (int num : numbers) {
    System.out.print(num + " ");
}

// 遍历方式二:迭代器
Iterator<Integer> it = numbers.iterator();
while (it.hasNext()) {
    System.out.print(it.next() + " ");
}

执行逻辑说明与参数解释

  • numbers.add(10) :向列表尾部添加整型元素10。
  • numbers.add(1, 20) :在索引1处插入元素20,原有元素后移。
  • numbers.set(0, 30) :修改索引0处的值为30。
  • numbers.remove(1) :删除索引1处的元素。
  • for (int num : numbers) :使用增强型for循环进行遍历,适用于顺序访问。
  • Iterator<Integer> it = numbers.iterator() :使用迭代器遍历,适用于边遍历边修改集合的场景。

2.3 Set接口及实现类

Set 接口是 Collection 的子接口,它不允许存储重复元素。常见的实现类有 HashSet TreeSet LinkedHashSet ,它们在存储方式和排序机制上有所不同。

2.3.1 HashSet、TreeSet和LinkedHashSet的内部机制

实现类 数据结构 是否有序 是否线程安全
HashSet 哈希表 无序
TreeSet 红黑树 有序(自然排序或自定义排序)
LinkedHashSet 哈希表 + 双向链表 插入顺序

代码示例:Set集合的去重与排序特性演示

// HashSet:无序且去重
Set<String> hashSet = new HashSet<>();
hashSet.add("C");
hashSet.add("A");
hashSet.add("B");
hashSet.add("A");  // 重复元素不会被添加
System.out.println(hashSet);  // 输出顺序可能为:[A, B, C]

// TreeSet:按自然顺序排序
Set<String> treeSet = new TreeSet<>();
treeSet.add("C");
treeSet.add("A");
treeSet.add("B");
System.out.println(treeSet);  // 输出:[A, B, C]

// LinkedHashSet:保持插入顺序
Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("C");
linkedHashSet.add("A");
linkedHashSet.add("B");
System.out.println(linkedHashSet);  // 输出:[C, A, B]

逐行分析与逻辑说明

  • Set<String> hashSet = new HashSet<>(); :创建一个 HashSet 集合,元素无序且自动去重。
  • hashSet.add("A") :添加元素,若已存在则不会重复添加。
  • Set<String> treeSet = new TreeSet<>(); :使用 TreeSet 时,元素会按照自然顺序或自定义比较器排序。
  • Set<String> linkedHashSet = new LinkedHashSet<>(); LinkedHashSet 保留了元素的插入顺序。

2.3.2 Set集合的去重原理与应用

Set 集合通过元素的 equals() hashCode() 方法实现去重。当两个对象的 hashCode() 相同且 equals() 返回 true 时, Set 认为它们是重复元素。

自定义对象去重示例

class Person {
    private String name;
    private int age;

    // 构造方法、getter、setter

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && name.equals(person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

// 使用Set存储自定义对象
Set<Person> people = new HashSet<>();
people.add(new Person("Tom", 20));
people.add(new Person("Tom", 20));  // 视为重复对象,不会添加

逻辑说明与参数解释

  • equals() 方法用于判断两个对象是否相等。
  • hashCode() 方法用于快速定位对象在哈希表中的位置。
  • 若两个对象 equals() true ,则它们的 hashCode() 必须相同,否则无法保证去重正确性。

2.4 Map接口及实现类

Map 接口用于存储键值对(Key-Value Pair),每个键(Key)映射一个值(Value),且键必须唯一。常见的实现类包括 HashMap TreeMap ConcurrentHashMap

2.4.1 HashMap、TreeMap与ConcurrentHashMap的区别

实现类 线程安全 排序 数据结构
HashMap 无序 哈希表+链表/红黑树
TreeMap 有序(按键排序) 红黑树
ConcurrentHashMap 是(线程安全) 无序 分段哈希表

代码示例:Map的常用操作

// HashMap使用示例
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("Java", 100);
hashMap.put("Python", 90);
System.out.println(hashMap.get("Java"));  // 输出:100

// TreeMap按Key排序
Map<String, Integer> treeMap = new TreeMap<>();
treeMap.put("Z", 1);
treeMap.put("A", 2);
System.out.println(treeMap);  // 输出:{A=2, Z=1}

// ConcurrentHashMap线程安全
Map<String, String> concurrentMap = new ConcurrentHashMap<>();
concurrentMap.put("key", "value");

逐行分析与逻辑说明

  • hashMap.put("Java", 100); :添加键值对,键为字符串,值为整数。
  • hashMap.get("Java") :通过键快速获取值。
  • treeMap.put("Z", 1); TreeMap 会自动按键的自然顺序进行排序。
  • concurrentMap ConcurrentHashMap 适用于多线程环境,避免线程安全问题。

2.4.2 Map的键值对操作与遍历方式

Map遍历的多种方式

Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);

// 遍历方式一:entrySet
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println(entry.getKey() + " -> " + entry.getValue());
}

// 遍历方式二:keySet
for (String key : map.keySet()) {
    System.out.println(key + " -> " + map.get(key));
}

// 遍历方式三:Java 8+ forEach
map.forEach((key, value) -> System.out.println(key + " -> " + value));

执行逻辑与参数说明

  • entrySet() :获取键值对的集合,适合同时访问键和值。
  • keySet() :获取键的集合,再通过 get() 获取值,适合只关注键的场景。
  • forEach((key, value) -> {}) :Java 8引入的Lambda表达式方式,代码更简洁。

2.5 集合框架的性能优化与线程安全

Java集合框架本身并不提供线程安全机制,但在并发环境中,开发者可以通过工具类或线程安全实现类来确保数据一致性。

2.5.1 Collections工具类的使用

Collections 类提供了多种静态方法用于对集合进行排序、查找、同步包装等操作。

常用方法示例

List<Integer> list = new ArrayList<>();
list.add(3);
list.add(1);
list.add(2);

// 排序
Collections.sort(list);  // 输出:[1, 2, 3]

// 查找最大值
int max = Collections.max(list);  // 输出:3

// 将集合包装为线程安全
List<Integer> synchronizedList = Collections.synchronizedList(list);

逻辑说明与参数解释

  • Collections.sort(list) :对列表进行升序排序。
  • Collections.max(list) :查找列表中的最大值。
  • Collections.synchronizedList(list) :返回一个线程安全的List视图,所有操作都会自动加锁。

2.5.2 线程安全集合类与并发控制

除了使用 Collections.synchronizedList() ,Java还提供了专门的并发集合类,如 ConcurrentHashMap CopyOnWriteArrayList 等。

并发集合使用示例

// CopyOnWriteArrayList适用于读多写少的并发场景
List<String> concurrentList = new CopyOnWriteArrayList<>();
concurrentList.add("Thread1");
concurrentList.add("Thread2");

// ConcurrentHashMap线程安全
Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();
concurrentMap.put("count", 1);
concurrentMap.compute("count", (k, v) -> v + 1);  // 线程安全的更新操作

执行逻辑与参数说明

  • CopyOnWriteArrayList :写操作时复制底层数组,读操作无需加锁,适用于高并发读场景。
  • compute() ConcurrentHashMap 提供的线程安全更新方法,避免并发冲突。

本章详细讲解了Java集合框架的核心接口和实现类,包括 List Set Map 的特性、使用方式以及线程安全机制。通过对集合结构、内部机制和实际操作的深入分析,读者可以更好地理解集合框架的设计思想与应用场景,为开发高性能、可维护的Java程序打下坚实基础。

3. IO流(InputStream/OutputStream/Reader/Writer)详解与实战

Java IO流是Java编程中处理输入输出的核心机制。无论是在网络通信、文件操作还是数据持久化方面,IO流都扮演着不可或缺的角色。本章将从流的基本概念入手,详细讲解字节流和字符流的区别,并通过实际案例演示其应用场景。通过本章的学习,读者将掌握Java IO流的基本结构、核心类及其使用方式,并能根据实际需求选择合适的流进行文件读写和数据传输。

3.1 IO流概述与分类

Java IO流体系庞大,但核心概念较为清晰。IO流主要分为输入流(InputStream/Reader)和输出流(OutputStream/Writer)两大类。根据处理数据的单位不同,又可分为字节流和字符流。

3.1.1 输入流与输出流的基本原理

Java IO流的基本原理是将数据从一个源(如文件、网络、内存等)读取到程序中,或者将数据从程序写入到目标位置。这种数据的流动是单向的,输入流用于读取,输出流用于写入。

输入流与输出流的类继承关系

Java IO流的类继承关系如下图所示:

graph TD
    A[InputStream] --> B[FileInputStream]
    A --> C[BufferedInputStream]
    A --> D[ObjectInputStream]
    A --> E[ByteArrayInputStream]
    F[OutputStream] --> G[FileOutputStream]
    F --> H[BufferedOutputStream]
    F --> I[ObjectOutputStream]
    F --> J[ByteArrayOutputStream]
    K[Reader] --> L[FileReader]
    K --> M[BufferedReader]
    K --> N[InputStreamReader]
    O[Writer] --> P[FileWriter]
    O --> Q[BufferedWriter]
    O --> R[OutputStreamWriter]

如上图所示, InputStream OutputStream 是字节流的基础类,而 Reader Writer 是字符流的基础类。

输入流与输出流的典型使用场景
流类型 用途 举例
InputStream 读取字节数据 读取图片、视频等二进制文件
OutputStream 写入字节数据 将二进制数据写入文件
Reader 读取字符数据 读取文本文件内容
Writer 写入字符数据 将字符串写入文件

3.1.2 字节流与字符流的区别

字节流以字节为单位进行读写,适用于处理所有类型的数据,包括二进制文件;而字符流以字符为单位进行读写,适合处理文本数据,尤其是涉及编码转换的场景。

字节流与字符流的核心类对比
类型 字节流 字符流
输入 InputStream Reader
输出 OutputStream Writer
常用子类 FileInputStream、ObjectInputStream FileReader、BufferedReader
处理单位 字节(8位) 字符(16位,Unicode)
使用场景分析
  • 字节流适用场景
  • 读取非文本文件(如图片、音频、视频)
  • 需要精确控制字节操作的场合(如网络传输、加密解密)

  • 字符流适用场景

  • 处理纯文本文件(如 .txt .java
  • 需要自动处理字符编码(如 UTF-8、GBK)的情况

3.2 字节流操作详解

字节流主要用于处理二进制数据,Java 提供了丰富的类来实现对字节流的操作。

3.2.1 InputStream与OutputStream常用子类

Java 提供了多个子类来实现字节流操作,常见的包括:

  • FileInputStream / FileOutputStream :用于文件的字节读写
  • BufferedInputStream / BufferedOutputStream :带缓冲区的字节流,提高读写效率
  • ByteArrayInputStream / ByteArrayOutputStream :在内存中操作字节流
  • ObjectInputStream / ObjectOutputStream :用于对象序列化与反序列化
示例:使用FileInputStream读取文件内容
import java.io.FileInputStream;
import java.io.IOException;

public class FileInputStreamExample {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("example.txt")) {
            int data;
            while ((data = fis.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
代码解析:
  • FileInputStream 创建了一个指向文件 example.txt 的输入流。
  • read() 方法每次读取一个字节,返回值为 -1 表示到达文件末尾。
  • (char) data 将字节转换为字符进行输出。
  • 使用 try-with-resources 自动关闭资源,避免资源泄露。
示例:使用FileOutputStream写入文件内容
import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStreamExample {
    public static void main(String[] args) {
        try (FileOutputStream fos = new FileOutputStream("output.txt")) {
            String content = "Hello, Java IO!";
            fos.write(content.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
代码解析:
  • write() 方法接受字节数组,因此需要将字符串转为字节数组 content.getBytes()
  • 文件 output.txt 会被创建或覆盖写入内容。

3.2.2 文件复制与对象序列化示例

文件复制示例(使用字节流)
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileCopyExample {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("source.jpg");
             FileOutputStream fos = new FileOutputStream("copy.jpg")) {
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, bytesRead);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
代码解析:
  • 使用缓冲区 buffer 提高复制效率,每次读取最多 1024 字节。
  • read(buffer) 返回实际读取的字节数, write(buffer, 0, bytesRead) 写入相应字节。
对象序列化与反序列化
import java.io.*;

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 class ObjectSerializationExample {
    public static void main(String[] args) {
        // 序列化
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
            oos.writeObject(new Person("Alice", 30));
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 反序列化
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) {
            Person p = (Person) ois.readObject();
            System.out.println(p);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
代码解析:
  • Person 类实现 Serializable 接口,表示可以被序列化。
  • ObjectOutputStream 用于将对象写入文件, ObjectInputStream 用于读取对象。
  • readObject() 返回 Object 类型,需要强制转换为具体类。

3.3 字符流操作详解

字符流用于处理字符数据,通常用于读写文本文件。

3.3.1 Reader与Writer的基本用法

Java 提供了多种字符流类,常见的有:

  • FileReader / FileWriter :用于读写字符文件
  • BufferedReader / BufferedWriter :带缓冲区的字符流,提高效率
  • InputStreamReader / OutputStreamWriter :桥接字节流与字符流,处理编码转换
示例:使用BufferedReader读取文本文件
import java.io.*;

public class BufferedReaderExample {
    public static void main(String[] args) {
        try (BufferedReader br = new BufferedReader(new FileReader("example.txt"))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
代码解析:
  • BufferedReader 包装 FileReader ,提供按行读取功能。
  • readLine() 每次读取一行,返回 null 表示文件结束。
示例:使用BufferedWriter写入文本文件
import java.io.*;

public class BufferedWriterExample {
    public static void main(String[] args) {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
            bw.write("Hello, BufferedWriter!");
            bw.newLine(); // 写入换行符
            bw.write("This is the second line.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
代码解析:
  • write() 方法写入字符串内容。
  • newLine() 方法用于插入换行符,平台兼容性更好。

3.3.2 文本文件的读写操作

示例:统计文本文件的行数
import java.io.*;

public class LineCounter {
    public static void main(String[] args) {
        int lineCount = 0;
        try (BufferedReader br = new BufferedReader(new FileReader("example.txt"))) {
            while (br.readLine() != null) {
                lineCount++;
            }
            System.out.println("Total lines: " + lineCount);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
代码解析:
  • 每次调用 readLine() 若不为 null 则计数器加一。
  • 最终输出文件总行数。

3.4 高级IO操作

Java IO流还提供了高级操作类,如缓冲流、转换流和随机访问文件类,以满足更复杂的IO需求。

3.4.1 缓冲流与转换流的使用

缓冲流(Buffered Streams)

缓冲流通过在内存中设置缓冲区减少磁盘IO次数,从而提升性能。

示例:使用BufferedInputStream提高读取效率
import java.io.*;

public class BufferedInputStreamExample {
    public static void main(String[] args) {
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("largefile.bin"))) {
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = bis.read(buffer)) != -1) {
                // 处理数据
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
代码解析:
  • BufferedInputStream 包装 FileInputStream ,通过缓冲机制提高读取速度。
转换流(InputStreamReader / OutputStreamWriter)

转换流用于将字节流转换为字符流,并指定字符编码。

示例:指定编码读取文本文件
import java.io.*;

public class InputStreamReaderExample {
    public static void main(String[] args) {
        try (InputStreamReader isr = new InputStreamReader(new FileInputStream("utf8file.txt"), "UTF-8");
             BufferedReader br = new BufferedReader(isr)) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
代码解析:
  • 使用 InputStreamReader 指定文件的编码格式(如 UTF-8)。
  • 配合 BufferedReader 实现高效按行读取。

3.4.2 随机访问文件RandomAccessFile

RandomAccessFile 允许对文件进行随机读写,适用于需要频繁访问文件特定位置的场景。

示例:使用RandomAccessFile修改文件内容
import java.io.*;

public class RandomAccessFileExample {
    public static void main(String[] args) {
        try (RandomAccessFile raf = new RandomAccessFile("data.txt", "rw")) {
            // 写入数据
            raf.write("Hello World".getBytes());
            // 移动文件指针到第6个字节位置
            raf.seek(6);
            // 修改部分内容
            raf.write("Java".getBytes());
            // 读取整个文件内容
            raf.seek(0);
            byte[] buffer = new byte[(int) raf.length()];
            raf.readFully(buffer);
            System.out.println(new String(buffer));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
代码解析:
  • seek(long pos) 方法用于设置文件指针位置。
  • write() readFully() 方法用于写入和读取数据。
  • 文件内容将被修改为 Hello Java

本章详细介绍了Java IO流的基本原理、字节流与字符流的使用方法,并通过多个实战示例展示了如何进行文件复制、对象序列化、文本处理以及高级IO操作。下一章将深入探讨Java多线程编程的核心机制与应用技巧。

4. 多线程编程(Thread、Runnable、线程池)详解

多线程编程是Java中实现并发处理的重要机制,它允许程序同时执行多个任务,从而提升系统性能和响应能力。本章将从线程的基本概念入手,逐步深入讲解线程的创建、状态管理、同步机制以及线程池的使用,帮助开发者构建高性能的并发应用程序。

4.1 多线程基础概念

4.1.1 线程与进程的区别

在操作系统中, 进程 是资源分配的基本单位,每个进程拥有独立的内存空间和系统资源。而 线程 是CPU调度的基本单位,一个进程可以包含多个线程,这些线程共享该进程的内存和资源。

特性 进程 线程
资源占用 占用资源多,切换成本高 占用资源少,切换成本低
通信方式 需要IPC机制(如管道、共享内存) 同一进程内的线程共享内存
安全性 独立性强,进程崩溃不影响其他 线程间共享资源,一个线程崩溃可能影响整个进程
创建与销毁成本

4.1.2 Java中线程的创建方式

Java提供了两种主要方式来创建线程:

1. 继承 Thread
public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程执行:" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();  // 启动线程
    }
}

代码逻辑分析:

  • MyThread 继承 Thread 类,并重写 run() 方法。
  • 调用 start() 方法启动线程, start() 内部会调用 run() 方法,但 run() 本身不能直接调用以启动线程。
  • 每个线程对象只能调用一次 start() ,否则会抛出 IllegalThreadStateException
2. 实现 Runnable 接口
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程执行:" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable(), "MyRunnable-Thread");
        thread.start();
    }
}

代码逻辑分析:

  • 使用 Runnable 接口可以避免类继承的局限性(Java不支持多继承)。
  • 构造 Thread 对象时传入 Runnable 实例,并指定线程名称。
  • 多个线程可以共享同一个 Runnable 实例,适用于任务共享的场景。

4.2 线程状态与调度

4.2.1 线程的状态转换图

Java线程在其生命周期中会经历多个状态,通过 Thread.State 枚举表示。其状态转换如下图所示:

graph TD
    A[NEW] --> B[RUNNABLE]
    B --> C[BLOCKED]
    B --> D[WAITING]
    B --> E[TIMED_WAITING]
    D --> F[TERMINATED]
    E --> F
    C --> B
    D --> B
    E --> B
  • NEW :线程刚被创建,尚未启动。
  • RUNNABLE :线程正在运行或等待CPU调度。
  • BLOCKED :线程因等待获取锁而阻塞。
  • WAITING :线程无限期等待其他线程通知。
  • TIMED_WAITING :线程在指定时间内等待。
  • TERMINATED :线程执行完毕或发生异常退出。

4.2.2 线程优先级与调度策略

Java线程具有优先级( Thread.MIN_PRIORITY=1 Thread.MAX_PRIORITY=10 ),默认为 Thread.NORM_PRIORITY=5 。优先级高的线程理论上更有可能获得CPU资源。

public class ThreadPriorityExample {
    public static void main(String[] args) {
        Thread highPriority = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("高优先级线程运行:" + i);
            }
        });
        highPriority.setPriority(Thread.MAX_PRIORITY);
        highPriority.start();

        Thread lowPriority = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("低优先级线程运行:" + i);
            }
        });
        lowPriority.setPriority(Thread.MIN_PRIORITY);
        lowPriority.start();
    }
}

代码逻辑分析:

  • setPriority() 方法设置线程优先级。
  • 优先级影响线程调度器的决策,但不能保证绝对执行顺序。
  • 线程调度依赖于JVM和操作系统,不同平台行为可能不一致。

4.3 线程同步与通信

4.3.1 synchronized关键字与Lock接口

多线程环境下,多个线程访问共享资源时可能引发数据不一致问题,因此需要使用同步机制进行控制。

使用 synchronized 实现同步
public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }

    public static void main(String[] args) throws InterruptedException {
        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();

        t1.join();
        t2.join();

        System.out.println("最终计数值:" + counter.getCount()); // 应输出2000
    }
}

代码逻辑分析:

  • synchronized 修饰方法,确保同一时间只有一个线程可以执行该方法。
  • join() 方法确保主线程等待两个线程执行完毕再输出结果。
使用 ReentrantLock 实现更灵活的同步
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockCounter {
    private int count = 0;
    private Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public int getCount() {
        return count;
    }

    public static void main(String[] args) throws InterruptedException {
        LockCounter counter = new LockCounter();
        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();

        t1.join();
        t2.join();

        System.out.println("最终计数值:" + counter.getCount()); // 应输出2000
    }
}

代码逻辑分析:

  • ReentrantLock 提供比 synchronized 更细粒度的控制,支持尝试加锁、超时等。
  • 必须在 finally 块中释放锁,避免死锁。

4.3.2 wait/notify机制与Condition类

线程之间可以通过 wait() notify() notifyAll() 实现通信。

public class WaitNotifyExample {
    private boolean flag = false;

    public synchronized void waitForFlag() throws InterruptedException {
        while (!flag) {
            wait();  // 等待flag变为true
        }
        System.out.println("Flag is true, continue execution");
    }

    public synchronized void setFlagTrue() {
        flag = true;
        notifyAll();  // 唤醒所有等待线程
    }

    public static void main(String[] args) throws InterruptedException {
        WaitNotifyExample example = new WaitNotifyExample();

        Thread waiter = new Thread(() -> {
            try {
                example.waitForFlag();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread notifier = new Thread(() -> {
            try {
                Thread.sleep(2000);
                example.setFlagTrue();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        waiter.start();
        notifier.start();

        waiter.join();
        notifier.join();
    }
}

代码逻辑分析:

  • wait() 会释放锁并进入等待状态,直到被 notify() notifyAll() 唤醒。
  • notifyAll() 会唤醒所有等待的线程,由线程调度器决定哪个线程继续执行。
使用 Condition 替代 wait/notify
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionExample {
    private boolean flag = false;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void waitForFlag() throws InterruptedException {
        lock.lock();
        try {
            while (!flag) {
                condition.await();  // 等待
            }
            System.out.println("Flag is true, continue execution");
        } finally {
            lock.unlock();
        }
    }

    public void setFlagTrue() {
        lock.lock();
        try {
            flag = true;
            condition.signalAll();  // 唤醒所有等待线程
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ConditionExample example = new ConditionExample();

        Thread waiter = new Thread(() -> {
            try {
                example.waitForFlag();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread notifier = new Thread(() -> {
            try {
                Thread.sleep(2000);
                example.setFlagTrue();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        waiter.start();
        notifier.start();

        waiter.join();
        notifier.join();
    }
}

代码逻辑分析:

  • Condition ReentrantLock 提供的条件变量,比 wait/notify 更灵活。
  • 可以绑定多个 Condition 到一个锁上,实现更细粒度的等待/通知机制。

4.4 线程池与并发工具类

4.4.1 Executor框架与线程池的创建

线程池是一种管理线程的机制,能够避免频繁创建和销毁线程带来的性能损耗。Java通过 Executor 框架提供线程池支持。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);  // 创建固定大小线程池

        for (int i = 0; i < 5; i++) {
            final int taskNumber = i;
            executor.submit(() -> {
                System.out.println("执行任务 " + taskNumber + ",线程:" + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        executor.shutdown();  // 关闭线程池
    }
}

代码逻辑分析:

  • Executors.newFixedThreadPool(3) 创建固定大小的线程池。
  • submit() 方法提交任务,线程池自动调度线程执行。
  • shutdown() 方法用于关闭线程池,确保任务完成后不再接受新任务。

4.4.2 Future与Callable的使用

当任务需要返回结果时,可以使用 Callable 接口配合 Future 获取结果。

import java.util.concurrent.*;

public class FutureExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newSingleThreadExecutor();

        Future<Integer> future = executor.submit(() -> {
            System.out.println("任务开始执行");
            Thread.sleep(2000);
            return 123;
        });

        System.out.println("等待结果...");
        Integer result = future.get();  // 阻塞直到结果返回
        System.out.println("任务结果:" + result);

        executor.shutdown();
    }
}

代码逻辑分析:

  • Callable 允许返回结果, submit() 返回 Future 对象。
  • future.get() 阻塞当前线程,直到任务完成并返回结果。

4.4.3 并发工具类CountDownLatch与CyclicBarrier

CountDownLatch

用于等待多个线程完成后再继续执行。

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);

        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " 正在执行任务");
                try {
                    Thread.sleep(1000);
                } finally {
                    latch.countDown();  // 计数减一
                }
            }).start();
        }

        latch.await();  // 等待所有线程完成
        System.out.println("所有线程执行完毕");
    }
}
CyclicBarrier

用于多个线程相互等待,直到所有线程到达某个屏障点后一起继续执行。

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(3, () -> {
            System.out.println("所有线程已到达屏障点,准备继续执行");
        });

        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " 正在执行任务");
                try {
                    Thread.sleep(1000);
                    barrier.await();  // 等待其他线程
                    System.out.println(Thread.currentThread().getName() + " 继续执行");
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

代码逻辑分析:

  • CountDownLatch 是一次性的,而 CyclicBarrier 可以重复使用。
  • CyclicBarrier 支持可选的“屏障动作”,所有线程到达屏障点后执行一次。

5. 网络编程(Socket、URL、HTTP通信)实现

Java提供了强大的网络编程支持,使得开发者可以轻松实现网络通信。本章将介绍Socket编程、URL访问和HTTP通信的基础知识,并通过示例代码展示其应用方式。本章内容将从底层Socket通信讲起,逐步过渡到更高层的HTTP通信机制,帮助开发者构建完整的网络通信知识体系。

5.1 网络编程基础

Java网络编程是构建分布式系统和网络应用的基础,掌握其基本概念对于开发网络通信程序至关重要。本节将介绍网络通信的核心协议TCP/IP、Socket通信的基本原理,以及URL和URI的基本区别,为后续的Socket编程和HTTP通信打下基础。

5.1.1 TCP/IP协议与Socket通信原理

TCP/IP(Transmission Control Protocol/Internet Protocol)是现代网络通信的基础协议族,包含多个层级,从物理层到应用层,分别承担不同的通信任务。在Java中,Socket编程主要涉及TCP和UDP协议,其中TCP是面向连接的、可靠的、基于字节流的传输协议,而UDP是无连接的、不可靠的、基于数据报的传输协议。

Socket是网络通信的端点,它允许程序通过网络发送和接收数据。Java中使用 java.net 包来支持Socket编程,主要类包括:

  • Socket :用于客户端编程,建立与服务器的连接。
  • ServerSocket :用于服务器端编程,监听客户端的连接请求。
TCP通信流程图

使用Mermaid语法描述TCP通信的基本流程如下:

graph TD
    A[客户端] -->|创建Socket| B[连接服务器]
    B --> C[服务器创建ServerSocket监听]
    C --> D[接受连接]
    D --> E[建立连接]
    E --> F[客户端发送数据]
    E --> G[服务器接收数据]
    G --> H[服务器处理数据]
    H --> I[服务器发送响应]
    F --> J[客户端接收响应]
代码示例:TCP通信的基本实现

下面是一个简单的TCP通信示例,包含客户端和服务器端的基本实现。

服务器端代码(TCPServer.java):
import java.io.*;
import java.net.*;

public class TCPServer {
    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(8888)) {
            System.out.println("服务器已启动,等待连接...");

            Socket socket = serverSocket.accept();  // 阻塞等待客户端连接
            System.out.println("客户端已连接");

            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);

            String inputLine;
            while ((inputLine = in.readLine()) != null) {
                System.out.println("收到消息: " + inputLine);
                out.println("服务器回复: " + inputLine);  // 回复客户端
            }

            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
客户端代码(TCPClient.java):
import java.io.*;
import java.net.*;

public class TCPClient {
    public static void main(String[] args) {
        try (Socket socket = new Socket("localhost", 8888)) {
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));

            String userInput;
            while ((userInput = stdIn.readLine()) != null) {
                out.println(userInput);  // 发送消息到服务器
                System.out.println("服务器回复: " + in.readLine());  // 接收服务器回复
            }

            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
代码逻辑分析
  • 服务器端 :创建一个 ServerSocket 监听端口 8888 ,等待客户端连接。当客户端连接后,服务器读取客户端发送的消息,并将其原样返回。
  • 客户端 :连接服务器后,用户输入消息,客户端将其发送到服务器,并接收服务器的回复。
  • 参数说明
  • ServerSocket(8888) :绑定端口8888,监听客户端连接。
  • socket.getOutputStream() :获取输出流,用于向客户端发送数据。
  • BufferedReader.readLine() :按行读取输入流中的数据。
  • PrintWriter.println() :向输出流写入字符串并换行。

5.1.2 URL与URI的区别

URL(Uniform Resource Locator)和URI(Uniform Resource Identifier)是网络资源标识的重要概念。虽然它们在形式上相似,但用途和语义有所不同。

比较项 URL URI
含义 统一资源定位符 统一资源标识符
功能 标识资源的网络位置 标识资源的身份
包含关系 是URI的一个子集 包含URL
示例 http://example.com/index.html urn:isbn:0451450523

在Java中, java.net.URL 类用于表示URL对象,支持打开连接、读取网页内容等操作;而 java.net.URI 类主要用于资源标识和解析,不直接用于网络通信。

代码示例:使用URL访问网页内容
import java.io.*;
import java.net.*;

public class URLReader {
    public static void main(String[] args) {
        try {
            URL url = new URL("https://www.example.com");
            BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
            String line;

            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }

            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
代码逻辑分析
  • new URL("https://www.example.com") :创建一个URL对象,指向目标网页。
  • url.openStream() :打开一个输入流,用于读取网页内容。
  • BufferedReader.readLine() :逐行读取网页内容并输出到控制台。
  • 注意 :该方法仅适用于GET请求,无法处理POST请求或设置请求头。

5.2 Socket编程实战

在掌握了Socket通信的基本原理之后,本节将深入实战,演示如何实现TCP和UDP通信。通过实际代码示例,帮助开发者理解网络编程中的常见问题,如连接建立、数据传输、异常处理等。

5.2.1 TCP客户端与服务器端的实现

前面已经展示了简单的TCP通信示例。在实际应用中,通常需要处理多个客户端连接,这就需要引入多线程技术。下面是一个支持多客户端连接的TCP服务器示例。

多线程TCP服务器端代码(MultiThreadedTCPServer.java):
import java.io.*;
import java.net.*;

public class MultiThreadedTCPServer {
    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(8888)) {
            System.out.println("服务器已启动,等待连接...");

            while (true) {
                Socket socket = serverSocket.accept();  // 接收客户端连接
                new ClientHandler(socket).start();  // 为每个客户端创建线程
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class ClientHandler extends Thread {
    private Socket socket;

    public ClientHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {

            String inputLine;
            while ((inputLine = in.readLine()) != null) {
                System.out.println("收到消息: " + inputLine);
                out.println("服务器回复: " + inputLine);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
代码逻辑分析
  • 多线程设计 :每当有客户端连接时,服务器就创建一个新的 ClientHandler 线程来处理该客户端的请求,从而实现并发处理多个客户端的能力。
  • 线程安全 :每个客户端连接都在独立线程中运行,互不影响。
  • 资源释放 :在finally块中关闭Socket连接,确保资源释放,避免内存泄漏。

5.2.2 UDP数据报通信的实现

UDP是一种无连接的通信方式,适用于实时性要求高、容忍一定丢包的场景。Java中使用 DatagramSocket DatagramPacket 类来实现UDP通信。

UDP服务器端代码(UDPServer.java):
import java.net.*;

public class UDPServer {
    public static void main(String[] args) {
        try (DatagramSocket socket = new DatagramSocket(9999)) {
            byte[] buffer = new byte[1024];
            DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

            System.out.println("UDP服务器已启动,等待数据...");

            while (true) {
                socket.receive(packet);  // 接收数据报
                String received = new String(packet.getData(), 0, packet.getLength());
                System.out.println("收到消息: " + received);

                // 回复客户端
                String response = "服务器收到: " + received;
                byte[] sendData = response.getBytes();
                DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, packet.getAddress(), packet.getPort());
                socket.send(sendPacket);

                packet.setLength(buffer.length);  // 重置缓冲区长度
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
UDP客户端代码(UDPClient.java):
import java.net.*;

public class UDPClient {
    public static void main(String[] args) {
        try (DatagramSocket socket = new DatagramSocket()) {
            InetAddress address = InetAddress.getByName("localhost");
            String message = "Hello, UDP Server!";
            byte[] sendData = message.getBytes();

            DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, address, 9999);
            socket.send(sendPacket);  // 发送数据报

            byte[] receiveData = new byte[1024];
            DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
            socket.receive(receivePacket);  // 接收响应

            String response = new String(receivePacket.getData(), 0, receivePacket.getLength());
            System.out.println("收到回复: " + response);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
代码逻辑分析
  • UDP通信特点 :无需建立连接,直接发送数据报即可。
  • 数据包处理 :使用 DatagramPacket 封装数据,包含目标地址和端口信息。
  • 接收与发送 :服务器端通过 receive() 方法接收数据报,客户端通过 send() 发送数据报。
  • 效率与实时性 :适用于视频流、在线游戏等需要低延迟的场景。

5.3 HTTP通信与URL访问

在现代应用开发中,HTTP通信是网络编程的重要组成部分。Java中可以通过 HttpURLConnection 类实现HTTP请求,包括GET和POST请求。

5.3.1 使用HttpURLConnection发起HTTP请求

Java标准库中的 HttpURLConnection 类可用于发送HTTP请求并获取响应。它支持GET、POST等方法,适用于简单的HTTP通信需求。

代码示例:GET请求
import java.io.*;
import java.net.*;

public class HttpGetExample {
    public static void main(String[] args) {
        try {
            URL url = new URL("https://jsonplaceholder.typicode.com/posts/1");
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");

            int responseCode = connection.getResponseCode();
            System.out.println("响应码: " + responseCode);

            BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String line;
            StringBuilder response = new StringBuilder();

            while ((line = reader.readLine()) != null) {
                response.append(line);
            }

            reader.close();
            System.out.println("响应内容: " + response.toString());

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
代码示例:POST请求
import java.io.*;
import java.net.*;

public class HttpPostExample {
    public static void main(String[] args) {
        try {
            URL url = new URL("https://jsonplaceholder.typicode.com/posts");
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            connection.setDoOutput(true);  // 允许输出

            String jsonInputString = "{\"title\":\"foo\",\"body\":\"bar\",\"userId\":1}";

            try (OutputStream os = connection.getOutputStream()) {
                byte[] input = jsonInputString.getBytes("utf-8");
                os.write(input, 0, input.length);
            }

            int responseCode = connection.getResponseCode();
            System.out.println("响应码: " + responseCode);

            BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String line;
            StringBuilder response = new StringBuilder();

            while ((line = reader.readLine()) != null) {
                response.append(line);
            }

            reader.close();
            System.out.println("响应内容: " + response.toString());

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
代码逻辑分析
  • GET请求 :发送GET请求并读取响应内容。
  • POST请求 :发送JSON格式的POST请求,并获取服务器响应。
  • 响应处理 :使用 BufferedReader 读取响应内容,并输出到控制台。
  • 编码设置 :注意使用 "utf-8" 编码发送和接收数据,避免乱码问题。

5.3.2 常见的HTTP状态码与响应处理

HTTP状态码是服务器响应的重要信息,常见的状态码包括:

状态码 含义
200 OK,请求成功
201 Created,资源已创建
400 Bad Request,客户端错误
401 Unauthorized,需要身份验证
403 Forbidden,服务器拒绝执行请求
404 Not Found,资源不存在
500 Internal Server Error,服务器内部错误

在Java中,可以通过 HttpURLConnection.getResponseCode() 获取状态码,并根据状态码进行相应的处理。

本章内容从Socket通信的基本原理出发,逐步介绍了TCP和UDP的实现方式,并深入讲解了HTTP通信的核心技术。通过实际代码示例,帮助开发者掌握网络编程的基本技能,并为后续更复杂的网络应用开发打下坚实基础。

6. 反射机制(Reflection)应用与动态调用

Java的反射机制(Reflection)是其语言特性中极为强大的一个功能,它允许程序在运行时动态地访问类的结构,包括类的构造方法、字段、方法等,并能够动态创建对象和调用方法。这种机制为框架设计、依赖注入、单元测试、插件系统等高级应用提供了基础支撑。

6.1 反射机制概述

反射机制的核心在于 java.lang.Class 类和相关的 java.lang.reflect 包中的类,如 Constructor Method Field 等。通过这些类,Java程序可以在运行时动态获取类的信息并操作类的成员。

6.1.1 Class类与类加载机制

每个类在JVM中都有一个唯一的 Class 对象,它包含了类的元信息,如类名、包名、方法、字段等。获取 Class 对象的方式有以下几种:

// 1. 使用类的.class属性
Class<?> clazz1 = String.class;

// 2. 使用对象的getClass()方法
String str = "hello";
Class<?> clazz2 = str.getClass();

// 3. 使用Class.forName()方法
Class<?> clazz3 = Class.forName("java.lang.String");

逻辑分析与参数说明:

  • String.class :直接通过类名获取 Class 对象,适用于编译时已知类的情况。
  • getClass() :通过实例对象获取其所属类的 Class 对象。
  • Class.forName("全限定类名") :动态加载类,适用于运行时动态加载类的场景,如插件机制、模块热加载等。

类加载机制由 ClassLoader 完成,JVM中常见的类加载器包括:

类加载器类型 作用说明
Bootstrap ClassLoader 加载JVM核心类库(如rt.jar)
Extension ClassLoader 加载扩展类库(如jre/lib/ext)
Application ClassLoader 加载应用程序类路径(classpath)中的类

6.1.2 获取类的构造方法、字段和方法

一旦获取了 Class 对象,就可以访问其构造方法、字段和方法:

public class Person {
    private String name;
    private int age;

    public Person() {}

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void sayHello() {
        System.out.println("Hello, my name is " + name + ", I'm " + age + " years old.");
    }
}

获取构造方法、字段和方法的代码如下:

Class<?> personClass = Person.class;

// 获取所有构造方法
Constructor<?>[] constructors = personClass.getConstructors();
for (Constructor<?> c : constructors) {
    System.out.println("Constructor: " + c);
}

// 获取所有字段
Field[] fields = personClass.getDeclaredFields();
for (Field f : fields) {
    System.out.println("Field: " + f);
}

// 获取所有方法
Method[] methods = personClass.getDeclaredMethods();
for (Method m : methods) {
    System.out.println("Method: " + m);
}

逻辑分析与参数说明:

  • getConstructors() :获取所有 public 构造方法,包括从父类继承的。
  • getDeclaredFields() :获取类中定义的所有字段(不包括父类)。
  • getDeclaredMethods() :获取类中定义的所有方法(不包括父类)。
  • 如果需要访问 private 成员,需要调用 setAccessible(true) 以绕过访问控制。

6.2 反射的实际应用

反射机制在实际开发中用途广泛,特别是在需要动态行为的场景中。

6.2.1 动态创建对象与调用方法

反射可以动态地创建对象并调用其方法。例如:

// 动态创建对象
Class<?> personClass = Class.forName("Person");
Constructor<?> constructor = personClass.getConstructor(String.class, int.class);
Object person = constructor.newInstance("Tom", 25);

// 动态调用方法
Method sayHelloMethod = personClass.getMethod("sayHello");
sayHelloMethod.invoke(person);

逻辑分析与参数说明:

  • getConstructor(Class<?>... parameterTypes) :根据构造方法的参数类型获取构造器。
  • newInstance(Object... initargs) :创建类的实例。
  • getMethod(String name, Class<?>... parameterTypes) :获取指定名称和参数类型的方法。
  • invoke(Object obj, Object... args) :调用方法,第一个参数是调用对象,后续是方法参数。

6.2.2 注解与反射的结合使用

反射机制可以与注解结合使用,实现诸如依赖注入、测试框架等功能。例如,定义一个自定义注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
    String value();
}

然后在类中使用该注解:

public class MyService {
    @MyAnnotation("Hello")
    public void greet() {
        System.out.println("Greeting...");
    }
}

通过反射读取注解并调用方法:

Class<?> serviceClass = MyService.class;
Object service = serviceClass.newInstance();

for (Method method : serviceClass.getDeclaredMethods()) {
    if (method.isAnnotationPresent(MyAnnotation.class)) {
        MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
        System.out.println("Annotation value: " + annotation.value());
        method.invoke(service);
    }
}

逻辑分析与参数说明:

  • isAnnotationPresent(Class<? extends Annotation> annotationClass) :判断方法是否被指定注解标注。
  • getAnnotation(Class<T> annotationClass) :获取方法上的注解实例。
  • 结合反射调用方法,可以实现类似Spring的自动扫描和调用逻辑。

6.3 反射的性能与安全问题

虽然反射机制非常强大,但在实际使用中也存在一些性能和安全方面的问题。

6.3.1 反射调用的性能优化

反射调用相比直接调用方法会有一定的性能损耗,主要原因包括:

  • 方法查找过程耗时(如 getMethod getDeclaredMethods
  • invoke 方法需要进行参数类型检查和封装
  • 破坏封装性,可能影响JVM的内联优化

优化方式包括:

优化方式 说明
缓存 Method 对象 避免重复调用 getMethod 查找方法
使用 setAccessible(true) 绕过访问控制,提升访问效率
使用 MethodHandle Java 7引入的高性能反射替代方案
使用 ASM 等字节码增强工具 静态生成代理类,避免运行时反射调用

例如缓存 Method 对象:

Map<String, Method> methodCache = new HashMap<>();

public void invokeCachedMethod(String methodName) throws Exception {
    Method method = methodCache.get(methodName);
    if (method == null) {
        method = personClass.getMethod(methodName);
        methodCache.put(methodName, method);
    }
    method.invoke(person);
}

6.3.2 安全限制与访问控制

反射机制可以访问类的私有成员,这可能会带来安全隐患。Java通过安全管理器( SecurityManager )对反射访问进行控制。

示例流程图:

graph TD
    A[尝试访问私有方法] --> B{是否有安全管理器?}
    B -->|有| C[检查权限]
    C --> D{权限是否允许?}
    D -->|允许| E[执行访问]
    D -->|不允许| F[抛出SecurityException]
    B -->|无| G[直接访问私有方法]

在没有安全管理器的情况下,调用 setAccessible(true) 可以访问私有成员:

Field nameField = personClass.getDeclaredField("name");
nameField.setAccessible(true); // 禁用访问控制检查
nameField.set(person, "Jerry");

逻辑分析与参数说明:

  • setAccessible(true) :允许访问私有字段或方法。
  • 在生产环境中,应避免滥用此功能,防止破坏封装性和安全性。

总结:

反射机制为Java程序提供了极高的灵活性和扩展性,是许多框架(如Spring、Hibernate、JUnit)实现动态行为的核心技术。然而,开发者在使用反射时也需权衡其带来的性能损耗和安全风险,合理使用并结合缓存、安全策略等手段加以优化和控制。在下一章中,我们将深入探讨Java的异常处理机制,进一步提升程序的健壮性和可维护性。

7. 异常处理机制(try-catch-finally)详解

Java的异常处理机制是构建健壮应用程序的重要基石。它允许程序在运行过程中对错误进行识别、响应和恢复,从而提升程序的容错能力和可维护性。本章将系统讲解Java异常体系结构、异常处理语法、自定义异常实现以及异常处理的最佳实践。

7.1 异常机制概述

Java的异常处理基于面向对象的思想,所有的异常都继承自 Throwable 类。Java异常体系可以分为检查异常(Checked Exceptions)和运行时异常(Unchecked Exceptions)。

7.1.1 异常的分类:检查异常与运行时异常

Java异常体系结构如下:

java.lang.Throwable
├── Error
└── Exception
    ├── RuntimeException(运行时异常)
    └── 其他非RuntimeException的子类(检查异常)
  • Error :表示严重问题,通常由JVM抛出,如 OutOfMemoryError StackOverflowError ,一般不需要程序处理。
  • 检查异常(Checked Exceptions) :必须在编译期处理的异常,如 IOException SQLException
  • 运行时异常(Unchecked Exceptions) :程序运行期间可能抛出的异常,如 NullPointerException ArrayIndexOutOfBoundsException ,编译期不强制捕获或声明。

7.1.2 异常的传播与捕获机制

当方法内部抛出异常时,异常会沿着调用栈向上传播,直到被 try-catch 语句捕获或者终止程序。如果未被捕获,JVM将打印异常堆栈信息并终止当前线程。

public class ExceptionPropagation {
    public static void main(String[] args) {
        methodA();
    }

    public static void methodA() {
        methodB();
    }

    public static void methodB() {
        throw new RuntimeException("运行时异常");
    }
}

在上述代码中,异常从 methodB() 抛出,依次传递到 methodA() ,最终到达 main() 方法,未被捕获,导致程序终止。

7.2 异常处理的语法结构

Java提供了多种异常处理语法结构,包括传统的 try-catch-finally 和现代的 try-with-resources

7.2.1 try-catch-finally的使用方式

基本语法结构如下:

try {
    // 可能抛出异常的代码
} catch (ExceptionType1 e1) {
    // 捕获并处理异常
} catch (ExceptionType2 e2) {
    // 多异常捕获
} finally {
    // 无论是否发生异常,都会执行
}

示例:文件读取异常处理

import java.io.*;

public class FileReadExample {
    public static void main(String[] args) {
        FileReader reader = null;
        try {
            reader = new FileReader("data.txt");
            int data = reader.read();
            while (data != -1) {
                System.out.print((char) data);
                data = reader.read();
            }
        } catch (FileNotFoundException e) {
            System.err.println("文件未找到:" + e.getMessage());
        } catch (IOException e) {
            System.err.println("IO异常:" + e.getMessage());
        } finally {
            try {
                if (reader != null) {
                    reader.close(); // 关闭资源
                }
            } catch (IOException e) {
                System.err.println("关闭资源失败:" + e.getMessage());
            }
        }
    }
}

7.2.2 try-with-resources语句的资源管理

Java 7引入了 try-with-resources 语法,自动关闭实现了 AutoCloseable 接口的资源,避免手动关闭资源带来的代码冗余和资源泄露问题。

public class TryWithResourcesExample {
    public static void main(String[] args) {
        try (FileReader reader = new FileReader("data.txt")) { // 自动关闭
            int data = reader.read();
            while (data != -1) {
                System.out.print((char) data);
                data = reader.read();
            }
        } catch (FileNotFoundException e) {
            System.err.println("文件未找到:" + e.getMessage());
        } catch (IOException e) {
            System.err.println("IO异常:" + e.getMessage());
        }
    }
}

7.3 自定义异常与异常链

在实际开发中,常常需要定义符合业务逻辑的异常类型,以增强代码的可读性和维护性。

7.3.1 自定义异常类的实现

自定义异常类应继承 Exception 或其子类,并提供构造方法。

public class InvalidUserInputException extends Exception {
    public InvalidUserInputException(String message) {
        super(message);
    }

    public InvalidUserInputException(String message, Throwable cause) {
        super(message, cause);
    }
}

使用示例:

public class CustomExceptionExample {
    public static void validateAge(int age) throws InvalidUserInputException {
        if (age < 0 || age > 120) {
            throw new InvalidUserInputException("年龄无效:" + age);
        }
    }

    public static void main(String[] args) {
        try {
            validateAge(-5);
        } catch (InvalidUserInputException e) {
            System.err.println("捕获自定义异常:" + e.getMessage());
        }
    }
}

7.3.2 异常链的构建与调试技巧

通过构造函数传递原始异常(cause),可以构建异常链,便于定位问题根源。

try {
    // 可能发生异常的代码
} catch (IOException e) {
    throw new CustomException("自定义异常信息", e);
}

调试时,可以使用 printStackTrace() 方法查看完整的异常链信息:

try {
    // ...
} catch (CustomException e) {
    e.printStackTrace();
}

输出示例:

com.example.CustomException: 自定义异常信息
    at com.example.Main.method(Main.java:10)
    at com.example.Main.main(Main.java:5)
Caused by: java.io.IOException: 文件读取失败
    at com.example.Main.readData(Main.java:15)
    at com.example.Main.method(Main.java:8)
    ... 1 more

7.4 异常处理的最佳实践

7.4.1 避免捕获所有异常的陷阱

尽量避免使用 catch (Exception e) ,这会隐藏真正的问题。应精确捕获需要处理的异常类型。

try {
    // ...
} catch (IOException e) { // 精确捕获
    // 处理IO异常
}

7.4.2 日志记录与异常信息的传递

使用日志框架(如Log4j、SLF4J)记录异常信息,避免直接输出堆栈到控制台。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LoggingExample {
    private static final Logger logger = LoggerFactory.getLogger(LoggingExample.class);

    public static void main(String[] args) {
        try {
            // 模拟异常
            int result = 10 / 0;
        } catch (ArithmeticException e) {
            logger.error("发生算术异常:", e);
        }
    }
}

通过日志记录,可以更清晰地追踪异常发生的位置和上下文,便于后期维护与分析。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:Java API是Java编程语言的核心类库,为开发者提供了丰富的功能支持,包括集合、IO流、多线程、网络编程、反射等常用模块。”Java API中文网页版2”是专为中国开发者打造的本地化文档资源,以网页形式呈现,支持离线查阅,涵盖Java SE平台所有公共类和接口。文档详细说明了每个类的方法、构造函数、异常处理及示例代码,极大提升了开发效率与学习体验。无论是初学者还是资深开发者,该文档都是不可或缺的参考工具,适合用于日常编程查询与技术学习。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

更多推荐