本篇笔记主要介绍了 Java 的六个高级特性:异常处理、IO、泛型、集合框架、多线程以及 Lambda 表达式与流式处理。在异常处理部分,详细讲解了异常的分类、捕获与处理机制;IO 部分介绍了文本和二进制的输入输出操作;泛型部分阐述了泛型的使用规则与限制;集合框架部分系统地介绍了 Java 常用的集合类;多线程部分包含了线程的创建、控制与同步机制;最后的 Lambda 与流式处理部分则介绍了 Java 的函数式编程特性。(由 claude-3.5-sonnet 生成摘要)
1. Ch13 Exception Handling and Text IO
1.1. Exceptions
- 异常(exception) 是程序运行时发生的错误事件,可能导致程序无法正常运行。
- 常见的异常:
- ArithmeticException:除零错误。
- InputMismatchException:输入数据类型不匹配。
- NullPointerException:访问未初始化的对象。
 
- 异常分类:
 
- 上半部分的 Exception是程序级错误,可以捕获并处理。- 运行时异常(RuntimeException)通常由逻辑错误引发,属于 非检查型异常(unchecked exception)。- 不需要强制捕获,通常由逻辑错误引发。
 
- 非运行时异常(如  IOException等)需要显式处理,属于 检查型异常(checked exception)。- 编译器强制程序员检查并处理,即程序员应该使用使用 try-catch块进行捕获这些异常并进行操作。
 
- 编译器强制程序员检查并处理,即程序员应该使用使用 
 
