Java核心技术卷一 5. java异常、断言和日志

处理错误

由于出现错误而使得某些操作没有完成,程序因该:

  • 返回到一种安全状态,并能够让用户执行一些其他命令
  • 允许用户保存所有操作的结果,并以适当的方式终止程序

需要关注的问题:

  1. 用户输入错误
  2. 设备错误
  3. 物理限制
  4. 代码错误

当某个方法不能够采用正常的路径完成它的任务,就可以通过另外一个一个路径退出方法。这种情况下,方法并不返回任何值,而是抛出(throw)一个封装了错误信息的对象。要注意这个方法将会立刻退出,并不返回任何值。调用这个方法的代码也将无法继续执行,异常处理机制开始搜索能够处理这种异常状况的异常处理器(exception handler)。

异常分类

全都派生于 Throwable 类,分解为两个分支层次结构:Error 和 Exception

  • Error 类层次结构描述 Java 运行时系统的内部错误和资源耗尽错误。应用程序不该抛出这种错误。
  • Exception 类层次结构分为两个分支。
  • 派生于 RuntimeException 异常,由程序错误导致的异常。
  • 其他不是程序导致的异常。

派生于 RuntimeException 异常的几种情况:

  • 错误的类型转换
  • 数组访问越界,ArrayIndexOutOfBoundsException异常
  • 访问空指针,NullPointerException异常

其他异常的几种情况:

  • 试图在文件尾部后面读取数据
  • 试图打开一个不存在的文件
  • 试图根据给定的字符串查找 Class 对象,而这个字符串表示的类并不存在

Java 将派生于 Error 类或 RuntieException 类额所有异常成为非受查异常,其他的异常成为受查异常

声明受查异常

一个方法不仅要告诉编译器要返回什么值,还要告诉编译器可能发生什么错误

例子:

public FileInputStream(String name) throws FileNotFoundException {
    ...
}

这个声明表示可能抛出异常,如果发生了这个异常构造器将不会初始化一个新的 FileInputStream 对象,而是抛出一个 FileNotFoundException 类对象。抛出异常类对象后,运行时系统就会搜索异常处理器,以便指定如何处理 FileNotFoundException 对象。

需要抛出异常的情况:

  1. 调用一个抛出受查异常的方法。
  2. 程序运行过程中发现错误,利用 throw 语句抛出一个受查异常。
  3. 程序出现错误,一些非受查对象。
  4. Java 虚拟机和运行时库出现的内部错误。

前两种情况,必须告诉调用这个方法的程序员有可能抛出异常。防止程序遇到异常停止线程。

根据异常规范在方法首部声明这个方法可能抛出的异常,多个受查异常用逗号隔开:

class MyAnimation{
    ...
    public Image loadImage(String s) throws IOException, FileNotFoundException {
        ...
    }
}

如果方法没有声明受查异常,编译器会发出一个错误信息。

除了声明异常外,还可以捕获异常,抛出让异常处理器处理。

另外:声明一个异常,可能抛出这个异常的子类异常。

抛出异常

抛出异常的语句:

throw new EOFException();

String readData(Scanner in) throws EOFException {
    ...
    while (...) {
        if (!in.hasNext()) { //EOF encountered
            if(n < len) throw new EOFException();
        }
    }
}

抛出以存在的异常类:

  1. 找到一个合适的异常类
  2. 创建这个类的一个对象
  3. 将对象抛出

一旦方法抛出了异常,这个方法就不可能返回到调用者。

创建异常类

标准异常无法充分的描述清楚问题,可以创建自己的异常类:

class FileFormatException extends IOException {
    public FileFormatException(){}
    public FileFormatException(String gripe){
        super(gripe);//构造一个带描述信息的异常
    }
}

//抛出自己定义的异常类型
String readDate(BufferedReader in) throws FileFormatException {
    ...
    while (...) {
        if (ch == -1) { //EOF encounteered
            if (n < len) throw new FileFormatException();
        }
        ...
    }
    return s;
}

api:

