Java -入门 异常(自学笔记)(郝斌)

1、什么是异常

  • 异常(Exception)是程序运行过程中发生的事件,该事件可以中断程序指令的正常执行流程

异常的处理机制(重点)

  1. 当Java程序运行出现问题时,系统会自动检测到该错误,并立即生成一个与该错误对应的异常对象
  2. 然后把该异常对象提交给Java虚拟机
  3. Java虚拟机会自动寻找相应的处理代码来处理这个异常,如果没有找到,则由Java虚拟机做一些简单的处理后,程序被强行终止
  4. 程序员可以自己编写代码来捕捉可能出现的异常,并编写代码来处理相应的异常

throw

  • throw用来抛出异常

  • 格式:

    • throw new 异常名(参数)
  • 假设f方法抛出了A类异常,则f方法有两种方式来处理A异常

    1. throws A
      • 谁调用f方法,谁处理A类异常,f方法本身不处理A类异常
    2. try{··· ···}catch( ){··· ···}
      • f方法本身自己来处理A类异常
  • /**这个程序建议看完全篇再回来看*/
    class DivisorIsZeroException extends Exception 
    {
        public DivisorIsZeroException(String errormessage)
        {
            super(errormessage);
        }
    }
    class A
    {
        int divide(int a, int b) throws DivisorIsZeroException
        {
            // try
            // {
            //     if(0 == b)
            //     {
            //         throw new DivisorIsZeroException("除数不能为零!");
            //     }
            // }
            // catch(DivisorIsZeroException e)
            // {
            //     e.printStackTrace();//打印错误信息的路径
            // }
            if(0 == b)
            {
                throw new DivisorIsZeroException("除数不能为零!");
            }
    
            int m = a/b;
            return m;
        }
    }
    public class TestA
    {
        public static void main(String[] args)
        {
            A aa = new A();
            // aa.divide(6,2);
        }
    }
    
  • 要抛出的异常必须得是Throwable的子类

  • 例子:

void f() throws A
{
  ··· ···
  ··· ···
}

  • throws A表示调用f方法时f方法可能会抛出A类异常,建议您调用f方法时最好对f方法可能抛出的A类异常进行捕捉
  • throws A不表示调用f方法时一定会抛出A类异常
    throws A,f方法也可以不抛出A类异常
  • thorws A不表示调用f方法时,必须的对A异常进行捕捉
  • 假设A是RuntimeException的子类异常
  • 由于RuntimeException的子类异常可以处理也可以不处理,所以编译器允许你调用f方法时,对f方法抛出的RuntimeException的子类异常不进行处理
  • 强烈建议你
    对throws出的所有异常进行处理
    如果一个方法内部已经对A异常进行了处理,则就不要再throws A

2、为什么需要异常

我们再引入异常处理之前先举一个例子:

C语言中的异常

#include <stdio.h>
int divide(int a,int b)
{
    int m;
    m = a/b;
    return m;
}
int main()
{
    // printf("%d\n",divide(6,3));
    printf("%d\n",divide(6,0)); 
    return 0;
}

执行以下命令:
  ————————————————————————
  ➜  JAVA gcc Test46.c
  ➜  JAVA ./a.out
  ————————————————————————
  
  程序运行示例:
  ————————————————————————
  [1]    1647 floating point exception  ./a.out
  ————————————————————————
  • 因为除数为0,所以导致C程序在运行的时候崩溃

Java 中的异常

class A
{
    int divide(int a,int b)
    {
      	//System.out.printf("1111\n");
        int m = a/b;
       // System.out.printf("2222\n");
        return m;
    }

}
public class Test46
{
    public static void main(String[] args)
    {
        A aa = new A();
      	aa.divide(6,0);  //编译时,无错,运行时,有错
        System.out.printf("今天我很高兴,因为我和世界的关系很nice\n");
    }
}
执行以下命令:
  ————————————————————————
➜  JAVA javac Test46.java
➜  JAVA java Test46      
  ————————————————————————
  
  程序运行示例:
  ————————————————————————
  Exception in thread "main" java.lang.ArithmeticException: / by zero
        at A.divide(Test46.java:5)     //程序第五行异常出错
        at Test46.main(Test46.java:16)  	//程序第十六行异常出错
  ————————————————————————
  • 因为这个程序也是除数是0,所以程序运行出现异常

**我们在程序的第六行的前后分别插入: **

 System.out.printf("1111\n");  //插入第六行的前面
