u01-异常处理

189 阅读7分钟

1. 异常

小王每天开车去上班,一般情况下半个小时内就能到,一旦发生堵车或者其他不可预知的情况,小王都会与公司取得联系然后告知情况,以免影响工作。而对于程序而言,堵车就是一种异常,与公司联系告知情况,就是一种异常处理。

概念: 异常也称例外,在程序运行时发生并打断程序运行,分类两类:

  • 非受检异常/运行异常:不接受javac检查,可以不处理,一般指逻辑错误,应积极避免:
    • ClassCastException:类型转换异常。
    • ArrayIndexOutOfBoundsException:数组越界异常。
    • NullPointerException:空指针异常。
  • 受检异常/可控异常:必须接受javac检查,必须处理,一般是程序运行时由于外界因素造成的:
    • ClassNotFoundException:类丢失异常。
    • IOException:IO异常。
    • SQLException:SQL异常。

部分异常关系继承结构图.md

Throwable [抛出能力类]
    |__Error [错误]
        |__VirtulMachineError [虚拟机错误]
            |__StackOverFlowError [栈溢出]
            |__OutOfMemoryError [内存溢出]
        ...
    |__Exception [异常]
        |__IOException [文件读写异常]
            |__EOFException [end of file文件已经读完了,你还在读]
            |__FileNotFoundException [文件没找到异常]
        |_ ReflectiveOperationException [响应操作异常]
            |__ClassNotFountException [类没找到异常]
        |__RuntimeException [运行时异常]
            |__ArithmeticException [数学异常]
            |__MissingResourceException [资源文件丢失异常]
            |__ClassCastException [类转换异常]
            |__NullPointerException [空指针异常:使用null调用,计算,取值等]
            |__IllegalArgumentException [非法参数异常]
            |_ UnknownEntityException [未知实体异常]
                |__UnknownTypeException [未知类型异常]
            |_ IndexOutOfBoundsException [角标越界]
                |__ArrayIndexOutOfBoundsException [数组角标越界异常]
                |__StringIndexOutOfBoundsException [字符串角标越界异常]
        ...

2. 异常处理

概念: 异常爆发后的处理方案有记录日志,System.exit(-1) 退出虚拟机,e.printStackTrace() 打印堆栈信息,发短信,发邮件等。

2.1 异常处理结构

概念: 在一套完整的try/catch/finally结构中:

  • try{}:包裹可能会爆发异常的代码,必须且只能存在一个:
    • 选中代码,然后使用快捷键 ctrl + alt + t 可以自动进行包裹。
  • catch(){}:捕获一个或者多个异常,以及之后的处理工作:
    • 允许同时捕获多个,但必须严格按照先小后大的顺序进行捕获。
    • 允许不存在,即不对异常进行捕获和处理。
    • jdk8中提供了multipleCatch写法,可同行捕获多个异常。
  • finally{}:无论程序是否爆发异常,其中的代码都一定会执行:
    • 允许不存在,但如果存在则只能存在一个。

源码: /javase-advanced/

  • src: c.y.exception.ExceptionTest.tryCatchStructure()
/**
 * @author yap
 */
public class ExceptionTest {
    @Test
    public void tryCatchStructure() {
        try {
            System.out.println(1 / 0);
        } catch (ArithmeticException e) {
            e.printStackTrace();
        } finally {
            System.out.println("程序结束...");
        }
        System.out.println("如果异常爆发,我不会被输出...");
    }
}
  • src: c.y.exception.ExceptionTest.multipleCatch()
    @Test
    public void multipleCatch() {
        int[] arr = {1, 2};
        int num = 0;
        try {
            arr[0] = 1 / num;
        } catch (NullPointerException | ArithmeticException e) {
            e.printStackTrace();
        }
    }

2.2 异常结构中的return

概念: 异常结构中无论是否存在 returnfinally{} 都一定会执行:

  • returntry{}/catch{} 中:
    • 执行 try{}/catch{} 获得 return 结果并缓存起来(此时不直接返回)。
    • 执行 finally{},若 finally{} 中对刚才的结果进行了修改:
      • 基本类型结果,修改无效,以 try{}/catch{} 中缓存的结果为准。
      • 引用类型结果,修改有效,以 finally{} 中修改后的结果为准。
    • 执行 try{}/catch{} 中的 return,将结果返回给调用方。
  • returnfinally{} 中,则最终只执行 finally{} 中的 return

