美文网首页
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