System.out.printf("2222\n");		//插入第六行的后面
执行以下命令:
  ————————————————————————
➜  JAVA javac Test46.java
➜  JAVA java Test46      
  ————————————————————————
  
  程序运行示例:
  ————————————————————————
1111
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at A.divide(Test46.java:6)
        at Test46.main(Test46.java:17)
  ————————————————————————
  • 再次证明程序在第六行异常出错终止程序

3、异常的处理机制

class A
{
    int divide(int a,int b)
    {
        // System.out.printf("1111\n");
        int m = a/b;
        // System.out.printf("2222\n");
        return m;
    }

}
public class Test46
{
    public static void main(String[] args)
    {
        A aa = new A();
        // aa.divide(6,0);  //编译时时,无错,运行时,有错
        try
        {
            aa.divide(6,0);
        }
        catch(ArithmeticException e)   //算术异常 e用来接收23行抛出的异常对象    
        {   
            e.printStackTrace();//可以简单理解为输出该异常的具体信息
            System.out.printf("除零错误,你的程序出错了!\n");
        }
        System.out.printf("今天我很高兴,因为我和世界的关系很nice\n");
    }
}
执行以下命令:
  ————————————————————————
➜  JAVA javac Test46.java
➜  JAVA java Test46      
  ————————————————————————
  
  程序运行示例:
  ————————————————————————
java.lang.ArithmeticException: / by zero
        at A.divide(Test46.java:6)
        at Test46.main(Test46.java:20)
除零错误,你的程序出错了!
今天我很高兴,因为我和世界的关系很nice
  ————————————————————————
  • 把可能会出错的语句放入语句

  • try
    {
      ··· ···
    }
    catch
    {
      
    }
    
    

    中的try{ } 语句块。如果该语句符合规范的,则不会对该语句进行处理,即程序会自动跳过==catch{}==语句执行其下一行语句,示例:

    class A
    {
        int divide(int a,int b)
        {
            int m = a/b;
            return m;
        }
    
    }
    public class Test46
    {
        public static void main(String[] args)
        {
            A aa = new A();
            // aa.divide(6,0);  //编译时时,无错,运行时,有错
            try
            {
                aa.divide(6,2);
            }
            catch(ArithmeticException e)   //算术异常 e用来接收23行抛出的异常对象    
            {   
                e.printStackTrace();//可以简单理解为输出该异常的具体信息
                System.out.printf("除零错误,你的程序出错了!\n");
            }
            System.out.printf("这是catch语句的下一条语句,因为返回值可以正常输出\n");
            System.out.printf("今天我很高兴,因为我和世界的关系很nice\n");
        }
    }
    
    执行以下命令:
      ————————————————————————
    ➜  JAVA javac Test46.java
    ➜  JAVA java Test46      
      ————————————————————————
      
      程序运行示例:
      ————————————————————————
    这是catch语句的下一条语句,因为返回值可以正常输出
    今天我很高兴,因为我和世界的关系很nice
      ————————————————————————
    

补充:

public class Test48
{
    public static void main(String[] args)
    {
        int m = 99;
        try
        {
            m = 2;
        }
        catch(Exception e)
        {

        }
        System.out.printf("m = %d\n",m);
    }
}
执行以下命令:
  ————————————————————————
➜  JAVA javac Test48.java
➜  JAVA java Test48      
  ————————————————————————
  
  程序运行示例:
  ————————————————————————
m = 2
  ————————————————————————

若把第五行的m未初始化,即修改第五行为

int m;
执行以下命令:
  ————————————————————————
➜  JAVA javac Test48.java 
  ————————————————————————
  
  程序运行示例:
  ————————————————————————
Test48.java:14: 错误: 可能尚未初始化变量m
        System.out.printf("m = %d\n",m);
                                     ^
1 个错误
  ————————————————————————

为什么?

扫描二维码关注公众号,回复: 11266389 查看本文章

倘若我们把14行的输出语句放入==try{}==语句内,即

public class Test48
{
    public static void main(String[] args)
    {
        int m;
        try
        {
            m = 2;
            System.out.printf("m = %d\n",m);
        }
        catch(Exception e)
        {

        }
        // System.out.printf("m = %d\n",m);
    }
}
执行以下命令:
  ————————————————————————
➜  JAVA javac Test48.java 
  ————————————————————————
  
  程序运行示例:
  ————————————————————————
m = 2
  ————————————————————————

