第二十一章 异常

第二十一章 异常

1 异常

1.1 异常的概述

package com.bjpowernode.javase.exception;

/*
1、什么是异常,异常机制有什么用?
   以下程序执行过程中发生了不正常的情况,而这种不正常的情况叫做:异常
   java语言是很完美的语言,提供了异常的处理方式,以下程序执行过程中出现了不正常的情况,
   java把异常信息打印输出到控制台,供程序员参考,程序员看到异常信息之后,可以对程序进制修改,让程序更加的健壮。

   什么是异常:程序执行过程中的不正常情况。
   异常的作用:增强程序的健壮性

2、以下程序执行控制台出现了:
   Exception in thread "main" java.lang.ArithmeticException: / by zero
   at com.bjpowernode.javase.exception.ExceptionTest01.main(ExceptionTest01.java:17)
   这个信息被我们称为:异常信息,这个信息是JVM打印的



*/

public class ExceptionTest01 {
   public static void main(String[] args) {
       int a = 10;
       int b = 0;
       // 实际上JVM在执行到此处的时候,会new异常对象:new ArithmeticException("/ by zero");
       // 并且JVM将new的异常对象抛出,打印输出信息到控制台了。
       int c = a / b;
       System.out.println(a + "/" + b + "=" + c);  // java.lang.ArithmeticException: / by zero

       // 我观察到异常信息之后,对程序进制修改,更加健壮
       /*
       int a = 10;
       int b = 0;
       if(b == 0){
           System.out.println("除数不能为0");
           return;
       }
       // 程序执行到这里表示除数一定不是0
       int c = a / b;
       System.out.println(a + "/" + b + "=" + c); // java.lang.ArithmeticException: / by zero
        */


  }
}

 

1.2 异常以类的形式出现

package com.bjpowernode.javase.exception;

/*
java语言中异常是以什么形式存在的呢?
   1、异常在java中以类的形式存在,每一个异常类都可以创建异常对象。

   2、异常对应的现实生活中是怎样的?
       火灾(异常类):
           2008年8月8日,小明家着火了(异常对象)
           2008年10月8日,小刚家着火了(异常对象)
           2008年11月8日,小美家着火了(异常对象)

       钱包丢了(异常类):
           2008年1月9日,小明的钱包丢了(异常对象)
           2008年2月9日,小华的钱包丢了(异常对象)
           ...
*/

public class ExceptionTest02 {
   public static void main(String[] args) {

       // 通过“异常类”实例化“异常对象”
       NumberFormatException nfe = new NumberFormatException("数字格式化异常");
       System.out.println(nfe);

       // 通过“异常类”实例化“异常对象”
       NullPointerException npe = new NullPointerException("空指针异常发生了");
       System.out.println(npe);
  }
}

 

 

2 java的异常处理机制

1.1 异常在java中以类和对象的形式存在,那么异常的继承结构是怎样的?

1 我们可以使用UML图来描述一下继承结构。
画UML图有很多工具,例如:Rational Rose(收费的)、startUML等...
   Object
   Object下有Throwable(可抛出的)
   Throwable下有两个分支:Error(不可处理,直接退出JVM)和Exception(可处理的)
   Exception下有两个分支:
  Exception的直接子类:编译时异常(要求程序员在编写程序阶段预先对这些异常进制处理,如果不处理编译器报错)
  RuntimeException:运行时异常。(在编写程序阶段程序员可以预先处理,也可以不管都行)
   
package com.bjpowernode.javase.exception;

public class ExceptionTest03 {
   public static void main(String[] args) {
       /*
       程序执行到此处发生了ArithmeticException异常。
       底层new了一个ArithmeticException异常对象,
       然后抛出了,由于是main方法调用了100 / 0,
       所以这个异常ArithmeticException抛给了main方法,
       main方法没有处理,将这个异常自动抛给了

       ArithmeticException继承RuntimeException,属于运行时异常。
       在编写程序阶段不需要对异常进行预先的处理。
        */
       System.out.println(100 / 0);
       // 这里的hello world没有输出,没有执行。
       System.out.println("hello world!");
  }
}

 

2.2 什么是UML?有什么用?

UML是一种统一建模语言。
一种图标式语言(画图的)
UML不是只有java中使用,只要是面向对象的编程语言,都有UML。
一般画UML图的都是软件架构师或者说是系统分析师。这些级别的人员使用的。
软件设计人员使用UML。