//java.lang.Throwable
Throwable() 构造一个 Throwable 对象,没有描述信息。
Throwable(String message) 构造一个 Throwable 对象,带描述信息。
String getMessage() 获取描述信息。

# 捕获异常

捕获异常

异常发生,没有捕获,程序就会终止执行,在控制台打印异常信息。

捕获一行,必须设置 try/catch 语句块:

try {
    code
    more code
} catch (ExceptionType e) {
    handler for this type
}

如果 try 内有代码抛出了 catch 中定义的异常:

  1. 程序将跳过 try 语句块的其余代码。
  2. 程序将执行 catch 子句中的处理器代码。

注意:如果 try 内的异常 catch 中没有,则程序终止。

将异常传递给调用者:

public void read(String filename) throws IOException {...}

将不知道怎样处理的异常继续进行传递,传递异常使用 throws 说明符,告知调用者这个方法可能出现异常。

例外:如果编写一个覆盖超类的方法,这个方法没有抛出异常,那么这个方法必须捕获方法代码中出现的每一个受查异常。不允许在子类的 throws 说明符中出现超过超类方法所列出的异常类范围。

捕获多个异常

有两种方式,第一种方式为每个异常类使用单独的 catch 子句。

try {
    code;
} catch (FileNotFoundException e) {
    emergency action;
} catch (UnknownHostException e) {
    emergency action;
} catch (IOException e) {
    emergency action;
}

第二种方式可以对动机一样的异常进行合并 catch 子句。

try {
    code;
} catch (FileNotFoundException | UnknownHostException | IOException e) {
    emergency action;
}

获得对象的更多信息,可以:

e.getMessage();
e.getClass().getName();

注意:捕获多个异常时,异常变量隐含为 final 变量,不可修改。

再次抛出异常与异常链

有些异常我们并不想指定发送错误的细节原因,但希望明确的指定它是否有问题:

try {
    access the database
} catch (SQLException e) {
    throw new ServletException("database error:" + e.getMessage());
}

//更好的方法,将原始异常设置为新异常的“原因”
try {
    access the database
} catch (SQLException e) {
    Throwable se = new ServletException("database error");
    se.initCause(e);
    throw se;
}

//重新得到原始异常
Throwable e = se.getCause();

这个受查异常,不允许抛出它,包装技术十分有用,可以捕获这个受查异常,把它包装成一个运行时异常。

finally 子句

不管是否有异常被捕获,finally 子句中的代码都被执行:

InputStream in = new FileInputStream(...);
try {
    core 1;
    core 2;
} catch (IOException e) {
    core 3;
} finally {
    core 4;
}

会遇到3种情况:

  1. 代码没有异常。执行完 try 块,然后执行 finally 块。
  2. 抛出一个异常。执行 try 块知道异常为止,跳过剩余 try 代码,转去执行与异常匹配的 catch 子句中的代码,最后执行 finally 子句中的代码。
  3. 代码抛出异常,但是 catch 没有匹配的异常。执行 try 块知道异常为止,跳过剩余 try 代码,转去执行 finally 子句中的代码,并将异常抛给这个方法的调用者。

解耦合try/catchtry/finally,提高代码的清晰度:

try {
    try {
        code;
    } finally {
        in.close();
    }
} catch (IOException e) {
    show error message;
}

内层确保关闭输入流;外层确保报告出现的错误。外层也会报告 finally 子句中出现的错误。

return的各种场景

  1. return 在 try 块中时,方法返回前 finally 子句的内容将被执行。
  2. return 在 try 块 finally 子句中都存在时,finally 子句中的返回值将会覆盖原始的返回值。

finally子句的坏处

try {
    code 1;
} finally {
    in.close();
}

如果 try 语句抛出了一些非 IOEception 的异常,这些异常只有调用者才能处理。执行 finally 语句块,并调用 close 方法,有可能抛出 IOException 异常。有这种情况时,原始的异常将会丢失,转而抛出 close 方法的异常。

带资源的 try 语句

资源属于实现了 AutoCloseable 接口的类时:

//接口的一个方法
void close() throws Exception

//最简形式
try (Resource res = ...) {
    work with res;
}