源码: /javase-advanced/

  • src: c.y.exception.ExceptionTest.returnTime()
    private int returnInTryOrCatchWithBase(int num) {
        try {
            System.out.println(100 / num);
            return 100;
        } catch (Exception e) {
            System.out.println("catch执行...");
            return 200;
        } finally {
            System.out.println("finally执行...");
            num = 300;
        }
    }

    private int[] returnInTryOrCatchWithReference(int num) {
        int[] arr = {0, 1};
        try {
            System.out.println(100 / num);
            arr[0] = 100;
            return arr;
        } catch (Exception e) {
            System.out.println("catch执行...");
            arr[0] = 200;
            return arr;
        } finally {
            System.out.println("finally执行...");
            arr[0] = 300;
        }
    }

    private int returnInTryOrCatchAndFinally(int num) {
        try {
            System.out.println(100 / num);
            return 100;
        } catch (Exception e) {
            System.out.println("catch执行...");
            return 200;
        } finally {
            System.out.println("finally执行...");
            return 300;
        }
    }

    @Test
    public void returnTime() {
        System.out.println(returnInTryOrCatchWithBase(100));
        System.out.println(returnInTryOrCatchWithBase(0));
        System.out.println(returnInTryOrCatchWithReference(100)[0]);
        System.out.println(returnInTryOrCatchWithReference(0)[0]);
        System.out.println(returnInTryOrCatchAndFinally(100));
        System.out.println(returnInTryOrCatchAndFinally(0));
    }

2.3 throws抛出多个

概念: 可以在方法名后通过 throws 将异常抛给调用者处理,抛出多种异常使用用逗号隔开,且在抛出的多种异常之间是在乎顺序和大小的。

源码: /javase-advanced/

  • src: c.y.exception.ExceptionTest.throwsException()

2.4 throw抛出一个

概念: throw 异常实例 用于主动抛出某个异常:

  • 如果抛出的是受检异常,则方法也必须配合 throws 进行二次抛出。
  • 如果抛出的是非受检异常,则不需要额外处理。

源码: /javase-advanced/

  • src: c.y.exception.ExceptionTest.throwException()
    private void method(int num) throws RuntimeException {
        System.out.println(100 / num);
    }

    @Test
    public void throwsException()  {
        try {
            method(0);
        } catch (RuntimeException e) {
            e.printStackTrace();
        }
    }

2.5 tryWithResources捕获异常

概念: jdk8中提供了tryWithResources写法,如果某个实例实现了Closeable接口,则可以将try-catch写法变形如下:

  • try{} 添加一个小括号,变为 try(){}
  • 将实例的声明放在小括号中,如 try(A a = new A()){}
  • 效果是tryWithResources自动添加 finally{} 且在其中调用实例的 close() 方法。

源码: /javase-advanced/

  • src: c.y.exception.TryWithTest
/**
 * @author yap
 */
public class TryWithTest {

    private static class Person implements Closeable {
        @Override
        public void close() throws WebServiceException {
            System.out.println("Person的close()被调用...");
        }
    }

    @Test
    public void tryWithResources() {
        try (Person person = new Person()) {}
    }
}

3. 自定义异常类

概念: 继承 Exception 即可自定义异常类。

源码: /javase-advanced/

  • src: c.y.exception.CustomExceptionTest

/**
 * @author yap
 */
public class CustomExceptionTest {
    private static class CustomException extends Exception {

        public CustomException(){ }

        public CustomException(String message) {
            super(message);
            System.out.println("my exception service...");
        }
    }

    @Test
    public void myException() throws CustomException {
        throw new CustomException("你触发了我自己写的异常...");
    }
}

4. 异常方法的重写原则

概念: 对于抛出异常的方法我们也可以对其进行Override,但是要遵循以下几条原则:

  • 非受检异常在什么情况都不需要被处理。
  • 原方法有受检异常,重写方法可以不抛出该异常:
    • 若没有使用多态调用重写方法,则这个受检异常无需被处理。
    • 若使用了多态来调用重写方法,则这个异常依然需要被处理(非受检除外)。
  • 原方法有异常,重写方法的异常只能比原方法小(非受检除外)。
  • 原方法没有异常,重写方法不允许抛出异常(非受检除外)。

5. Date工具类