在UML图中可以描述类和类之间的关系,程序执行的流程图,对象的状态等

在java软件开发当中,软件分析师/设计师负责设计类,java软件开发人员必须要能看懂。

 

2.3 编译时异常和运行时异常

1 编译时异常和运行时异常,都是发生在运行阶段,编译阶段异常是不会发生的。编译时异常因为什么而得名?
因为编译时异常必须在编译(编写)阶段预先处理,如果不处理编译器报错,因此得名。
所有异常都是在运行阶段发生的。因为只有程序在运行阶段才可以new对象。
因为异常的发生就是new异常对象。

2 编译时异常和运行时异常的区别?
编译时异常一般发生的概率比较高。
举个例子:
你看到外面下雨了,倾盆大雨的。
你出门之前会预料到:如果不打伞,我可能会生病(生病是一种异常)
而且这个异常发生的概率很高,所以我们出门之前要拿一把伞。
"拿一把伞"就是对"生病异常"发生之前的一种处理方式。
对于一些发生概率较高的异常,需要在运行之前对其进行预处理。

运行时异常一般发生的概论比较低。
举个例子:
小明走在大街上,可能会天上的飞机轮子砸到。
被飞机轮子砸到也算一种异常。
但是这种异常发生的概率较低。
在出来之前你没必要提前对这种发生概率较低的异常进制预处理。
如果你预处理这种异常,你将活的很累。
假设java中没有对异常进制划分,没有分为:编译时异常和运行时异常,所有的异常都需要在编写程序阶段对其进制预处理,将是怎样的效果呢?
  首先,如果这样的话,程序肯定是绝对的安全的。
  但是程序员编写程序太累,代码到处都是处理异常的代码。

3 编译时异常还有其他名字:
受检异常:CheckedException
受控异常

4 运行时异常还有其它名字:
未受检异常:UnCheckedExcetion
非受控异常

5 再次强调:所有异常都是发生在运行阶段的。

6 Java语言中对异常的处理包括两种方式:
第一种方式:在方法声明的位置上,使用throws关键字。抛给上一极。
谁调用我,我就抛给谁,抛给上一级。

第二种方式:使用try...catch语句进制异常的捕捉。
这件事发生了,谁也不知道,因为我给抓住了。
举个例子:
我是某集团的一个销售员,因为我的失误,导致公司损失了1000元,“损失1000元”这可以看做一个异常发生了,我有两种处理方式
第一种方式:我把这件事告诉我的领导(异常上抛)
第二种方式:我自己掏腰包把这个钱补上(异常的捕捉)

7 注意:Java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续向上抛,抛给了调用者JVM,JVM知道这个异常发生,只有一个结果终止程序的执行。


package com.bjpowernode.javase.exception;

/*
以下代码报错的原因是什么?
   因为doSome()方法声明位置上使用了:throws ClassNotFoundException
   而ClassNotFoundException是编译时异常。必须编写代码时处理,没有处理编译器报错。
*/

public class ExceptionTest04 {
   public static void main(String[] args) {
       // main方法中调用doSome()方法
       // 因为doSome()方法声明位置上有:throws ClassNotFoundException
       // 我们在调用doSome()方法的时候必须对这种异常进行预先的处理。
       // 如果不处理,编译器就报错
       // 编译器报错信息:Unhandled exception: java.lang.ClassNotFoundException
       // doSome();
  }

   /**
    * doSome方法在方法声明的位置上使用了:throws ClassNotFoundException
    * 这个代码表示doSome()方法在执行过程中,有可能会出现ClassNotFoundException异常。
    * 叫做类没有找到异常,这个异常直接父类是:Exception,所以ClassNotFoundException属于编译时异常。
    * @throws ClassNotFoundException
    */
   public void doSome() throws ClassNotFoundException{

  }
}

 

2.4 异常的两种处理方式

package com.bjpowernode.javase.exception;

/*
以下代码报错的原因是什么?
   因为doSome()方法声明位置上使用了:throws ClassNotFoundException
   而ClassNotFoundException是编译时异常。必须编写代码时处理,没有处理编译器报错。
*/

public class ExceptionTest05 {
   // 第一种处理方式:在就去声明的位置上继续使用:throws,来完成异常的继续上抛,抛给调用者
   // 上抛类似于推卸责任。(继续把异常传递给调用者)
   /*
   public static void main(String[] args) throws ClassNotFoundException{
       doSome();
   }
    */

