异常处理的理解

82 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

引言

基本类型、类、接口、枚举类都是在表示和操作数据,操作的过程中可能会出现报错,可能来自内部、外部或者代码写错,统称为异常

异常分类

  • 异常大的分为两类:
    • Error:JVM 无法解决的问题
    • Exception:分为三大类,IO、运行时异常和 数据库异常

处理原则

异常处理主要是为了提示用户或者程序员操作的时候出错了,比方说一个网页中输入手机号码,要求是数字,但是用户输成字母了,就需要爆出消息提示用户,这是异常处理的常见场景

对于运行时异常,我们发现代码错误后必须立即解决,保证代码正确运行,所以不需要使用 try-catch-finally 捕获并处理异常,直接找到具体代码然后修改即可

而对于编译时异常,就类似上面说的场景,通过 try-catch-finally,捕获异常并进行一定的处理

处理方式

try-catch-finally

//格式
try{
    //可能出现异常的代码
}catch(异常类型 1 变量名 1){
    //异常处理方式1
}catch(异常类型 2 变量名 2){
    //异常处理方式2
}finally{
    //一定会执行的代码
}


//例子
@Test
public void test1(){
    try{
        String str = "123";
        str  = "abc";
        int num = 0;
        System.out.println("hello-----0");
        num = Integer.parseInt(str);
        System.out.println("hello-----1");
    }catch(NumberFormatException e){
        System.out.println(e.getMessage());
        e.printStackTrace();
        System.out.println("出现数值转换异常,不要着急");
    }catch(NullPointerException e){
        System.out.println("出现空指针异常,不要着急");
    }
    finally{
        System.out.println("hello-----2");
    }
    System.out.println("hello-----3");
}

//结果
hello-----0
For input string: "abc"
java.lang.NumberFormatException: For input string: "abc"
        at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
        at java.lang.Integer.parseInt(Integer.java:580)
        at java.lang.Integer.parseInt(Integer.java:615)
        at Error.ExceptionTest.test212(ExceptionTest.java:20)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
        at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
        at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
        at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
        at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
        at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
        at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
        at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
        at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
        at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
        at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
        at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
出现数值转换异常,不要着急
hello-----2
hello-----3

针对上述结果,可以反推在 try-catch-finally 的执行顺序:

  1. 执行 try 中的语句
  1. 一旦出现异常,就对照着异常进入相应的 catch 中,然后执行里面的代码,一般显示异常信息或者输出一些提示信息。
  1. 执行完 catch 后不再执行 try 后面的语句,直接执行 finally 中的内容
  1. try catch finally 执行结束,执行后面的代码

注意:

  1. try 中定义的变量作用域为 try 语句中,如果后面想用,需要把变量放到外面
@Test
public void test2(){
    String str = "123";
    str  = "abc";
    int num = 0;
    try{
        System.out.println("hello-----0");
        num = Integer.parseInt(str);
        System.out.println("hello-----1");
    }catch(NumberFormatException e){
        System.out.println(e.getMessage());
        e.printStackTrace();
        System.out.println("出现数值转换异常,不要着急");
    }catch(NullPointerException e){
        System.out.println("出现空指针异常,不要着急");
    }
    finally{
        System.out.println("hello-----2");
    }
    System.out.println("hello-----3");
    System.out.println(num);
}

//结果
hello-----0
For input string: "abc"
java.lang.NumberFormatException: For input string: "abc"
        at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
        at java.lang.Integer.parseInt(Integer.java:580)
        at java.lang.Integer.parseInt(Integer.java:615)
        at Error.ExceptionTest.test212(ExceptionTest.java:20)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
        at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
        at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
        at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
        at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
        at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
        at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
        at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
        at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
        at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
        at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
        at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
出现数值转换异常,不要着急
hello-----2
hello-----3
0

finally

finally 主要用于关闭一些连接,比如数据库连接,输入流输出流,socket 连接等等。举个例子就是进行数据库操作的时候,如果出现异常了,即使我们使用 try catch finally,也可能无法关闭数据库连接,所以需要用 finally 防止代码出现异常时,无法执行关闭连接的代码。

@Test
public void testMethod1(){
    try {
        File file = new File("hello.md");
        FileInputStream fis = new FileInputStream(file);

        int data = fis.read();
        while(data != -1){
            System.out.println((char) data);
            data = fis.read();
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }finally {
        fis.close();
    }
}

//但是这样子 finally 中的 fis 没有被定义,会报错,所以一般都这样子写,把声明防外面
@Test
public void testMethod1(){
    FileInputStream fis = null;
    try {
        File file = new File("hello.txt");
        fis = new FileInputStream(file);
        int data = fis.read();
        while(data != -1){
            System.out.print((char) data);
            data = fis.read();
        }
    } catch (FileNotFoundException e){
        e.printStackTrace();
    } catch (IOException e) {
        throw new RuntimeException(e);
    } finally {
        try {
            if(fis != null) fis.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

注意

finally 后面不建议写 return,因为会把 catch 中的 return 覆盖掉

throws

上面的 try-catch-finally 主要用于处理异常,如果无法处理异常,就需要用 throws,把程序往上抛,直到有程序进行处理

public static void main(String[] args) {
    try {
        method02();
    } catch(IOException e){
        System.out.println("输入的文件名字不正确");
    }
}


public static void method02() throws IOException{
    method01();
}
public static void method01() throws FileNotFoundException, IOException {
    File file = new File("hello1.txt");
    FileInputStream fis = new FileInputStream(file);
    int data = fis.read();
    while(data != -1){
        System.out.print((char) data);
        data = fis.read();
    }
    fis.close();
}

//结果
输入的文件名字不正确