# 第七章 异常、断言和日志
## 7.1处理错误
如果由于错误而使得某些操作没有完成,
程序应该返回到一种安全的状态,并能够让用户执行其他指令
或者,允许用户保存所有工作结果,以妥善的方式终止程序

需要考虑的错误:
用户输入错误
设备错误(硬件)
物理限制(剩余空间不足)
代码错误

### 7.1.1异常分类
所有的异常都由Throwable继承而来,下一层分解为Error和Exception
Error类是Java运行时系统内部错误和资源耗尽错误,一般无能为力
Exception类分为IOException和RuntimeException,后者属于编程错误

RuntimeException一般包括
错误的强制类型转换
数组方位越界
访问null指针

IOException一般包括
试图打开一个不存在的文件
试图超越文件末尾继续读取数据
查找不存在的Class对象

派生于Error类和RuntimeException类的所有异常称为非检查型异常,其他的称为检查型异常
编译器将检测你是否为所有的检查型异常提供了异常处理器

### 7.1.2声明检查型异常
```
public FileInputSream(String name) throws FIleNotFindException
```
这个声明表示这个构造器将根据给定的String参数产生一个FileInputStream对象,但也有可能出错而抛出一个FileNotFoundException异常。

下面四种情况会抛出异常
1. 调用了一个抛出检查型异常的方法,例如FileInputStream构造器
2. 检测到一个错误,并利用throw语句抛出检查型异常
3. 程序出现错误,例如a[-1]会抛出一个非检查型异常
4. Java虚拟机或运行时库出现错误

若在子类中覆盖超类一个方法,子类方法声明的检查性异常不能比超类的更通用,只能抛出更特定的。若超类未抛出,子类也不能抛出。
### 7.1.3如何抛出异常
```
throw new EOFException()//输入过程意外遇到了EOF
```
### 7.1.4创建异常类
自定义类一般包含两个构造器,一个是默认的,一个是包含详细信息的构造器
```
class FileFormatException extends IOException
{
    public FileFormatException(){}
    public FileFormatException(String gripe){
        super(gripe)
    }
}
String readData(BufferedReader in) throws FileFormatException
{
    ...
    while(...)
    {
        if(ch==-1)//EOF encountered
        {

        }
        if(n<len)
        {
            throw new FileFormatException;
        }
    }
}
```
## 7.2 捕获异常
```
try{
    ...
    ...
}
catch(ExceptionType e)
{
    ...
}
```
如果try语句块中任何代码抛出了catch子句中指定的一个异常类
1. 语句将跳过说剩余的try语句
2. 执行catch中的语句

如果try没有抛出异常,程序将跳过catch\
如果抛出catch未列出的类型,方法将立即退出

如果编写一个方法覆盖超类的方法,超类没有抛出异常,你就必须捕获代码中每一个检查型异常。不允许在子类的throw里抛出超类未列出的异常类
### 7.2.2 捕获多个异常
```
try{
    ...
}
catch(IOException e)
{
    ...
}
catch(FileNotFoundException e)
{
    ...
}
catch(ExceptionTypeA|ExceptionTypeB e)
{
    ...
}
```
e为final变量,e.getMessage();获得异常详细信息,e.getClass().getName();获得异常类型
### 7.2.4 finally子句
不管是否有异常被捕获,finally子句都会执行
```
var in=new FileInputStream(...);
try{
    //1
    //code might throw Exception
    //2
}
catch(IOException e)
{
    //3
    //show error message
    //4
}
finally
{
    //5
    in.close();
}
//6
```
上述程序会有3种执行情况,
1. 代码没有抛出异常,1256
2. 代码抛出一个异常并被catch捕获 如果catch没有抛出异常13456,如果catch抛出异常 135
3. 代码抛出异常且未被捕获,15

try可以只有finally语句,而没有catch子句,finally语句的主要作用在于关闭资源。
### 7.2.5 try-with-resources语句
```
try(Resource res=...)
{
    //work with res
}
```
try块退出,会自动调用res.close(),还可以指定多个资源
```
try(var in=new Scanner(new FileInputStream("/words"),StandardCharsets.UTF_8);
    var out=new PrintWriter("out.txt",StandardCharsets.UTF_8))
    {
        while(in.hasNext())
        {
            out.println(in.next().toUpperCase());
        }
    }
```
无论这个块如何退出,in和out都会关闭
### 7.2.6 分析堆栈轨迹
```
var walker=StackWalker.getInstance();
walker.forEach(System.out::println);
```
## 7.3 使用异常的技巧
1. 异常不能代替简单测试(捕获异常时间长)
2. 不要过分细化异常,将正常处理和错误处理语句分开
3. 充分利用异常层次结构(异常子类)
4. 早抛出,晚捕获,适量传递
## 7.4 断言
断言允许在测试期间向代码插入一些检查,在生产代码中自动删除这些检查。
```
assert condition: expression;//expression会产生消息字符串
assert x>=0:x//判断x>=0,显示x值
```
默认情况下,断言是禁用的
```
java -enableassertions MyApp
java -ea:MyClass -ea:com.mycompany.mylib Myapp//在某个类或某个包中打开断言
```
## 7.5 日志
基本日志
```
Logger.getGlobal().info("x="+x);//x为变量
```
## 7.6 调试技巧
1. 使用语句打印或记录变量的值
```
System.out.println(x);
Logger.getGlobal().info("x="+x);
Logger.getGlobal().info("this="+this);//记录隐式参数对象状态
```
2. 单元测试,每一个类可单独放置一个main方法 JUnit(单元测试框架)
3. 日志代理是一个子类对象,截获方法调用记录日志
```
var generator =new Random()
{
    public double nextDouble()
    {
        double result=super.nextDouble();
        Logger.getGlobal().info("nextDouble:"+result);
        return result;
    }
};
```
只要调用nextDouble方法,就会生成日志消息
4. 记录堆栈轨迹
```
var out=new StringWriter();
new Throwable().printStackTrace(new PrintWriter(out));
String description=out.toString();
```
5. 要想观察类的加载过程,启动Java虚拟机时给参数-verbose
6. javac -Xlint sourceFile 会返回一些错误提示