Java学习——Java异常处理(上)
文章目录
一、异常
概念:
异常(Exception)是在程序中导致程序中断运行的一种指令流。
在Java中程序的错误主要是语法错误和语义错误,一个程序在编译和运行时出现的错误我们统一称之为异常,它是JVM(虚拟机)通知你的一种方式,通过这种方式,JVM让你知道,你(开发人员)已经犯了个错误,现在有一个机会来修改它。
在Java中所有的异常都有一个基类,叫做Exception。
用个简单的除法运算,举例:
public class Demo2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("输入a的值:");
int a = sc.nextInt();
System.out.println("输入b的值:");
int b = sc.nextInt();
System.out.println(a/b);
}
}
当我们将 b 的值输入为0时,从图中可以看到是在main方法中触发了算术异常,由于被除数为0的原因导致( / by zero):
而上面出现的算术异常便是异常中的一种。
(1). 异常与错误
错误概念:
Error(错误):它指的是一个合理的应用程序不能截获的严重的问题。大多数都是反常的情况。”,错误是JVM的一个故障(虽然它可以是任何系统级的服务)。所以,错误是很难处理的,一般的开发人员(当然不是你)是无法处理这些错误的。比如内存溢出。
和异常一样,在Java中用错误类来表示错误,不同的错误类代表了不同的错误。 但是在Java中所有的错误都有一个基类,叫做Error。
异常与错误的关系与区别:
在Java中,异常和错误同属于一个类:Throwable。异常和错误都是Java异常处理重要的类,且各自都包含大量子类。
区别:
Error都是继承自父类java.lang.Error,而Exception都继承自java.lang.Exception。
异常可以捕获,错误不可以被捕获。
异常和错误最本质的区别就是异常能被开发人员处理而错误通常是系统来自带的,一般无法处理也不需要我们程序员来处理。
(2). 异常分类
打开异常Exception 的API文档查看,可以看到Exception的父类Trowable 与众多子类,说明异常被分为多种异常。
这里我们将异常大致分为两类:runtime exception(执行异常)和checked exception(检查)
检查异常,写出来的代码被标红,例如,这里表示未导入Scanner包:
checked exception,是除 RuntimeException以外的异常类。也就是我们经常遇到的IO异常,以及SQL异常都是这种异常。对于这种异常,JAVA编译器强制要求我们必需对出现的这些异常进行catch。所以,面对这种异常不管我们是否愿意,只能自己去写一大堆catch块去处理可能的异常。
runtime exception,也称运行时异常,运行时异常是继承RuntimeException类的直接或间接子类。运行时异常往往是程序员所犯错误 导致的,健壮的程序不应该发生运行时异常。 它们的共同特点是:编译器不检查这类异常是 否进行了处理,也就是对于这类异常不捕获也 不抛出,程序也可以编译通过。由于没有进行 异常处理,一旦运行时异常发生就会导致程序 的终止,这是用户不希望看到的。
两种异常都是可以进行处理的。
二、 异常处理
当前方法有能力解决,则捕获异常进行处理;没有能力解决,则抛出给上层调用方法处理。如果上层调用方法还无力解决,则继续抛给它的上层调用方法,异常就是这样向上传递直到有方法处理它,如果所有的方法都没有处理该异常,那么JVM会终止程序运行。
(1). 捕获异常
捕获异常是通过try-catch语句实现的。
格式:
try{
// 有可能发生异常的代码段
}catch(异常类型1 对象名1){
// 异常的处理操作
}catch(异常类型2 对象名2){
// 异常的处理操作
} ...
finally{
// 异常的统一出口
}
try+catch 的处理流程
- 一旦产生异常,则系统会自动产生一个异常类的实例化对象。
- 那么,此时如果异常发生在try语句,则会自动找到匹配的catch语句执行,如果没有在try语句中,则会将异 常抛出.
- 所有的catch根据方法的参数匹配异常类的实例化对象,如果匹配成功,则表示由此catch进行处理。
举例:
(1). 两种异常分开处理
这段代码可能出现两个异常,输入时不为int型与被除数为0时:
import java.util.Scanner;
public class Demo2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("输入num的值:");
int num = sc.nextInt();
System.out.println("10除以的值为:" + 10/num);
}
}
我们使用try + catch语句进行处理:
第一个异常,输入异常 InputMismatchException,不能输入除 int 类型外的数据。将这个异常作为 catch 语句中的类型异常。
import java.util.InputMismatchException;
import java.util.Scanner;
public class Demo2 {
public static void main(String[] args) {
int num = hello();
System.out.println("num的值为:" + num);
}
public static int hello(){
Scanner sc = new Scanner(System.in);
System.out.println("输入a的值:");
try {
int a = sc.nextInt();
return a;
}catch(InputMismatchException e1) { //这个是需要导包的
System.out.println("\n请输入数字!!!");
return hello(); //发现异常,我们便返回并再次运行此方法
}
}
}
第二种类型异常,算术异常ArithmeticException,不能将被除数置为0。将这个异常作为 catch 语句中的类型异常,这个异常类型不用导包。
改过后的代码:
import java.util.InputMismatchException;
import java.util.Scanner;
public class Demo2 {
public static void main(String[] args) {
int num = hello();
System.out.println("num的值为:" + num);
}
public static int hello(){
Scanner sc = new Scanner(System.in);
System.out.println("输入a的值:");
try {
int a = sc.nextInt();
int b = 10/a;
return b;
}catch(InputMismatchException e1) {
System.out.println("\n请输入数字!!!");
return hello();
}catch(ArithmeticException e2) { //当输入数据a为0时,发生算术异常并捕获
System.out.println("\n被除数不能为0!!!");
return hello();
}
}
}
(2). 两种异常一并处理(很少用)
将两种异常合并处理:
import java.util.InputMismatchException;
import java.util.Scanner;
public class Demo2 {
public static void main(String[] args) {
int num = hello();
System.out.println("num的值为:" + num);
}
public static int hello(){
Scanner sc = new Scanner(System.in);
System.out.println("输入a的值:");
try {
int a = sc.nextInt();
int b = 10/a;
return b;
}catch(InputMismatchException | ArithmeticException e) {
System.out.println("\n输入有误!!!");
return hello();
}
}
}
(3). 使用异常的父类作为异常类型(常用)
无论算术异常或是输入异常等均属于异常 Exception
import java.util.Scanner;
public class Demo2 {
public static void main(String[] args) {
int num = hello();
System.out.println("num的值为:" + num);
}
public static int hello(){
Scanner sc = new Scanner(System.in);
System.out.println("输入a的值:");
try {
int a = sc.nextInt();
int b = 10/a;
return b;
}catch(Exception e) { //表现Java的多态性,只有出现异常,就会被捕获
System.out.println("\n输入有误!!!");
return hello();
}
}
}
三、try catch中的finally
在进行异常的处理之后,在异常的处理格式中还有一个finally语句,那么此语句将作为异常的统一出口,不管是否产生了异常,最终都 必然 会执行此段代码。
我们将 catch 语句中异常处理去掉。
public class Demo2 {
public static void main(String[] args) {
int num = hello();
System.out.println("num的值为:" + num);
}
public static int hello(){
Scanner sc = new Scanner(System.in);
System.out.println("输入a的值:");
try {
int a = sc.nextInt();
int b = 10/a;
return b;
}catch(Exception e) {
}finally {
System.out.println("\nfinally语句中的内容必然执行!!!");
return 0;
}
}
}
可以看到无论 try 语句中是否发生异常,finally中的语句时必然会执行的。返回了0,并赋值给了num。
finally特殊用法:
public class Demo2 {
public static void main(String[] args) {
Person p1 = test();
System.out.println(p1.age);
}
public static Person test() {
Person p = new Person();
try {
p.age = 18;
return p;
}catch(Exception e) {
return null;
}finally {
p.age = 28;
}
}
static class Person { //定义一个静态内部类
int age;
}
}
因为 finally 语句执行的必然性,所以即便 try 语句中赋值为18,但在 finally 中age的值依然会发生改变。
public class Demo2 {
public static void main(String[] args) {
int a = test();
System.out.println(a);
}
public static int test() {
int a = 18;
try {
return a;
}catch(Exception e) {
return 0;
}finally {
a = 28;
}
}
}
然而在这个例子中,finally 语句中的语句执行a被赋值为28后,为何没有表输出控制台中呢?
这个例子中我们看到,try 语句中的 return 返回值的操作过程是这样的: return 语句 返回时,会备份(大概这个意思) a = 18 这个语句中,等号右边的值:18;将其作为 retrun 的返回值。
而第一个例子中 return p ,这个语句中只是备份为p对象所指的类开辟的内存空间地址,当内存中age的值被改变后;return 语句将p对象所指的地址返回给了main方法中的p1,将对象p1所对应地址的内存空间中age值输出出来,即是已经发生改变的值:28.