   // 第二种处理方式:try...catch进行捕捉。
   // 捕捉等于反异常拦下来,异常真正的解决了。(调用者是不知道的)
   public static void main(String[] args) {
       try {
           doSome();
      } catch (ClassNotFoundException e) {
           e.printStackTrace();
      }
  }

   public static void doSome() throws ClassNotFoundException{
       System.out.println("doSome");
  }
}

 

2.5 异常捕捉和上报的联合使用

package com.bjpowernode.javase.exception;

/*
处理异常的第一种方式:
   在方法声明的位置上使用throws关键字抛出,谁调用我这个方法我就抛给谁,抛给调用者来处理。
   这种处理异常的态度:上报。

处理异常的第二种方式:
   使用try...catch语句对异常进行捕捉
       这个异常不会上报,自己把这个事处理了
       异常抛到这里就不会继续上抛了

注意:
   只要异常没有捕捉,采用上报的方式,此方法的后续代码不会执行。
   另外需要注意,try语句块中某一行出现异常,该行后面的代码不会执行。
   try...catch捕捉之后后续代码可以执行。


*/

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class ExceptionTest06 {
   // 一般不建议在main方法上使用throws,因为这个异常如果真正的发生了,一定会抛给JVM,JVM只有终止。
   // 异常处理机制的作用就是增强程序的健壮性,怎么能做到,异常发生了也不影响程序的执行。所以
   // 一般main方法中的异常建议使用try...catch进行捕捉,main就不要继续上抛了。
   /*
   public static void main(String[] args) throws FileNotFoundException {
       System.out.println("main begin");
       m1();
       System.out.println("main over");
   }
    */
   public static void main(String[] args) {

       // 100 / 0 这是算术异常,这个异常是运行时异常,你在编译阶段可以处理,也可以不处理。编译器不管。
       // System.out.println(100 / 0);

       System.out.println("main begin");
       try {
           // try尝试
           m1();
           // 以上代码出现异常,直接进入catch语句块中执行。
           System.out.println("hello World");
      } catch(FileNotFoundException e) {
           // 这个分支中可以使用e引用,e引用保存的内存地址是那个new出来异常的内存地址。
           // 捕捉异常之后走的分支
           System.out.println("文件不存在,可能路径错误,也可能该文件被删除了");
           System.out.println(e);  // java.io.FileNotFoundException: C:\Users\xiaoh寺ong\Desktop\学习方法.txt (系统找不到指定的路径。)

      }
       // try...catch把异常抓住之后,这里的代码会继续执行。
       System.out.println("main over");

  }

   private static void m1() throws FileNotFoundException {
       System.out.println("m1 begin");
       m2();
       System.out.println("m1 over");
  }

   // 抛别的异常不行,抛ClassCastException说明你还是没有对FileNotFoundException进行处理
   // private static void m2() throws ClassCastException{
   // 抛FileNotFoundException的父对象IOException,这样是可以的。因为IOException包括FileNotFoundException
   // private static void m2() throws IOException {
   // 这样也可以,因为Exception包括所有的异常。
   // private static void m2() throws Exception{
   // throws后面也可以写多个异常,用逗号隔开。
   // private static void m2() throws ClassCastException, FileNotFoundException{
   private static void m2() throws FileNotFoundException{
       System.out.println("m2 begin");
       // 编译器报错原因是:m3()方法声明位置上有:throws FileNotFoundException
       // 我们在这里调用m3()没有对异常进行预处理,所以编译报错。
       m3();
       System.out.println("m2 over");
  }

   private static void m3() throws FileNotFoundException {
       // 调用SUN JDK中某个类的构造方法
       // 这个类没有接触过,后期IO流的时候就知道了
       // 创建一个输入流对象,该流指向一个文件
       /*
       编译报错的原因是什么?
           第一:这里调用了一个构造方法:FileInputStream(String name)
           第二:这个构造方法的声明位置上有:throws FileNotFoundException
           第三:通过类的继续结构看到:FileNotFoundException父类是IOException,IOException的父类是Exception,最终得知,FileNotFoundException是编译时异常。
           错误原因?编译时异常要求程序员编写程序阶段必须对它进行处理,不处理编译器就会报错。
        */
       // new FileInputStream("\"C:\\Users\\xiaohong\\Desktop\\学习方法.txt\"");

       // 我们采用第一种处理方式:在方法声明的位置上使用throws继续上抛。
       // 一个方法体当中的代码出现异常之后,如果上报的话,此方法结束。
       new FileInputStream("C:\\Users\\xiaohong\\Desktop\\学习方法.txt");

       System.out.println("如果以上代码出异常,这里会执行吗?不会");
  }
}

 

