美文网首页
Java 学习基础篇 ---- Java 异常处理

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

作者: 瀑月流光 | 来源:发表于2020-05-14 18:14 被阅读0次

一、错误处理

(一) 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 的接口

相关文章

网友评论

      本文标题:Java 学习基础篇 ---- Java 异常处理

      本文链接:https://www.haomeiwen.com/subject/ricpnhtx.html