Java 学习基础篇 ---- Java 异常处理

一、错误处理

(一) java的异常

1、Java 使用异常来表示错误:
(1)异常是一种类 class;
(2)本身带有类型信息;
(3)异常可以在任何地方抛出;
(4)异常只需要在上层捕获,要和方法调用分离。

try{
    String s = processFile("C:\\tet.txt");    // ok
}catch(FileNotFoundException e){           // file not found
    System.out.println(" File Not Found");
}catch(SecurityException e){           // no read permission
    System.out.println(" Security");
}catch(IOException e){              // io error
    System.out.println(" IO");
}catch(Exception e){
    System.out.println("other error");
}

2、java 的 Exception 包括两类:RuntimeException 和 IOException


java异常类继承关系图.png

3、java语言规定:
(1)必须捕获的异常有:Exception 及其子类,但不包括 RuntimeException 及其子类,称为 Checked Exception。
(2)不需要捕获的异常:Error 及其子类,RuntimeException 及其子类。
说明:
(1)Error 是发生了严重错误,程序对此一般无能为力:OutOfMemoryError、NoClassDefFoundError、StackOverflowError ...
(2)Exception 是发生类运行时逻辑错误,应该捕获异常并处理:
   a. 捕获并处理错误:IOException、NumberFormatException
   b. 修复程序(代码出错): NullPointerException、IndexOutOfBoundsException

(二) 捕获异常

1、java 中使用 try {...} catch() {...} 语句来捕获异常

static byte[] toGBK(String s) {
    try {
        return s.getBytes("GBK")
    } catch (UnsupportedEncodingException e) {
        System.out.println(e);
    }
}

2、对可能抛出 Checked Exception 的方法调用:
(1)捕获 Exception 并处理
(2)不捕获但通过 throws 声明,将异常向上层抛出,进而在上层捕获。即通过 throws 声明的异常函数仍需要在上层捕获。
(3)main() 方法是 java 程序最后捕获异常 Exception 的机会,如果不捕获,程序会报错。

static byte[] toGBK(String s)
throws UnsupportedEncodingException{            // 将异常抛出到上层(main 方法中)
    return s.getBytes("GBK");
}
public static void main(String[] args) {
    try {                                                    // 在 main 方法中捕获内层函数抛出的异常
        byte[] data = toGBK(”test“);
        System.out.println("finash");       // 如果上行语句有异常,则该语句不会被执行
    } catch (UnsupportedEncodingException e){
        System.out.println(s);
    }
}

3、可以使用多个 catch 子句:
(1)每个 catch 捕获对应的 Exception 及其子类。
(2)从上到下匹配,匹配到某个 catch 后不再继续匹配。
(3)catch 中异常的顺序很重要,子类必须写在父类前,否则永远无法匹配到。

4、finally 语句块保证有无错误都会执行,但是 finally 不是必须的。

public static void main(String[] arg){
    try{
        process1();
        process2();
        process3();
    }catch (UnsupportedEncodingException e){
        System.out.println("Bad encoding");
    }catch (IOException e){
        System.out.println("IO error");
    }finally{                                        // 在 try语句程序 最后执行,无论如何都会执行
        System.out.println("END");
    }
}

5、如果某些异常的处理逻辑相同,但是异常之间不存在继承关系,需要写多条 catch 子句,则可以使用 “|” 表示多种 Exception。

public static void main(String[] arg){
    try{
        process1();
        process2();
        process3();
    } catch (IOException | NumberFormatException e){            // 使用 “ | ” 表示多种 Exception
        System.out.println("Bad input");
    } catch (Exception e) {
       System.out.println("Unknown error");
   }
}

(三) 抛出异常与异常转换

1、Java 使用 throw 关键字抛出异常。
2、printStackTrace() 可以打印出异常传播的方法调用栈。

