每天学一点之异常真不难

异常

在这里插入图片描述

1、异常概述

  • 异常 :指的是程序在执行过程中,出现的非正常的情况,如果不处理最终会导致JVM的非正常停止。

异常指的并不是语法错误。语法错了,编译不通过,不会产生字节码文件,根本不能运行。

异常也不是指逻辑代码错误而没有得到想要的结果,例如:求a与b的和,你写成了a-b

对于异常的发生,要么直接终止程序的运行,要么在编写程序时,预判到可能会出现异常的地方,做好针对性处理措施,当异常发生时,经异常处理后,可以保证程序还可以继续执行。

2、异常体系

异常的根类是java.lang.Throwable,Java提供的所有异常类均继承自此类,其下有两个子类:java.lang.Errorjava.lang.Exception,平常所说的异常指java.lang.Exception
在这里插入图片描述

Throwable体系:

  • Error:严重错误Error,一般因为是内存不足或程序存在严重逻辑问题,只能通过扩大内存或重新修改代码解决。只能事先避免,好比绝症。
    • 例如: StackOverflowErrorOutOfMemoryError
  • Exception:表示异常,其它因编程错误或偶然的外在因素导致的一般性问题,程序员可以通过相应预防处理措施,使程序发生异常后还可以继续运行。好比感冒、阑尾炎。
    • 例如:空指针访问、试图读取不存在的文件、网络连接中断、数组角标越界

Throwable中的常用方法:

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

    包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。

  • public String getMessage():获取发生异常的原因。

    提示给用户的时候,就提示错误原因。

3、异常分类

运行期异常(unchecked Exception):这类异常的发生多数是因为程序员编写的代码逻辑不够严谨造成的(如数组脚标越界异常),可以选择进行处理或不处理,最好是通过修正、优化代码避免异常的发生(或者使用异常处理简化复杂的逻辑判断代码)。

编译期异常(checked Exception):这类异常一般由程序之外的因素引起的(如程序读取的文件不存在、网络中断),而不是程序员写的代码逻辑有问题,所以程序员容易忽略对这类异常的处理,而恰恰这类异常又很常发生,所以Java要求针对这类可能发生的异常必须进行处理,否则编译无法通过。(只有java语言有需强制处理的异常)

常见的错误和异常演示示例

1、VirtualMachineError
最常见的就是:

StackOverflowError:虚拟机栈内存不足,无法分配栈帧所需空间。

OutOfMemoryError:没有足够的内存空间可以分配。

	@Test
	public void test01(){
		//StackOverflowError
		digui();
	}
	public void digui(){
		digui();
	}
	@Test
	public void test03(){
		//OutOfMemoryError
		StringBuilder s = new StringBuilder();
		while(true){
			s.append("atguigu");
		}
	}

2、运行时异常

@Test
	public void test01(){
	    //NullPointerException
		int[] arr=null;
		System.out.println(arr.length);
	}
	
	@Test
	public void test02(){
		//ClassCastException
		Person p = new Man();
		Woman w = (Woman) p;
	}
	
	@Test
	public void test03(){
		//ArrayIndexOutOfBoundsException
		int[] arr = new int[5];
		for (int i = 1; i <= 5; i++) {
			System.out.println(arr[i]);
		}
	}
	
	@Test
	public void test04(){
		//InputMismatchException
		Scanner input = new Scanner(System.in);
		System.out.print("请输入一个整数:");
		int num = input.nextInt();
	}
	
	@Test
	public void test05(){
		int a = 1;
		int b = 0;
		//ArithmeticException
		System.out.println(a/b);
	}

3、编译时异常

@Test
	public void test06() throws InterruptedException{
		Thread.sleep(1000);//休眠1秒
	}
	
	@Test
	public void test07() throws FileNotFoundException{
		FileInputStream fis = new FileInputStream("Java学习秘籍.txt");
	}
	
	@Test
	public void test08() throws SQLException{
		Connection conn = DriverManager.getConnection("....");
	}

4、异常的生成与抛出机制throw

异常对象的生成与抛出有两种方式:

由虚拟机自动生成:程序运行过程中,虚拟机检测到程序发生了问题,就会在后台自动创建一个对应异常类的实例对象并自动抛出
在这里插入图片描述
在这里插入图片描述
由此看出,异常对象被JVM创建后,在产生异常的方法中会自动抛出,抛给方法的调用者,抛给main方法,最后抛给虚拟机,虚拟机打印异常信息后终止程序。

由开发人员手动创建: Exception exception = new ClassCastException();——创建好的异常对象不抛出对程序没有任何影响,和创建一个普通对象一样,手动创建的异常对象需要手动抛出,才会对程序产生影响。

在Java中,使用throw 关键字手动抛出一个异常对象,throw用在方法内,将这个异常对象传递到方法调用者处,同时结束当前方法的执行。

使用格式:

throw new 异常类名(参数);

例如:

throw new NullPointerException(“要访问的arr数组不存在”);
throw new ArrayIndexOutOfBoundsException(“该索引在数组中不存在,已超出范围”);

注意:如果产生了问题,我们就会throw将问题描述类即异常进行抛出,也就是将问题返回给该方法的调用者。
那么对于调用者来说,该怎么处理呢?一种是进行捕获处理,另一种就是继续讲问题声明出去,使用throws声明处理。

5、异常的处理机制

1、捕获异常try…catch
捕获异常语法如下:

try{
编写可能会出现异常的代码
}catch(异常类型1 e){
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}catch(异常类型2 e){
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}

  • 可以有多个catch块,按顺序匹配。
  • 如果多个异常类型有包含关系,那么小上大下

