————————————————
声明:本文是在CSDN
博主「lsqstudy」
的原创文章基础上修改的,遵循CC 4.0 BY-SA版权协议
原文链接:https://blog.csdn.net/PorkBird/article/details/113666542
在使用计算机语言进行项目开发的过程中,即使程序员把代码写得尽善尽美,在系统的运行过程中仍然会遇到一些问题,因为很多问题不是靠代码能够避免的,比如:客户输入数据的格式,读取文件是否存在,网络是否始终保持通畅等等。
1.异常:在Java语言中,将程序执行中发生的不正常情况称为“异常”。(开发过程中的语法错误和逻辑错误不是异常)
2.Java程序在执行过程中所发生的异常事件可分为两类:
(1)Error:Java
虚拟机无法解决的严重问题。如:JVM
系统内部错误、资源耗尽等严重情况。比如:StackOverflowError
和OOM
。
一般不编写针对性的代码进行处理。
/* * Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。 * 比如:StackOverflowError和OOM。 * 一般不编写针对性的代码进行处理。 */ public class ErrorTest { public static void main(String[] args) { //1.栈溢出:java.lang.StackOverflowError // main(args); //2.堆溢出:java.lang.OutOfMemoryError 简称OOM // Integer[] arr = new Integer[1024*1024*1024]; } }
(2)Exception(例外):其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:
3.对于这些错误,一般有两种解决方法:
捕获错误最理想的是在编译期间,但有的错误只有在运行时才会发生。比如:除数为0,数组下标越界等
4.异常(Exception)分类:编译时异常和运行时异常
java.lang.RuntimeException
类及它的子类都是运行时异常。import java.io.File; import java.io.FileInputStream; import java.util.Date; import java.util.Scanner; import org.junit.Test; /* * 一、java异常体系结构 * * java.lang.Throwable * |----java.lang.Error:一般不编写针对性的代码进行处理 * |----java.lang.Exception:可以进行异常处理 * |----编译时异常(checked) * |----IOEXception * |----FileNotFoundException * |----ClassNotFoundException * |----运行时异常(unchecked, RuntimeException) * |----NullPointerException * |----ArrayIndexOutOfBoundsException * |----ClassCastException * |----NumberFormatException * |----InputMismatchException * |----ArithmaticException * 面试题:常见的异常有哪些?举例说明 * */ public class ExceptionTest { // ******************以下是编译时异常*************************** //7.Unhandled exception type xxxException,且编译不通过,打叉 @Test public void test7() { // File file = new File("hello.txt"); // FileInputStream fis = new FileInputStream(file); // // int data = fis.read(); // while(data != -1){ // System.out.print((char)data); // data = fis.read(); // } // // fis.close(); } // ******************以下是运行时异常*************************** // 6.ArithmeticException 算数异常 @Test public void test6() { int a = 10; int b = 0; System.out.println(a / b); } // 5.InputMismatchException 输入不匹配 @Test public void test5() { Scanner scanner = new Scanner(System.in); int score = scanner.nextInt(); System.out.println(score); scanner.close(); } // 4.NumberFormatException 数值转换异常 @Test public void test4() { String str = "123"; str = "abc"; int num = Integer.parseInt(str);//String类型---> 基本数据类型、包装类 } // 3.ClassCastException 类型转换异常 @Test public void test3() { Object obj = new Date(); String str = (String)obj; } // 2.IndexOutOfBoundsException @Test public void test2() { //ArrayIndexOutOfBoundsException // int[] arr = new int[10]; // System.out.println(arr[10]); //StringIndexOutOfBoundsException // String str = "abc"; // System.out.println(str.charAt(3)); } // 1.NullPointerException @Test public void test1() { // int[] arr = null; // System.out.println(arr[3]);//输出数组角标为三的元素 // String str = "abc"; // str = null; // System.out.println(str.charAt(0)); } }
注意:异常一旦被抛出以后,其后的代码就不再执行!
在编写程序时,经常要在可能出现错误的地方加上检测的代码,如进行x/y运算时,要检测分母为0,数据为空,输入的不是数据而是字符等。过多的if-else分支会导致程序的代码加长、臃肿,可读性差。因此采用异常处理机制。
1.Java异常处理:
Java采用的异常处理机制,是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅,并易于维护。
方式一:try-catch-finally
方式二:throws + 异常类型
2.Java异常处理的方式: try-catch-finally
(1)try
(2)catch(Exceptiontype e
)
在catch语句块中是对异常对象进行处理的代码。每个try语句块可以伴随一个或多个catch语句,用于处理可能产生的不同类型的异常对象。
捕获异常的有关信息:与其它对象一样,可以访问一个异常对象的成员变量或调用它的方法。
import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import org.junit.Test; /* * 一、异常的处理:抓抛模型 * * 1.过程一:“抛”:程序在正常执行过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象 * 并将此对象抛出。 * 一旦抛出对象以后,其后的代码就不再执行。(所以在抛出异常前,会把finally里面的内容执行了) * * 关于异常对象的产生,即“抛”的两种情况:(1)系统自动生成的异常对象 * (2)手动地生成一个异常对象,并抛出(throw) * * 2.过程二:“抓”:可以理解为异常的处理方式:① try-catch-finally ② throws * * 二、try-catch-finally的使用 * * try{ * //可能出现异常的代码 * }catch(异常类型1 变量名1){ * //处理异常的方式1 * }catch(异常类型2 变量名2){ * //处理异常的方式2 * }catch(异常类型3 变量名3){ * //处理异常的方式3 * } * ... * finally{ * //一定会执行的代码 * } * * 说明: * 1.finally是可选的。 * 2.使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象 * 的类型,去catch中进行匹配。 * 3.一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前的 * try-catch结构(在没有写finally的情况)。继续执行其后的代码。 * 4.catch中的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓。 * catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面。否则,报错 * 5.常用的异常对象处理的方式: ① String getMessage() ② printStackTrace() * 6.在try结构中声明的变量,再出了try结构以后,就不能再被调用。 * 7.try-catch-finally结构可以嵌套 * * 体会1:使用try-catch-finally处理编译时异常,使得程序在编译时就不再报错,但是运行时仍可能报错。 * 相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现。 * * 体会2:开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了。 * 针对于编译时异常,我们说一定要考虑异常的处理。 */ public class ExceptionTest1 { //编译时异常 @Test public void test2(){ try{ File file = new File("hello.txt"); FileInputStream fis = new FileInputStream(file); int data = fis.read(); while(data != -1){ System.out.print((char)data); data = fis.read(); } fis.close(); }catch(FileNotFoundException e){ e.printStackTrace(); }catch(IOException e){ e.printStackTrace(); } } //运行时异常 @Test public void test1(){ String str = "123"; str = "abc"; int num = 0;//局部变量,没有初始化值,在调用之前一定要显示的赋值 try{ num = Integer.parseInt(str); System.out.println("hello-----1");//不会执行 }catch(NumberFormatException e){ System.out.println("出现数值转换异常了,不要着急...."); //方式1.String getMessage(): // System.out.println(e.getMessage()); //方式2.printStackTrace(): // e.printStackTrace(); }catch(NullPointerException e){ System.out.println("出现空指针异常了,不要着急...."); }catch(Exception e){ System.out.println("出现异常了,不要着急...."); } System.out.println(num);//num声明在try结构外面了,可以调用。 System.out.println("hello----2"); } }
import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import org.junit.Test; /* * try-catch-finally中finally的使用: * * 1.finally是可选的。 * 2.finally中声明的是一定会被执行的代码。即使catch中又出现异常了,或者try中有return语句, * 或者catch中有return语句等情况。 * 3.像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动的回收的,我们需要自己手 * 动的进行资源的释放。此时的资源释放,就需要声明在finally中。 * */ public class FinallyTest { //3.知识点3的例子 @Test public void test2() { 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) { e.printStackTrace(); } finally { try { if (fis != null) fis.close(); } catch (IOException e) { e.printStackTrace(); } } } //2.catch中有return语句 @Test public void testMethod() { int num = method(); System.out.println(num); } public int method() { try { int[] arr = new int[10]; System.out.println(arr[10]);//出现了异常,直接去catch中匹配 return 1; } catch (ArrayIndexOutOfBoundsException e) { e.printStackTrace(); return 2; } finally { System.out.println("我一定会被执行"); return 3; //如果此时有return 3,那么就执行这个语句 } } //1.catch中又出现异常 @Test public void test1() { try { int a = 10; int b = 0; System.out.println(a / b); } catch (ArithmeticException e) { // e.printStackTrace(); int[] arr = new int[10]; System.out.println(arr[10]); } catch (Exception e) { e.printStackTrace(); } // System.out.println("我好帅呀~~~"); finally { System.out.println("我好帅呀~~~"); } } }
/* * 异常处理的方式二:throws + 异常类型 * * 1. "throws + 异常类型"写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。 * 一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后面的异常 * 类型时,就会被抛出。异常代码后续的代码,就不再执行! * * 关于异常对象的产生:① 系统自动生成的异常对象 * ② 手动生成一个异常对象,并抛出(throw) * * 2. 体会:try-catch-finally:真正的将异常给处理掉了。 * throws的方式只是将异常抛给了方法的调用者。 并没有真正将异常处理掉。 * * 3. 开发中如何选择使用try-catch-finally 还是使用throws? * 3.1 如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果 * 子类重写的方法中有异常,必须使用try-catch-finally方式处理。 * 3.2 执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws * 的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方式进行处理。 * */ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; public class ExceptionTest2 { public static void main(String[] args) { try { method2(); } catch (IOException e) { e.printStackTrace(); } } public static void method2() throws IOException{ method1(); } public static void method1() throws FileNotFoundException,IOException{ File file = new File("hello.txt"); FileInputStream fis = new FileInputStream(file); int data = fis.read(); while(data != -1){ System.out.print((char)data); data = fis.read(); } fis.close(); System.out.println("hahaha!");//如果前面的代码出现异常,此句代码不再执行 } }
import java.io.FileNotFoundException; import java.io.IOException; /* * 方法重写的规则之一: * 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型 */ public class OverrideTest { public static void main(String[] args) { OverrideTest test = new OverrideTest(); test.display(new SubClass()); } public void display(SuperClass s){ try { s.method(); } catch (IOException e) { e.printStackTrace(); } } } class SuperClass{ public void method() throws IOException{ } } class SubClass extends SuperClass{ public void method()throws FileNotFoundException{//子类中重写方法抛出的异常类型不大于父类,也可以不抛出异常 } }
1.Java异常类对象除在程序执行过程中出现异常时由系统自动生成并抛出,也可根据需要使用人工创建并抛出。
2.面试题:throw 和 throws 的区别:
//**************************1.手动抛出RuntimeException异常****************************** //因为抛出的是运行时异常,可以不进行处理 public class StudentTest1 { public static void main(String[] args) { Student1 s = new Student1(); s.regist(-1001); } } class Student1{ private int id; public void regist(int id){ if(id > 0){ this.id = id; }else{ //手动抛出异常 throw new RuntimeException("您输入的数据非法呀"); } } } //**************************2.手动抛出Exception异常****************************** //手动抛出Exception异常时,需要进行处理 public class StudentTest { public static void main(String[] args) { try { Student s = new Student(); s.regist(-1001); } catch (Exception e) { e.printStackTrace(); } } } class Student{ private int id; public void regist(int id) throws Exception {//1.这里throws体现的是异常处理 if(id > 0){ this.id = id; }else{ //手动抛出异常 throw new Exception("你输入的数据非法啊啊");//2.这里throw体现的是:生成一个异常对象 } } }
/* * 如何自定义异常类? * 1.继承于现有的异常结构:RuntimeException 、Exception * 2.提供全局常量:serialVersionUID * 3.提供重载的构造器 * * 注意:如果自定义异常类继承于Exception,则使用 自定义的异常类 时,就一定要进行处理 */ public class MyException extends RuntimeException{ static final long serialVersionUID = -1034897193246939L;//可以理解为对类的标识 public MyException(){ } public MyException(String msg){ super(msg); } }
自定义异常类之后可以使用(两个类在同一个包下):
//因为抛出的异常MyException继承于RuntimeException,可以不进行处理 public class StudentTest1 { public static void main(String[] args) { Student1 s = new Student1(); s.regist(-1001); } } class Student1{ private int id; public void regist(int id){ if(id > 0){ this.id = id; }else{ //手动抛出异常 throw new MyException("不能输入负数");//使用自定义的异常类 } } }
练习1
public class ReturnExceptionDemo { static void methodA() { try { System.out.println("进入方法A");//第一句执行 throw new RuntimeException("制造异常");//第三句执行 } finally { System.out.println("用A方法的finally");//第二句执行 } } static void methodB() { try { System.out.println("进入方法B");//第一句执行 return;//第三句,结束 } finally { System.out.println("调用B方法的finally");//第二句执行 } } public static void main(String[] args) { try { methodA(); } catch (Exception e) { System.out.println(e.getMessage()); } methodB(); } }
练习2:综合题,比较好。
/* * 编写应用程序EcmDef.java,接收命令行的两个参数, * 要求不能输入负数,计算两数相除。 * 对 数 据 类 型 不 一 致(NumberFormatException)、 * 缺 少 命 令 行 参 数(ArrayIndexOutOfBoundsException、 * 除0(ArithmeticException)及输入负数(EcDef自定义的异常)进行异常处理。 * * 提示: * (1)在主类(EcmDef)中定义异常方法(ecm)完成两数相除功能。 * (2)在main()方法中使用异常处理语句进行异常处理。 * (3)在程序中,自定义对应输入负数的异常类(EcDef)。 * (4)运行时接受参数java EcmDef20 10//args[0]=“20”args[1]=“10” * (5)Interger类的static方法parseInt(Strings)将字符串转换成对应的int值。 * 如:int a=Interger.parseInt(“314”);//a=314; */ public class EcmDef { public static void main(String[] args) { try { int i = Integer.parseInt(args[0]); int j = Integer.parseInt(args[1]); int result = ecm(i,j); System.out.println(result); } catch (NumberFormatException e) { System.out.println("数据类型不一致"); }catch (ArrayIndexOutOfBoundsException e){ System.out.println("缺少命令行参数"); }catch (ArithmeticException e){ System.out.println("除0"); }catch (EcDef e) { System.out.println(e.getMessage()); } } public static int ecm(int i, int j) throws EcDef{ if(i < 0 || j < 0){ throw new EcDef("分子或分母为负数了!"); } return i / j; } }
练习2的自定义异常类——
EcDef
//自定义异常类 public class EcDef extends Exception { static final long serialVersionUID = -33875164229948L; public EcDef() { } public EcDef(String msg) { super(msg); } }
总结:异常处理5个关键字