同样是局部变量m,为什么这个m可以在try{}块内部输出,但是却不能在try{}块之外输出呢?

  • 因为try{}语句存放程序中可能存在风险的语句,换句话说,对于其花括号里的语句,运行程序时并不一定保证能够顺利执行,否则程序员也就不会把好好的语句朝==try{}语句块里塞了,综上,倘若try{}==语句未能顺利执行,则对于让JAVA输出未初始化的的局部变量是不允许的。

异常的分类:

  • Throwable(抛出)

    • Error(严重的错误)
      • 由Java虚拟机生成并抛出,包括动态链接失败、虚拟机错误等,Java程序无法对此错误进行处理
    • Exception(此集合中的异常必须处理)
      • 一般程序中可预知的问题,其产生的异常可能会带来意想不到的结果,因此==Java编译器要求Java程序必须捕获或声明所有的非运行时异常==
    • RuntimeException(此集合的异常可处理可不处理)
      • Java虚拟机在运行时生成的异常,如被0除等系统错误、数组下标越界等,其产生比较频繁,处理麻烦,对程序可读性和运行效率影响太大。因此由系统检测,用户可不做处理,系统将它们交给缺省的异常处理程序(当然,必要时,用户可对其处理)
        在这里插入图片描述
  1. Error 是系统错误,程序员无法处理这些异常
  2. Exception 是程序员可以捕获并处理的异常
  3. RuntimeException的子类异常, 是你可以处理也可以不处理的异常
  4. 凡是继承自Exception但又不是RuntimeException子类的异常我们都必须的捕获并进行处理

4、异常处理步骤:

  • thorw try catch finally throws

    try
    {
        可能出现异常的代码块
    }
    catch(ExceptionName1)
    {
        当产生ExceptionName1 异常时的处理措施
    }
    catch(ExceptionName2)
    {
        当产生ExceptionName2 异常时的处理措施
    }
    ··· ···
    finally
    {
        无论是否捕捉到异常都必须处理的代码
    }
    
    • 首先在出现异常的语句周围查找是否有**try{} 和catch{} **语句
    • 其次在主调函数中的调用语句附近查找是否有**try{} 和catch{} **语句
    • 如果都没有,则有Java虚拟机简单处理**(终止程序)**

5、自定义异常

  • ==public Exception(String message)==含义
  • 异常与重写关系

6、异常的优缺点

下面说一些目前为止关于异常的困惑:

  • 目前为止if····else···语句似乎完全可以替代异常处理机制

  • 显然,在某些方法上似乎是的,但是这不是完全的,下面就具体列举一些例子:

  • import java.util.*;
    
    public class Test50
    {
        public static void main(String[] args)
        {
            Scanner sc = null;
            try
            {
                sc = new Scanner(System.in);
                int i = sc.nextInt();//输入的字符类型a与int类型不匹配
                System.out.printf("%d\n",i);
    
            }
            catch(InputMismatchException e)
            {
                System.out.println("读取错误,程序将终止");
            }
        }
    }
    
    程序运行示例:
      ——————————————————————
      ➜  JAVA javac Test50.java
    ➜  JAVA java Test50      
    a
    读取错误,程序将终止
    ➜  JAVA java Test50
    34
    34
    ➜  JAVA 
      ——————————————————————
    
  • 本程序出现的问题是无法通过逻辑判断来解决的问题Java提供的异常处理机制可以很好的解决这个问题

异常的优点

  • 没有错误处理的程序:

    • openTheFile
    • determine its size
    • Allocate that much memory
    • read-file
    • closeTheFile
  • 以常规方法处理错误:

    openFile:
    if(the FileOpen)
    {
        determine the length of the file;
        if(gotTheFileLength)
        {
            allocate that much memory;
            if(gotEnoughtMemory)
            {
                read the file into memory;
                if(readFailed) errorCode = -1;
                else errorCode = -2;
            }
            else
                errorCode = -3;
        }
        else    
            errorCode = -4;
    }
    else
        errorCode = -5;
    

    以常规方法处理错误存在的问题

    • 观察前面的程序,大家会发现大部分精力花在出错处理上了
    • 只把能够想到的错误考虑到,对以外的情况无法处理
    • 程序可读性差,大量的错误处理代码混杂在程序中
    • 出错返回信息量少,无法更确切的了解错误状况或原因
  • 用异常的形式处理错误:

{

    try
    {
        openFile;
        determine the length of the file;
        allocate that much memory;
        read-File;
        closeTheFile;
    }
    catch(fileopenFailed)   {dosomething}
    catch(sizeDetemineFailed) {dosomething}
    catch(memoryAllocatedFailed) {dosomething}
    catch(readFailed)           {dosomething}
    catch(fileCloseFailed)      {dosomething}

    finally                 {dosomething}
}

优点

  • 强制程序员考虑程序的安全性与健壮性
  • 增强了程序员对程序的可控性
  • 有利于代码的调试
  • 把错误处理代码从常规代码中分离出来

注意:

  • 异常并不一定能够使程序的逻辑更清晰
    • 因为有时我们必须得编写代码捕捉异常,所以可能会导致程序的逻辑非常混乱
  • 异常并不能解决所有的问题

7、常见异常举例:

常见异常之空指针异常

class Person
{
    public int age;
}
public class TestNullPointerException
{
    public static void main(String[] args)
    {
        Person p = null;		//定义指针赋为空
        System.out.println(p.age);
    }
}

程序运行示例:
  ——————————————————————
➜  JAVA javac TestNullPointerException.java
➜  JAVA java TestNullPointerException      
Exception in thread "main" java.lang.NullPointerException
        at TestNullPointerException.main(TestNullPointerException.java:10)
  ——————————————————————

解决办法:

class Person
{
    public int age;
}
public class TestNullPointerException
{
    public static void main(String[] args)
    {
        // Person p = null;
        Person p = new Person();
        System.out.println(p.age);  //age在未初始化的情况下,系统自动为其赋值为0
    }
}
程序运行示例:
  ——————————————————————
➜  JAVA javac TestNullPointerException.java
➜  JAVA java TestNullPointerException      
0
  ——————————————————————

常见异常之下标越界异常

public class TestIndexOutOf
{
    public static void main(String[] args)
    {
        String friends[] = {"Lisa","Bily","Kessy"};
        for(int i = 0; i < 5; i++)
        {
            System.out.println(friends[i]);
        }

        System.out.println("\nthis is the end");
    }
}

程序运行示例:
  ——————————————————————
➜  JAVA javac TestIndexOutOf.java                        
➜  JAVA java TestIndexOutOf                        
Lisa
Bily
Kessy
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
        at TestIndexOutOf.main(TestIndexOutOf.java:8)
  ——————————————————————

常见异常之除数为0异常

class A
{
    int divide(int a, int b)
    {
        return a/b;
    }
}
public class TestArithExcep
{
    public static void main(String[] args)
    {
        A aa = new A();
        int i = aa.divide(3,0);
        System.out.println(i);
    }
}
程序运行示例:
  ——————————————————————
➜  JAVA javac TestArithExcep.java
➜  JAVA java TestArithExcep              
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at A.divide(TestArithExcep.java:5)
        at TestArithExcep.main(TestArithExcep.java:13)
  ——————————————————————

8、异常处理的两种方式:

import java.io.*;
class A
{
    public void f() throws IOException    //throws语句表示交给主调函数处理 //第二种方法
    {
        /* 第一种方法
        try
        {
            throw new IOException();        //throw不表示抛异常
        }
        catch(IOException e)
        {

        }
        */
        throw new IOException();        //throw不表示抛异常
          
    }
}
public class TestExcep
{
    public static void main(String[] args) throws IOException   /*throws语句表示交给Java虚拟机处理*/ //第二种方法
    {
        A aa = new A();
        aa.f();
        // try   //第一种方法
        // {
        //     aa.f();
        // }
        // catch(IOException e)
        // {

        // }

    }
}

9、Finally的作用

  • 无论try所指定的程序块中是否抛出异常,也无论catch语句的异常类型是否与所抛弃的异常的类型一致,finally中的代码一定会得到执行
  • finally语句为异常处理提供一个统一的出口,使得在控制流程转到程序的其他部分以前,能够对程序的状态作统一的管理
  • 通常在finally语句中可以进行资源的清除工作,如关闭打开的文件、删除临时文件等

10、注意问题

  • 所有的catch只能有一个被执行
  • 有可能所有的catch都没有执行
  • 先catch子类异常再catch父类异常
    • 如果先catch父类异常再catch子类异常,则编译出错
  • catch与catch之间不能有其他代码
  • 重写方法抛出的异常的范围不能大于被重写方法排除的异常范围

猜你喜欢

转载自blog.csdn.net/qq_43656353/article/details/105323487