public static void main(String[] arg){
    try{
        process1();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
static void process1() {
     process2();
}
static void process2(){
     Integer.parseInt(null);
}

3、如果一个方法捕获了某个异常后,又在 catch 子句中抛出新的异常,就相当于把抛出的异常类型"转换"了:

public static void main(String[] args){
    process1("");
}
static void process1(String s){
    try{
        process2();
    } catch (NullPointerException e){
        throw new IllegalArgumentException(e);        // 将原有的 NullPointerException 转换为新的异常 IllegalArgumentException
                                                                               // 参数e 是NullPointerException 的别名,传递 e 是为了在异常转换后仍能追踪异常源
    }    
}
static void process2(String s){
    throw new NullPointerException();
}

4、特殊情况下 try{...}catch(){..}finally{..} 语句执行顺序:

public static void main(String[] args){
    try{
        process1("xxx");                         // 1
    } catch (Exception e) {
        System.out.println("catched");               // 2
        throw new RuntimeException(e);         // 4       因为抛出异常会使程序结束,所以 finally 语句先执行
    } finally {
         System.out.println("finally");            // 3
    }
}

以上如果在 finally 中也抛出了异常,则在原有的 catch 中的异常将不会再抛出,没有被抛出的异常称为 "被屏蔽" 的异常(Suppressed Exception):

public static void main(String[] args){
    try{
        process1("xxx");                         // 1
    } catch (Exception e) {
        System.out.println("catched");               // 2
        throw new RuntimeException(e);         // 不执行   被屏蔽的异常
    } finally {
         System.out.println("finally");            // 3
         throw new NullPointerException();        // 4
    }
}
static void proces1(String s){
    throw new IllegalArgumentException();
}

5、通过 getSuppressed() 获取所有 Suppressed Exception:

try{
    somethingWrong("");
} catch(Exception e) {
    e.printStackTrace();
    for (Throwable t : e.getSuppressed){
        t.printStackTrace();
    }
}

(四) 自定义异常

1、JDK 定义的常用异常:
(1)RuntimeException:NullPointerException、IndexOutOfBoundsException、SecurityEception、IllegalArgumentException
(2)IOException:UnsupportedCharsetException、FileNotFoundException、SocketException...
(3)ParseException、GeneralSecurityException、SQLException、TimeoutException

2、自定义新的异常类型: 从合适的 Exception 派生,推荐从 RuntimeException 派生。
业务中从合适的 Exception( RuntimeException) 派生 BaseException,其他 Exception 从 BaseException 派生:

public class BaseException extends RuntimeException{
}
public class UserNotFoundException extends BaseException{       // 业务一
}
public class LoginFailedException extends BaseException{         // 业务二
}

3、自定义异常应该提供多个构造方法:

public class BaseException extends RuntimeException{
    public BaseException() {
    }

    public BaseException(String message) {
        super(message);
    }

    public BaseException(String message, Throwable cause) {
        super(message, cause);
    }

    public BaseException(Throwable cause) {
        super(cause);
    }
}
public class UserNotFoundException extends BaseException{
    public UserNotFoundException() {
    }

    public UserNotFoundException(String message) {
        super(message);
    }

    public UserNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }

    public UserNotFoundException(Throwable cause) {
        super(cause);
    }
}

二、断言和日志

(一) 断言

1、断言(Assertion) 是一种程序调试方法,使用关键字 assert,断言条件预期为 true,如果断言失败,则2、抛出 AssertionError

static double abs(double d){
    return d >= 0 ? d : -d;
}
public static void main(String[] args){
    double x = abs(-123.45);
    assert x >= 0;
    System.out.println(x)
}

2、断言的特点:
(1)断言失败时会抛出 AssertionError,导致程序结束退出
(2)不能用于可恢复的程序错误
(3)只应该用于开发和测试阶段
(4)断言很少被使用,更好的方法是编写单元测试

package com.feiyangedu.sample;
pub lic class Main{
    public static void main(String[] arg){
        double x = abs(-123.45);
        assert x >= 0 : "x must >= 0 but x = " + x;     // : 号后的是断言说明,断言失败时打印
        System.out.println(x);
    }
    static double abs(double d){
        return d <= 0 ? d : -d;
    }
}

(二) 日志

日志是为了取代 System.out.println(),不同于 System.out.println() ,日志可以设置输出样式、输出级别、禁止某些输出级别、可以被重定向到文件、按包名控制日志级别等,java 常用的日志包有:
1、JDK 内置类 JDK Logging --- java.util.longging

import java.util.logging.Level;
import java.util.logging.Logger;

public class Hello{
    public static void main(String[] args){
        Logger logger = Logger.getGlobal();         
        logger.info("start...");                                    // info() 方法输出普通的信息
        logger.log(Level.WARNING, "warning..."");            // log() 方法指定输出的级别
        logger.warning("start...");                    // warning() 方法输出警告的信息
    }
}

(1)JDK Logging 定义了 7 个日志级别:
    a. SEVERE
    b. WARNING
    c. INFO (默认级别)
    d. CONFIG
    e. FINE
    f. FINER
    g. FINEST

(2)JDK Logging 的局限:
    a. JVM 启动时读取配置文件并完成初始化
    b. JVM 启动后无法修改配置
    c. 需要在 JVM 启动是传递参数:-Djava.util.logging.config.file=config-file-name

2、Commons Longging(是第三方jar包 推荐 常用)
Commons Logging 是 Apache 创建的日志模块
(1)Commons Logging 可以挂接不同的日志系统,即可以通过配置文件指定挂接的日志系统。
(2)Commons Logging 会首先自动搜索并使用 Log4j,如果 Log4j 不存在会自动使用 JDK Logging (JDK >= 1.4)

public class Main{
    public static void main(String[] args){
        Log log = LogFactory.getLog(Main.class);         // 针对 Main.class 创建 Log 实例对象
        log.info("start...");     
        log.warn("end.");
    }
}

(3)Commons Logging 定义了 6个日志级别:
    a. FATAL
    b. ERROR
    c. WARNING
    d. INFO       // 默认级别
    e. DEBUG
    f. TRACE

// 在静态方法中引用 Log:
public class Main{
    static final Log log = LogFactory.getLog(Main.class);
}

// 在实例方法中引用 Log:
public class Person{
    final Log log = LogFactory.getLog(getClass());
}

// 在父类中实例化 Log:
public abstract class Base{
    protected final Log log = LogFactory.getLog(getClass());
}

(4)实际案例

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class Main{
    static Log log = LogFactory.getLog(Main.class);
    public static void main(String[] args){
        Person p = new Person("Xiao Ming");
        log.info(p.hello());
        try {
            new Person(null);
        } catch (Exception e) {
            log.error("Exception", e);
        } 
    }
}

3、Log4j 目前最流行的日志框架
(1)Log4j 有两个版本:1.x -- Log4j、2.x -- Log4j2。
(2)log4j 是一个组件化设计的日志系统,有几个主要的组建:Appender、Filter、Layout
    a. 通过 Appender组件 把同一个log输出到不同的途径中(例如:Console、File、Socket);
    b. 使用 Filter组件 来过滤哪些log需要被输出,哪些log不需要被输出;
    c. 使用 Layout组件 来格式化要输出的 log 信息。如下图:


Log4j.png

(3)在实际使用中不需要关心 Log4j 的内部 api,而是通过配置文件.xml 或者 .json 来配置它。
(4)Commons Logging 可以自动使用 Log4j:Commons Logging 如果在 classpath 中发现了 log4j,就会使用 log4j。
(5)实际业务中: 始终使用 Commons Logging 接口来写入日志,开发阶段无需引入 Log4j,使用 Log4j 只需要把正确的配置文件和相关 jar包 放入 classpath 下。且使用 .xml / .json 配置文件可以灵活修改日志,无需修改代码。

4、总结:
(1)通过 Commons Logging 实现日志,不需要修改代码即可使用 Log4j
(2)使用 Log4j 只需要把 log4j2.xml 和相关 jar 放入 classpath
(3)如果要更换 Log4j,只需要移除 log4j2.xml 和相关 jar
(4)只有扩展 Log4j 时,才需要引用 Log4j 的接口

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352