2.6 try...catch深入

package com.bjpowernode.javase.exception;

/*
深入try...catch
   1、catch后面的小括号中的类型可以是具体的异常类型,也可以是该异常类型的父类型。
   2、catch可以写多个,建议catch的时候,精确的一个一个处理,这样有利于程序的调试。
   3、catch写多个的时候,从上到下,必须遵守从小到大。
*/

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class ExceptionTest07 {
   /*
   public static void main(String[] args) throws Exception, FileNotFoundException, NullPointerException {

   }
    */

   public static void main(String[] args) {
       // 编译报错
       /*
       try{
           FileInputStream fis = new FileInputSteam("");
       } catch(NullPointerException e){

       }
        */

       /*
       try{
           FileInputStream fis = new FileInputStream("C:\\Users\\xiaohong\\Desktop\\学习方法.txt");
           System.out.println("以上代码出现问题,这里的代码不再执行");
       } catch(FileNotFoundException e){
           System.out.println("文件不存在");
       }
       System.out.println("hello world");
        */

       /*
       try{
           FileInputStream fis = new FileInputStream("C:\\Users\\xiaohong\\Desktop\\学习方法.txt");
           System.out.println("以上代码出现问题,这里的代码不再执行");
       } catch(IOException e){
           System.out.println("文件不存在");
       }
        */

       /*
       try{
           FileInputStream fis = new FileInputStream("C:\\Users\\xiaohong\\Desktop\\学习方法.txt");
           System.out.println("以上代码出现问题,这里的代码不再执行");
       } catch(Exception e){
           System.out.println("文件不存在");
       }
        */

       /*
       try{
           // 创建输入流
           FileInputStream fis = new FileInputStream("C:\\Users\\xiaohong\\Desktop\\学习方法.txt");
           // 读文件
           fis.read();
       } catch(Exception e){ // 所有的异常都走这个分支
           System.out.println("文件不存在");
       }
        */

       /*
       try{
           // 创建输入流
           FileInputStream fis = new FileInputStream("C:\\Users\\xiaohong\\Desktop\\学习方法.txt");
           // 读文件
           fis.read();
       } catch(FileNotFoundException e){ // 所有的异常都走这个分支
           System.out.println("文件不存在");
       } catch(IOException e){

       }
        */

       // 编译报错
       /*
       try{
           // 创建输入流
           FileInputStream fis = new FileInputStream("C:\\Users\\xiaohong\\Desktop\\学习方法.txt");
           // 读文件
           fis.read();
       } catch(IOException e){ // 所有的异常都走这个分支
           System.out.println("文件不存在");
       } catch(FileNotFoundException e){

       }
        */

       // JDK8的新特性
       try{
           // 创建输入流
           FileInputStream fis = new FileInputStream("C:\\Users\\xiaohong\\Desktop\\学习方法.txt");

           // 进行数学运算
           System.out.println(100 / 0);  // 这个异常是运行时异常,编写程序时可以处理,也可以不处理
      } catch(IOException | ArithmeticException e){  // 所有的异常都走这个分支
           System.out.println("文件不存在?数学异常?");
      }

  }
}

 

2.7 异常对象常用的方法

ExceptionTest08.java

package com.bjpowernode.javase.exception;

/*
异常对象有两个非常重要的方法:
   获取异常简单的描述信息:
       String msg = exception.getMessage();
   打印异常追踪的堆栈信息:
       exception.printStackTrace();

*/

public class ExceptionTest08 {
   public static void main(String[] args) {
       // 这里只是new了异常对象,但是没有将异常对象抛出。JVM会认为这是一个普通java对象
       NullPointerException e = new NullPointerException("空指针异常");
       // 获取异常简单描述信息:这个信息实际上就是构造方法上面String参数。
       String msg = e.getMessage();
       System.out.println(msg);

       // 打印异常堆栈信息
       // java后台打印异常堆栈追踪信息的时候,采用了异步线程的方式打印的。
       e.printStackTrace();
  }
}

ExceptionTest09.java

package com.bjpowernode.javase.exception;

