异常

一.异常

1.1 概念

异常, 就是不正常的意思

在程序中的意思就是 : 在程序的执行过程中,出现的非正常的情况,最终会导致 JVM的非正常停止

在Java等面向对象的编程语言中,异常本身就是一个类, 产生异常就是创建异常对象并抛出了一个异常对象. Java处理异常的方式是中断处理

如, 在程序的执行过程中出现了空指针异常,就会创建一个空指针异常的对象,然后把他交给虚拟机处理,虚拟机就会中断我们的程序, 并在控制台打印出空指针异常的信息异常指的并不是语法错误,语法错了,编译不能通过,不会产生字节码文件,根本不能运行

同理, 逻辑错误也不是异常

1.2 异常体系

异常机制其实是帮助我们找到程序中的问题,异常的根类是java.lang.Throwable,其下有两个子类:java.lang.Error ——>工程师不能处理,只能尽量避免

java.lang.Exception ——>由于使用不当导致,可以避免这就是即平常所说的异常

Throwable(可抛出的) 体系:

● Error 严重错误,无法通过处理的错误 , 只能事先避免. 好比绝症

如内存溢出,系统崩溃. 是系统内部的错误,系统问题

● Exception 表示异常,异常产生后程序员可以通过代码方式纠正, 使程序继续运行, 是必须要处理的. 可分为RuntimeException和IOException等Throwable: 是java语言中所有错误或异常的超类, Error和Exception是他两个子类的实例

Throwable中的常用方法:

● public void printStackTrace ():打印异常的详细信息

● public String getMessage():获取发生异常的原因 (提示给用户的时候,就提示错误原因)

~~● public String toString():获取异常的类型和异常描述信息(不用)~~

出现异常不慌, 把异常的简单类名拷贝到API中去查

我们平常说的异常就是指Exception , 因为这类异常一旦出现,我们就要对代码进行修正,修复程序.

异常的体系

​ 根: Throwable

​ Error类 Exception

​ checkedException RuntimeException

● Error: 如创建了一个超级大的数组, 系统为JVM分配的内存不够而导致的内存溢出异常,只能通过修改代码改变

● checked Exception: checked异常 :在编译时期,就会检查,如果没有处理异常则编译失败

● unchecked Exception: 运行时期异常( runtime异常 ) 即在运行时期,检查异常.

​ 在编译时期,运行异常不会编译器检测(不报错) ------>例如数学异常

错误 Error

● Error: 如创建了一个超级大的数组, 系统为JVM分配的内存不够而导致的内存溢出异常,只能通过修改代码改变

//几个Error错误public static void main (String [] args) {

//java.lang.StackOverflowError 栈溢出错误. main(args);

//java.lang.OutOfMemoryError OOM错误. Integer[] arr = new Integer[1024 * 1024 * 1024];

}

//所以无法通过代码处理

1.3 异常的产生过程解析

异常代码示例

public class ArrayTools {

public static int getElement (int [] arr , int index){

int element = arr[index];

return element;

}

}

//测试类public class ExceptionDemo {

public static void main(String [] args){

int []arr = {34,35,35,35,23};

int num = ArrayTools.getElement(arr,5);

System.out.println("num" + num);

System.out.println("over");

}

}

二.异常的处理机制

2.0 引入

在程序员编写程序时, 就考虑到异常的检测/ 错误消息的提示/ 以及错误的处理

捕获错误最好的时机就是在编译时期, 但有的错误只有在运行时才会发生 . 比如: 除数为 0 / 数组下标越界

java异常处理的五个关键字: try , catch , finally , throw , throws

在编写程序时, 经常要在可能出现错误的地方加上检测的代码.

比如进行x/y的计算时, 要检测分母为0, 数据为空, 输入的不是数据而是字符等.

过多的if- else 分支会导致程序的代码加长, 臃肿, 可读性差.

因此采用异常处理机制, 将异常处理的程序代码集中在一起, 与正常的程序代码分开, 使得程序简洁优雅, 并易于维护.

异常的处理 : 抓抛模型过程1——“ 抛 ”: 程序在正常执行的过程中, 一旦出现异常, 就会在异常代码处生成一个对应异常类的对象, 并将此对象抛出给调用者. 一旦抛出对象以后, 其后的代码便不再执行.

过程2——" 抓 ": 可以理解为异常的处理方式, 即抓住抛出的对象进行处理 .@1 try - catch -finally: 比较靠谱, 真正的把异常处理掉了

@2 throws: 只是把异常抛给了方法的调用者, 并没有真正的将异常处理掉

2.1 机制一 : try-catch-finally

如果想要解决异常, 而不是往外抛, 可以使用try - catch

问题. catch没捕获到抛到哪里

2.1.1 try-catch-finally的使用

