Java通识基础27(异常处理)
存在意义
有了异常处理的机制——我们的程序会更加健壮!
也有更高的容错率
程序在运行时会面临:
物理环境的改变
用户的误操作
恶意操作
……
面临以上情况的处理——异常处理
传统的错误处理机制
if(错误1){
}
else if(错误2){
}
......
处理方式存在的问题
- 所有的异常情况无法被穷举,总有漏网之鱼
- 流程处理混杂(业务处理逻辑的和异常处理逻辑混在一起),程序可读性差
异常处理流程
if(任何错误){
//异常处理语句
}
else{
//正常处理
}
改进后
try{
//正常处理逻辑
}
catch(异常){
//处理错误
}
举例:
import java.util.Scanner;
public class Demo1 {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
while (sc.hasNextLine()){
try {
String line= sc.nextLine();
String[] strArr=line.split(",");
Integer x=Integer.parseInt(strArr[0]);
Integer y=Integer.parseInt(strArr[1]);
System.out.println("x="+x);
System.out.println("y="+y);
}
catch (Exception ex){
System.out.println(ex);
System.out.println("写的时候动动脑子!");
}
}
}
}
/*
x
java.lang.NumberFormatException: For input string: "x"
写的时候动动脑子!
23
java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1
写的时候动动脑子!
213xxas
java.lang.NumberFormatException: For input string: "213xxas"
写的时候动动脑子!
1,2,3
x=1
y=2
*/
此时将正常的业务处理代码放在try
块里面,将异常处理逻辑放在catch
块里面
如果遇到异常
- Java会自动将该异常封装称为
Exception
对象 - 该对象会提交给Java的运行环境——这个过程叫做(抛出)
- Java运行环境收到该异常之后会寻找匹配的
catch
块——这个过程叫做(捕获)
- Java会自动将该异常封装称为
如果找不到异常,程序就会报错结束
- 刚刚例子里见面的捕捉的是所有的
Exception
(所有异常的父类),如果捕捉异常做出相应的改变,改变后捕捉的异常与抛出的异常不匹配,程序又会报错 - 所以,咱们可以动态的
catch
不同的异常,在相应的处理快中编写相应的处理代码
- 刚刚例子里见面的捕捉的是所有的
import java.util.Scanner;
public class Demo1 {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
while (sc.hasNextLine()){
try {
String line= sc.nextLine();
String[] strArr=line.split(",");
Integer x=Integer.parseInt(strArr[0]);
Integer y=Integer.parseInt(strArr[1]);
System.out.println("x="+x);
System.out.println("y="+y);
}
catch (ArrayIndexOutOfBoundsException ex){
System.out.println("格式不符合,你没有加逗号");
}
catch (NumberFormatException ex){
System.out.println("填数字,你个nt");
}
}
}
}
/*
12
格式不符合,你没有加逗号
x
填数字,你个nt
x2
填数字,你个nt
2,y
填数字,你个nt
2,3,3
x=2
y=3
*/
注意事项
- 异常处理流程每次最多进入一个
catch
块 - 处理异常的时候要先处理隶属子类异常,在处理隶属父类的异常
异常的体系
两大类
Error
代表严重且不可恢复的错误(一般不要求处理)Exception
代表可以恢复的错误,一般会要求程序处理
多异常捕捉
接上例:
catch (NumberFormatException|IndexOutOfBoundsException ex){
System.out.println("填数字,你个nt");
}
将两个异常归并为一类进行处理
catch (Exception ex){
System.out.println("你整出了我没想到的异常,你真是个小可爱");
}
最后再将Exception
类放进去防止漏网之鱼,通用,处理开发者还没有想到的异常
完整的异常处理流程
try{
//业务代码
}
catch(异常类1 ex){
}
catch(异常类2 ex){
}
......//catch块可以出现1-n次
finally{
//final块最多出现1次
}
final\finally\finalize
的区别:
final
作为修饰符,用于修饰变量方法和类;finally
作为异常处理中最后的一个块,用于回收资源
Java会保证无论是否出现异常,甚至有`return`都不会阻止`finally`语块的执行。
你要阻止`final`语块的执行,要么`exit`虚拟机,要么拔电源
finalize
所有对象被GC之前,系统会自动调用finalize
方法(基本用不到)
public class FinallyEx {
public int Test(){
int a = 0;
try{
return ++a;
}
finally {
//Java会最后执行finally再return结束方法
return ++a;
}
}
public static void main(String[] args) {
FinallyEx m1=new FinallyEx();
System.out.println(m1.Test());
}
}
异常处理的嵌套
一般来说,为了处理可能在catch
块中出现的异常,就出现了异常处理的嵌套流程
Java7以后的改进
try(
//需要声明并创建可以自动关闭的资源
)
注意,圆括号内的资源必须声明并创建
自动关闭会调用AutoCloseable
接口和其子接口Closeable
自动关闭资源的try
语句自带隐藏finally
,可以既没有catch
也没有finally
块
异常处理方法
Exception
继承Throwable
所有的异常都是Exception
的子类
getMessage()
获取异常信息getStackTrace()
获取异常的跟踪栈(堆栈信息)printStackTrace()
打印跟踪栈信息(堆栈信息)
import java.util.Random;
public class A {
public void foo() {
System.out.println("foo");
B b = new B();
b.bar();
}
}
class B {
public void bar() {
System.out.println("bar");
C c1 = new C();
c1.info();
}
}
class C {
public void info() {
int a = 20 / (new Random().nextInt(3));
//三分之一的可能性报错(整除以0的时候)
System.out.println(a);
}
}
class Test{
public static void main(String[] args) {
A a1=new A();
a1.foo();
}
}
/*
foo
bar
Exception in thread "main" java.lang.ArithmeticException: / by zero
at C.info(A.java:21)
at B.bar(A.java:15)
at A.foo(A.java:7)
at Test.main(A.java:29)
*/
从上面例子中的报错可以总结出来的技巧——通过跟踪栈调试错误的时候,应该从第一个自己写的错误类下手进行调试!
checked
异常与runtime
异常
RuntimeException
以及其子类的实例,都是runtime
(运行时)异常
Java编译器**不强行要求处理`Runtime`异常**
checked
异常,不是RuntimeException
,就是checked
异常
**这是必须处理的异常**
要么显示用`try catch`来处理该异常,要么就**声明抛出**该异常——否则编译无法通过
runtime
异常比checked
异常用起来更方便
一些框架喜欢把checked
异常包装为runtime
异常
声明抛出异常语法
在方法签名上使用,
throws 异常类1,异常类2,异常类3
throws
可以一次声明抛出多个异常
import java.util.Random;
public class RuntimeE {
public static void main(String[] args){
try {
//下面的代码有可能出异常
Random rnd = new Random();
int a = 20 / (rnd.nextInt(3));
//ArithmeticException
//动态初始化定义一个长度为4的数组
int[] iArr = new int[4];
System.out.println(iArr[rnd.nextInt(8)]);
//ArrayIndexOutOfBoundsException
String str=null;
System.out.println(str.length());
}
catch (ArrayIndexOutOfBoundsException|ArithmeticException|NullPointerException ex){
System.out.println("嗯,出异常了");
}
}
}
回顾方法重写的规则
两同两小一大——两小:
- 返回值类型相同或者更小
- 抛出异常相同或是更小
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Date;
class Base{
public Object info() throws Exception{
System.out.println("父类的Info");
return new Date();
}
}
public class Sub extends Base{
@Override
public String info() throws FileNotFoundException {
new FileInputStream("Sub.java");
System.out.println("重写的info");
return "Java";
}
public static void main(String[] args) {
try {
new Sub().info();
}
catch (Exception ex){
ex.printStackTrace();
}
}
}
throws throw throwable
的区别
throws
——声明抛出异常,表明该方法暂时不处理这个异常,交给方法的调用者处理
throwable
——Exception
和Error
的共同的父类
throw
——一个自定义异常的类,一次只能new
一个异常
喜闻乐见的五大Runtime
异常
NullPointException
空指针异常IndexOutOfBoundsException、ArrIndexOutOfBoundsException
索引越界异常、数组索引越界异常ArithmeticException
算术异常NumberFormatException
数字格式异常IllegalArgumentException
非法参数异常
运行时异常的常见子类——NPENullPointerException
出现原因:
- 掉用一个空对象的实例方法
- 访问一个空对象的成员变量
- 访问一个空数组的
length
属性 - 访问空数组的元素
手动抛出异常与自定义异常
抛出异常
在某些时候,如果出现了 和业务不符合的情况,就可以主动抛出异常
throw
异常实例:
——throw
是一个独立使用的语句,用于抛出一个异常
throw
是可以放在方法体,构造器,初始化块中使用
如果一个方法中throw
了一个异常,表明这个代码可能抛出异常,
如果该异常是checked
异常,按惯例是声明抛出
如果该异常是runtime
异常,编译总可以通过,那么可以不用管
public class User {
private String name;
private String passwd;
public User() {
}
;
public User(String name, String passwd) {
if (name == null || name.length() > 10 || name.length() < 6) {
throw new IllegalArgumentException("用户名长度必须在6到10位");
}
if (passwd == null || passwd.length() > 20 || passwd.length() < 6) {
throw new IllegalArgumentException("用户密码长度必须在6到20位");
}
this.name = name;
this.passwd = passwd;
}
;
public String getName() {
return name;
}
public String getPasswd() {
return passwd;
}
public void setName(String name) {
if (name == null || name.length() > 10 || name.length() < 6) {
throw new IllegalArgumentException("用户名长度必须在6到10位");
}
this.name = name;
}
public void setPasswd(String passwd) {
if (passwd == null || passwd.length() > 20 || name.length() < 6) {
throw new IllegalArgumentException("用户密码长度必须在6到20位");
}
this.passwd = passwd;
}
}
class UserTest{
public static void main(String[] args) {
User user=new User();
user.setName("ld");
}
}
自定义异常
自定义runtime
异常,就要继承RuntimeException
自定义checked
异常,就要继承CheckedException
public class ShopService {
public void sellItem(double price)throws ItemException{
//刚定义的是一个checked异常
//要么抛出,要么try catch
if (price<0){
throw new ItemException("价格怎么是负数,滚吧");
}
System.out.println("price="+price);
}
}
class ItemException extends CheckedException{
public ItemException(){
}
public ItemException(String msg){
super(msg);
}
}
catch
和throw
的结合使用
在某些情况下,方法捕获了异常,程序也对该异常进行了部分处理,但仍然无法处理完成
此时就需要在catch
块中再次抛出新的异常对象
例子——把checked
异常改为Runtime
异常
import java.io.FileInputStream;
public class CatchAndThrow {
public void Test(String fileName){
try {
//该代码可能抛出FileNotFound的checked异常
new FileInputStream(fileName);
}
catch (Exception ex){
System.out.printf("%s该文件不存在\n",fileName);
throw new IllegalArgumentException("传入的文件名不存在"+fileName);
}
}
public static void main(String[] args) {
CatchAndThrow ct=new CatchAndThrow();
ct.Test("add.txt");
//会抛出checked异常,编译不通过
//FileInputStream fi=new FileInputStream("a.txt");
}
}