获取异常信息:

捕获到了异常对象,就可以获取异常对象中封装的异常信息,Throwable类中定义了一些方法用于获取异常对象中的信息:

  • public String getMessage():获取异常的描述信息。

  • public void printStackTrace():打印异常的跟踪栈信息并输出到控制台。这些信息包含了异常的类型,异常信息,还包括异常出现的位置,在开发和调试阶段,建议使用printStackTrace。

2 、finally块
finally:在finally代码块中存放的代码都是一定会被执行的。由于异常会引发程序跳转,导致后面有些语句执行不到,如果一定要执行这些语句就可以使用finally,finally常用于释放系统资源。

比如:当我们在try语句块中打开了一些物理资源(磁盘文件/网络连接/数据库连接等),无论异常有没有发生,我们都要在使用完之后关闭已打开的资源,避免系统资源的浪费。

finally的语法:

try{
}catch(…){
}finally{
无论try中是否发生异常,也无论catch是否捕获异常,也不管try和catch中是否有return语句,都一定会执行
}

try{
}finally{
无论try中是否发生异常,也不管try中是否有return语句,都一定会执行。
}

注意:finally不能单独使用。

当只有在try或者catch中调用退出JVM的相关方法,例如System.exit(0),此时finally才不会执行,否则finally永远会执行。

面试题
public static void main(String[] args) {
		int test = test(3,5);
		System.out.println(test);//8
	}

	public static int test(int x, int y){
		int result = x;
		try{
			if(x<0 || y<0){
				return 0;
			}
			result = x + y;
			return result;
		}finally{
			result = x - y;
		}
	}

public class Test04 {
	static int i = 0;
	public static void main(String[] args) {
		System.out.println(test());//2
	}

	public static int test(){
		try{
			return ++i;
		}finally{
			return ++i;
		}
	}
}

6、声明异常throws

throws:用在方法上,表明此方法可能会产生的异常类型。
如果在某方法内通过抛出了必须要处理的编译期异常,有两种选择:要么在当前方法进行捕获处理,要么通过throws在当前方法上进行声明,让方法的调用者去处理。

声明异常格式:

修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{ }

throws用于进行异常类的声明,若该方法可能有多种异常情况产生,那么在throws后面可以写多个异常类,用逗号隔开。

7、自定义异常

异常类如何定义:
一般自定义一个异常比如UserException作为“根异常”,然后在此基础上再派生出不同的异常类型,自定义的“根异常”需要从一个合适的现有异常中派生出来,通常建议派生自java.lang.RuntimeException

"根异常"public class UserException extends RuntimeException {
 }
 "其他异常从”根异常“派生出来"public class UserExistedException extends UserException {
}

public class UserNotFoundException extends UserException {
}

自定义的“根异常”通常提供多个构造方法,直接调用父类的即可:

//用户异常类
public class UserException extends RuntimeException {
    public UserException() {
    }

    public UserException(String message) {
        super(message);
    }

    public UserException(String message, Throwable cause) {
        super(message, cause);
    }

    public UserException(Throwable cause) {
        super(cause);
    }

}

派生出的异常,提供简单两个构造方法,够用即可:

//用户已经存在异常类
public class UserExistedException extends UserException {
    public UserExistedException() {
    }
	
    public UserExistedException(String message) {
        super(message);
    }
}

代码演示:

public class DemoUserException {
    //字符串数组模拟数据库已存在的账号
    static String[] names = {"tom", "jack", "rose"};

    public static void main(String[] args) {
        registUser("tom");
    }

    //注册用户
    public static void registUser(String name) {
        int length = names.length;
        //验证是否注册过
        if (checkUserExist(name)) //已注册
            throw new UserExistException(name + "用户已存在,注册失败");
        else { //如果未注册过,才进行存储到数据库中(数组)
            System.out.println("注册成功!");
            names = Arrays.copyOf(names, length * 2);
            names[length] = name;
            for (int i = 0; i < names.length; i++) {
                System.out.println(names[i]);
            }
        }
    }
    //验证用户是否已注册
    public static boolean checkUserExist(String name) {
        for (int i = 0; i < names.length; i++) {
            if (names[i].equals(name))
                return true;//表示存在,已注册
        }
        return false;//表示未注册的用户
    }
}

小结:

  • 自定义异常从Exception类或者它的子类派生一个子类即可,通常建议从RuntimeException派生。
  • 自定义异常类通常至少包含2个构造器:一个是无参构造,另一个是带有详细信息的构造器
  • 自定义的异常只能通过throw关键字抛出。
  • 自定义异常最重要的是异常类的名字,当异常出现时,可以根据名字判断异常类型。

8、异常关键字和注意事项总结

在这里插入图片描述

异常处理注意事项

  • 编译期异常必须处理,要么捕获处理,要么声明在方法上,让调用者处理。
  • 运行时异常被抛出可以不处理。即不捕获也不声明抛出。
  • try语句范围要尽量小的包围在可能出现异常的一行或几行代码上,不要把大量无异常的代码一起包起来,虽然这样很省事。
  • catch语句捕获的异常类型要尽量小,尽量精准,好针对性的做处理。
  • 如果finally有return语句,永远返回finally中的结果,但要避免该情况.
  • 如果父类方法抛出了多个异常,子类重写父类方法时不能抛出更大异常,可以抛出和父类相同的异常或者是父类异常的子类或者不抛出异常。
  • 父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力奋斗的JAVA小余

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值