概念: java.util.Date 表示指定的日期信息,可以精确到毫秒,Date中许多方法已经过时但仍能使用。

  • public Date([long time]):创建当前系统日期,可传入时间戳以创建指定日期。
  • void setTime(long time):设置自定义日期,参数为时间戳。
  • long getTime():获取时间戳。

源码: /javase-advanced/

  • src: c.y.exception.DateTest

/**
 * @author yap
 */
public class DateTest {
    @Test
    public void build(){
        Date date = new Date();
        System.out.println(date);

        date = new Date(1000L);
        System.out.println(date);

        date.setTime(5000L);
        System.out.println(date.getTime());
    }
}

6. DateFormat工具类

概念: java.text.DateFormat 用于日期的格式化和解析:

  • new SimpleDateFomat()/DateFormat.getInstance():创建 DateFormat 实例。
  • String format(Date date):将日期按照指定格式格式化成字符串。
  • Date parse(String str):将字符串按照指定格式解析成日期。

源码: /javase-advanced/

  • src: c.y.exception.DateFormatTest
/**
 * @author yap
 */
public class DateFormatTest {
    @Test
    public void format() {
        Date now = new Date();
        String pattern = "yyyy年MM月dd日 hh:mm:ss";
        DateFormat dateFormat = new SimpleDateFormat(pattern);
        String result = dateFormat.format(now);
        System.out.println(result);
    }

    @Test
    public void parse() {
        String olympicDate = "2008-08-08";
        String pattern = "yyyy-MM-dd";
        DateFormat dateFormat = new SimpleDateFormat(pattern);
        Date result = null;
        try {
            result = dateFormat.parse(olympicDate);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        System.out.println(result);
    }
}

7. Calendar工具类

概念: java.util.Calendar 是一个抽象基类,主要用于完成日期字段之间的相互操作功能,可以设置和获取日期数据的特定部分。

  • Calendar.getInstance():创建 Calendar 实例。
  • setTime(Date date):使用给定的Date设置此CalendarA的时间。
  • getTime():转化成Date对象
  • add(int field, int amount): 将日历的field字段添加或较少指定值。
  • set(int field, int value): 将日历的field字段改为value值。
    • Calendar.MONTH:老外将12月作为每年的第一个月。
    • Calendar.DAY_OF_WEEK:老外将周日作为每周的第一天。
  • get(int field):获得当前时间中field字段的值。

源码: /javase-advanced/

  • src: c.y.exception.CalendarTest
/**
 * @author yap
 */
public class CalendarTest {

    @Test
    public void calendarApi() {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");

        // 将年份减去1
        calendar.add(Calendar.YEAR, -1);
        System.out.println(dateFormat.format(calendar.getTime()));

        // 将月份设置为1月
        calendar.set(Calendar.MONTH, 0);
        System.out.println(dateFormat.format(calendar.getTime()));

        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH);
        int date = calendar.get(Calendar.DATE);
        int week = calendar.get(Calendar.DAY_OF_WEEK);
        int hour = calendar.get(Calendar.HOUR);
        int minute = calendar.get(Calendar.MINUTE);
        int second = calendar.get(Calendar.SECOND);
        System.out.printf("%d-%d-%d 星期%d %d:%d:%s",
                year, month + 1, date, week + 1, hour, minute, second);
    }

}

8. Optional工具类

概念: java.util.Optional 可以更优雅的避免空指针异常:

  • Optional.of(obj):生成obj对应的Optional对象。
  • Optional.ofNullable(obj):obj不为空则生成对应的Optional对象,否则返回空Optional对象。
  • optional.map(Function fun):调用函数,若参数为空,直接返回空Optional对象。
  • optional.orElse(null):终止Optional调用链,返回null。

源码: /javase-advanced/

  • src: c.y.exception.OptionalTest
/**
 * @author yap
 */
public class OptionalTest {
    @Test
    public void option() {

        A a = new A();

        // no good
        if (a != null && a.getB() != null && a.getB().getC() != null) {
            System.out.println(a.getB().getC().getName());
        }

        // good
        // map(): return empty optional if param is null to avoid NullPointException
        String result = Optional.ofNullable(a)
                .map(e -> e.getB())
                .map(e -> e.getC())
                .map(e -> e.getName())
                .orElse(null);
        System.out.println(result);
    }

    @Data
    class A {
        private B b = new B();
    }

    @Data
    class B {
        private C c = new C();
    }

    @Data
    class C {
        private String name = "z4";
    }
}