3. Java 高级特性

Ch13 Exception Handling and Text IO

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) { } 来处理可能抛出的异常。

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) 的构造方法。

Assertions

  • 断言(assertions):用于验证程序中的某些假设是否成立。
  • 语法:
    • assert condition;,其中 condition 是一个布尔表达式。
    • assert condition : message;,其中 message 可以是基本数据类型或对象。
  • 当断言失败时,JVM 会抛出 AssertionError 异常。
  • 设计原则:
    • 断言仅用于开发阶段的内部检查。
    • 不应在公共方法中使用断言检查参数。
  • 断言默认在运行时禁用,需要在命令行手动使用 -enableassertions-ea 选项启用。

Text IO

  • File 类:提供文件和路径的抽象。
    • 常用方法
      • exists():检查文件是否存在。
      • createNewFile():创建新文件。
      • delete():删除文件。
  • 使用 Scanner 类读取数据。
    • 使用 new Scanner(Paths.get(path)) 从文件中创建 Scanner 对象,注意不能直接写字符串
    • 使用 new Scanner((new URL(url)).openStream()) 从网络资源中创建 Scanner 对象
    • 第二个参数(可选)用于指定编码,否则使用该系统的默认编码
  • 使用 PrintWriter 类向文件写入数据。
    • 支持 print(...)println(...)printf(...) 等方法。
    • 第二个参数(可选)用于指定编码,否则使用该系统的默认编码。

Ch14 Binary IO

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:字符流转字节流
    • BufferedReaderBufferedWriter:字符缓冲流
    • FileReaderInputStreamReader 类的直接子类,用来读取字符文件
    • FileWriterOutputStreamWriter 类的直接子类,用来写入字符文件

Object IO Stream

  • transient 关键字:在序列化时忽略(静态成员也会被忽略)。
  • 如果一个对象不止一次写入对象流,不会存储对象的多份副本。JVM 会将对象的内容和序号一起写入对象流。
  • 反序列化后的对象,不需要调用构造函数重新构造。
  • 序列前的对象与序列化后的对象是深复制。
  • 类需要显示地实现 Serializable 接口才能被序列化。(即使成员都可以序列化也需要这么做)
  • 如果数组中的所有元素都是可序列化的,这个数组就是可序列化的。
  • 24-25 秋冬期末考试还考察了子类/父类中只有一个实现了序列化接口在序列化/反序列化时的行为,读者可以自行查阅相关资料

Piped IO Stream

  • PipedOutputStream 和 PipedInputStream 的作用是让多线程可以通过管道进行线程间的通讯。PipedReader 和 PipedWriter 和这两个的区别也类似:操作字符而不是字节。
  • 我们在线程 A 中向 PipedOutputStream 中写入数据,这些数据会自动的发送到与 PipedOutputStream 对应的 PipedInputStream 中,进而存储在 PipedInputStream 的缓冲中;此时,线程 B 通过读取 PipedInputStream 中的数据。就可以实现,线程 A 和线程 B 的通信。

Ch16 Generics

  • 构造方法中不需要写泛型(如是 Stack() 而不是 Stack<E>()
  • 泛型方法若编译器可以推断则可省略,否则用类似 GenericMethodDemo.<Integer>print(integers) 的语法。
  • 有界泛型类型(bounded generic type)<E extends GeometricObject> 表示 E 必须是 GeometricObject 或其子类。
  • 泛型类型之间没有继承关系
    • IntegerNumber 有继承关系但是 GenericStack<Integer>GenericStack<Number> 没有
    • List<Integer> list = new List<Object>(); 报错且反之亦然
  • 三种通配
    • unbound wildcard<?> 适用于任何类型
    • bound wildcard<? extends T> 类型参数必须是 T 或其子类。
      • List<? extends Number> list = new ArrayList<Integer>();
      • 不能添加 NumberIntegerDouble 到 list 中
    • lower-bound wildcard<? super T> 表示类型参数必须是 T 或其父类。
      • List<? super Integer> list = new ArrayList<Number>();
      • 只能添加 IntegerInteger 的子类到 list 中
  • 泛型的限制
    • 不能创建泛型类型的实例
      • 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> 是错误的。

Ch17 Java Collections Framework

  • Collection 接口关系图(P22
    • Map 是独立的接口,不继承自 Collection
    • ArrayListLinkedList 实现了 List 接口。
    • HashSetTreeSet 实现了 Set 接口。
    • HashMapTreeMap 实现了 Map 接口。
  • addAllremoveAllretainAll 类似于集合的并、差、交。
  • Set
    • HashSet
      • 通过 hashCodeequals 方法保证元素唯一性。
      • 集合元素可以是 null,但只能放入一个 null
    • LinkedHashSet
      • 额外使用双向链表维护元素的插入顺序(似乎访问也还会被提到最前,即遵循 LRU)。
    • TreeSet
      • TreeSetSortedSet 接口的唯一实现类
      • 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:基于双向链表。
      • 插入和删除速度快。查询效率较低。
  • Map:HashMapTreeMapLinkedHashMap
    • 对于 HashMapLinkedHashMap,自定义对象作为 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()); }

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(终止):线程执行完毕或被中断。
      • 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 调用中返回。

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:去重(基于 hashCodeequals
      • sorted:排序
      • limit(int n):只保留前至多 nn
      • skip(int n):跳过前 nn
    • 终端操作:
      • 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()
  • IntStreamgetAsInt()
  • RTTI
    • object.getClass()TheClass.class 来获取类对象。
    • clazz.isInstance(obj) 判断 obj 是不是当前类或当前类子类的实例
    • getSuperclass()getInterfaces()getModifiers()
    • Method 类
      • invoke()
      • instanceof 运算符

Comments