/*
异常对象的两个方法:
   String msg = e.getMessage();
   e.printStackTrace();

我们以后查看异常的追踪信息,我们应该怎么查看,可以快速的调试程序呢?
   异常追踪信息,从上往下一行一行看。
   但是需要注意的是:SUN写的代码就不要看了,主要的问题是出现在自己编写的代码上。
   看包名就知道是自己写的还是SUN公司写的
*/

import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class ExceptionTest09 {
   public static void main(String[] args) {
       try {
           m1();
      } catch (FileNotFoundException e) {
           // 获取异常的简单描述信息
           String msg = e.getMessage();
           System.out.println(msg);

           // 打印异常堆栈追踪信息
           // 在实际的开发中,建议使用这个。养成好习惯
           // 这行代码要写上,不然出问题你也不知道。
           e.printStackTrace();
           /*
               java.io.FileNotFoundException: C:\U地sers\xiaohong\Desktop\学习方法.txt (系统找不到指定的路径。)
               at java.base/java.io.FileInputStream.open0(Native Method)
               at java.base/java.io.FileInputStream.open(FileInputStream.java:219)
               at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157)
               at java.base/java.io.FileInputStream.<init>(FileInputStream.java:112)
               at com.bjpowernode.javase.exception.ExceptionTest09.m3(ExceptionTest09.java:32)
               at com.bjpowernode.javase.exception.ExceptionTest09.m2(ExceptionTest09.java:28)
               at com.bjpowernode.javase.exception.ExceptionTest09.m1(ExceptionTest09.java:24)
               at com.bjpowernode.javase.exception.ExceptionTest09.main(ExceptionTest09.java:15)
               因为32行出现问题导致了28行
               28行出问题导致24行
               24行出问题导致15行。
               应该先查看32行代码,32行是代码错误的根源。
            */
      }

       // 这里程序不耽误执行,很健壮。《服务器不会因为遇到异常而宕机》
  }

   public static void m1() throws FileNotFoundException {
       m2();
  }

   private static void m2() throws FileNotFoundException {
       m3();
  }

   private static void m3() throws FileNotFoundException {
       new FileInputStream("C:\\U地sers\\xiaohong\\Desktop\\学习方法.txt");
  }
}

 

2.8 finally子句的使用

ExceptionTest08

关于try...catch中的finally子句:
   1、在finally子句中的代码是最后执行的,并且是一定会执行的,即使try语句代码出现了异常。
       finally子句必须和try一起出现,不能单独编写。

   2、finally语句通常使用在哪些情况下呢?
       通常在finally语句块中完成资源的释放/关闭。
       因为finally中的代码比较有保障。
       即使try语句块中的代码出现异常,finally中代码也会正常执行。


package com.bjpowernode.javase.exception;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class ExceptionTest10 {
   public static void main(String[] args) {
       FileInputStream fis = null;  // 声明位置放到try外面。这样在finally中才能用。
       try {
           // 创建输入流对象
           fis = new FileInputStream("C:\\Users\\xiaohong\\Desktop\\学习方法.txt");

           String s = null;
           // 这里一定会出现空指针异常
           s.toString();
           System.out.println("hello world");

           // 流使用完需要关闭,因为流是占用资源的
           // 即使以上程序出现异常,流也必须要关闭
           // 放在这里有可能流关不了。
           fis.close();
      } catch (FileNotFoundException e) {
           e.printStackTrace();
      } catch(IOException e){
           e.printStackTrace();
      } finally{
           // 流的关闭放在这里比较保险
           // finally中的代码是一定会执行的。
           // 即使try中出现了异常
           if(fis != null){  // 避免空指针异常
               try {
                   // close()方法有异常,采用捕捉的方式。
                   fis.close();
              } catch (IOException e) {
                   e.printStackTrace();
              }
          }
      }

       System.out.println("hello kitty");
  }
}

ExceptionTest11.java

package com.bjpowernode.javase.exception;

/*
finally语句
   放在finally语句块中的代码是一定会执行的
*/

public class ExceptionTest11 {
   public static void main(String[] args) {
       /*
       try和finally,没有catch可以吗?可以
           try不能单独使用。
           try finally可以联合使用。

       以下代码的执行顺序:
           先执行try...
           再执行finally...
           最后执行return (return语句只要执行方法必然结束)
        */
       try{
           System.out.println("try...");
           return;
      } finally {
           // finally中的语句会执行,能执行到。
           System.out.println("finally...");
      }

       // 这里不能写语句,因为这个代码是无法执行到的。
       // System.out.println("hello world");
  }
}

