目录
概念:程序执行过程中发生的不正常行为。
观察异常:异常时发生在程序执行过程中,碰到某些代码执行不下去,称为异常。
语法的错误不是异常,编译阶段就报错的,程序并没有执行。
一场产生后会明确告诉异常产生的原因以及出错的代码位置,出现在哪个原文件的第几行代码。出现异常之后的代码就无法正常执行了,之前的还可以正常执行。
出现多个异常的时候只会出现一个异常,异常是有逻辑上的优先级的。
一、程序开发中三大常见异常
1、数组越界异常
索引下标非法
public class ExceptionLearn {
public static void main(String[] args) {
int[] num = new int[3];
System.out.println(num[3]);
}
}
因为数组只有三个数字并且下标是从零开始的所以查找下标为3的数组会出现数组越界的情况。
2、NPE
空指针异常,通过一个值为null的引用调用成员方法和属性。
public class ExceptionLearn {
public static void main(String[] args) {
String str = null;
str.charAt(1);
}
}
3、类型转换异常
发生在两个毫无关系的类之间对象的转换(要发生向下转型首先得发生向上转型)。
二、异常体系核心的两个父类
在java中异常也是类,一切皆对象。程序抛出的异常其实抛出的是异常对象,只不过这个对象是有JVM产生并返回的。
1、Error
属于非受查异常:在编译阶段可以不进行任何的异常处理,但是在程序执行过程中出现的异常。除了非受查异常其他都属于受查异常:在程序编译阶段必须显示异常处理的异常,之后才能运行起来。
JVM无法解决的严重问题,当出现Error问题时,程序无法解决只能退出。
栈溢出(StackOverFlowError)和堆溢出(OutOfMemoryError)属于受查异常。
2、Exception
异常产生后,程序员可以通过异常的处理流程来解决此类问题,程序能继续执行。
三大类常见异常都属于Exception以及其子类。
三、异常处理
1、捕获并处理异常。
try catch finally代码块
可能产生的异常放在try代码块中,若捕捉到了相应类型的异常对象如何处理放在catch代码块中,无论是否有异常产生或者是否处理异常最终一定会执行的代码放在finally代码块中。
(1)try catch组合代码块
public static void tryCode(){
System.out.println("异常代码之前的代码");
int[] num = new int[4];
try {
System.out.println(num[0]);
System.out.println("try中的其他代码块");
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("捕获到数组越界异常");
}
System.out.println("异常产生之后的代码");
}
a.当没有异常产生时,不走catch代码块(输出num[0]时)
b.当异常产生(输出num[4]时),且被正确的捕获到,走相应的catch代码块,try代码块中从出现异常之后的代码不再执行,异常体系之后的代码能够正常执行。
有异常处理之后,保证程序抛出异常之后,正确捕获该异常,就可以是的异常体系之后的代码可以继续执行。
c.关于catch快捕获异常的说明
异常也是类,在catch代码块中只能捕获一种类型的异常,当try中的出现的异常,在catch块中并没有对应,就会报出异常。
public static void tryCode(){
System.out.println("异常代码之前的代码");
int[] num = new int[4];
try {
num = null;
System.out.println("try中的其他代码块");
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("捕获到数组越界异常");
}
System.out.println("异常产生之后的代码");
}
这种时候就需要多个catch代码块。
public static void tryCode(){
System.out.println("异常代码之前的代码");
int[] num = new int[4];
try {
num = null;
System.out.println(num[0]);
System.out.println("try中的其他代码块");
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("捕获到数组越界异常");
}catch(NullPointerException e){
System.out.println("捕获到空指针异常");
}
System.out.println("异常产生之后的代码");
}
当try中可能会产生多种异常时,可以使用多个catch块来捕获,也可以捕获也可以捕获异常的父类,通过向上转型来进行异常捕获。
public static void tryCode(){
System.out.println("异常代码之前的代码");
int[] num = new int[4];
try {
num = null;
System.out.println(num[4]);
System.out.println("try中的其他代码块");
}catch (Exception e){
System.out.println("捕捉到异常");
}
System.out.println("异常产生之后的代码");
}
这种情况下能捕获到所有Exception类的子类的异常。但是这种捕获并不推荐,最好还是捕获具体的子类。
若捕获到相应的异常之后,输出错误产生的原因以及出错的位置,通过异常对象的printStackTrace方法来进行打印输出。
public static void tryCode(){
System.out.println("异常代码之前的代码");
int[] num = new int[4];
try {
num = null;
System.out.println(num[0]);
System.out.println("try中的其他代码块");
}catch (Exception e){
System.out.println("捕捉到异常");
e.printStackTrace();
}
System.out.println("异常产生之后的代码");
}
若catch代码块中有多个分支,且多个分支有父子类关系的情况,一定是子类异常的捕获要写在父类异常之前。若在定义时,父类异常写在子类异常前面,会报错。
public static void tryCode(){
System.out.println("异常代码之前的代码");
int[] num = new int[4];
try {
num = null;
System.out.println(num[0]);
System.out.println("try中的其他代码块");
}catch(NullPointerException e){
System.out.println("捕获到空指针异常");
e.printStackTrace();
}catch (Exception e){
System.out.println("捕捉到异常");
e.printStackTrace();
}
System.out.println("异常产生之后的代码");
}
程序是出现了空指针异常,当遇到能捕获该异常的catch代码块就直接运行了,不会再去运行其他的catch。
多个catch块只会走一个,从上向下匹配,发现匹配的catch就进入,其他的不执行。
(2)cty catch finally组合代码块
a.没有捕获到异常
public static void finallyCode(){
System.out.println("异常代码之前的代码");
int[] num = new int[4];
try {
System.out.println(num[4]);
System.out.println("try中的其他代码块");
}finally{
System.out.println("finally代码块");
}
System.out.println("异常产生之后的代码");
}
b.使用catch捕捉到异常
public static void finallyCode(){
System.out.println("异常代码之前的代码");
int[] num = new int[4];
try {
System.out.println(num[4]);
System.out.println("try中的其他代码块");
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("捕获到数组越界异常");
}finally{
System.out.println("finally代码块");
}
System.out.println("异常产生之后的代码");
}
无论是否有异常产生,且是否有返回值,JVM保证finally代码块一定会执行。
当没有异常产生时走完finally代码块程序程序就会结束,体系之外的代码就不会执行,如果有异常会执行体系之外的代码。
以后写的所有的关于资源关闭的操作都放在finally代码块中,确保该资源一定会被关闭。
c.若finally中有return值
无论是否有异常产生,都会返回finally的返回值,因此一般不在finally中写return。
public static int finallyCode(){
System.out.println("异常代码之前的代码");
int[] num = new int[4];
try {
System.out.println(num[4]);
System.out.println("try中的其他代码块");
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("捕获到数组越界异常");
return 1;
}finally{
System.out.println("finally代码块");
return 2;
}
}
d.finally不执行的情况。
调用System.exit(0); 时将系统退出,JVM进程直接退出,则不执行finally。
2、抛回异常处理
若出现异常,不处理,将异常抛回给调用者处理。
(1)throws的使用方法
用在方法声明上,明确表示该方法可能产生某些异常,但是该方法不处理,若出现异常,将异常对象抛回给调用者处理。
如果在正给调用过程中,没有一个位置处理异常,最终这个异常对象就会抛回给JVM,然后整个程序退出。
关键点:
throws关键字在方法列表之后进行定义。
throws可以抛出多个异常,多个异常使用逗号分割,若有父子关系,只需抛出父类异常即可。
throws抛出的必须是Exception以及其子类。
若throws抛出的是受查异常,则调用者必须进行显式的异常处理(要么用try-catch捕获,要么继续通过throws向上输出)
a.继续通过throws向上输出
public static void main(String[] args) throws CloneNotSupportedException {
ThrowTest throwTest = new ThrowTest().clone();
}
public ThrowTest clone()throws CloneNotSupportedException{
return (ThrowTest) super.clone();
}
b. 用try-catch捕获
public static void main(String[] args) {
try{
ThrowTest throwTest = new ThrowTest().clone();
}catch (CloneNotSupportedException e){
e.printStackTrace();
}
}
public ThrowTest clone()throws CloneNotSupportedException{
return (ThrowTest) super.clone();
(2)throw的使用方法
用在方法内部,程序员在出现异常时,自己产生异常对象并向外抛出(原来异常对象是由JVM产生的,现在程序员自己产生对象)一般搭配自定义异常使用。
抛出异常之后的代码不能被执行。
a.自定义异常并且不被捕获
public static void main(String[] args) {
throwTest();
}
public static void throwTest(){
System.out.println("异常代码之前的代码");
throw new RuntimeException("抛个异常");
}
b.自定义异常也可以被捕获
public static void throwTest(){
System.out.println("异常代码之前的代码");
try{
throw new RuntimeException("抛个异常");
}catch (RuntimeException e){
System.out.println("捕获异常");
}
System.out.println("异常产生之后的代码");
}
若throw抛出的是受查异常,则调用者必须进行显式的异常处理(要么用try-catch捕获,要么继续通过throws向上输出)
无论是那种方式产生的对象,JVM产生的还是自己throw new出来的对象,跟到底怎么产生异常对象无关。
到底是否需要显式进行异常处理,要看这个异常对象是受查异常还是非受查异常。只要是抽查异常,无论那种方式产生的异常对象,都必须显式进行异常处理。
四、自定义异常
JDK内部提前定义好了很多的内置异常,这些异常在具体的业务场景下是不够用的。
在写具体项目时会有很多问题是和场景有关的,例如用户名错误,这些问题JDK无从得知,所以就需要继承异常类,拓展属于自己的异常类。
需要拓展的是非受查异常,继承RuntimeException类
若需要拓展的是受查异常,继承Exception类。
1、自定义异常实现登录
public class MeException {
static String userName = "洋洋";
static String password = "123456";
public static void main(String[] args) throws PasswordException, UserNameException {
Scanner s = new Scanner(System.in);
System.out.println("请输出您的用户名:");
String name = s.nextLine();
System.out.println("请输出您的密码:");
String pass = s.nextLine();
Login(name,pass);
}
public static void Login(String userName,String password) throws PasswordException, UserNameException {
if(!password.equals(MeException.password)){
throw new UserNameException("用户名错误!");
}
if(!userName.equals(MeException.userName)){
throw new PasswordException("密码错误");
}
System.out.println("登陆成功!");
}
}
class UserNameException extends Exception{
public UserNameException(String message){
super(message);
}
}
class PasswordException extends Exception{
public PasswordException(String message) {
super(message);
}
}
在上述代码中定义了两个类做Exception的子类,借用他们抛出异常,在login方法中抛出用户名错误和密码错误两个异常。
当用户名和密码都输入正确时,会提示登陆成功。
当密码输入错误时,会抛出PasswordException异常,提示密码错误。
当密码和用户名都输入错误的时候,只会抛出一个异常 UserNameException,并且会提示用户名错误。