什么是异常?
异常其实说白了,就是程序中可能出现的问题,Jvm告诉你,你去解决。
其实从1995年java就在一直收集各种各样的异常问题,java把这一个个异常变成对象。jvm读到异常,创建一个对象,然后返回给你异常信息,异常信息就是报错红线
如图,这就是一个异常,main指的是顺序走下来在你的主线程中出现了异常,InputMismatchException就是异常封装的对象,灰色字体是工具类的资源路径,蓝色字体是你程序中发生异常的位置,我们检查异常一定要先看蓝色的部分。
那么这个异常是怎么产生的呢?
public static void main(String[] args){
haha();
System.out.println("程序执行完毕 , 正常结束");
}
private static void haha() {
Scanner input = new Scanner(System.in);
System.out.println("请输入一个数字");
int x = input.nextInt();
System.out.println("请再输入一个数字");
int y = input.nextInt();
System.out.println(x / y);
System.out.println("处理完毕");
System.out.println("亲, 除数不能为0");
}
原因:我们输入的时候没有输入数字而是输入了字母,所以导致了异常的发生。这时JVm给我们抛出了一个异常,如果我不想继续抛出则需要自己处理异常,这时就需要用到try catch
处理异常
如果要想对异常进行处理,则必须采用标准的处理格式,处理格式语法如下: try{
// 有可能发生异常的代码段
}
catch(异常类型1 对象名1){
// 异常的处理操作
}catch(异常类型2 对象名2){
// 异常的处理操作
} …
finally{
// 异常的统一出口
}
其实你把try catch 可以理解为if语句如果try发现异常就执行 catch(里面是对意料到的异常处理操作)
try+catch处理流程
1.一旦发生一个异常,则系统会自动产生一个异常类的实例化对象。
2.那么,如果异常发生在try语句,则就会自动去找到匹配的catch语句去执行。如果没有发生在try语句中,则会将异常抛出。
3.所有的catch根据方法的参数匹配异常类的实例化对象,如果匹配成功,则表示由此catch进行处理。
异常处理格式
多异常捕获的注意点:
1、 捕获更粗的异常不能放在捕获更细的异常之前。
2、 如果为了方便,则可以将所有的异常都使用Exception进行捕获。
(图2)
特殊的多异常捕获写法 catch(异常类型1 |异常类型2 对象名){
//表示此块用于处理异常类型1 和 异常类型2 的异常信息
}.
/**
* 处理多异常的格式 1
*/
public class Demo2 {
public static void main(String[] args){
haha();
System.out.println("程序执行完毕 , 正常结束");
}
private static void haha() {
try {
Scanner input = new Scanner(System.in);
System.out.println("请输入一个数字");
int x = input.nextInt();
System.out.println("请再输入一个数字");
int y = input.nextInt();
System.out.println(x / y);
System.out.println("处理完毕");
}catch(InputMismatchException e){
System.out.println("必须输入数字啊, 帅哥");
}catch(ArithmeticException e){
System.out.println("除数不能为0啊 , 帅哥");
}
}
}
如上图,如果x/y发生了异常,那么下面那句话就不会打印输出
package com.java.demo1;
import java.util.InputMismatchException;
import java.util.Scanner;
/**
* 处理多异常的格式 2 了解
*/
public class Demo3 {
public static void main(String[] args){
haha();
System.out.println("程序执行完毕 , 正常结束");
}
private static void haha() {
try {
Scanner input = new Scanner(System.in);
System.out.println("请输入一个数字");
int x = input.nextInt();
System.out.println("请再输入一个数字");
int y = input.nextInt();
System.out.println(x / y);
System.out.println("处理完毕");
}catch(InputMismatchException|ArithmeticException e){
System.out.println("输入有误");
}
}
}
package com.java.demo1;
import java.util.InputMismatchException;
import java.util.Scanner;
/**
* 处理多异常的格式 3 常用
*/
public class Demo4 {
public static void main(String[] args){
haha();
System.out.println("程序执行完毕 , 正常结束");
}
private static void haha() {
try {
Scanner input = new Scanner(System.in);
System.out.println("请输入一个数字");
int x = input.nextInt();
System.out.println("请再输入一个数字");
int y = input.nextInt();
System.out.println(x / y);
System.out.println("处理完毕");
}catch(Exception e){//多态
System.out.println("输入有误");
}finally {
//必然执行的异常统一处理出口
//无论是否发生异常, finally必然执行.
System.out.println("213123");
}
}
}
finally
在进行异常的处理之后,在异常的处理格式中还有一个finally语句,那么此语句将作为异常的统一出口,不管是否产生 了异常,最终都要执行此段代码。但也有几个特殊情况如电脑断电或者程序关闭,下面我们展示第二种
public static void main(String[] args) {
haha();
}
public static void haha(){
try{
int a = 10;
int b = 0;
System.out.println(a/b);
}catch(Exception e){
//退出JVM
System.out.println("出现了异常");
System.exit(0);
}finally {
System.out.println("锄禾日当午,汗滴禾下土");
}
}
如上图,程序执行完毕并没有打印“锄禾日当午,汗滴禾下土”这是为什么呢?
当我们try中的语句抛出异常时,catch进行捕获然后执行到System.exit(0)时程序就关闭了,所以我们无法执行finally里面的语句,但是前面的 System.out.println(“出现了异常”); 可以打印
package com.java.demo1;
public class Demo5 {
public static void main(String[] args) {
haha();
}
public static void haha(){
try{
System.out.println("1");
System.out.println("2");
System.out.println("3");
System.out.println("4");
return;
}catch(Exception e){
}finally {
System.out.println("锄禾日当午,汗滴禾下土");
}
}
}
上面这段代码的finally语句会不会执行呢?
答案是会的,当try中的四条输出语句打印完毕后,程序并没有立即执行return从而结束整个方法而是等待finally执行完毕才执行return。
下面还有几个例子
如上图,结果age为28,知道了执行return之前,还要等待finally执行完毕所以age被重新赋值为28.(注意:由于是new的对象Person所以在栈中存在Person对象的引用地址,return在等待时会赋值一份地址出来(此时的p是引用类型的),我们将来会return复制的内容,指向引用的对象,所以此时finally是顺着地址改变的对象值)
下面还有一种情况
如上图,此时return的值为10,那么为什么呢?
根据之前的描述我们知道了,return返回的是复制的备份,那么由于a是变量所以我们复制的就是a本身的值(a是基本类型),finally没有改变a的值。
异常体系结构
异常指的是Exception , Exception类, 在Java中存在一个父类Throwable(可能的抛出) Throwable存在两个子类:
1.Error:表示的是错误,是JVM发出的错误操作,只能尽量避免,无法用代码处理。(系统觉得你处理不了的)
2.Exception:一般表示所有程序中的错误,所以一般在程序中将进行try…catch的处理。(系统觉得你可以处理的)
如图我们看到在Exception类中下面还有两大类一个是受检查异常,还有一个是非受检查异常他们是什么意思呢?
受检查异常(非运行时异常):你的代码写出来就漂红的就异常
非受检查异常(运行时异常):你编写的时候看不出来,运行的时候有可能发生异常
throws
在程序中异常的基本处理已经掌握了,但是随异常一起的还有一个称为throws关键字,此关键字主要在方法的声明上使 用,表示方法中不处理异常,而交给调用处处理。
格式:返回值 方法名称()throws Exception{ }
public static void shutdown(String text) throws IOException {
Runtime.getRuntime().exec(text);
}
如图所示,当执编译exec方法时,idea提示飘红,这时我们需要抛出一个异常,因为这是我们不能用try catch去解决的异常,换言之如果程序发生的异常我们无法解决,则需要使用throws抛给方法调用者。反之异常我们可以解决并且我们希望发生的异常不影响程序执行,那么我们就使用try catch处理异常
原则:如果该功能内部可以将问题处理,用 try,如果处理不了,交由调用者处理,这是用 throws
区别:
当前程序需要继续运行就 try
当前程序不需要继续运行就throws
举例:
感冒了就自己吃点药就好了,try
吃了好几天药都没好,结果得了 H7N9,那就得throws到医院去对人进行治疗
如果医院没有特效药,就变成 Error 了
throw
throw关键字表示在程序中人为的抛出一个异常,因为从异常处理机制来看,所有的异常一旦产生之后,实际上抛出 的就是一个异常类的实例化对象,那么此对象也可以由throw直接抛出。 代码: throw new Exception(“抛着玩的。”) ;
throw通常是程序员手动抛出的异常,那么为什么这样做呢?
比如你写一个方法别的程序需要调用,那么他可能调用你的方法会发生异常,这个时候你就需要吧这个异常主动的抛出去,让他去处理。是不是和throws很像,但其实还是有很大区别的。
区别1:
throws:
跟在方法声明后面,后面跟的是异常类名
throw:
用在方法体内,后面跟的是异常类对象名
public static void method() throws ArithmeticException {// 跟在方法声明后面,后面跟的是异常类名
int a=10;
int b=0;
if(b==0) {
throw new ArithmeticException();用在方法体内,后面跟的是异常类对象名
}else {
System.out.println(a/b);
}
}
}
区别2:
throws:
可以跟多个异常类名,用逗号隔开
throw:
只能抛出一个异常对象名
public static void method() throws ArithmeticException,Exception {//跟多个异常类名,用逗号隔开
int a=10;
int b=0;
if(b==0) {
throw new ArithmeticException();// 只能抛出一个异常对象名
}else {
System.out.println(a/b);
}
}
}
区别3:
throws:
表示抛出异常,由该方法的调用者来处理
throw:
表示抛出异常,由该方法体内的语句来处理
public class throwandthrows {
public static void main(String[] args) {
try {
method();//由该方法的调用者来处理
}catch (ArithmeticException e) {
e.printStackTrace();
}
}
public static void method() throws ArithmeticException {
int a=10;
int b=0;
if(b==0) {
throw new ArithmeticException();//由该方法体内的语句来处理
}else {
System.out.println(a/b);
}
}
}
区别4:
throws:
throws表示有出现异常的可能性,并不一定出现这些异常
throw:
throw则是抛出了异常,执行throw一定出现了某种异常
我们向上面例子代码里throws一个IndexOutOfBoundsException异常,编译发现并没有报错,这就体现了throws表示有出现异常的可能性
public class throwandthrows {
public static void main(String[] args) {
try {
method();
}catch (ArithmeticException e) {
e.printStackTrace();
}
}
public static void method() throws ArithmeticException,IndexOutOfBoundsException {
int a=10;
int b=0;
if(b==0) {
throw new ArithmeticException();
}else {
System.out.println(a/b);
}
}
}
RuntimeExcepion与Exception的区别
注意观察如下方法的源码:
Integer类: public static int parseInt(String text)throws NumberFormatException
此方法抛出了异常, 但是使用时却不需要进行try。。。catch捕获处理,原因: 因为NumberFormatException并不是Exception的直接子类,而是RuntimeException的子类,只要是 RuntimeException的子类,则表示程序在操作的时候可以不必使用try…catch进行处理,如果有异常发生,则由JVM进 行处理。当然,也可以通过try catch处理。
自定义异常(了解)
虽然从1995年,java汇总了许许多多的异常并封装成了对象告诉我们。但我们在实际开发中,有一些异常需要我们人为的去编写,非共性的异常。我们自定义异常时需要注意,如果我们编写的异常继承了RuntimeException运行时异常(非受检异常)你抛出异常那么别人调用的时候出现异常不会飘红,如果非RuntimeException的异常(受检异常,直接继承Exception也算),你抛出了这个异常那么别人调用的时候代码就会飘红。(受检查的异常必须要抛出去或者trycatch,别人调用的时候要么抛出去要么try catch)
RuntimeException自定义异常
public class AgeRuntimeException extends RuntimeException{
public AgeRuntimeException(String message) {
super(message);
}
}
如上图我们编写了一个异常类继承RuntimeException,调用父类的构造方法,用来自定义抛出异常的信息,大部分情况下我们也是这么做的。
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if(age<0 || age>180){
AgeRuntimeException e = new AgeRuntimeException("年龄不合理");
throw e;
}else{
this.age = age;
}
}
}
如上图我们在创建自定义异常类对象的时候就传入了要抛出异常的信息。
public class Demo {
public static void main(String[] args) {
Person p = new Person();
p.setAge(-1);
}
}
如上图如果我们年龄赋值不合规范那么结果就是:
红字部分就是我们传入的异常信息。
异常处理常见面试题
- try-catch-finally 中哪个部分可以省略?
答: catch和finally可以省略其中一个 , catch和finally不能同时省略 注意:格式上允许省略catch块, 但是发生异常时就不会捕获异常了,我们在开发中也不会这样去写代码. - try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗? 答:finally中的代码会执行 详解:执行流程: 1. 先计算返回值, 并将返回值存储起来, 等待返回 2. 执行finally代码块 3. 将之前存储的返回值, 返回出去; 需注意:1. 返回值是在finally运算之前就确定了,并且缓存了,不管finally对该值做任何的改变,返回的值都不 会改变 2. finally代码中不建议包含return,因为程序会在上述的流程中提前退出,也就是说返回的值不是try或 catch中的值3. 如果在try或catch中停止了JVM,则finally不会执行.例如停电- -, 或通过如下代码退出 JVM:System.exit(0);