Java学习笔记-异常处理

本文详细介绍了Java中的异常处理机制,包括异常的结构、类型、处理和捕获,强调了正确处理异常的重要性。同时,文章讨论了如何自定义异常以及日志记录在程序中的作用,提到了几种常见的日志框架如JDK Logging、Commons Logging和Log4j,并探讨了日志记录的益处。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

学习目标

  1. 什么是异常
  2. 异常的结构
  3. 异常怎么处理
  4. 异常怎么捕获
  5. 异常怎么抛出
  6. 怎么自定义异常
  7. 熟悉一种日志记录框架

基础知识

异常

异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。
异常发生的原因有很多,通常包含以下几大类:

  • 用户输入了非法数据。
  • 要打开的文件不存在。
  • 网络通信时连接中断,或者JVM内存溢出。

这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。
要理解Java异常处理是如何工作的,你需要掌握以下三种类型的异常:

  • 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
  • 运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
  • 错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。

Java 程序通常不捕获错误。错误一般发生在严重故障时,它们在Java程序处理的范畴之外。
Error 用来指示运行时环境发生的错误。

异常的结构

在这里插入图片描述
从继承关系可知:Throwable是异常体系的根,它继承自ObjectThrowable有两个体系:ErrorExceptionError表示严重的错误,程序对此一般无能为力:

  • OutOfMemoryError:内存耗尽
  • NoClassDefFoundError:无法加载某个Class
  • StackOverflowError:栈溢出

而Exception则是运行时的错误,它可以被捕获并处理。
某些异常是应用程序逻辑处理的一部分,应该捕获并处理。例如:

  • NumberFormatException:数值类型的格式错误
  • FileNotFoundException:未找到文件
  • SocketException:读取网络失败

还有一些异常是程序逻辑编写不对造成的,应该修复程序本身。例如:

  • NullPointerException:对某个null的对象调用方法或字段
  • IndexOutOfBoundsException:数组索引越界

Exception又分为两大类:

  • RuntimeException以及它的子类;
  • RuntimeException(包括IOException、ReflectiveOperationException等等)

异常的捕获

Java规定:

  • 必须捕获的异常,包括Exception及其子类,但不包括RuntimeException及其子类,这种类型的异常称为Checked Exception。
  • 不需要捕获的异常,包括Error及其子类RuntimeException及其子类。

捕获异常使用try...catch语句,把可能发生异常的代码放到try {…}中,然后使用catch捕获对应的Exception及其子类:

//常规的异常捕获
try
{
   // 程序代码
}catch(ExceptionName e1)
{
   //Catch 块
}

//多重异常捕获
try{
   // 程序代码
}catch(异常类型1 异常的变量名1){
  // 程序代码
}catch(异常类型2 异常的变量名2){
  // 程序代码
}catch(异常类型3 异常的变量名3){
  // 程序代码
}

//完成的异常捕捉,无论是否发生异常,finally 代码块中的代码总会被执行。
try{
  // 程序代码
}catch(异常类型1 异常的变量名1){
  // 程序代码
}catch(异常类型2 异常的变量名2){
  // 程序代码
}finally{
  // 程序代码
}

//处理IOException和NumberFormatException的代码是相同的,所以我们可以把它两用|合并到一
public static void main(String[] args) {
    try {
        process1();
        process2();
        process3();
    } catch (IOException | NumberFormatException e) { // IOException或NumberFormatException
        System.out.println("Bad input");
    } catch (Exception e) {
        System.out.println("Unknown error");
    }
}

技巧

  1. 不推荐捕获了异常但不进行任何处理

    //错误示例
    static byte[] toGBK(String s) {
        try {
            return s.getBytes("GBK");
        } catch (UnsupportedEncodingException e) {
            // 什么也不干
        }
        return null;
    }
    //正确示例
    static byte[] toGBK(String s) {
        try {
            return s.getBytes("GBK");
        } catch (UnsupportedEncodingException e) {
            // 先记下来再说:
            e.printStackTrace();
        }
        return null;
    }    
    
  2. 所有异常都可以调用printStackTrace()方法打印异常栈,这是一个简单有用的快速打印异常的方法,类似:

    java.lang.NumberFormatException: null
        at java.base/java.lang.Integer.parseInt(Integer.java:614)
        at java.base/java.lang.Integer.parseInt(Integer.java:770)
        at Main.process2(Main.java:16)
        at Main.process1(Main.java:12)
        at Main.main(Main.java:5)
    
  3. finally语句保证了有无异常都会执行,它是可选的。

异常的抛出

使用throwsthrow抛出异常
①使用 throws 关键字来声明,throws 关键字放在方法签名的尾部。

import java.io.*;
public class className
{
  public void deposit(double amount) throws RemoteException
  {
    // Method implementation
    throw new RemoteException();
  }
  //Remainder of class definition
}
//一个方法可以声明抛出多个异常,多个异常之间用逗号隔开
import java.io.*;
public class className
{
   public void withdraw(double amount) throws RemoteException,
                              InsufficientFundsException
   {
       // Method implementation
   }
   //Remainder of class definition
}

②使用throw抛出异常

