异常处理是java中很重要的部分,我们平时写代码其实有很多情况考虑不到,或者我考虑到这一行代码有可能会报错我就要想如果报错了我怎么去处理他能让他正常的返回信息给我。程序一旦发生了异常,就会直接在异常代码地方直接退出程序,不再往后执行了。
异常通常分为两类:运行时异常(RuntimeException)和非运行时异常,运行时异常指在写代码时,开发工具(eclips、idea、sts等)不会提示你必须在这一行加异常处理,但是在运行是有可能会抛出异常(一般习惯把程序报错叫做抛异常、抛出异常、报异常),非运行时异常是指我们在写代码时开发工具就会在这一行报错,提示你必须在该处加异常处理机制,不然不给你编译通过。
提示:1、在eclips/sts中先点击一行(没必要把这一行完全选中),然后按ctrl+/,可以实现快速注释掉这一行,便于你测试代码时控制哪些运行哪些不运行。2、alt+/可以快速联想或自动补齐后面的代码,大多数情况下不需要输入完整的代码。尝试下输入syso之后按alt+/
1、运行时异常
先举一个java中最最最常见没有之一的异常场景:空指针异常,空指针异常是指一个对象在声明的时候没有实例化(new),直接调用其方法所报的异常。
public class Test {
public static void main(String[] args) {
String s = null;
Test test = new Test();
//其实这里应该判断s是否为null,但是我没判断
test.printLength(s);
}
/**
* 打印符串长度
*/
public void printLength(String s) {
System.out.println(s.length());
}
}
运行结果:
java.lang.NullPointerException为异常信息,NullPointerException表示空指针异常,点击Test.java:16或者Test.java:9可以直接定位到该行。
按照开发正常做法,我们应该是在调用printLength()之前就应该对s判断是否为null,因为不是所有开发人员都能考虑到调这个方法会先判断的,所以我们也可以在printLength()中用两种方法处理:1、判断s是否为null,如果是的话就打印-1。2、用异常处理。
代码如下:
public class Test {
public static void main(String[] args) {
String s = null;
Test test = new Test();
//其实这里应该判断s是否为null,但是我没判断
test.printLength(s);
}
/**
* 打印符串长度
*/
public void printLength(String s) {
try {
System.out.println(s.length());
}
catch (NullPointerException e) {
System.out.println(-1); //这是我们想要的异常返回值
//下面的代码纯粹是为了告诉你e里面有以下东西可以查看
System.out.println(e); //打印e
System.out.println(e.getMessage()); //打印e.getMessage()
e.printStackTrace(); //打印完成的异常堆栈信息
}
System.out.println("成功执行结束!");
}
}
上面的代码可以看出,异常处理一般分为两个部分(其实还有一个finally关键字在之后的章节里介绍),try-catch,try中写我们认为会报错的代码,catch中写如果报错了需要怎么处理的代码。catch后面括号里面的NullPointerException表示我们要处理的是try中的空指针异常,如果try中多加了一段会触发其它类型异常的代码,我们就需要追加catch代码块。如下代码:
public class Test {
public static void main(String[] args) {
String s = "abc123";
try {
System.out.println(s.length());
int i = 0 / 0; //这个地方会报错,因为除数不能为0
System.out.println("try块结束");
}
catch (NullPointerException e) {
System.out.println("提示:空指针异常!");
}
catch (ArithmeticException e) {
e.printStackTrace();
System.out.println("提示:除数不能为0");
}
System.out.println("程序执行完成");
}
}
上面的代码中,我们事先知道了这个除法会报ArithmeticException,所以我在空指针异常下面追加了一个ArithmeticException代码块的处理,当try中的代码走到int i = 0 / 0时,直接会出错,程序会查找catch中的异常类型,从上到下先查到了NullPointerException,发现这个异常符合抛出的这个异常,跳过,然后继续往下查找到了ArithmeticException,发现这个异常类型符合抛出的这个异常,就进到ArithmeticException的catch代码块中执行代码了。当catch中的代码执行完,程序会继续往下走,打印“程序执行完成”,而不会走try中的打印“try块结束”。如果try中的代码抛出了异常,我的catch块中并没有对应的处理类型,那就相当于没法处理这个异常程序直接报错中断,代码如下:
public class Test {
public static void main(String[] args) {
String s = null;
try {
System.out.println(s.length());
System.out.println("try块结束");
}
//这个是处理除数不能为0异常,并不能处理上面的空指针异常,程序找不到相应的处理,直接报错停止
catch (ArithmeticException e) {
System.out.println("提示:空指针异常!");
}
System.out.println("程序执行完成");
}
}
假如说上面的我并不知道会报空指针异常和除数不能为0异常,但是我又害怕他报错,我们还有另外的方法解决这个问题,这个方法适用于所有异常,代码如下:
public class Test {
public static void main(String[] args) {
String s = "123";
try {
System.out.println(s.length());
int i = 0 / 0;
System.out.println("try块结束");
}
//我Exception是所有异常的父类,管你是什么类型的异常我都能处理!
catch (Exception e) {
System.out.println("程序出现异常,请联系管理员!");
e.printStackTrace(); //打印堆栈信息
System.out.println(e.getMessage()); //打印异常信息
}
System.out.println("程序执行完成");
}
}
上面的这段代码中,catch里面我们处理了Exception,因为Exception是所有异常的父类,空指针异常与除数不能为0异常都属于Exception的子类,所以它可以统一处理。查看API:
第一个图可以看出空指针异常的父类是运行时异常,运行时异常的父类是Ecxeption
第二个图可以发现运行时异常包括了这么多子类
第三个图可以发现Exception下面原来中这么做的子类异常,随便一个子类异常点进去又有继承于子类异常的异常。
所以,Exception可以处理所有类型的异常。
上面的代码还有其他很多的写法,我再举两个例子:
1、try分开写,一个try处理一个异常
public class Test {
public static void main(String[] args) {
String s = null;
try {
System.out.println(s.length());
}
catch (NullPointerException e) {
System.out.println("错误:出现空指针异常!");
}
try {
int i = 0 / 0;
}
catch (ArithmeticException e) {
System.out.println("错误:除数不能为0!");
}
System.out.println("程序执行完成");
}
}
2、外层处理异常
public class Test {
public static void main(String[] args) {
Test test = new Test();
//这个调用可能会抛异常,我先try一下保险点
try {
test.run();
}
catch(Exception e) {
System.out.println("糟糕,程序出错了,快联系管理员!");
}
System.out.println("程序执行完成");
}
/**
* 这个方法我就不处理异常了,我让调用他的代码处理异常
*/
public void run() {
int i = 0 / 0;
}
}
上面的代码注释我写的很详细了,表示我写代码时担心出错,就加try-catch块。反正异常处理的写法有很多,灵活运用达到你的目的就行了。
再次说明下,我们使用对象时,一般会确定他一定是非空对象才敢直接对象点方法,如果不确定,我们或先进行非空校验,如果是空对象,就另外处理了而不是完全交给try-catch来处理!进行除法时也必须先保证除数一定不为0才敢直接除,不敢保证就需要先进行if判断!try-catch一般是用来处理你所有的该考虑到的都考虑到了,但是还是怕有遗漏才用的!或者你懒得分类处理异常了,只要有任何一行代码报错我就对外提示失败,这种情况你可以把所有代码都写在一个try中,最后catch中用一个Excption来统一处理!
2、非运行时异常
先举一个例子
这段程序的本意是暂停两秒,但是我在写程序的时候eclips提示我这里有个错误,我必须改正。用鼠标点开这个x,,得到两个自动修正的提示,第一行意思是异常不处理直接向外抛出,第二行意思是使用try-catch块来解决这个异常,其实用那种方式都一样,我们先点击第二行这种方式处理,得到以下代码:
public class Test {
public static void main(String[] args) {
System.out.println("程序开始");
try {
Thread.sleep(2000L);
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("程序结束");
}
}
嗯,看起来跟运行时异常处理没什么区别嘛,只是写代码是就要求我必须加上去而已,确实如此。然后我们在点击第一行的处理方式得到如下代码:
public class Test {
public static void main(String[] args) throws InterruptedException {
System.out.println("程序开始");
Thread.sleep(2000L);
System.out.println("程序结束");
}
}
这次就跟上面的代码有差异了,方法里面本身没有处理异常,而是在方法上面直接抛出了一个InterruptedException,这个意思是异常直接抛给上层,由调用main()的地方去处理,我们不管,这里的上层指的是jvm。如果代码中有多个非运行时异常,我们可以让throw跟多个异常,如下代码:
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws InterruptedException, IOException {
System.out.println("程序开始");
Thread.sleep(2000L);
try {
FileWriter writer = new FileWriter("");
BufferedWriter out = new BufferedWriter(writer);
}
catch (IOException e) {
System.out.println("路径不能为空");
}
System.out.println("---------------");
//如果路径不写,就会往上层抛异常,如果想自己处理,就写成try-catch,就是上面的代码
FileWriter writer = new FileWriter("");
System.out.println("程序结束");
}
}
以上就是运行时异常和非运行时异常的定义和处理方式,其实处理方式没什么区别,都是通用的,我下面在举出几个格式各样的异常处理,各位可以参考领悟下。
1、我抓到异常了,但是我不处理,我要抛给上层处理:
public class Test {
public static void main(String[] args) {
Test test = new Test();
try {
test.run();
}
catch (Exception e) {
System.out.println("哎呀方法调用出错了!");
}
System.out.println("程序结束");
}
/**
* 这里的throws可以不加,现在加上可以让上面的test.run()这个地方报错,让你强制加try-catch,类似非运行时异常的感觉,告诉你这里可能会出错你调用是必须考虑异常的处理
* 如果不加throws,上面的test.run()不会提示报错,可以不加try-catch,这样会导致程序不健壮
*/
public void run() throws Exception {
try {
String str = "123";
System.out.println("字符串的长度为:" + str.length());
int i = 0 / 0;
}
catch (Exception e) {
//这里我catch到了异常,但是我就是不处理,我要告诉调用我的人我出错了,让他来考虑怎么处理
throw e; //往上层抛,注意这里的throw和方法上的throws拼写
}
System.out.println("这一句其实是不打印的,因为该方法因为报错往上抛异常导致没执行完直接就结束了。");
}
}
2、往上抛的异常也可以分类处理:
public class Test {
public static void main(String[] args) {
Test test = new Test();
test.run();
try {
test.run();
}
catch (NullPointerException e) {
System.out.println("空指针异常");
}
catch (ArithmeticException e) {
System.out.println("除数不能为0异常");
}
//这里是捕获到没有指定类型的异常,假如上面的异常种类都不符合,那么到这一行我就能够处理了。
//因为Exception是所有类型的父类(大异常),这个catch又是从上往下走的,所以不能写到上面,因为 写到了上面就直接进Exception而不走小异常了
catch (Exception e) {
System.out.println("哎呀未知的异常");
}
System.out.println("程序结束");
}
/**
* 这里的throws NullPointerException, ArithmeticException可以用写,反正报错了自动往上层抛
*/
public void run() throws NullPointerException, ArithmeticException {
String str = "123";
System.out.println("字符串的长度为:" + str.length());
int i = 0 / 0;
System.out.println("这一句其实是不打印的,因为该方法因为报错往上抛异常导致没执行完直接就结束了。");
}
}
总结下,异常处理很简单,就是我捕获(catch)到了异常,我想处理就处理,我处理完了程序继续往下走;我不想处理了我就throw往上抛,让上层处理,下面的程序我就不走了。