25中级 - Java的异常体系【爬虫项目实战】

284 阅读5分钟

exception

  • return语句之外,方法的另外一个出口
  • IOException通常代表"预期之内的异常"
  • 万能解决方案,alt+enter

异常会击穿所有的栈帧

package com.github.hcsp.io;w


import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;

public class Crawler {
    public static void main(String[] args) throws IOException {
        readFile(null);
    }

    public static String readFile(File file) throws IOException {
        int i = 0;
        i++;
        int j = 1;
        j++;
        if (true) {
            throw new RuntimeException();
        }
        return FileUtils.readFileToString(file, Charset.defaultCharset());
    }
}
  • 加try catch去避免击穿栈帧

  • 不管正确错误最终一定要处理,最常见的就是关闭文件,关闭数据库连接,做一些清理工作,

  • finally里面是不赞成使用return语句,finally 里return会打破之前的return

 private static void a() {
        try {
            b();
            return 0;
        } catch (Exception e) {
            e.printStackTrace();
            return 1;
        } finally {
            closeFile();
        }
    }

    private static void closeFile() {
    }

    private static void b() {
    }
  • catch可以干掉,catch里面一般都要
// 处理异常
// 在日志里打印异常
// 返回对应对异常的处理

try with resources 有限制只能在同一个方法

  • 一些远古代码用file leak detector
  • try with resources,try后面加括号,java会偷偷摸摸的加一个

2.png

  • 两者等价,生命在括号里面的东西,finally会自动被关闭,必须实现了autoCloseable接口的东西
  • 如果可以的话, IDE会有提示

3.png

  • 例子2
public static void main2() throws IOException {
        FileInputStream is = null;
        try {
            is = new FileInputStream("name");
        } finally {
            if (is != null) {
                is.close();
            }
        }
    }
  • 多行代码变一行代码,可以写成
    public static void main2() throws IOException {
        try (FileInputStream is = new FileInputStream("name")) {
        }
    }

throw和throws

  • throw抛一个异常
  • throws只是一个声明
  • throw的使用
 public static void main(String[] args) throws IOException {
        a();
    }

    private static void a() {
        b();
    }

    private static void b() {
        foo();
    }

    private static void c() throws Exception {
        if (true) {
            throw new Exception();
        }
    }

Java的异常体系

  • Throwable - 可以被抛出的东西(有毒)
    • Exception - checked exception (受检异常,有毒)
      • RuntimeException(运行时异常,无毒)
    • Error(错误,无毒)
  • catch的级联与合并
  • throwable是所有错误和异常的父类表示可以被丢出的东西
  • 有毒的意思表示有传递性,声明了Throwable的方法,被调用的地方也一定要声明throws Throwable,学名叫受检异常checked exception
  • unchecked exception 无毒 不受检异常
public static void main(String[] args) throws Throwable {
    throwCheckedException();
}

private static void throwCheckedException() throws Throwable {

}

