Java中的异常(java中的异常类)
lipiwang 2024-11-16 23:21 9 浏览 0 评论
异常
1,异常的定义
首先,从名字上就可以得出,异常,指的就是程序执行过程中发生生的不正常行为。
比如,我们之前经常遇到的算数异常,空指针异常等等。
public class TestDemo220606 {
public static void main(String[] args) {
//System.out.println(10/0);
//Exception in thread "main" java.lang.ArithmeticException: / by zero 除数为0,算数异常
String s1 = null;
s1.length();
//Exception in thread "main" java.lang.NullPointerException //空指针异常
}
}
2,异常的体系
异常的体系结构比较复杂,种类繁多,这里简单的从宏观上给大家总结下:
从图片中,我们可以看出:
1. Throwable:是异常体系的顶层类,其派生出两个重要的子类, Error 和 Exception
2. Error:指的是Java虚拟机无法解决的严重问题,它是错误,比如:JVM的内部错误、资源耗尽等,典型代表:StackOverflowError和OutOfMemoryError。形象的说法就是你的程序得了’‘癌症’‘。
3. Exception:异常产生后程序员可以通过代码进行处理,使程序继续执行。形象的说法就是你的程序得了”小感冒“。
3,异常的分类
从上面的图片也可以看出,异常分为两大类,编译时异常与运行时异常。
1,编译时异常,也称受查异常,在编译的时候发生。
2,运行时异常,也称受查异常,在程序运行的时候发生。
注意:在编写程序的过程中,出现的语法错误不是异常,这个点不要混淆了。
二,异常的处理
1,防御式编程
异常在代码中是经常存在的,所以我们就需要当异常发生时及时的通知程序员,主要方式如下:
1.1,事前防御型(LBLY)
在操作之前就做好充分的检查,如果没有问题才会继续。
boolean ret = false;
ret = 登陆游戏();
if (!ret) {
处理登陆游戏异常;
return;
}
ret = 开始匹配();
if (!ret) {
处理匹配异常;
return;
}
.....
但是这种方法的缺陷在于,我们的正常流程与异常的处理混在了一起,这样就导致代码的结构非常的不清晰。
1.2,事后认错型(EAFP)
先进行操作,遇到问题后再进行处理。
try {
登陆游戏();
开始匹配();
}catch (登陆游戏异常) {
处理登陆游戏异常;
} catch (开始匹配异常) {
处理开始匹配异常;
}
通过我们的EAFP的思想,这样就把整个程序的正常执行流程与异常的处理分开了,使得代码的结构会更加的清晰。其实,我们异常处理的核心思想就是EAFP思想。
2,异常的抛出
当我们在编写程序的时候,如果说出现了异常情况,此时就需要将异常的信息反馈给我们的程序员。在Java里面,我们借助关键字throw,抛出一个指定的异常对象,将错误信息反馈给调用者。
public class TestDemo220608 {
public static void func(int a){
if(a == 0){
throw new RuntimeException("a==0");//new 一个异常对象并抛出,一般情况下我们new的都是一个自定义类型的异常
}
}
public static void main(String[] args) {
int a = 0;
func(a);
}
}
可以看到,如果当a=0传入函数func后,就会抛出一个a ==0的异常,这个时候是我们的程序员手动地抛出的异常。如果出现异常但是我们没有手动抛出的时候,其实是JVM检测到后帮我们抛出的。一般情况下,我们手动抛出的都是我们自己定义的异常类型。
注意:
1,throw必须写在方法体的内部。
2,抛出的必须是Expection或者Expection的子类对象。
3,如果抛出的是运行时异常或者是其子类,那么就可以不用管,交给JVM处理就好。
4,如果你抛出的是一个受查异常,也就是编译时异常,那你就必须要手动地进行异常的声明,不然程序连编译都过不去。
public class TestDemo220608 {
public static void func(int a) throws CloneNotSupportedException {
//throws进行异常的声明
if(a == 0){
throw new CloneNotSupportedException();
}
}
public static void main(String[] args) throws CloneNotSupportedException{
int a = 0;
func(a);
}
}
5,异常一旦抛出,其后面的代码就不会执行了。
public class TestDemo220608 {
public static void func(int a) throws CloneNotSupportedException {
if(a == 0){
throw new CloneNotSupportedException();
}
}
public static void main(String[] args) throws CloneNotSupportedException{
int a = 0;
func(a);
System.out.println("这是异常后的逻辑部分");
}
}
我们可以看到当调用func抛出异常之后,主函数后面的内容就没有再执行了。
总结,其实抛出异常我们一般都是自定义类型的异常,按道理来说其实它的本质不能算是一个异常,只是在逻辑业务要求上它在这个情况下就是一个异常,所以我们就需要把它手动抛出,因为这个时候JVM层面上是不认为它是一个异常的,所以捕获不到,只能是进行手动的抛出。抛出的如果是一个编译时异常,那么我们就需要进行事先的声明,你得先让它通过编译,才能在运行的时候的特定情况下把异常抛出去。
3,异常的捕获
异常的捕获,也就是异常的具体处理方式。主要有两种:异常的声明throws,以及try - catch捕获处理。
1,异常声明throws
处在方法声明的参数列表之后,当方法中抛出编译时异常,此时用户不想处理该异常,此时就可以借助throws声明一下这个异常,然后让方法的调用者去处理。即当前方法不处理异常,提醒方法的调用者处理。
语法格式:
修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2...{
}
注意:
1,声明的异常必须是Expection或者Expection的子类。
2,方法内部如果抛出了多个编译时异常,那么参数列表后面throws需要声明多个异常,之间用逗号隔开。如果他们之间有父子类关系的话,那么就可以只声明父女类就好。
public class TestDemo220608 {
public static void func(int a) throws Exception {
if(a == 0){
throw new CloneNotSupportedException();
}
if(a == 1){
throw new FileNotFoundException();
}
}
public static void main(String[] args) throws Exception {
int a = 0;
func(a);
}
}
这里要抛出的是两个编译时异常,我们可以在方法的后面一个个进行声明,但是也可以直接声明它们的父类Exception就好。但是这只是一种方法,为了让程序更加的清晰明了,这种方法是不推荐的。
3,方法将编译时异常用throws进行声明之后,那么就需要我们的调用者去进行处理了。如果调用者也不想处理,那就继续用throws进行声明。
2,try - catch
对于throws而言,它其实对异常没有进行真正的处理,而是将其甩给了调用者,然后如果就这样一层层的甩出去的话,最终还是会交给到JVM的手上进行处理。如果想实现自己真正的处理,就需要使用try - catch。
public class TestDemo220608 {
public static void main(String[] args) {
String s1 = null;
try{
// 这是可能会发生异常的程序主体
s1.length();
}catch (NullPointerException e) {
// 这是对异常进行捕获并实际处理的部分
System.out.println("捕捉到一个空指针异常进行处理!");
}
System.out.println("这是其余的代码逻辑!");
}
}
这个时候,因为异常捕获到后是我们程序员自己进行处理的,所以整个程序在遇到异常后是不会结束运行的,所以其余的代码逻辑能够进行执行。但像之前,如果最后还是抛给了JVM处理的话,那程序就会直接结束掉。
当然,如果你try - catch捕获到的异常与你catch中定义的不是同一个类型,那照样还是处理不了,会交给JVM。
public class TestDemo220608 {
public static void main(String[] args) {
String s1 = null;
try{
// 这是可能会发生异常的程序主体
s1.length();
}catch (ArithmeticException e) {
// 这是对异常进行捕获并实际处理的部分
System.out.println("捕捉到一个算数异常进行处理!");
}
System.out.println("这是其余的代码逻辑!");
}
}
可以看到,JVM在处理异常后会把程序终止并且输出我们的异常的具体信息,那我们的try - catch同样也是可以把异常信息输出的。
public class TestDemo220608 {
public static void main(String[] args) {
String s1 = null;
try{
// 这是可能会发生异常的程序主体
s1.length();
}catch (NullPointerException e) {
// 这是对异常进行捕获并实际处理的部分
e.printStackTrace();//将异常信息输出
System.out.println("捕捉到一个空指针异常进行处理!");
}
System.out.println("这是其余的代码逻辑!");
}
}
try - catch可以捕获多个异常,但不是同时捕获,因为不会同时触发多个异常。
public class TestDemo220608 {
public static void main(String[] args) {
String s1 = null;
try{
// 这是可能会发生异常的程序主体
s1.length();
}catch (NullPointerException e) {
// 这是对异常进行捕获并实际处理的部分
e.printStackTrace();//将异常信息输出
System.out.println("捕捉到一个空指针异常进行处理!");
}catch (ArithmeticException e1) {
e1.printStackTrace();
System.out.println("捕获到一个算数异常进行处理!");
}
System.out.println("这是其余的代码逻辑!");
}
}
如果我们要捕获的多个异常之间存在父子类关系,那一定是子类在前,父类在后。因为父类异常类包含所有的子类异常类,所以你把父类写在前面,会把所有有关的异常都会进行捕获,那么后面写的子类的捕获也就没有任何的意义了。
如图,Exception类就包含空指针异常以及算数异常,所以后面的catch没有任何的作用,会报错。
正确的写法只能是:
public class TestDemo220608 {
public static void main(String[] args) {
String s1 = null;
try{
// 这是可能会发生异常的程序主体
s1.length();
} catch (NullPointerException e) {
// 这是对异常进行捕获并实际处理的部分
e.printStackTrace();//将异常信息输出
System.out.println("捕捉到一个空指针异常进行处理!");
}catch (ArithmeticException e1) {
e1.printStackTrace();
System.out.println("捕获到一个算数异常进行处理!");
}catch (Exception e2) {
e2.printStackTrace();
System.out.println("捕捉到了除算数异常,空指针异常外的其他异常!");
}
System.out.println("这是其余的代码逻辑!");
}
}
把Exception放到最后,能够捕获除开算数异常,空指针异常外的其他异常。当然有的同学可能会说直接用一个catch,里面的异常类型放Exception,这样就可以直接捕获所有的异常。但是这种方法是不好的,因为范围太广了,这样你必须得把异常信息打印一遍你才能知道这是一个什么异常。
同样的,对于一些编译时异常,我们也可以通过try - catch进行捕获处理。
class Person implements Cloneable{
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class TestDemo220608 {
public static void main(String[] args) {
try{
Person p1 = new Person();
Person p2 = (Person)p1.clone();
}catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
如果说在之前,那么main函数的参数列表后面是必须要用throws进行这个编译时异常的声明的,因为方法要声明一次,调用者也要声明一次,并且最终会由JVM进行处理,但是现在有了try - catch后,我们可以直接捕获到这个异常并且由我们自己进行处理。
【注意】
try块和之前的throw手动抛异常一样,try块中抛出异常的位置之后的代码是不会执行的。
public class TestDemo220608 {
public static void main(String[] args) {
String s1 = null;
try{
// 这是可能会发生异常的程序主体
s1.length();
System.out.println("hahahaha");//这一句是不会执行的
}
catch (NullPointerException e) {
// 这是对异常进行捕获并实际处理的部分
e.printStackTrace();//将异常信息输出
System.out.println("捕捉到一个空指针异常进行处理!");
}
System.out.println("这是其余的代码逻辑!");//这一句会执行
}
}
其实这个过程就相当于try中抛出异常被catch捕获然后并处理后,就直接顺着catch往后执行了,所以之前try块中的抛出异常后的部分根本就执行不到。
3,finally
当我们在写代码的时候,有些特定的代码是必须要执行的,不管程序是否发生异常,比如我们打开的资源需要进行回收。但是异常的处理会导致程序执行发生跳转,那么可能有的代码就执行不到,所以就需要使用finally。
public class TestDemo220608 {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
try{
// 这是可能会发生异常的程序主体
String s1 = null;
s1.length();
}
catch (NullPointerException e) {
// 这是对异常进行捕获并实际处理的部分
e.printStackTrace();//将异常信息输出
System.out.println("捕捉到一个空指针异常进行处理!");
}finally{
scan.close();
System.out.println("在finally进行资源的关闭!");
}
System.out.println("这是其余的代码逻辑!");
}
}
可以看到,当我们发生异常之后,finally中的内容依然是被执行的,完全点来说,finally中的内容不管你是否发生异常,都会被执行,所以可以很好的用来关闭一些资源。
那看到这,大家可能会有疑问,那既然其余的代码逻辑也能执行,要finally干啥,直接在后面进行操作不是一样的吗?那你看看这段代码:
public class TestDemo220608 {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
try{
// 这是可能会发生异常的程序主体
String s1 = null;
s1.length();
}
catch (NullPointerException e) {
// 这是对异常进行捕获并实际处理的部分
e.printStackTrace();//将异常信息输出
System.out.println("捕捉到一个空指针异常进行处理!");
return;//在这里直接return掉
}finally{
scan.close();
System.out.println("在finally进行资源的关闭!");
}
System.out.println("这是其余的代码逻辑!");
}
}
可以看到,当我们将异常处理好后,直接使用return结束掉程序的执行的话,那你后买你的其余代码逻辑是不是都执行不到了,但是你看即使是这样,finally中的内容还是被执行了,所以,由此可以看出,finally中的内容,无论如何都会执行,用来进行一些必须代码的执行最好不过。
总结,finally 执行的时机是在方法返回之前(try 或者 catch 中如果有 return 会在这个 return 之前执行 finally). 但是如果 finally 中也存在 return 语句, 那么就会执行 finally 中的 return,从而不会执行到 try 中原有的 return。(但是在finally中写return不是一个好的习惯)
:white_check_mark:【面试题:】
throw 与 throws 的区别?
throw是直接抛出一个异常,多用于抛出自定义类型的异常,抛出的异常会直接被JVM处理,此时程序也会结束执行。throws是声明一个异常,此时只是让方法本身不用去处理这个异常,而让调用者去处理,如果调用者也不处理继续使用throws最终会被JVM处理,throws可以让程序暂时编译层面上不会报错。
4,异常的处理流程
【调用栈】
关于 “调用栈” 方法之间是存在相互调用关系的, 这种调用关系我们可以用 “调用栈” 来描述. 在 JVM 中有一块内存空间称为"虚拟机栈" 专门存储方法之间的调用关系. 当代码中出现异常的时候, 我们就可以使用 e.printStackTrace() 的方式查看出现异常代码的调用栈。
public class TestDemo220608 {
public static void func(){
String s1 = null;
s1.length();
}
public static void main(String[] args) {
try{
func();
}catch (NullPointerException e) {
e.printStackTrace();
}
}
}
因为是20行func内部s1.length(),这里就是出现了空指针异常,然后main函数中调用了func,所以24行也会显示有错误。根据这一点,我们以后在解决异常的时候,看报的第一行错误就好,一般只要找到报的第一行的异常的位置,然后将其解决掉程序就正常了。
:white_check_mark:总结:异常的处理流程
1,首先是执行try块中的代码,看是否有异常。
2,如果发生异常,终止try块中的代码执行,去catch中进行异常的匹配。
3,如果catch中匹配到了异常类型,那就执行catch中的代码将异常解决。
4,如果catch中没有匹配到异常类型,那就将异常传递给上层调用者。
5,当然对于finally而言,无论是否匹配到,都会执行。
6,如果上层调用者也没有具体的解决措施,那就继续往上层调用者传递。
7,最终如果传递到main函数,而main函数也没有解决措施,那就会直接交给JVM处理,届时程序也会被终止执行。
5,自定义异常类
Java中虽然已经给我们提供了很多的异常类,但是考虑到实际开发过程中的遇到的一些特殊的异常情况,此时需要我们自己去定义这么一个异常类。
class myException extends RuntimeException{
//这是一个自定义异常类
public myException() {
}
public myException(String message) {
super(message);//可能会需要输出异常的具体信息,所以要有一个有参的构造
}
}
public class Test {
public static void isLegal(String s){
if(s.length() > 5){
throw new myException("用户名长度超过限制!");
}else{
System.out.println("登录成功!");
}
}
public static void main(String[] args) {
// 模拟设置用户名中的异常
Scanner scan = new Scanner(System.in);
String s = scan.next();
try{
isLegal(s);
}catch (myException e){
e.printStackTrace();
}finally {
scan.close();
}
}
}
正如上面的代码所展示,我们可以自定义异常类,如果是继承于Exception类,那么你自定义的这个异常就是一个受查异常,如果是继承于RuntimeExpection,那么你自定义的就是一个非受查异常。这个自定义异常类的内部,根据你自己的需求去定义它的构造函数。
相关推荐
- 前端入门——css 网格轨道详细介绍
-
上篇前端入门——cssGrid网格基础知识整体大概介绍了cssgrid的基本概念及使用方法,本文将介绍创建网格容器时会发生什么?以及在网格容器上使用行、列属性如何定位元素。在本文中,将介绍:...
- Islands Architecture(孤岛架构)在携程新版首页的实践
-
一、项目背景2022,携程PC版首页终于迎来了首次改版,完成了用户体验与技术栈的全面升级。作为与用户连接的重要入口,旧版PC首页已经陪伴携程走过了22年,承担着重要使命的同时,也遇到了很多问题:维护/...
- HTML中script标签中的那些属性
-
HTML中的<script>标签详解在HTML中,<script>标签用于包含或引用JavaScript代码,是前端开发中不可或缺的一部分。通过合理使用<scrip...
- CSS 中各种居中你真的玩明白了么
-
页面布局中最常见的需求就是元素或者文字居中了,但是根据场景的不同,居中也有简单到复杂各种不同的实现方式,本篇就带大家一起了解下,各种场景下,该如何使用CSS实现居中前言页面布局中最常见的需求就是元...
- CSS样式更改——列表、表格和轮廓
-
上篇文章主要介绍了CSS样式更改篇中的字体设置Font&边框Border设置,这篇文章分享列表、表格和轮廓,一起来看看吧。1.列表List1).列表的类型<ulstyle='list-...
- 一文吃透 CSS Flex 布局
-
原文链接:一文吃透CSSFlex布局教学游戏这里有两个小游戏,可用来练习flex布局。塔防游戏送小青蛙回家Flexbox概述Flexbox布局也叫Flex布局,弹性盒子布局。它决定了...
- css实现多行文本的展开收起
-
背景在我们写需求时可能会遇到类似于这样的多行文本展开与收起的场景:那么,如何通过纯css实现这样的效果呢?实现的难点(1)位于多行文本右下角的展开收起按钮。(2)展开和收起两种状态的切换。(3)文本...
- css 垂直居中的几种实现方式
-
前言设计是带有主观色彩的,同样网页设计中的css一样让人摸不头脑。网上列举的实现方式一大把,或许在这里你都看到过,但既然来到这里我希望这篇能让你看有所收获,毕竟这也是前端面试的基础。实现方式备注:...
- WordPress固定链接设置
-
WordPress设置里的最后一项就是固定链接设置,固定链接设置是决定WordPress文章及静态页面URL的重要步骤,从站点的SEO角度来讲也是。固定链接设置决定网站URL,当页面数少的时候,可以一...
- 面试发愁!吃透 20 道 CSS 核心题,大厂 Offer 轻松拿
-
前端小伙伴们,是不是一想到面试里的CSS布局题就发愁?写代码时布局总是对不齐,面试官追问兼容性就卡壳,想跳槽却总被“多列等高”“响应式布局”这些问题难住——别担心!从今天起,咱们每天拆解一...
- 3种CSS清除浮动的方法
-
今天这篇文章给大家介绍3种CSS清除浮动的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。首先,这里就不讲为什么我们要清楚浮动,反正不清除浮动事多多。下面我就讲3种常用清除浮动的...
- 2025 年 CSS 终于要支持强大的自定义函数了?
-
大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!1.什么是CSS自定义属性CSS自...
- css3属性(transform)的一个css3动画小应用
-
闲言碎语不多讲,咱们说说css3的transform属性:先上效果:效果说明:当鼠标移到a标签的时候,从右上角滑出二维码。实现方法:HTML代码如下:需要说明的一点是,a链接的跳转需要用javasc...
- CSS基础知识(七)CSS背景
-
一、CSS背景属性1.背景颜色(background-color)属性值:transparent(透明的)或color(颜色)2.背景图片(background-image)属性值:none(没有)...
- CSS 水平居中方式二
-
<divid="parent"><!--定义子级元素--><divid="child">居中布局</div>...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- maven镜像 (69)
- undefined reference to (60)
- zip格式 (63)
- oracle over (62)
- date_format函数用法 (67)
- 在线代理服务器 (60)
- shell 字符串比较 (74)
- x509证书 (61)
- localhost (65)
- java.awt.headless (66)
- syn_sent (64)
- settings.xml (59)
- 弹出窗口 (56)
- applicationcontextaware (72)
- my.cnf (73)
- httpsession (62)
- pkcs7 (62)
- session cookie (63)
- java 生成uuid (58)
- could not initialize class (58)
- beanpropertyrowmapper (58)
- word空格下划线不显示 (73)
- jar文件 (60)
- jsp内置对象 (58)
- makefile编写规则 (58)