try {

//可能出现异常的代码 }catch(异常类型1 变量名1){

//处理异常的方式1 变量名1.printStackTrace();

}catch(异常类型2 变量名2){

//处理异常的方式2 }

...

//finally是可选的 finally{

//一定会执行的代码 }

2.1.2 执行流程:如果try中的代码没有异常, 那么代码执行完try中的内容后就会跳过catch, 继续往下执行

如果try中的代码有异常, 并且catch捕获到了这个异常, 代码会从try直接执行到catch

如果try中的代码有异常, 并且catch没有捕获到这个异常, 异常依旧会往外抛

2.1.3 注意事项使用try将可能出现异常的对象包装起来, 执行过程中,一旦出现异常, 就会生成一个异常类的对象, 根据此对象的类型, 去catch中进行匹配.

一旦try中的异常对象匹配到catch时, 就进入catch中进行处理, 一旦处理完成, 就跳出当前的try-catch结构. 继续执行其后的代码

catch中的异常类型, 如果没有子父类关系, 则声明顺序不作要求.

但是如果有子父类关系, 那么子类一定声明在父类的上面 否则报错

常用的异常对象处理方式 :e.printStackTrace();: 常用

System.out.println(e.getMessage());

在try{ }中声明的变量, 出了try结构以后就不能再被调用

try中在异常之前的代码还是会执行, 遇到异常再抛出异常, 被catch捕获, 执行catch的内容, 如果没有被catch捕获到, 则依旧把异常往外抛.体会: 我们使用try - catch - finally 处理编译时异常, 使得程序在编译时不再报错, 但是运行时仍可能报错.

相当与我们使用try - catch - finally将一个编译时可能出现的异常, 延长到运行时出现

2.2 机制二 : throws + 异常类型

throw: 用来手动抛出异常.

throws: 用来进行异常声明, 声明某个方法有可能会抛出异常. ( 甩锅的方式 )

如果调用了使用throws声明异常的方法, 调用者要么解决异常, 要么也使用throws进行声明.

如果一个方法内抛出了编译时异常( checked Exception ), 那么必须要使用throws进行异常声明.

如果方法中抛出了运行时异常( unchecked Exception ) ,可以加throws 也可以不加.

如果方法中有可能抛出多个异常, 那么需要使用throws进行多异常声明, 或者直接声明他们的父类.

throws的使用格式

修饰符 返回值类型 方法名 ( 参数列表 ) throws 异常类名1, 异常类名2, 异常类名3... {

//方法体

}

2.2.1 throws + 异常类型的使用

package drafts.Throws;

import java.io.File;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.IOException;

public class ThrowsDemo01 {

//main方法调用方法1, 如果再抛出异常, 那么就会把异常交给JVM处理, JVM就会终止程序, 所以在main方法中必须要对抛出的异常进行处理

public static void main(String[] args) {

//用try - catch - finally 捕获处理异常 try {

method1();

} catch (IOException e) {

e.printStackTrace();

}

System.out.println("程序还能执行");

}

//方法method1()的内容是调用方法2, 但还是抛出异常不做处理 public static void method1() throws FileNotFoundException, IOException {

method2();

}

//方法method2()把异常抛出, 不在方法2里处理 public static void method2() throws FileNotFoundException, IOException {

File file = new File("HELLO.txt");

FileInputStream fis = new FileInputStream(file);

int data = fis.read();

while (data != -1) {

System.out.println((char) data);

data = fis.read();

}

fis.close();

}

}

2.2.2 机制二的流程" throws + 异常类型 " 写在方法的声明处, 指明此方法执行时, 可能会抛出的异常类型

一旦当方法体执行时, 出现异常, 仍会在异常代码处生成一个异常类对象, 此对象满足throws

后异常类型时, 就会被抛出. 异常后续的代码就不再执行了 !

对当前方法来讲, 异常的处理就结束了子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型

父类如果没有抛出异常, 子类也不能抛

2.3 二者如何选择

开发中如何选择使用 try - catch - finally 还是使用 throws ?如果父类中被重写的方法没有throws 方式处理异常, 则子类重写的方法也不能使用throws, 意味着如果子类

重写的方法中有异常, 必须使用try - catch - finally处理

执行的方法 a 中, 先后又调用了另外的几个方法, 而这几个方法又是递进关系执行的, 我们建议这几个方法使用

throws的方式进行处理, 而执行的方法 a 可以考虑用try - catch - finally方式进行处理

以上2者选一种使用就可以了.

三. 手动抛出异常

关于异常 对象的产生:系统自动生成异常对象

手动的生成一个异常对象, 并抛出 ( throw )

throw关键字:

作用: 用来手动抛出一个异常

格式: throw new 异常类名();

throw new ArrayIndexOutOfBoundsException ( msg )

3.1 抛出异常throw

编写程序是需考虑程序出现问题的情况, 如传递数据的时候,如果用户传递了不合法的参数,那么就应告诉调用者,传递合法的数据进来.这时需要用抛出异常的方式来告诉调用者

在java中提供了一个throw关键字,用来在指定的方法中抛出一个指定的异常对象

具体操作:创建一个异常对象,封装一些提示信息(信息可以自己编写)

需要通过关键字throw将这个异常对象告知给调用者, throw用在方法内,用来抛出一个异常对象, 将这个异常对象传递到调用者处,并结束当前方法的执行.