最早设计的时候只有有毒

  • 想法是保证安全性,但是后来发现实际使用太麻烦了
  • java异常设计中,exception表示预期之内被抛出的异常,比如IOException
  • RuntimeException是运行时异常,意料之外的,因此不需要声明
  • Error是不能恢复的,比如outMemeryError,代表不正常的情况
  • catch的级联和合并,根据抛出不同的异常进行处理,从小到大的排
 private static void throwCheckedException() throws Exception {
        if (fileNotFound()) {
            throw new FileNotFoundException();
        } else if (reachFileEnd()) {
            throw new EOFException();
        } else {
            throw new IOException();
        }
    }

   

    public static void main(String[] args) {
        try {
            throwCheckedException();
        } catch (FileNotFoundException e) {
            System.out.println("文件没找到");
        } catch (EOFException e) {
            System.out.println("文件已到达末尾");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  • java7之后引入了异常合并,按alt+enter用|来去掉重复代码

Throwable

  • 栈轨迹Stacktrace(排查问题最重要的信息,没有之一)
  • 异常链(Caused by)
  • 栈轨迹

5.png

把有毒的受检异常包成没有毒的异常

  • 包装一层,把环境异常也抛出
private static class UserAlreadyExistException extends RuntimeException {
    public UserAlreadyExistException(String message, Throwable cause) {
        super(message, cause);
    }
}

private static void foo() {
    Integer userId = 1;
    try {
        insertIntoDatabase();
    } catch (SQLException e) {
        throw new UserAlreadyExistException("插入id为" + userId + "的数据时候发生了异常", e);
    }
}

private static void insertIntoDatabase() throws SQLException {
    throw new SQLException("重复的健值");
}
  • 异常链,包一层

6.png

异常抛出的原则

  • 能用if/else处理的,不要使用异常,原因有两个1.不能保证catch的异常是你想要抓住,2.异常的创建非常昂贵,能不用异常就不用
  • 今早抛出异常,尽早抛出,问题简单化
  • 异常要准确,比如文件没找到EOF,带有详细信息,尽可能把环境放进日志
  • 抛出异常也比悄悄地执行错误的逻辑强的多
  • e.printStackTrace,打印到当前进程的标准错误流
  • 本方法是否有责任处理这个异常?不要处理不归自己管的异常
  • 本方法是否有能力处理这个异常?如果自己无法处理,就抛出,比如数据库异常
  • 如非万分必要,不要忽略异常, 以下这种是特例
        try {
            URLDecoder.decode("", "UTF-8");
        } catch (UnsupportedEncodingException ignored) {
            
        }

了解和使用JDK内置的异常

  • NullPointerException,空指针异常
  • ClassNotFoundException/NoClassDefFoundError,类不存在的异常
  • IllegalStateException,不正常的状态
  • IllegalArgumentException,非法的参数
  • IllegalAccessException,非法的访问

爬虫持久化

  • 第三方库,搜索github api sdk
  • String是不可变的

课后练习题

  • 1-grep-file
package com.github.hcsp.io;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class FileSearch {
    // 找到第一个包含text的行的行号,行号从1开始计算。若没找到,则返回-1。
    // 如果指定的文件不存在或者无法被读取,抛出一个IllegalArgumentException。
    // 请不要让这个方法抛出checked exception


    private static class NotThrowCheckedException extends RuntimeException {
        private NotThrowCheckedException(String message, Throwable cause) {
            super(message, cause);
        }
    }


    public static int grep(File target, String text) {
        try {
            FileInputStream inputStream = new FileInputStream(target);
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            int count = 0;
            String str;
            while ((str = bufferedReader.readLine())!= null) {
                count += 1;
                if (str.contains(text)) {
                    return count;
                }
            }
            return -1;
        } catch (IOException e) {
            throw new IllegalArgumentException();
        }
    }

    public static void main(String[] args) {
        File projectDir = new File(System.getProperty("basedir", System.getProperty("user.dir")));
        System.out.println("结果行号:" + grep(new File(projectDir, "log.txt"), "BBB"));
    }
}
  • 2-fix-exception-handling
package com.github.hcsp.exception;

import java.io.File;
import java.sql.*;

public class DatabaseReader {
    public static void main(String[] args) {
        File projectDir = new File(System.getProperty("basedir", System.getProperty("user.dir")));
        String jdbcUrl = "jdbc:h2:file:" + new File(projectDir, "test").getAbsolutePath();
        System.out.println(jdbcUrl);
        Connection connection = null;
        try {
            connection = DriverManager.getConnection(jdbcUrl, "sa", "");
            PreparedStatement statement =
                    connection.prepareStatement("select * from PULL_REQUESTS where number > ?");
            statement.setInt(1, 0);
            ResultSet resultSet = statement.executeQuery();
            while (resultSet.next()) {
                System.out.println(
                        resultSet.getInt(1)
                                + " "
                                + resultSet.getString(2)
                                + " "
                                + resultSet.getString(2));
            }
        } catch (SQLException e) {
            System.out.println(e);
        } finally {
            try {
                connection.close();
            } catch (SQLException e) {
                System.out.println(e);
            }
        }
    }
}