1.异常的概念与体系结构
1.1在Java中,将程序执行过程中发生的不正常行为称为异常.比如
1.算数异常
public class Main1 {
public static void main(String[] args){
System.out.println(10/0);
}
}
//异常信息为:Exception in thread "main" java.lang.ArithmeticException: / by zero
// at Object1.work.error.Main1.main(Main1.java:5)
2.数组越界异常
public class Main1 {
public static void main(String[] args){
int[] auto = new int[10];
System.out.println(auto[100]);
}
}
//异常信息为:Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
// at Object1.work.error.Main1.main(Main1.java:6)
3.空指针异常
public class Main1 {
public static void main(String[] args){
int[] arr = null;
System.out.println(arr.length);
}
}
//异常信息为:Exception in thread "main" java.lang.NullPointerException
// at Object1.work.error.Main1.main(Main1.java:6)
上述过程中我们可以看见Java中不同类型的异常都有,不同的描述方式
1.2异常的体系结构
异常的种类繁多,为了对不同异常或者错误进行很好的分类管理,Java内部维护了一个异常的专门体系结构:
大致可以分为两个大方向
Throwable:异常的主类
Error:是指Java虚拟机无法解决的严重问题
Exception:异常产生后程序员可以通过代码进行处理,是程序继续执行.
1.3异常的分类
异常可能在编译的时候发生,也可能在程序运行的时候发生,但根据时间的不同,异常可以被分类为:
1.编译时的异常(受检查异常)
2.运行时的异常(非受检查异常)
2.异常处理
2.1防御式编程
1.LBYL:Look Before You Leap.在操作前就做充分的检查
public class Main1 {
public static void main(String[] args){
boolean ret = false;
if(!ret){
//处理登录异常的相关代码
return;
}
//登录游戏
if(!ret){
//处理游戏匹配异常的相关代码
return;
}
//进入游戏
}
}
像上述代码就是属于,防御型的异常处理,在我们为了防止异常的出现提前设置好相应异常的处理方式. 这种代码格式会使正常的代码和处理错误的代码混和在一起,显的代码比较混乱
2.EAFP:先操作,遇到问题在处理,即:事后认错型
这种操作的优势在于:正常的流程和错误的流程是分开操作的,我们更加关注正常流程
在java中,异常处理的主要的5个关键字分别是: throw,try,catch,final,throws.
2.2异常的抛出
在编写程序的时候,如果程序出现错误,我们需要把错误信息返回给此信息的调用者
在Java中可以通过throw关键字,抛出一个指定的异常对象,把错误信息告诉调用者
throw new xxxxException("异常产生的原因");
需求:实现一个获取数组中任意位置元素的方法
public class Main2 {
public static int getElement(int[] array, int index){
if(null == array){
throw new NullPointerException("传递的数组为 null");
}
if(index < 0 || index >=array.length){
throw new IndexOutOfBoundsException("数组索引越界");
}
return array[index];
}
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
getElement(array,9);
}
}
当array数组为"null"和数组索引越界时都会发生报错报错信息是根据各自抛出的throw中的内容.
注意事项:
1.throw必须写在方法体的内部
2.抛出的对象必须是Exception或者是Exception的子类对象
3.如果抛出的是RunTimeException或者是RunTimeException的子类,我们可以不用处理交给jvm处理就行
4.如果抛出的异常是编译的异常,必须要用户处理,否则无法通过编译
5.异常一旦抛出后面的代码是不会继续执行的
2.3异常的捕获
异常的捕获,也就是异常的具体的处理方式,通常有两种,throws以及try-catch捕获处理
2.31 异常声明throws
当方法中抛出编译异常的时候,用户不想处理该异常,此时可以借助throws将异常抛给方法的调用者来处理,就是说:当前方法不处理异常,提醒方法的调用者来处理该异常.
单独使用throw出现异常会让程序终止
public class Main3 {
private static double divide(double a,double b){
if(b == 0){
throw new ArithmeticException("不能以0为除数");
}
return a/b;
}
public static void main(String[] args) {
System.out.println(divide(10,0));
}
}
// 输出结果:
// Exception in thread "main" java.lang.ArithmeticException: 不能以0为除数
// at Object1.work.error.Main3.divide(Main3.java:3)
// at Object1.work.error.Main3.main(Main3.java:7)
使用try-catch进行结合处理
public class Main3 {
private static double divide(double a,double b){
if(b == 0){
throw new ArithmeticException("不能以0为除数");
}
return a/b;
}
public static void main(String[] args) {
System.out.println("抛出异常");
try{
//把也能抛出异常的代码放进try块中
System.out.println(divide(10,0));
}catch(ArithmeticException e){
//捕获异常并打印异常信息
System.out.println("捕获到的异常信息为:"+e.getMessage());
}
System.out.println("异常抛出后");
}
}
// 输出结果:
// 抛出异常
// 捕获到的异常信息为:不能以0为除数
// 异常抛出后
可以看出程序是正常进行的并没有影响的程序的正常进行,可以理解为,自己抛出的异常被自己给处理掉了.
public class Main3 {
private static double divide(double a,double b){
if(b == 0){
throw new ArithmeticException("不能以0为除数");
}
return a/b;
}
private static void func1(){
divide(10,0);
}
private static void func2(){
//func2()调用了func1(),如果func1()抛出了异常,func2()也会抛出异常
func1();
}
public static void main(String[] args) {
System.out.println("抛出异常");
try{
//把也能抛出异常的代码放进try块中
func2();
}catch(ArithmeticException e){
//捕获异常并打印异常信息
System.out.println("捕获到的异常信息为:"+e.getMessage());
}
System.out.println("异常抛出后");
}
}
也可以通过方法之间进行调用先调用func2()通过func2()在去调用func1()
public class Main4 {
public static void main(String[] args) {
try{
int a = 10/0;
}catch(ArithmeticException e){
System.out.println("发生算数异常");
}catch(NullPointerException e){
System.out.println("发生空指针异常");
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("发生数组越界异常");
}
}
}
产生的异常如果try下面出现多个catch它会依次进行匹配匹配到相应的错误信息后会打印出相关的错误内容,如果catch中没有相对应的异常时,就会交给jvm处理程序终止.
public class Main5 {
public static void main(String[] args) {
int[] arr = new int[5];
try{
System.out.println(arr[6]);
}catch(ArithmeticException e){
System.out.println("发生算数异常");
} catch(NullPointerException e){
System.out.println("发生空指针异常");
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("发生数组越界异常");
}catch(Exception e){
System.out.println("发生其他异常");
}
}
}
//输出结果
//发生数组越界异常
public class Main5 {
public static void main(String[] args) {
int[] arr = new int[5];
try{
System.out.println(arr[6]);
}catch(ArithmeticException e){
System.out.println("发生算数异常");
} catch(NullPointerException e){
System.out.println("发生空指针异常");
}catch(Exception e){
System.out.println("发生其他异常");
}
}
}
//输出:
//发生其他异常
public class Main5 {
public static void main(String[] args) {
int[] arr = new int[5];
try{
System.out.println(arr[6]);
}
catch(Exception e){
System.out.println("发生其他异常");
}catch(ArithmeticException e){
System.out.println("发生算数异常");
} catch(NullPointerException e){
System.out.println("发生空指针异常");
}
}
}
//发生报错无法正常进行
通过上述代码进行比较:我们可以看出我们可以使用Exception来进行处理抛出的异常,但是用Exception是无法准确表达异常信息的一般我们是不建议使用的,还有就是Exception是上面三个异常的父类,而在catch时 Exception是是不能放在它子类前面的否则会发生报错.
就是说:当代码catch中既有父类异常也有子类异常时,父类异常应该放在最下面
public class Main6 {
public static void main(String[] args){
try {
Scanner sc = new Scanner(System.in);
int num = sc.nextInt();
if (num == 1) {
int a = 10 / 0;
} else if (num == 2) {
String s = null;
System.out.println(s.length());
} else if (num == 3) {
int[] arr = new int[5];
System.out.println(arr[10]);
}
} catch (ArithmeticException e) {
System.out.println("发生算数异常");
}catch (NullPointerException e){
System.out.println("发生空指针异常");
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("数组下标越界异常");
}catch(Exception e){
System.out.println("未知异常");
}
}
}
当try中同时出现多个异常时try中的程序从上向下走发现异常时就会直接跳转到catch中去,后面的错误异常是无法识别到的,在一个try中是无法同时出现两个异常的,如果try中抛出的异常在catch中没有相对应的异常接收,就会交给gvm来处理,程序发生报错,停止运行
4.错误中的比较顶层的类Throwable和Exception
未来出现的异常是可以被 Throwable和Exception类捕获到的,但是我们是不建议这样的,抛出的异常,无法给到有效的异常提醒的,使得程序员无法快速查找到相应的错误位置,效果低,所以我们就提供了各种不同的异常,提供不同的异常抛出的异常信息,程序员根据异常信息,可以快速捕获到异常代码出现的位置,效果更高效,
finally关键字
public class Main1 {
public static void main(String[] args){
try{
int a = 10/0;
}catch(ArithmeticException e){
System.out.println("An error occurred");
}finally{
System.out.println("Finally");
}
}
}
//输出结果
//抛出算数异常
//finally 代码块执行
fianlly关键字的作用在于,无论try是否抛出异常finally关键字中的内容都会被执行的 ,一般是在和文件的使用有关,通常在用来关闭文件
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class Main8 {
private static void func(){
//打开一个文件
try{
InputStream inputStream = new FileInputStream("text.txt");
//读取文件内容
//...
int n = 10;
if(n > 0){
//一旦这里的return执行,后续的 .close() 语句将不会执行
return;
}
//或者抛出异常
if(n <= 0){
throw new FileNotFoundException();
}
//当文件使用完毕后,一定要进行关闭操作
// inputStream.close();
//如果代码跟这样写,则后续的 .close() 语句可能将将不会执行
//更好的做法是使用finally块来确保关闭操作的执行
} catch (IOException e) {
System.out.println("文件未找到");
} finally {
//关闭文件流
try {
inputStream.close();
}catch(IOException e){
}
}
}
public static void main(String[] args) {
func();
}
}
finally关键字中的return语句可以覆盖前面出现的已经执行到的return语句
public class Main10 {
private static int func(){
try{
return 1;
}catch(Exception e){
return 2;
}finally{
return 3;
}
}
public static void main(String[] args){
System.out.println(func());
}
}
//输出结果是:3
异常的分类
Java中把异常分成两个大类
1.受查异常:必须在代码中显示处理异常(try-catch,throws声明)
2.非受查异常:不可以显示处理
import java.io.IOException;
public class Main11 {
//声明可能抛出的异常
private static void func() throws IOException {
//IOException异常就是属于受查异常
//手动捕获异常
// try {
// throw new IOException("抛出一个:IOException异常");
// } catch (IOException e) {
//
// }
throw new IOException("抛出一个:IOException异常");
}
public static void main(String[] args) throws IOException {
//处理方式1,
// try{
// func();
// }catch(IOException e){
//
// }
//处理方式2,
//方法中如果有了throws IOException,则调用该方法时,必须也要throws IOException
func();
}
}