try 块退出时,会自动调用 res.close() 。并且可以指定多个资源。

分析堆栈轨迹元素

堆栈轨迹元素是一个方法调用过程的列表,包含了程序执行过程中方法调用的特定位置。

调用 Throwable 类的 printStackTrace 方法访问堆栈轨迹的文本描述信息。

使用异常机制的技巧

  1. 异常处理不能代替简单的测试
  2. 不要过分地细化异常
  3. 利用异常层次结构
  4. 不要压制异常
  5. 在检测错误时,“苛刻”要比放任更好
  6. 不要羞于传递异常

使用断言

断言的概念

断言机制允许在测试期间向代码插入一些检查语句。当代码发布时,这些插入的检测语句会被自动地移走。

assert 条件;
和
assert 条件 : 表达式;

这两种形式都会对条件进行检测, 如果结果为 false,则抛出一个 AssertionError 异常。 在第二种形式中,表达式将被传人 AssertionError 的构造器,并转换成一个消息字符串。 表达式部分的目的是产生一个消息字符串。

例子,断言 x 是一个非负数值:

assert x >= 0;

assert x >= 0 : x;

启用和禁用断言

启用:java -enableassertions MyApp

启用某个类中的断言:java -ea:MyClass -ea:com.mycompany.mylib... MyApp

禁用特定类和包的断言:java -ea:... -da:MyClass MyApp

不能应用没有类加载器的系统类上,要使用:-enablesystemassertions/-esa

使用断言完成参数检查

使用断言的场景:

  • 断言失败是致命的、不可恢复的错误
  • 断言用于开发和测试阶段

不可通告可恢复性的错误,不该作为程序向用户通告问题的手段。

不允许用 null 数组调用这个方法,并在这个方法的开头使用断言:assert a != null;

为文档假设使用断言

if (i % 3 == 0) ...
else if (i % 3 == 1) ...
else //i % 3 ==2 

assert i >= 0;
if (i % 3 == 0) ...
else if (i % 3 == 1) ...
else assert i % 3 ==2;

记录日志

基本日志

使用全局日志记录器并调用 info 方法:

Logger.getGlobal().info("File->Open menu item selected");

内容:

May 10, 2013 10:23:43 PM LoggingImageViewer fileOpen

INFO: File->Open menu item selected

在适当的地方(如 main 开始)以下将会取消所有日志:

Logger.getGlobal().setLevel(Level.OFF);

调试技巧

  1. 打印任意变量的值:System.out.println("x=" + x);
  2. 在每个类中放置一个单独的 main 方法,做单元测试。
  3. 可以使用 JUnit 单元测试框架。

原文地址:https://www.cnblogs.com/lovezyu/p/9127463.html

时间: 2024-07-29 23:24:47

Java核心技术卷一 5. java异常、断言和日志的相关文章

Java核心技术卷一 4. java接口、lambda、内部类和代理

接口 接口概念 接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义. 如果类遵从某个特定接口,那么久履行这项服务. public interface Comparable<T>{ int compareTo(T other); } 任何实现 Comparable 接口的类都需要包含 compareTo 方法,并且这个方法的参数必须是一个 T 对象,返回一个整形数值. 接口的特点: 接口中所有方法自动地属于 public,所以接口的方法不需要提供关键字 public .

Java核心技术卷一 6. java泛型程序设计

泛型程序设计 泛型程序设计:编写的代码可以被很多不同类型的对象所重用. 类型参数:使用<String>,后者可以省略,因为可以从变量的类型推断得出.类型参数让程序更具更好的可读性和安全性. 通配符类型:很抽象,让库的构建者编写出尽可能灵活的方法. 定义简单泛型类 泛型类就是具有一个或多个类型变量的类. //引用类型变量 T ,可以有多个类型变量,public class Pair<T, U>{...} public class Pair<T> { //类定义的类型变量制

Java核心技术卷一 8. java并发