- 运行时异常(
- 下半部分的 Error是系统级错误,由 JVM 检测,通常不可恢复,比如内存不足。
 
- 抛出异常:用 throw关键词抛出异常示例,异常示例通常使用new运算进行创建。- 语法:throw new TheException();
 
- 语法:
- 声明异常:定义方法时用 throws关键词声明可能抛出的异常,多个异常之间用逗号隔开。- 使用这种方法声明的异常是检查型异常,必须在调用该方法的代码中进行显示捕获,否则会报 CE。
- 语法:public void myMethod() throws IOException
- 调用时,必须用:try { } catch(IOException ex) { }来处理可能抛出的异常。
 
1.2. Exception Handling
- 用 try-catch块捕获异常。- 语法:try { } catch(ExceptionType ex) { }
 
- 语法:
- finally块:- finally块中的代码无论是否发生异常都会执行。
- 使用 finally块与在try-catch块之后跟语句的区别时:如果在catch块中抛出异常(或者 return 等情况),则try-catch块之后的代码不会执行,但是finally中的代码始终会执行。
  
- finally关键字保证无论程序使用何种方式退出- try-catch块,- finally块中的代码都会被执行。更具体的,- finally块会在以下情况发生之后执行:- try 块中的代码正常执行完毕。
- 在 try 块中抛出异常。
- 在 try 块中执行 return、break、continue。
- catch 块中代码执行完毕。
- 在 catch 块中抛出异常。
- 在 catch 块中执行 return、break、continue
 
- 通常使用 finally块来释放资源。
 
- 多个 catch块的情况:- 拥有多个 catch块时,JVM 会由上而下来检测每个异常是否被捕获。
- 不可达代码检测机制:如果先 catch了一个异常,又catch了另一个异常的子类,则后者是不可达的。因为无论抛出什么异常,都会被先前的catch块捕获。编译器会检测这种不可达的catch块,并报 CE。- 所以捕获子类异常的 catch块要放在捕获父类异常的catch块之前。
 
- 所以捕获子类异常的 
 
- 拥有多个 
- 捕获到异常 e后:- 使用 e.getMessage()获取异常信息。
- 使用 e.printStackTrace()打印异常调用堆栈。
- 如果需要进一步抛出异常,建议带上 e的异常信息:可以使用Throwable(Throwable cause)或者Throwable(String message, Throwable cause)的构造方法。
 
- 使用 
1.3. Assertions
- 断言(assertions):用于验证程序中的某些假设是否成立。
- 语法:
- assert condition;,其中- condition是一个布尔表达式。
- assert condition : message;,其中- message可以是基本数据类型或对象。
 
- 当断言失败时,JVM 会抛出 AssertionError异常。
- 设计原则:
- 断言仅用于开发阶段的内部检查。
- 不应在公共方法中使用断言检查参数。
 
- 断言默认在运行时禁用,需要在命令行手动使用 -enableassertions或-ea选项启用。
1.4. Text IO
- File类:提供文件和路径的抽象。- 常用方法:
- exists():检查文件是否存在。
- createNewFile():创建新文件。
- delete():删除文件。
 
 
- 常用方法:
- 使用 Scanner类读取数据。- 使用 new Scanner(Paths.get(path))从文件中创建Scanner对象,注意不能直接写字符串。
- 使用 new Scanner((new URL(url)).openStream())从网络资源中创建Scanner对象。
- 第二个参数(可选)用于指定编码,否则使用该系统的默认编码。
 
- 使用 
- 使用 PrintWriter类向文件写入数据。- 支持 print(...)、println(...)、printf(...)等方法。
- 第二个参数(可选)用于指定编码,否则使用该系统的默认编码。
 
- 支持 
2. Ch14 Binary IO
2.1. Binary IO Basic
- 几乎所有方法都需要显示捕捉 java.io.Exception异常,故应显式声明或捕捉。
- inputStream.read()的返回值和- outputStream.write(int b)都是- int,但实际上都是以- byte为单位读写的。- 其中 inputStream.read()在 EOF 时会返回 -1,这就是为什么需要用int而不是byte表示。
 
- 其中 
- DataInputStream在到达文件末尾后继续读取数据会抛出- EOFException异常。
- Reader是读取字符流的抽象类,- Writer是写入字符流的抽象类。- InputStreamReader:字节流转字符流
- OutputStreamWrite:字符流转字节流
- BufferedReader、- BufferedWriter:字符缓冲流
- FileReader:- InputStreamReader类的直接子类,用来读取字符文件
- FileWriter:- OutputStreamWriter类的直接子类,用来写入字符文件
 
2.2. Object IO Stream
- transient关键字:在序列化时忽略(静态成员也会被忽略)。
- 如果一个对象不止一次写入对象流,不会存储对象的多份副本。JVM 会将对象的内容和序号一起写入对象流。
- 反序列化后的对象,不需要调用构造函数重新构造。
- 序列前的对象与序列化后的对象是深复制。
- 类需要显示地实现 Serializable接口才能被序列化。(即使成员都可以序列化也需要这么做)
- 如果数组中的所有元素都是可序列化的,这个数组就是可序列化的。
- 24-25 秋冬期末考试还考察了子类/父类中只有一个实现了序列化接口在序列化/反序列化时的行为,读者可以自行查阅相关资料。
2.3. Piped IO Stream
- PipedOutputStream 和 PipedInputStream 的作用是让多线程可以通过管道进行线程间的通讯。PipedReader 和 PipedWriter 和这两个的区别也类似:操作字符而不是字节。
- 我们在线程 A 中向 PipedOutputStream 中写入数据,这些数据会自动的发送到与 PipedOutputStream 对应的 PipedInputStream 中,进而存储在 PipedInputStream 的缓冲中;此时,线程 B 通过读取 PipedInputStream 中的数据。就可以实现,线程 A 和线程 B 的通信。
3. Ch16 Generics
- 构造方法中不需要写泛型(如是 Stack()而不是Stack<E>())
- 泛型方法若编译器可以推断则可省略,否则用类似 GenericMethodDemo.<Integer>print(integers)的语法。
- 有界泛型类型(bounded generic type):<E extends GeometricObject>表示E必须是GeometricObject或其子类。
- 泛型类型之间没有继承关系
- 如 Integer和Number有继承关系但是GenericStack<Integer>和GenericStack<Number>没有
- List<Integer> list = new List<Object>();报错且反之亦然
 
- 如 
- 三种通配
- unbound wildcard:<?>适用于任何类型
- bound wildcard:<? extends T>类型参数必须是 T 或其子类。- List<? extends Number> list = new ArrayList<Integer>();
- 不能添加 Number或Integer或Double到 list 中
 
- lower-bound wildcard:<? super T>表示类型参数必须是 T 或其父类。- List<? super Integer> list = new ArrayList<Number>();
- 只能添加 Integer或Integer的子类到 list 中
 
 
- unbound wildcard:
- 泛型的限制
- 不能创建泛型类型的实例
- new E()会 CE
 
- 不能创建泛型数组
- new E[10]会 CE,应改为- (E[]) new Object[10]
 
- 泛型类的静态上下文中不能使用类型参数
- 泛型类的所有实例都有相同的运行时类,所以泛型类的静态变量和方法是被它的所有实例共享的。
- 在静态方法、数据域或初始化语句中,为了类而引用泛型参数是非法的。
 
- 泛型类不能继承 Throwable- class MyException<T> extends Exception {}会 CE
- 主要是为了 catch 的问题
 
 
- 不能创建泛型类型的实例
- ArrayList<String>不是一个类,所以- ArrayList<String> list1 = new ArrayList<String>();后调用- list1 instanceof ArrayList<String>是错误的。
4. Ch17 Java Collections Framework
- Collection 接口关系图(P22)
- Map是独立的接口,不继承自- Collection
- ArrayList和- LinkedList实现了- List接口。
- HashSet和- TreeSet实现了- Set接口。
- HashMap和- TreeMap实现了- Map接口。
 
- addAll、- removeAll、- retainAll类似于集合的并、差、交。
- Set
- HashSet- 通过 hashCode和equals方法保证元素唯一性。
- 集合元素可以是 null,但只能放入一个 null
 
- 通过 
- LinkedHashSet- 额外使用双向链表维护元素的插入顺序(似乎访问也还会被提到最前,即遵循 LRU)。
 
- TreeSet- TreeSet是- SortedSet接口的唯一实现类
- SortedSet是 Set 的一个子接口。first()和 last()返回集合中的第一个和最后一个元素;headSet(toElement)和 tailSet(fromElement)返回集合中元素小于 toElement 和大于 fromElement 的那部分。
- NavigableSet 扩展了 SortedSet,提供导航方法 lower(e),floor(e), ceiling(e)和 higher(e),分别返回小于、小于或等于、大于或等于、大于一个元素的元素,若没这样的元素,则返回 null。
- pollFirst()和 pollLast()则分别删除和返回 TreeSet 中的第一个和最后一个元素。
 
 
- List
- ArrayList:基于动态数组。
- 支持随机访问,访问速度快。插入和删除操作效率低(特别是中间位置)。
 
- LinkedList:基于双向链表。
- 插入和删除速度快。查询效率较低。
 
 
- ArrayList:基于动态数组。
- Map:HashMap、TreeMap、LinkedHashMap。- 对于 HashMap和LinkedHashMap,自定义对象作为 key 时需要重写hashcode()和equals()方法
 
- 对于 
- ArrayList 中迭代器的陷阱
- 在迭代过程中,调用容器的删除方法,则会抛出异常
- 迭代器内部会维护索引位置相关的数据(包含最近返回的元素的索引和下一个返回的元素的索引),在迭代过程中,容器不能发生结构性变化
 
- List<String> a = new ArrayList<String>();和- List<String> a = new ArrayList<>();都是合法的写法。
- Map不能直接使用- Iterator遍历,但可以通过- entrySet()、- keySet()或- values()方法间接获取可迭代的集合。- for (Map.Entry<String, Integer> entry : map.entrySet()) { System.out.println(entry.getKey() + ": " + entry.getValue()); }
5. Ch25 Multithreading
- 创建线程
- 实现 Runnable接口并将实例传递给Thread构造函数
- 继承 Thread类:重写run()方法
 
- 实现 
- 线程控制
- 使用 run()启动在当前线程创建,使用start()启动在新线程创建
- Thread.yield():让出当前线程的 CPU 时间片。
- Thread.sleep(long millis)静态方法:当前线程休眠指定时间。
- anotherThread.join():等待另一个线程结束。
- 中断
- InterruptedException:是 sleep 和 join 的必检异常,注意放在循环体中要手动 break。
- 线程的生命周期
- New(新建):线程对象被创建,但未调用 start()方法。
- Runnable(就绪):调用 start()方法后,线程处于可运行状态,但可能未获得 CPU 时间。
- Running(运行):线程获得 CPU 时间,正在执行任务。
- Blocked(阻塞):线程等待某些资源(如锁或 IO)。
- Terminated(终止):线程执行完毕或被中断。
 
- New(新建):线程对象被创建,但未调用 
- isAlive():用于检查线程是否处于活动状态(Ready、Blocked 或 Running)
- interrupt():设置中断标志。对于受阻塞(- Object.wait()、- Thread.join()、- Thread.sleep())的线程,将其唤醒并抛出 InterruptedException;否则将暂时不起作用。
- isInterrupted():检查线程的中断标志是否被设置。不会清除中断标志。
 
- 优先级控制
- setPriority(int newPriority):设置线程的优先级。
- Thread.MAX_PRIORITY/- Thread.MIN_PRIORITY/- Thread.NORM_PRIORITY
 
 
- 使用 
- 线程同步(thread synchronization)
- 内存可见性问题:一个线程对共享变量的修改,可能不被另一个线程及时看到。因为每个线程都有自己的工作内存,而共享变量存储在主内存中。如果线程对共享变量的修改没有及时刷新到主内存就可能出现问题。
- volatile关键词:保证变量的可见性。
- synchronized关键词或- synchronized块- 成员方法加 synchronized相当于对this上锁,静态方法加synchronized相当于对XXClass.class上锁。
- 功能
- 保证可见性:释放锁时,所有的写入都会写回内存,获得锁后,会从内存中读最新数据。
- 保证互斥性:同一时刻只有一个线程可以执行 synchronized 块中的代码。
 
- 可重入性:对同一个线程,它在获得锁之后,在调用其他需要同样锁的代码时,可以直接调用。
 
- 成员方法加 
 
- wait()&- notify()- 必须在 synchronized方法或代码块中使用。
 
- 用法
- wait():当前线程等待,释放锁。
- notify():唤醒一个等待线程。
- notifyAll():唤醒所有等待线程。
 
- 机制
- 把当前线程放入条件等待队列,释放对象锁,阻塞等待,线程状态变为 WAITING 或 TIMED_WAITING。
- 等待时间到或被其他线程调用 notify/notifyAll 从条件队列中移除,这是,要重新竞争对象锁
- 如果能获得锁,线程变为 RUNNABLE,并从 wait 调用中返回
- 否则,线程加入对象锁等待队列,线程变为 BLOCKED,只有在获得锁后才会从 wait 调用中返回。
 
 
- 必须在 
6. Ch28 Lambda & Stream & RTTI
- Lambda 表达式是一种特殊的匿名内部类。
- 任何可以接受一个函数式接口(Functional Interface, FI)实例的地方,都可以使用 Lambda 表达式。
- 函数式接口:只能有一个抽象方法。可以有多个静态方法和默认方法。接口中的方法默认是 public abstract。- java.util.function包中包含了多种函数式接口,例如:- Function:接收参数并返回结果。- Predicate:接收参数并返回布尔值。- Consumer:接收参数但无返回值。- Supplier:无参数但返回结果。
 
- Stream 对象:实现了  java.util.stream.Stream接口。- 生成流:stream()或parallelStream()或Stream.of("a1", "a2", "a3")
- 常用方法:
- filter:过滤满足条件的元素。
- map:映射为新值
- flatMap:映射成流并 flat 成一个
- distinct:去重(基于 hashCode和equals)
- sorted:排序
- limit(int n):只保留前至多 个
- skip(int n):跳过前 个
 
- 终端操作:
- boolean allMatch(Predicate<? super T> predicate);检查是否匹配所有元素。
- boolean anyMatch(Predicate<? super T> predicate);检查是否至少匹配一个元素。
- boolean noneMatch(Predicate<? super T> predicate);检查是否没有匹配所有元素。
- Optional<T> findFirst();返回当前流中的第一个元素。
- Optional<T> findAny();返回当前流中的任意元素。
- long count();返回流中元素总数。
- Optional<T> max(Comparator<? super T> comparator);返回流中最大值。
- Optional<T> min(Comparator<? super T> comparator);返回流中最小值。
- T reduce(T identity, BinaryOperator<T> accumulator);可以将流中元素反复结合起来,得到一个值。返回 T。这是一个归约操作。
- collect:转化为其他形式,如- .collect(Collectors.toSet())、- toList()
 
 
- 生成流:
- IntStream与- getAsInt()
- RTTI
- object.getClass()或- TheClass.class来获取类对象。
- clazz.isInstance(obj)判断- obj是不是当前类或当前类子类的实例
- getSuperclass()、- getInterfaces()、- getModifiers()
- Method 类
- invoke()
- instanceof 运算符