ExceptionTest12.java

package com.bjpowernode.javase.exception;

/*
finally语句
*/

public class ExceptionTest12 {
   public static void main(String[] args) {
       try{
           System.out.println("try...");
           // 退出JVM
           System.exit(0);  // 退出JVM之后,finally语句中的代码就不执行了。
      } finally {
           // finally中的语句会执行,能执行到。
           System.out.println("finally...");
      }

  }
}

ExceptionTest13.java

package com.bjpowernode.javase.exception;

/*
finally面试题
*/

public class ExceptionTest13 {
   public static void main(String[] args) {
       int result = m();
  }

   /*
   java语法规则(有一些规则是不能破坏的,一旦这么说了,就必须这么做!):
       java中有一条这样的规则:
           方法体中的代码必须遵循自上而下顺序依次逐行执行
       java中还有一条语法规则:
           return语句一旦执行,整个方法必须结束
    */

   public static int m(){
       int i = 100;
       try{
           // 这行代码出现在int i = 100;的下面,所以最终结果必须是返回100
           // return语句还必须保证是最后执行的,一旦执行,整个方法结束。
           return i;
      } finally {
           i++;
      }
  }
}

ExceptionTest14.java

final finally finalize有什么区别?
   final 关键字
       final修饰的类无法继承
       final修饰的方法无法覆盖
       final修饰的变量不能重新赋值
   finally 关键字
       和try一起联合使用。
       finally语句块中的代码是必须执行的
   finalize 标识符
       是一个Object类中的方法名
       这个方法是由垃圾回收器GC负责调用的。


package com.bjpowernode.javase.exception;

public class ExceptionTest14 {
   public static void main(String[] args) {
       // final是一个关键字,表示最终的,不变的
       final int i = 100;
       // i = 200;

       // finally也是一个关键字,和try联合使用,使用在异常处理机制中
       // 在finally语句块中的代码是一定会执行的。
       try{

      } finally {
           System.out.println("finally");
      }

       // finalize()是Object类中的一个方法,作为方法名出现。
       // 所以finalize是标识符。
  }
}

 

2.9 如何自定义异常

MyException.java

package com.bjpowernode.javase.exception;

/*
1、SUN提供的JDK内置的异常肯定是不够用的,实际的开发中,有很多的业务,
这些业务出现异常之后,JDK中都是没有的,和业务挂钩的,那么异常类我们
程序员可以自己定义吗?
   可以。

2、Java中怎么自定义异常呢?
   同步:
       第一步:编写一个类继承Exception或者RuntimeException。
       第二步:提供两个构造方法,一个无参的,一个带有String参数的。
*/

/*
public class MyException extends Exception{ // 编译时异常

}
*/

public class MyException extends RuntimeException{  // 运算时异常
   public MyException(){

  }
   public MyException(String s){
       super(s);
  }
}

ExceptionTest15.java

package com.bjpowernode.javase.exception;

public class ExceptionTest15 {
   public static void main(String[] args) {

       // 创建异常对象(只new了异常对象,并没有手动抛出)
       MyException e = new MyException("用户名不能为空");

       // 打印异常堆栈信息
       e.printStackTrace();

       // 获取异常简单描述信息
       String msg = e.getMessage();
       System.out.println(msg);
  }
}

 

2.10 异常在实际开发中的应用

MyStackOperationException.java

package com.bjpowernode.javase.exception;

/*
栈操作异常: 自定义异常
*/

public class MyStackOperationException extends Exception {  // 编译时异常
   public MyStackOperationException(){}
   public MyStackOperationException(String s){
       super(s);
  }
}

MyStack.java

package com.bjpowernode.javase.exception;

/*
编写程序,使用一维数组,模拟栈数据结构。
要求:
   1、这个栈可以存储java中的任何引用类型的数据。
   2、在栈中提供push方法模拟压栈。(栈满了,要有提示信息)
   3、在栈中提供pop方法模拟弹栈。(栈空了,也有提示信息)
   4、编写测试程序,new栈对象,调用push pop方法来模拟压栈和弹栈的动作。
   5、假设栈的默念初始化容量是10。(请注意无参构造方法的编写方式)
*/