使用格式:

​ throw new 异常名Exception ("异常产生原因") ;

注意:throw 关键字必须写在方法内部

throw 关键字后边new的对象必须是Exception或者Exception的子类对象

throw 关键字抛出指定的异常对象, 我们就必须处理这个异常对象throw 关键字后边创建的是RuntimeException 或者是RuntimeException的子类对象我们可以不处理, 默认交给JVM处理 (打印异常对象, 中断程序)

throw关键字后面创建的是编译异常, (写代码的时候报错), 我们就必须要处理这个异常, 要么try...catch要么throws仔细体会第三条的区别

例如:

throw new NullPointerException("要访问的arr数组不存在");

throw new ArrayIndexOutOfBoundsException("该索引在数组中不存在,已超出范围");

看一段程序案例:

public class ThrowDemo {

public static void main (String [] args) {

//创建一个数组 int [] arr = {2,1,4,5};

//根据索引找到对应元素 int index = 4;

int element = getElement(arr, index);

System.out.println(element);

System.out.println("over");

}

/** 根据 索引找到数组中对应的元素*/

public static int getElement(int [] arr,int index){

//判断, 索引是否越界 if(index < 0||index >arr.length-1){

//判断条件如果满足,当执行完throw抛出异常对象后,方法已经无法继续运算.这时就会结束当前方法的执行,并将异常告知给调用者,这时就需要通过异常来解决

throw new ArrayIndexOutOfBoundsException("哥们,角标越界了")

}

int element = arr[index];

return element;

}

}注意: 如果产生了问题,我们就会用throw将问题返回给该方法调用者

对于方法的调用者,处理方式有两种

● 进行捕获处理

● 继续将问题声明出去,使用throws(见下)声明处理

(了解) Objects非空判断

在JDK7 的时候, 提供了一个工具类叫做Objects, 这个类中提供了很多对对象进行操作的方法.

其中有一个方法可以判断对象是否为null, 这个方法叫做requireNonNull

还记得我们学习过一个类Objects吗,曾经提到过它由一些静态的实用方法组成,这些方法是null-save(空指针安全的)或null-tolerant(容忍空指针的),那么在它的源码中,将对象为null的值进行了抛出异常操作。

Objects类的静态方法:

public static T requireNonNull(T obj):查看指定引用对象是不是null, 如果是null, 那么抛出空指针异常

//requireNonNull方法的源码public static T requireNonNull(T obj) {

if (obj == null)

throw new NullPointerException();

return obj;

}

使用示例:

public class requireNonNullTest{

public static void main (String [] args) {

method(null);

public static void method (Object obj){

Objects.requireNonNull(obj)

//重载的方法,可以再返回一个提示信息message Objects.requireNonNull(obj,"传递的对象的值是Null")

//好处:不用再自己写if语句来判断Object是否为空 }

}

}

四. 编译时异常注意事项运行时异常被抛出可以不处理,即不捕获也不声明抛出.

如果父类没有抛出异常,子类重写父类该方法时也不能抛出异常,此时子类产生该异常,只能捕获处理,不能声明抛出

如果父类方法抛异常, 子类重写的方法可以抛( 要么抛出和父类一样的异常, 或者父类方法异常的子类异常 ), 当然也可以不抛

在try后面可以写多个catch, 用于多异常处理.

五. 自定义异常

5.1 概述

为什么要自定义异常类:

​ 我们说了Java中不同的异常类, 分别表示某一种具体的异常情况. 那么在开发中总是有些异常情况是SUN没有定义好的, 此时我们根据自己业务的异常情况来定义异常类. 例如年龄负数/ 考试成绩负数问题

异常类如何定义:自定义一个编译器异常, 自定义类, 并继承于java.lang.Exception.

自定义一个运行时期的异常类, 自定义类, 并继承与java.lang.RuntimeException.

5.2 自定义异常的练习

要求: 我们模拟注册操作, 如果用户名已经存在, 则抛出异常并提示 : 亲, 该用户名已被注册.

首先定义一个登录异常类LoginException :

public class LoginException extends Exception {

public LoginException(){

}

public LoginException(String message) {

super(message);

}

}

模拟登录操作, 使用数组模拟数据库中储存的数据, 并提供当前注册账号是否存在方法用于判断.

public class Demo {

// 模拟数据库中已存在账号 private static String[] names = {"bill","hill","jill"};

public static void main(String[] args) {

//调用方法 try{

// 可能出现异常的代码 checkUsername("nill");

System.out.println("注册成功");//如果没有异常就是注册成功 }catch(LoginException e){

//处理异常 e.printStackTrace();

}

}

//判断当前注册账号是否存在 //因为是编译期异常,又想调用者去处理 所以声明该异常 public static boolean checkUsername(String uname) throws LoginException{

for (String name : names) {

if(name.equals(uname)){//如果名字在这里面 就抛出登陆异常 throw new LoginException("亲"+name+"已经被注册了!");

}

}

return true;

}

}

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