void process2(String s) {
    if (s==null) {
    	//创建某个Exception的实例;
        NullPointerException e = new NullPointerException();
        //用throw语句抛出。
        throw e;
    }
}
//简写
void process2(String s) {
    if (s==null) {
        throw new NullPointerException();
    }
}
//保持原有的异常信息,抛出一个新异常
public class Main {
    public static void main(String[] args) {
        try {
            process1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void process1() {
        try {
            process2();
        } catch (NullPointerException e) {
            throw new IllegalArgumentException(e);
        }
    }

    static void process2() {
        throw new NullPointerException();
    }
}

③当catchfinally都抛出了异常,最后以finally抛出的异常为最终结果。
在极少数的情况下,我们需要获知所有的异常。如何保存所有的异常信息?方法是先用origin变量保存原始异常,然后调用Throwable.addSuppressed(),把原始异常添加进来,最后在finally抛出:

public class Main {
    public static void main(String[] args) throws Exception {
        Exception origin = null;
        try {
            System.out.println(Integer.parseInt("abc"));
        } catch (Exception e) {
            origin = e;
            throw e;
        } finally {
            Exception e = new IllegalArgumentException();
            if (origin != null) {
                e.addSuppressed(origin);
            }
            throw e;
        }
    }
}

自定义异常

Java标准库定义的常用异常包括:

Exception
│
├─ RuntimeException
│  │
│  ├─ NullPointerException
│  │
│  ├─ IndexOutOfBoundsException
│  │
│  ├─ SecurityException
│  │
│  └─ IllegalArgumentException
│     │
│     └─ NumberFormatException
│
├─ IOException
│  │
│  ├─ UnsupportedCharsetException
│  │
│  ├─ FileNotFoundException
│  │
│  └─ SocketException
│
├─ ParseException
│
├─ GeneralSecurityException
│
├─ SQLException
│
└─ TimeoutException
  1. 当我们在代码中需要抛出异常时,尽量使用JDK已定义的异常类型。例如,参数检查不合法,应该抛出IllegalArgumentException

    static void process1(int age) {
        if (age <= 0) {
            throw new IllegalArgumentException();
        }
    }
    
  2. 自定义异常体系时,推荐从RuntimeException派生“根异常”,再派生出业务异常;

    public class BaseException extends RuntimeException {
    }
    public class UserNotFoundException extends BaseException {
    }
    public class LoginFailedException extends BaseException {
    }
    
  3. 自定义异常时,应该提供多种构造方法。

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

日志

为了程序的健壮性、可维护性我们加了异常捕捉,通过记录日志我们可以将日常程序中的操作信息、异常信息记录下来,帮助我们更好的完善软件。

输出日志,而不是用System.out.println(),有以下几个好处:

  • 可以设置输出样式,避免自己每次都写"ERROR: " + var;
  • 可以设置输出级别,禁止某些级别输出。例如,只输出错误日志;
  • 可以被重定向到文件,这样可以在程序运行结束后查看日志;
  • 可以按包名控制日志级别,只输出某些包打的日志;
  • 可以将日志存储到本地或者数据库
使用JDK Logging

JDK的Logging定义了7个日志级别,从严重到普通:

  • SEVERE
  • WARNING
  • INFO
  • CONFIG
  • FINE
  • FINER
  • FINEST
    因为默认级别是INFO,因此,INFO级别以下的日志,不会被打印出来。使用日志级别的好处在于,调整级别,就可以屏蔽掉很多调试相关的日志输出。
// logging
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 process...");
        logger.warning("memory is running out...");
        logger.fine("ignored.");
        logger.severe("process will be terminated...");
    }
}

输出信息:

Mar 02, 2019 6:32:13 PM Hello main
INFO: start process...
Mar 02, 2019 6:32:13 PM Hello main
WARNING: memory is running out...
Mar 02, 2019 6:32:13 PM Hello main
SEVERE: process will be terminated...

缺点:Logging系统在JVM启动时读取配置文件并完成初始化,一旦开始运行main()方法,就无法修改配置;配置不太方便,需要在JVM启动时传递参数-Djava.util.logging.config.file=<config-file-name>

使用Commons Logging

和Java标准库提供的日志不同,Commons Logging是一个第三方日志库,它是由Apache创建的日志模块。
Commons Logging的特色是,①它可以挂接不同的日志系统,并通过配置文件指定挂接的日志系统。②默认情况下,Commons Loggin自动搜索并使用Log4j(Log4j是另一个流行的日志系统),如果没有找到Log4j,再使用JDK Logging。

  1. 使用Commons Logging只需要和两个类打交道,并且只有两步:
    第一步,通过LogFactory获取Log类的实例; 第二步,使用Log实例的方法打日志。
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    
    public class Main {
        public static void main(String[] args) {
            Log log = LogFactory.getLog(Main.class);
            log.info("start...");
            log.warn("end.");
        }
    }
    
  2. Commons Logging定义了6个日志级别:
    • FATAL
    • ERROR
    • WARNING
    • INFO
    • DEBUG
    • TRACE
      默认级别是INFO
  3. 在实例方法中引用Log,通常定义一个实例变量:
    // 在实例方法中引用Log:
    public class Person {
        protected final Log log = LogFactory.getLog(getClass());
    
        void foo() {
            log.info("foo");
        }
    }
    
  4. 除了标准的info(String)外,还提供了一个非常有用的重载方法:info(String, Throwable),这使得记录异常更加简单。
    try {
        ...
    } catch (Exception e) {
        log.error("got exception!", e);
    }
    
使用Log4j

待补充

使用SLF4J和Logback

待补充

思考题

  1. 以下代码执行后输出结果为(A

    public class Test { 
        public static void main(String[] args) { 
            System.out.println("return value of getValue(): " +
            getValue()); 
        } 
         public static int getValue() { 
             try { 
                 return 0; 
             } finally { 
                 return 1; 
             } 
         } 
     }
    

    A:return value of getValue(): 1
    B:return value of getValue(): 0
    C:return value of getValue(): 0return value of getValue(): 1
    D:return value of getValue(): 1return value of getValue(): 0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值