java之异常处理
文章内容选自尚硅谷,jdk8,eclipse环境
throws抛出异常
前文java之异常处理.已经讲到,java处理异常的方式有两种,一种是用try-catch-finally语句块处理异常,另一种方式就是抛出异常,交给调用该方法的方法来处理。
抛出异常的表现形式为throws 异常类型
异常代码后续的代码,依然不再执行
示例代码如下
package com.atguigu.java;
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 (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
}
}
public static void method2() throws FileNotFoundException,IOException{
method1();
}
public static void method1() throws FileNotFoundException,IOException{
File file = new File("hello1.txt");
FileInputStream fis = new FileInputStream(file);
int data = fis.read();
while(data != -1){
System.out.print((char)data);
data = fis.read();
}
fis.close();
}
}
分析代码,在原始的method1方法中,存在着FileNotFoundException,IOException这两类可能存在的问题,因此这儿的处理方法是将系统生成的两个异常往上抛,抛到method2方法中,method2方法如果不用try-catch方法处理异常,那么也要将异常往上报,一直报到main方法内部,main方法其实也可以往上抛,抛到JVM那儿,但是一般main方法都要用try-catch处理异常,防止JVM处理不了异常而挂掉。
总而言之,调用方法必须要解决被调用方法的异常,throws异常(抛出异常)也算是处理异常的方法之一。
上面的代码还可以写成
package com.atguigu.java;
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("hello1.txt");
FileInputStream fis = new FileInputStream(file);
int data = fis.read();
while(data != -1){
System.out.print((char)data);
data = fis.read();
}
fis.close();
}
}
因为IOException异常是FileNotFoundException异常的父类,也可以理解为IOException罩住了FileNotFoundException异常,因此method2在抛出异常的时候,可以只写IOException异常。在main方法中处理的时候也只处理IOException异常。那么这种方式和时候上一种方式的区别就在于,如果两种异常的处理方法都一样,那么就没必要throwsFileNotFoundException异常了,直接抛一个IOException异常就行了,如果两种异常的处理方法不一样,就按照上一种的书写方法,分别处理两种不同异常。
try-catch方式是真正处理掉了异常,而throws方式只是把异常抛给了方法的调用者,并没有真正处理掉异常。
子类重写方法抛出的异常必须不大于父类被重写方法抛出的异常
示例代码如下
import java.io.FileNotFoundException;
import java.io.IOException;
public class OverrideTest {
}
class SuperClass{
public void method() throws IOException{
}
}
class SubClass extends SuperClass{
public void method() throws FileNotFoundException{
}
}
子类重写方法抛出的异常必须不大于父类被重写方法抛出的异常,当然,子类也可以不抛出异常
class SubClass extends SuperClass{
public void method() {
}
}
这样写也是合法的,简单理解为异常无限小,小到没有异常了。
之所以子类重写方法抛出的异常必须不大于父类被重写方法抛出的异常,是因为当用到多态的时候,确保父类的异常罩得住子类的异常,演示代码如下
package com.atguigu.java;
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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class SuperClass{
public void method() throws IOException{
}
}
class SubClass extends SuperClass{
public void method() {
}
}
当用多态性的时候,若子类抛出了异常,该异常类型必须要不大于父类的异常类型。
开发中如何选择那种方式处理异常
- 方才说了子类重写方法抛出的异常必须不大于父类被重写方法抛出的异常,且将不抛出异常理解为最小的异常,那么如果父类中被重写的方法没有抛出异常,那么子类中也不允许抛出异常,只能采用try-catch的方式处理异常。
- 如果a,b,c三个方法是递进的关系,即a方法得出的结果被b方法调用,b方法的结果被c方法调用,那么这三个方法在声明的时候,最好都用throws抛出异常。假如说d方法调用了a,b,c三个方法,将a,b,c三个方法一起放到try结构内进行解决。因为如果在a,b,c三个方法声明的时候就采用了try-catch来解决,虽然编译器肯定能通过,但是逻辑上很可能已经出问题了(因为a,b,c三个方法是递进的关系),那么运行的结果肯定不对,不如一起在d方法中用try-catch结构进行异常的统一解决,避免出现逻辑上的混乱。
- try-catch和throws作为处理异常的两种方式,最好不要一起用,因为try-catch已经解决问题了,再throws出去的话,那么调用该方法的方法还得写一个try-catch结构来处理异常。
- 再次强调,解决运行时异常,一般不用try-catch进行处理,而是修改代码处理异常,try-catch顶多起一个提示错误信息的作用,提醒用户哪儿出问题了,但是并不能真正处理异常。
手动处理异常
异常对象的产生有两种方式,一种是系统自动生成(throws),另一种是自己手动生成一个异常对象,并抛出(throw)
手动抛异常对象是有具体的应用场景的,演示代码如下
package com.atguigu.java2;
public class StudentTest {
public static void main(String[] args) {
Student s = new Student();
s.regist(-1001);
System.out.println(s);
}
}
class Student{
private int num ;
public void regist(int num){
if(num > 0)
this.num = num;
else
System.out.println("输入格式非法");
}
@Override
public String toString() {
return "Student [num=" + num + "]";
}
}
运行结果为
输入格式非法
Student [num=0]
假如说我们正常输入一个正数,那么会直接输出Student [num=正数的值] ,即toString重写后的内容。
但是这儿我们输入了一个负数,输入格式非法了,此时不希望再继续调用toString方法输出值了,因此需要手动抛出一个异常。
手动抛出异常对象的类型
手动抛出异常对象的类型有两种,一种是RuntimeException,另一种是Exception。
RuntimeException
编译器在检测到RuntimeException是不会报错的,因为这是一个运行时异常。检查到Exception会报错。
class Student{
private int num ;
public void regist(int num){
if(num > 0)
this.num = num;
else
// System.out.println("输入格式非法");
throw new RuntimeException("输入格式非法");
}
运行结果为
此时编译器编译时不会报错,因为编译器检查到运行时异常是不会处理的。运行时异常只有在运行时才会报错。
按住 Alt+/,查看异常类型的构造器,RuntimeException类有一个构造器是RuntimeException(String arg0),因此里面可以放一个字符串,该字符串会在控制台被打印出来,此处可以联想到前文讲的e.getMessage()方法。
Exception
也可以手动抛出Exception对象,由于Exception不是运行时异常,它是运行时异常和编译时异常的父类,编译时如果不对异常进行处理,编译器会报错。
package com.atguigu.java2;
import javax.management.RuntimeErrorException;
public class StudentTest {
public static void main(String[] args) {
Student s = new Student();
try {
s.regist(-1001);
} catch (Exception e) {
// e.printStackTrace();
System.out.println(e.getMessage());
}
System.out.println(s);
}
}
class Student{
private int num ;
public void regist(int num) throws Exception{
if(num > 0)
this.num = num;
else
// System.out.println("输入格式非法");
// throw new RuntimeException("输入格式非法");
throw new Exception("输入格式非法");
}
@Override
public String toString() {
return "Student [num=" + num + "]";
}
}
运行结果为
输入格式非法
regist方法内的throw是手动抛出异常对象,而方法声明处的throws是我们对异常的处理方式之一,在main方法中再用try-catch结构处理掉异常。
由于Exception继承了父类throwable,e.getMessage()是调用了父类结构throwable的方法
public String getMessage() {
return detailMessage;
}
这儿Exception的构造器为
public Exception(String message) {
super(message);
}
继承了父类throwable的
public Throwable(String message) {
fillInStackTrace();
detailMessage = message;
}
因此,构造器赋的值能够被e.getMessage()方法输出。
用户自定义异常
用户自定义异常可以参照java库中的异常对象的书写方法来写
- 用户自定义异常可以继承自RuntimeException和Exception
- 用户自定义异常必须有一个全局常量serialVersionUID(序列号),理解为类的标识。
- 提供重载的构造器
package com.atguigu.java2;
public class MyException extends RuntimeException{
static final long serialVersionUID = -70348971907766939L;
public MyException(){
}
public MyException(String msg){
super(msg);
}
}
再在之前的代码中修改
public void regist(int num) throws Exception{
if(num > 0)
this.num = num;
else
// System.out.println("输入格式非法");
// throw new RuntimeException("输入格式非法");
// throw new Exception("输入格式非法");
throw new MyException("不能输入负数");
}
运行结果为
不能输入负数
注意一点,MyException是运行时异常,运行时异常可以不做出处理,编译器不会报错。因为MyException继承的是RuntimeException,故不抛出Exception也可以运行。(抛出的Exception是MyException的父类,合法)(throws也是处理异常的方法之一)
public void regist(int num) {
if(num > 0)
this.num = num;
else
// System.out.println("输入格式非法");
// throw new RuntimeException("输入格式非法");
// throw new Exception("输入格式非法");
throw new MyException("不能输入负数");
}
这样是合法的,而且运行结果为
不能输入负数
如果把MyException继承自Exception,那么regist方法中手动抛出的异常必须立刻做出处理,否则编译器报错
public class MyException extends Exception{
regist方法内要么抛出,要么try-catch处理。
public void regist(int num) throws MyException {
if(num > 0)
this.num = num;
else
// System.out.println("输入格式非法");
// throw new RuntimeException("输入格式非法");
// throw new Exception("输入格式非法");
throw new MyException("不能输入负数");
}
这儿如果抛出MyException的父类Exception,也是合法的。