public class MyStack {
   // 向栈当中存储元素,我们这里使用一维数组模拟。存到栈中,就表示存储到数组中。
   // 因为数组是我们学习java的第一个容器。
   // 为什么选择Object类型的数组?因为这个栈可以存储java中任何引用类型的数据

   private Object[] elements;

   // 栈帧,永远指向栈顶部元素
   // 那么这个默认初始值应该是多少。注意:最初的栈是空的,一个元素都没有。
   // private int index = 0; // 如果index采用0,表示栈帧指向了顶部元素的上方。
   private int index;  // 如果index采用-1,表示栈帧指向了顶部元素。

   // 无参数构造方法。默认初始化栈容量10
   public MyStack() {
       // 一维数组动态初始化
       // 默认初始化容量是10
       this.elements = new Object[10];
       this.index = -1;
  }

   // 压栈的方法
   public void push(Object obj) throws MyStackOperationException {
       if(this.index >= this.elements.length-1){
           // 创建异常对象
           // MyStackOperationException e = new MyStackOperationException("压栈失败,栈已满");
           // 手动将异常抛出去
           // throw e; // 这里捕捉没有意义,自己new一个异常,自己捉,没有意义。栈已满这个信息你要传出去

           // 合并(手动抛出异常)
           throw new MyStackOperationException("压栈失败,栈已满");
      }
       // 程序能够走到这里,说明栈没满
       // 向栈中加1个元素,栈帧账上移动一个位置
       this.elements[++index] = obj;
       System.out.println("压栈" + obj + "成功,栈帧指向" + index);
  }

   // 弹栈的方法
   public void pop() throws MyStackOperationException {
       if(index < 0){
           // System.out.println("弹栈失败,栈里已经没有东西了");
           // return;
           throw new MyStackOperationException("弹栈失败,栈里已经没有东西了");
      }
       System.out.println("弹栈" + elements[index--] + "成功,栈帧指向" + index);

  }

   // set和get也许用不上,但是你必须写上,这是规矩。
   public Object[] getElements(){
       return elements;
  }
   public void setElements(Object[] elements){
       this.elements = elements;
  }

   public int getIndex() {
       return index;
  }

   public void setIndex(int index) {
       this.index = index;
  }
}

ExceptionTest16.java

package com.bjpowernode.javase.exception;

// 测试改良之后的MyStack
// 注意:最后这个例子,是异常最重要的案例,必须掌握,自定义异常在实际开发中的应用。
public class ExceptionTest16 {
   public static void main(String[] args) {
       // 创建栈对象
       MyStack stack = new MyStack();
       // 压栈
       try {
           stack.push(new Object());
           stack.push(new Object());
           stack.push(new Object());
           stack.push(new Object());
           stack.push(new Object());
           stack.push(new Object());
           stack.push(new Object());
           stack.push(new Object());
           stack.push(new Object());
           stack.push(new Object());
           stack.push(new Object());
      } catch (MyStackOperationException e) {
           // e.printStackTrace();
           // 输出异常的简单信息
           System.out.println(e.getMessage());
      }

       try {
           stack.pop();
           stack.pop();
           stack.pop();
           stack.pop();
           stack.pop();
           stack.pop();
           stack.pop();
           stack.pop();
           stack.pop();
           stack.pop();
           stack.pop();
      } catch (MyStackOperationException e) {
           // e.printStackTrace();
           System.out.println(e.getMessage());
      }
  }
}

 

2.11 异常与方法覆盖

package com.bjpowernode.javase.exception;

/*
之前在讲解方法覆盖的时候,当时遗留了一个问题?
   重写之后的方法不能比重写之前的方法抛出更多的异常,可以更少。
*/

public class ExceptionTest17 {

}


class Animal {
   public void doSome(){

  }

   public void doOther() throws Exception{

  }
}

class Cat extends Animal{
   // 编译报错。
   /*
   public void doSome() throws Exception{}
    */

   // 编译正常
   /*
   public void doSome() throws RuntimeException{

   }
    */

   // 编译正常
   /*
   public void doOther(){

   }
    */

   // 编译正常
   /*
   public void doOther() throws Exception{

   }
    */

   // 编译正常
   /*
   public void doOther() throws Exception{

   }
    */


}

/*
总结异常中的关键字:
   异常捕捉:
       try
       catch
       finally

   throws 在方法声明位置上使用,表示上报异常信息给调用者
   throw 手动抛出异常
*/
 
 
 
posted @   路走  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示