zoukankan      html  css  js  c++  java
  • java的异常处理简介

    异常概述

    • 任何一种程序设计语言设计的程序在运行时都有可能出现错误,例如除数为0,数组下标越界,要读写的文件不存在等等。
    • 捕获错误最理想的是在编译期间,但有的错误只有在运行时才会发生。
    • 对于这些错误,一般有两种解决方法:
      1. 遇到错误就终止程序的运行。(不合法)
      2. 由程序员在编写程序时,就考虑到错误的检测、错误消息的提示,以及错误的处理。

     

    java异常

      •在Java语言中,将程序执行中发生的不正常情况称为“异常”。
      •Java中的异常用于处理非预期的情况,如文件没找到,网络错误,非法的参数
    • Java程序运行过程中所发生的异常事件可分为两类:

        Error:  JVM系统内部错误、资源耗尽等严重情况(不属于程序解决范畴)

        Exception: 其它因编程错误或偶然的外在因素导致的一般性问题,例如:
          –空指针访问
          –试图读取不存在的文件
          –网络连接中断
     1 package com.atguigu.java;
     2 
     3 public class TestException {
     4     
     5     public static void main(String[] args) {
     6         
     7         int i = 10;
     8         //数学异常: java.lang.ArithmeticException
     9 //        int j = i / 0;
    10 //        System.out.println(j); 
    11     
    12         int [] scores = new int[]{1, 2, 4, 5};
    13         //数组下标越界异常: java.lang.ArrayIndexOutOfBoundsException
    14 //        System.out.println(scores[4]); 
    15         
    16         Person p1 = new Man();
    17         //类型转换异常: java.lang.ClassCastException
    18 //        Woman p2 = (Woman) p1;
    19         
    20         p1 = null;
    21         //空指针异常: java.lang.NullPointerException
    22         System.out.println(p1.toString()); 
    23         
    24         System.out.println("end...");
    25     }
    26     
    27 }
    28 
    29 class Person{
    30     
    31 }
    32 
    33 class Man extends Person{
    34     
    35 }
    36 
    37 class Woman extends Person{
    38     
    39 }
    Java异常举例


    异常处理机制

    常见异常

    • RuntimeException(运行时异常)
        –错误的类型转换
        –数组下标越界
        –空指针访问
    • IOExeption(IO异常)
        –从一个不存在的文件中读取数据
        –越过文件结尾继续读取
        –连接一个不存在的URL

      异常机制:在编写程序时,经常要在可能出现错误的地方加上检测的代码,如进行x/y运算时,要检测分母为0,数据为空,输入的不是数据而是字符等。过多的分支会导致程序的代码加长,可读性差。因此采用异常机制。

      Java异常处理:Java采用异常处理机制,将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁,并易于维护。
    • Java提供的是异常处理的抓抛模型
    • Java程序的执行过程中如出现异常,会自动生成一个异常类对象,该异常对象将被提交给Java运行时系统,这个过程称为抛出(throw)异常
    • 如果一个方法内抛出异常,该异常会被抛到调用方法中。如果异常没有在调用方法中处理,它继续被抛给这个调用方法的调用者。这个过程将一直继续下去,直到异常被处理。这一过程称为捕获(catch)异常
    • 如果一个异常回到main()方法,并且main()也不处理,则程序运行终止。
            程序员通常只能处理Exception,而对Error无能为力。

    异常处理是通过try-catch-finally语句实现的

    try
    {
        ......    //可能产生异常的代码
    }
    catch( ExceptionName1 e )
    {
        ......    //当产生ExceptionName1型异常时的处置措施
    }
    catch( ExceptionName2 e )
    {
    ......     //当产生ExceptionName2型异常时的处置措施
    }  
    [ finally{
    ......     //无条件执行的语句
            }  ]

    try

          捕获异常的第一步是用try{…}语句块选定捕获异常的范围,将可能出现异常的代码放在try语句块中。

    catch (Exceptiontype e)

         在catch语句块中是对异常对象进行处理的代码。每个try语句块可以伴随一个或多个catch语句,用于处理可能产生的不同类型的异常对象。

         如果明确知道产生的是何种异常,可以用该异常类作为catch的参数;也可以用其父类作为catch的参数。

         可以用ArithmeticException类作为参数,也可以用RuntimeException类作为参数,或者用所有异常的父类Exception类作为参数。但不能是与ArithmeticException类无关的异常,如NullPointerException,那么,catch中的语句将不会执行。

     1 public static void main(String[] args) {
     2         try {
     3             int i = 10;
     4             int j = i / 0;
     5         } catch(NullPointerException e){
     6             System.out.println(e.getMessage());
     7         } catch(ArithmeticException e){
     8             System.out.println(e.getMessage());
     9         } catch(Exception e){
    10             System.out.println(e.getMessage());
    11         }
    12         System.out.println("end...");
    13         }

    捕获异常的有关信息:

    与其它对象一样,可以访问一个异常对象的成员变量或调用它的方法。

         –getMessage( ) 方法,用来得到有关异常事件的信息
         –printStackTrace( )用来跟踪异常事件发生时执行堆栈的内容。

    finally

      –捕获异常的最后一步是通过finally语句为异常处理提供一个统一的出口,使得在控制流转到程序的其它部分以前,能够对程序的状态作统一的管理。不论在try、catch代码块中是否发生了异常事件,finally块中的语句都会被执行
      –finally语句是可选
          -finally可以没有catch
    public class TestException {    
        public static void main(String[] args) {
            try {
                int i = 10;
                int j = i / 0;
            } finally{
                 System.out.println("finally...");
             }        
            //不论在try、catch代码块中是否发生了异常事件,finally块中的语句都会被执行。
            
            System.out.println("end...");
        }    
    }

    运行时异常和编译时异常

      •前面但使用的异常都是RuntimeException类或是它的子类,这些类的异常的特点是:即使没有使用try和catch捕获,Java自己也能捕获,并且编译通过  ( 但运行时会发生异常使得程序运行终止 )。
      •如果抛出的异常是IOException类的异常,则必须捕获,否则编译错误。


      必须进行处理(选择提示的第二种方法(提示的第一种方法是抛出异常,下面讲))

    1 public static void main(String[] args) {
    2         try {
    3             InputStream is = new FileInputStream("abc.txt");
    4         } catch (FileNotFoundException e) {            
    5             e.printStackTrace();
    6         }
    7     }    


     

    声明抛出异常

      

      任何Java代码都可以抛出异常,如:自己编写的代码、来自Java开发环境包中代码,或者Java运行时系统。无论是谁,都可以通过Java的throw语句抛出异常。从方法中抛出的任何异常都必须使用throws子句。

    throws抛出异常

      声明抛出异常是Java中处理异常的第二种方式(第一种方式就是在catch中捕获并处理异常,第二种方式是只管抛出去,不管处理,给调用异常抛出方法的方法处理)

      如果一个方法可能会出现异常,但没有能力处理这种异常,可以在方法声明处用throws子句来声明抛出异常。例如汽车在运行时可能会出现故障,汽车本身没办法处理这个故障,那就让开车的人来处理。

    1. 如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显式地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
    2. 在方法声明中用 throws 子句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。

      声明抛出异常举例(编译时异常(IO异常)):

          选择上面例子的第一种处理方式

    public class TestException {    
        public static void main(String[] args) {
            try {
                test();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }    
        
        public static void test() throws FileNotFoundException
        {
            InputStream is = new FileInputStream("abc.txt");
        }
    }

      也可以一次抛出多个异常

    public class TestException {    
        public static void main(String[] args) {
            try {
                test();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (SQLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }    
                                      //多个异常在抛出时,是没有父类和子类先后的写法顺序的,只有处理时才有顺序
        public static void test() throws IOException, FileNotFoundException, SQLException
        {
            InputStream fs = new FileInputStream("abc.txt");  //抛出FileNotFoundException
            
            Connection connection = null; 
            String sql = null;
            PreparedStatement ps = connection.prepareStatement(sql); //抛出SQLException
            
            byte [] buffer = new byte[fs.available()];      //抛出IOException
        }
    }


      运行时异常举例

    public class TestException {    
        public static void main(String[] args) {
            try {
                test();
            } catch (Exception e) {
                e.printStackTrace();
            } 
        }                              
        public static void test() 
        {
            int i = 10 / 0;
            System.out.println(i);
        }
    }

      关键点:
         * 1. 在 Java 中使用 throws 关键字声明抛出异常.
         * 2. throws 方法抛出的异常可以是方法中出现的异常的类型或其父类类型.
         * 3. throws 可以声明抛出多个异常, 多个异常使用 , 分割.
         * 4. 运行时异常不需要使用 throws 关键字进行显式的抛出.
         * 5. 重写方法不能抛出比被重写方法范围更大的异常类型

    public class TestException {    
        public static void main(String[] args) {
            //从多态的角度考虑,调用了a的方法,就应该按a对象方法的抛出异常的方法来处理,即是父类的异常类型,如果子类抛了范围更大的异常,就无法处理
            //这就与java的继承或者多态机制相悖
            A a = new B();
            try {
                a.method();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }                              
    }
    
    class A{
        void method () throws FileNotFoundException{        
        }
    }
    class B extends A{
        @Override
        //这种是不合法的
        void method() throws IOException {
    
        }
    }
    //重写方法不能抛出比被重写方法范围更大的异常类型
    //从理解的角度:
    //我们现在有个动物类,它有个方法叫  eat() ,抛出"被毒死"的异常! 那么我们的子类在重写eat()方法时可以抛出 "被毒死"异常或其子类,如"被毒药毒死异常”、
    //"被毒蘑菇毒死异常“.....
    //但是却不能抛出毒死异常之外的  如”撑死异常“或者”噎死异常“
    //就好比比如说宪法规定骂人只承担民事责任,到了刑法里就不能说直接枪毙了,这就是一个约束能力的问题,子类行为不能超出父类约束范畴。否者就容易乱套。
    //就像父类的某方法公有的,子类就不能重写改成私有的了。这就是一个约束力的问题。

     Throws抛出异常的规则:

        1) 如果是不可查异常(unchecked exception),即Error、RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。

        2)必须声明方法可抛出的任何可查异常(checked exception)。即如果一个方法可能出现受可查异常,要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则会导致编译错误

        3)仅当抛出了异常,该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出,而不是囫囵吞枣。

        4)调用方法必须遵循任何可查异常的处理和声明规则。若覆盖一个方法,则不能声明与覆盖方法不同的异常。声明的任何异常必须是被覆盖方法所声明异常的同类或子类。

    void fun()throws IOException,SQLException 
    { 
    ... 
    } 
    这表示 fun方法可能会丢两个异常出来,那么在调用fun的时候就会做好准备,比如可以这样 
    try 
    { 
    fun(); 
    }catch(IOException e) 
    { 
    }catch(SQLException e) 
    { 
    } 

    throw异常

      throw总是出现在函数体中,用来抛出一个Throwable类型的异常程序会在throw语句后立即终止,它后面的语句执行不到,然后在包含它的所有try块中(可能在上层调用函数中)从里向外寻找含有与其匹配的catch子句的try块。
      我们知道,异常是异常类的实例对象,我们可以创建异常类的实例对象通过throw语句抛出。该语句的语法格式为:
        throw new exceptionname;
        例如抛出一个IOException类的异常对象:
        throw new IOException;
        要注意的是,throw 抛出的只能够是可抛出类Throwable 或者其子类的实例对象。下面的操作是错误的:
        throw new String("exception");

        这是因为String 不是Throwable 类的子类。

         如果抛出了检查异常,则还应该在方法头部声明方法可能抛出的异常类型。该方法的调用者也必须检查处理抛出的异常。

           如果所有方法都层层上抛获取的异常,最终JVM会进行处理,处理也很简单,就是打印异常消息和堆栈信息。如果抛出的是Error或RuntimeException,则该方法的调用者可选择处理该异常。

     

    不抛出,异常处不中断

     

    直接抛出;不进行处理,异常处中断

     


    让调用者来处理:异常处中断

     

     

    人工抛出异常

      Java异常类对象除在程序执行过程中出现异常时由系统自动生成并抛出,也可根据需要人工创建并抛出

        –首先要生成异常类对象,然后通过throw语句实现抛出操作(提交给Java运行环境)。

            IOException e =new IOException();

            throw e;

        –可以抛出的异常必须是Throwable或其子类的实例。下面的语句在编译时将会产生语法错误:

                   throw new String("want to throw");

    public class TestException {    
        public static void main(String[] args) {
            
        }    
        
        public static void test(){        
            //1. 创建一个异常类对象
            RuntimeException ex = new RuntimeException();        
            //2. 把异常类对象抛出去
            throw ex;
        }
    }

     

    创建用户自定义异常类

        有些异常可以不处理,直接抛出去,有些异常需要处理,要try/catch,还有些异常需要以一个包装的形式抛出去(使用throw关键字和自定义异常类的结合处理)(比较方法内部的throw关键字和方法签名处的throws关键字:throw一次只能抛出一个)

            用户自定义异常类MyException,用于描述数据取值范围错误信息。用户自己的异常类必须继承现有的异常类。

    public class AgeTooLargeException extends RuntimeException{
    
    
        private static final long serialVersionUID = 1L;
    
        public AgeTooLargeException() {
            // TODO Auto-generated constructor stub
        }
        
        public AgeTooLargeException(String msg) {
            super(msg); 
        }
    }

      然后在程序中使用异常

    import java.util.Scanner;
    /**
     * 人工手动抛出异常:
     * 1. 创建一个异常类对象
     * 2. 在方法内部使用 throw 关键字把该异常类对象抛出去!
     * 
     * 自定义的异常类:
     * 1. 通常继承自 RuntimeException(可以继承 Exception)
     * 2. 自定义的异常类就是用来被人工抛出的!
     */
    public class TestException {    
        public static void main(String[] args) {
            inputAge();
            System.out.println("end...");
        }    
        /**
         * 输入年纪: 要求年纪必须在 15-30 之间, 超出 30 则年纪偏大
         */
        public static void inputAge(){
            Scanner sc = new Scanner(System.in);        
            System.out.print("age=");
            int age = sc.nextInt();        
            if(age > 30){
                //System.out.println("年纪偏大.");
                throw new AgeTooLargeException("年纪偏大.");
            }
        }
    }

      但是如果这样写的话,程序会中断并且抛出异常

      所以,手动抛出异常可以一定程度上的取代if else,但是如果要让程序继续进行,必须对异常进行处理

    public static void main(String[] args) {
            try {
                inputAge();
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
            System.out.println("end...");
        }    


    练习:

    编写应用程序EcmDef.java,接收命令行的两个参数,要求不能输入负数,计算两数相除。

       对缺少命令行参数(ArrayIndexOutOfBoundsException)、

        除0(ArithmeticException)及输入负数(EcDef 自定义的异常)进行异常处理。

    提示:

      (1)在主类(EcmDef)中定义异常方法(ecm)完成两数相除功能。

      (2)在main()方法中使用异常处理语句进行异常处理。

      (3)在程序中,自定义对应输入负数的异常类(EcDef)。

      (4)运行时接受参数 java EcmDef 20 10  

      //args[0]=“20” args[1]=“10”

      (5)Interger类的static方法parseInt(String s)将s转换成对应的int值。如int a=Interger.parseInt(“314”);  //a=314;

    package com.atguigu.java;
    
    public class EcmDef {
        public static void main(String[] args) {
            
            try {
                int i = Integer.parseInt(args[0]); // "a"
                int j = Integer.parseInt(args[1]);            
                System.out.println(ecm(i, j));
            } catch (ArrayIndexOutOfBoundsException e) {
                System.out.println("输入的参数个数不足.");
            } catch (ArithmeticException e) {
                System.out.println("除数不能为 0");
            } catch (EcDef e) {
                System.out.println(e.getMessage()); 
            } catch (NumberFormatException e) {
                System.out.println("输入的参数不能转为整型.");
            }
    
        }
        
        public static int ecm(int i, int j){
            
            if(i < 0 || j < 0){
                throw new EcDef("不能处理负数. ");
            }        
            int result = i / j;
            return result;
        }
    }
    
    
    //自定义一个输入负数的异常类
    public class EcDef extends RuntimeException{
    
        public EcDef() {
            // TODO Auto-generated constructor stub
        }
        
        public EcDef(String msg) {
            super(msg);
        }
    }

     注意问题:

    一、出现问题

    当finall块中包含return语句时,Eclipse会给出警告"finally block does not complete normally"
    二、分析原因

    1 finally块中的return语句会覆盖try块、catch块中的return语句

    2 如果finally块中包含了return语句,即使前面的catch块重新抛出了异常,则调用该方法的语句也不会获得catch块重新抛出的异常,而是会得到finally块的返回值,并且不会捕获异常

    三、结论

    finally内部使用 return 语句是一种很不好的习惯,如果try中还有return语句,它会覆盖了try 区域中 return语句的返回值,程序的可读性差。面对上述情况,其实更合理的做法是,既不在try block内部中使用return语句,也不在finally内部使用 return语句,而应该在 finally 语句之后使用return来表示函数的结束和返回。

     2.

     这里会报出 Unhandled exception type Exception 的错误

    因为:

    在Java中除了RuntimeException及其任何子类,其他异常类都被Java的异常强制处理机制强制异常处理。
        关于那些被强制异常处理的代码块,必须进行异常处理,否则编译器会提示“Unhandled exception type Exception”错误警告。

    也就是说,这些类异常不能通过throw抛!

     throws主要是声明这个方法会抛出这种类型的异常,使其他地方调用它时知道要捕获这个异常。
    throw是具体向外抛异常的动作,所以它是抛出一个异常实例。

    throws说明你有哪个可能,倾向 
    throw的话,那就是你把那个倾向变成真实的了
    同时:
    1)throws出现在方法函数头;而throw出现在函数体; 
    2)throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常; 
    3)两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。

  • 相关阅读:
    Open source cryptocurrency exchange
    Salted Password Hashing
    95. Unique Binary Search Trees II
    714. Best Time to Buy and Sell Stock with Transaction Fee
    680. Valid Palindrome II
    Java compiler level does not match the version of the installed Java project facet.
    eclipse自动编译
    Exception in thread "main" java.lang.StackOverflowError(栈溢出)
    博客背景美化——动态雪花飘落
    java九九乘法表
  • 原文地址:https://www.cnblogs.com/tech-bird/p/3523000.html
Copyright © 2011-2022 走看看