什么是线程 每个进程拥有自己的一整套变量,而线程则共享数据. 没有使用多线程的程序,调用 Thread.sleep 不会创建一个新线程,用于暂停当前线程的活动.程序未结束前无法与程序进行交互. 使用线程给其他任务提供机会 将代码放置在一个独立的线程中,事件调度线程会关注事件,并处理用户的动作. 在一个单独的线程中执行一个任务的简单过程: 将任务代码移到实现了 Runnable 接口的类的 run 方法中. public interface Runnable{ void run(); } Runn

java核心技术卷一

java核心技术卷一 java基础类型 整型 数据类型 字节数 取值范围 int 4 +_2^4*8-1 short 2 +_2^2*8-1 long 8 +_2^8*8-1 byte 1 -128-127       浮点类型 数据类型 字节数 取值范围 小数位数 float 4 10^-38~10^38和-10^-38~-10^38 小数位数6-7 double 4 10^-308~10^308和-10^-308~-10^308 15位小数         boolean 类型和char 类

读《java核心技术卷一》有感

过去一个多月了吧.才囫囵吞枣地把这书过了一遍.话说这书也够长的,一共706页.我从来不是个喜欢记录的人,一直以来看什么书都是看完了就扔一边去,可能有时候有那么一点想记录下来的冲动,但算算时间太紧,很多也是有始无终,毕竟在之前研究研究程序也只是自己的一个爱好而已,但没有想到签了一个程序员的工作.唉,这老天也太捉弄人了吧,让一个学电气工程(强电方向)学生毕业之后去写代码,而且是与硬件完全无关的代码.真是白念几年大学了.行了,就行发这么多牢骚吧. <java核心技术>有两个卷,我只看了卷一,从我的感

Java核心技术 卷一 笔记六 Date类

在Java核心技术卷就行了一前期  date类出现的频率很高  所以就对date类进行了小小的整合 Date类有两个 date类 表示特定时间的类 这个构造函数分配一个Date对象并初始化它代表指定的毫秒数,因为被称为"纪元",即1970年1月1日00:00:00 GMT标准基准时间. 就像格林尼治时间一样  作为一种基准值而存在 一般常用的是date转为string类型 String s=new Date(0).toString(); System.out.println(s.toS

Java系列:《Java核心技术 卷一》学习笔记,cchapter11 异常

11.1.1 异常分类     如果一个程序出现了RuntimeException,那么就一定是你的问题. 11.1.2 声明已检测异常 如果子类覆盖了父类的一个方法,那么子类方法中声明的检查异常不能比超类方法中声明的异常更通用. 11.1.4 创建异常类 所有自定义的异常类,都应该实现两个构造函数,一个是默认构造函数:一个是带string参数的构造函数: 11.2 捕获异常 通常来说应该捕获那些知道如何处理的异常,而将那些不知道如何处理的异常继续进行传递. 11.2.1 捕获多个异常 在jav

《Java核心技术卷一》笔记 多线程

有时,我们需要在一个程序中同时并行的处理多个任务,如播放器一边要播放音乐同时还要不断更新画面显示,或者是一边执行耗时任务,UI还能一边继续响应各种事件.还有的时候,一个任务需要很长时间才能完成,如果分成多份一起执行,可以极大的缩短需要的时间.多线程可以很好的解决这类问题. 一个程序(进程)如果可以同时执行多个任务,每个并行的任务都是通过一个线程来完成,这就是一个多线程程序.进程拥有自己的一整套数据(变量),各个线程共享进程的数据,线程间通信比进程间通信更简单,线程开销比进程小. Java中为多线

《Java核心技术卷一》笔记 多线程同步(底层实现)

一.锁的基本原理 多个线程同时对共享的同一数据存取 ,在这种竞争条件下如果不进行同步很可能会造成数据的讹误. 例如:有一个共享变量int sum=0, 一个线程正调用 sum+=10,另一个线程正好也在调用sum+=20,期望的结果应该是sum=30. 但是+=操作并不是原子的,虚拟机需要用多条指令才能来完成这个操作(load,add, store),每个指令执行完都有可能被剥夺执行权,同时让另一个线程继续运行.(可以使用javap -c -v CLASS命令将class文件反编译为可阅读的虚拟