引入
说到异常先写一个demo
public class Introduce {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入被除数:");
int divisor = scanner.nextInt();
System.out.print("请输入除数:");
int dividend = scanner.nextInt();
System.out.println(div(divisor, dividend));
System.out.println("继续执行----------------");
}
public static int div(int divisor, int dividend) {
return divisor / dividend;
}
}
正常情况下没问题,比如
万一手欠把除数输成0,那么执行结果就变成这样
或者:
像上面那样在程序运行期间出现了不正常的情况导致程序无法正常运行就称作为异常,出现异常之后的代码也不能执行!
那如何解决呢?第一种方案是可以用if-else来解决
public class Solution {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入被除数:");
if (scanner.hasNextInt()) {
int divisor = scanner.nextInt();
System.out.print("请输入除数:");
if (scanner.hasNextInt()) {
int dividend = scanner.nextInt();
if (dividend != 0) {
System.out.println(div(divisor, dividend));
} else {
System.out.println("除数不能为0");
}
} else {
System.out.println("录入的不是int数据!");
}
} else {
System.out.println("录入的不是int数据!");
}
System.out.println("继续执行----------------");
}
public static int div(int divisor, int dividend) {
return divisor / dividend;
}
}
try-catch语句
看完之后是不是感觉这个if嵌套代码有些臃肿,业务代码也与处理异常的代码混在一起;可读性还很差;
最重要的是程序员无法想到所有可能发生异常情况。基于if-else处理机制的缺点,java提供了一套处理异常机制
public class Solution2 {
public static void main(String[] args) {
try {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入被除数:");
int divisor = scanner.nextInt();
System.out.print("请输入除数:");
int dividend = scanner.nextInt();
System.out.println(div(divisor, dividend));
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("继续执行----------------");
}
public static int div(int divisor, int dividend) {
return divisor / dividend;
}
}
这段代码的try-catch块包围的代码就是用来处理异常的也叫做捕获异常,
try块用来处理可能出现异常的代码,catch块用来处理try块中出现的异常
语法:try{
代码,如果代码有异常就会将异常生成对应的异常对象,传递给catch块
}catch(异常){
异常处理
}
代码执行异常还分为几种情况:
-
try块中出现异常,则异常之后的代码不会执行,直接运行catch块中的语句
-
try块中没出现异常,catch块中的语句不会执行
-
catch中的异常类型和你给出的异常类型匹配的话,会执行catch块中的语句
-
catch中的异常类型和你给出的异常类型不匹配的话,则相当于没进行异常处理,catch块中和之后的代码不会执行
//catch中处理异常
public class Catch {
public static void main(String[] args) {
try {
int[] num = {42, 25, 32, 20, 14, 18};
System.out.println(num[num.length]);
} catch (Exception e) {
//第一种:空处理,什么都不写
//第二种:输出自定义异常信息
System.out.println("代码出现异常!");
//第三种:打印异常信息
System.out.println(e.toString());//打印异常的全限定名(包名+类名)
System.out.println(e.getMessage());// 显示异常描述信息,这段代码提示你数组长度是6
e.printStackTrace();//显示异常的详细信息
//第四种:抛出异常
throw e;
}
}
}
另外try-catch语句还可以进行嵌套,catch语句也可以有多个形成多层catch
public class MultipleCatch {
public static void main(String[] args) {
try {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入被除数:");
int divisor = scanner.nextInt();
System.out.print("请输入除数:");
int dividend = scanner.nextInt();
new MultipleCatch().div(divisor, dividend);
//多层catch分别匹配InputMismatchException和ArithmeticException异常,匹配到哪一个就执行哪一个,如果没匹配到相当于没处理异常
} catch (InputMismatchException e) {
e.printStackTrace();
} catch (ArithmeticException e) {
e.printStackTrace();
} finally {
System.out.println("继续执行----------------");
}
}
public void div(int num1, int num2) {
System.out.println(num1 / num2);
}
}
下面这个InputStream和OutputStream不知道是什么,这没关系,重点是演示try-catch语句
public class NestedTryCatch {
public static void main(String[] args) {
//InputStream和OutputStream都是一个流
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = new FileInputStream(new File("F:\\study"));
outputStream = new FileOutputStream(new File("F:\\study"));
//多层catch,子类异常必须写在父类异常上面
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch(Exception e) {
e.printStackTrace();
} finally {
try {
//流使用完毕需要关闭,这里就是一个嵌套try-catch语句
inputStream.close();
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
上面的多层匹配分别匹配FileNotFoundException、IOException和Exception异常,匹配到哪一个就执行哪一个
值得注意的是这几个异常有继承关系的,FileNotFoundException继承自IOException,它也是接着继承Exception的
异常体系
再看一下继承体系
总结:
所有的子类都继承与Throwable类,然后分为Error和Exception
Error:系统内部错误,代码级别无法解决的异常,比如:内存溢出、系统崩溃等
Exception类则又分为编译时异常和运行时异常
-
编译时异常:就指程序编译时产生的异常,必须处理,否则代码不能通过运行
-
运行时异常:运行之后才可能出现的异常,可以不做处理,继承自RuntimeException,一般是程序的逻辑错误,尽量避免!
越往上的父类能处理的异常范围越大,所以就可以想象出子类异常必须写在父类,如果同级别的异常就不用关心顺序
异常代码之后可能还有代码语句,但try-catch语句块可能运行完之后后续代码不会执行
-
throw抛出异常
-
catch语句块没有捕获成功
-
try语句块中有return语句
public class Try {
public static void main(String[] args) {
try {
String[] str = {"a", "b", "c", "d"};
//System.out.println(str[str.length]);
System.out.println(str.length);
return;
} catch (Exception e) {
//throw e;
}
System.out.println("后续代码-------------");
}
}
finally语句
但就想异常代码处理之后,无论异常信息是否捕获成功,后续的代码都会运行,可以加finally语句
public class Finally {
public static void main(String[] args) {
int num = 10;
try {
System.out.println(num); //执行结果:10
return;
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("后续代码-------------");
num += 100;
System.out.println(num); //执行结果:110,因为先执行finally再执行try中的return
}
}
}
//finally块一般操作关闭数据库资源,关闭IO流资源,关闭socket资源。
在进行异常捕获的时候,return语句位置的不同其实可能会造成结果不同
//1、try中有return,finally中没有return
public class Test1 {
public static void main(String[] args){
System.out.println(test());
}
private static int test(){
int num = 10;
try{
System.out.println("try");
return num += 80;
}catch(Exception e){
System.out.println("error");
}finally{
if (num > 20){
System.out.println("num>20 : " + num);
}
System.out.println("finally");
}
return num;
}
}
/*
执行结果:
try
num>20 : 90
finally
90
这里把return num += 80拆分成两个语句了,num+=80和return,num+=80的结果用第三方变量存储起来了,return的是第三方变量,接下来看一下class反编译的代码
*/
public class Test1
{
public static void main(String[] args)
{
System.out.println(test());
}
private static int test() {
int num = 10;
try {
System.out.println("try");
num += 80; int i = num;
return i;
} catch (Exception e) {
System.out.println("error");
} finally {
if (num > 20) {
System.out.println("num>20 : " + num);
}
System.out.println("finally");
}
return num;
}
}
//2、try和finally中均有return
public class Test2 {
public static void main(String[] args) {
System.out.println(test());
}
private static int test() {
int num = 10;
try {
System.out.println("try");
return num += 80;
} catch (Exception e) {
System.out.println("error");
} finally {
if (num > 20) {
System.out.println("num>20 : " + num);
}
System.out.println("finally");
num = 100;
return num;
}
}
}
/*
执行结果:
try
num>20 : 90
finally
100
这里同样把return num += 80拆分成两个语句了,num+=80和return,而且try中原有的return的第三方变量被finally中的return被覆盖了。还是看一下class反编译的代码
*/
public class Test2
{
public static void main(String[] args)
{
System.out.println(test());
}
private static int test() {
int num = 10;
try {
System.out.println("try");
num += 80; int i = num;
return num;
}
catch (Exception e)
{
System.out.println("error");
return num;
}
finally
{
if (num > 20) {
System.out.println("num>20 : " + num);
}
System.out.println("finally");
num = 100;
}return num;
}
}
//3、finally中改变返回值num,但是不返回
public class Test3 {
public static void main(String[] args){
System.out.println(test());
}
private static int test(){
int num = 10;
try{
System.out.println("try");
return num;
}catch(Exception e){
System.out.println("error");
}finally{
if (num > 20){
System.out.println("num>20 : " + num);
}
System.out.println("finally");
num = 100;
}
return num;
}
}
/*
执行结果:
try
finally
10
finally没有return时,这里把num的值还是用第三方变量存储起来,return的还是第三方变量,接着看class反编译的代码
*/
public class Test3
{
public static void main(String[] args)
{
System.out.println(test());
}
private static int test() {
int num = 10;
try {
System.out.println("try");
int i = num;
return i;
} catch (Exception e) {
System.out.println("error");
} finally {
if (num > 20) {
System.out.println("num>20 : " + num);
}
System.out.println("finally");
num = 100;
}
return num;
}
}
//将num的值包装在Num类中
public class Test4 {
public static void main(String[] args){
System.out.println(test().num);
}
private static Num test(){
Num number = new Num();
try{
System.out.println("try");
return number;
}catch(Exception e){
System.out.println("error");
}finally{
if (number.num > 20){
System.out.println("number.num>20 : " + number.num);
}
System.out.println("finally");
number.num = 100;
}
return number;
}
}
class Num{
public int num = 10;
}
/*
执行结果:
try
finally
100
这里finally也没有return,这里第三方变量存储的是创建对象的那个地址值,finally执行结束创建的对象改变值,还是看一下class反编译的代码
*/
public class Test4
{
public static void main(String[] args)
{
System.out.println(test().num);
}
private static Num test() {
Num number = new Num();
try {
System.out.println("try");
Num localNum1 = number;
return localNum1;
} catch (Exception e) {
System.out.println("error");
} finally {
if (number.num > 20) {
System.out.println("number.num>20 : " + number.num);
}
System.out.println("finally");
number.num = 100;
}
return number;
}
}
class Num {
public int num = 10;
Num() {
}
}
上面return写了好几个情况,但其实也是有迹可循的
-
try中有return,finally中没有return,这种情况会把return的结果值先保存起来,等执行完finally语句之后再返回之前保存的值
-
try和finally中均有return,这种情况try中的return会被finally"覆盖"掉,执行finally中的return
-
try中有return,finally中没有return但是改变值了,这种情况如果是基本数据类型或者字符串,finally语句更改的值无效;
如果是引用数据类型,finally对该引用的值会起作用,try中return的就是改变之后的值 -
try中没有return,finally中有return,这种情况相当于正常从try-finally执行
throws
java除了提供了try-catch块这种捕获异常的解决方案,还提供了一种声明抛出异常的解决方案throws,即自己不处理这些异常,而是丢给调用方处理,如果整个程序的运行过程中都没有异常的处理的话,最终异常会抛给jvm,不太友好,一般都要对异常进行处理
public class ThrowsDemo {
public static void main(String[] args) throws Exception {
new ThrowsDemo().test();
}
public void test() throws FileNotFoundException {
FileInputStream inputStream=new FileInputStream("F:\\study\\test.txt");
}
}
⾃定义异常
开发人员还可以⾃定义异常,⼀般通过继承Exception的⼦类的⽅式实现,本质上是覆盖原有异常API的信息
public class CustomException extends Exception{
static final long serialVersionUID = -70348971907L;
public CustomException(String message) {
super(message);
}
}
//模拟一下余额不足的情况
public class Test {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int money = scanner.nextInt();
new Test().getMoney(money, 2000);
}
public int getMoney(int money, int price) {
if (money < price) {
try {
throw new CustomException("余额不⾜!");
} catch (CustomException e) {
e.printStackTrace();
}
}
System.out.println("继续执行程序~~~");
return money;
}
}
自定义异常的步骤:
-
继承于现的异常结构:RuntimeException 、Exception
-
提供全局常量:serialVersionUID
-
编写构造方法,可以传入自己想打印的异常信息
-
调用的时候通过throw向外抛出异常
如果继承的是运行时异常,那么在使用的时候无需额外处理;
如果继承的是检查异常,那么使用的时候需要try-catch捕获或者throws向上抛
throw和throws的区别:
- 位置不同:throw:方法内部,throws: 方法的声明处
- 内容不同:throw+异常对象(检查异常,运行时异常);throws+异常的类型(可以多个类型,用,拼接)
- 作用不同:throw:异常出现的源头,制造异常。
throws:在方法的声明处,告诉方法的调用者,这个方法中可能会出现这些异常。然后调用者对这个异常进行处理。
jdk7新写法
- 在JDK1.7以后,异常新处理方式:可以并列用|符号连接
- try-with-resources语句
Java里,对于文件操作IO流、数据库连接等开销非常昂贵的资源,用完之后必须及时通过close方法将其关闭,否则资源会一直处于打开状态,可能会导致内存泄露等问题。
关闭资源的常用方式就是在finally块里是释放,即调用close方法。比如,我们经常会写这样的代码:
public static void test() {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
File f = new File("F:\\demo.txt");
FileWriter fw = null;
BufferedWriter bw = null;
try {
fw=new FileWriter(f);
bw = new BufferedWriter(fw);
String s = br.readLine();
while (!s.equals("exit")) {
bw.write(s);
bw.newLine();//文件中换行
s = br.readLine();
}
} catch (IOException e) {
e.printStackTrace();
}
//4.关闭流:
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
从Java 7开始,jdk提供了一种更好的方式关闭资源,使用try-with-resources语句,改写一下上面的代码,效果如下:
public static void test2(){
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try(FileWriter fw=new FileWriter(new File("F:\\demo.txt"));
BufferedWriter bw=new BufferedWriter(fw)){
String s = br.readLine();
while (!s.equals("exit")) {
bw.write(s);
bw.newLine();
s = br.readLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}
是不是感觉简洁了好多,其实这就是一个语法糖,它将在编译时编译成关闭资源的代码。我们将上述例子中的代码编译成class文件,再反编译回java文件,就能看到如下代码:
public static void test2() {
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
File f = new File("F:\\demo.txt");
try {
FileWriter fw = new FileWriter(f);
Throwable var4 = null;
try {
BufferedWriter bw = new BufferedWriter(fw);
Throwable var6 = null;
try {
for(String s = br.readLine(); !s.equals("exit"); s = br.readLine()) {
bw.write(s);
bw.newLine();
}
} catch (Throwable var31) {
var6 = var31;
throw var31;
} finally {
if (bw != null) {
if (var6 != null) {
try {
bw.close();
} catch (Throwable var30) {
var6.addSuppressed(var30);
}
} else {
bw.close();
}
}
}
} catch (Throwable var33) {
var4 = var33;
throw var33;
} finally {
if (fw != null) {
if (var4 != null) {
try {
fw.close();
} catch (Throwable var29) {
var4.addSuppressed(var29);
}
} else {
fw.close();
}
}
}
} catch (IOException var35) {
var35.printStackTrace();
}
}
除了异常代码,我们看到其实关闭流的处理是底层帮我们处理的