Java异常处理--异常处理的方式2:throws

2024-01-16 12:20
文章标签 java 异常 处理 方式 throws

本文主要是介绍Java异常处理--异常处理的方式2:throws,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 一、方式2:声明抛出异常类型(throws)
  • 二、throws基本格式
  • 三、 throws 使用举例
    • (1)针对于编译时异常
      • 1、案例1
      • 2、案例2
    • (2)针对于运行时异常
  • 四、 方法重写中throws的要求
    • (1)说明
    • (2)举例1
    • (3)举例2
  • 五、 两种异常处理方式的选择

一、方式2:声明抛出异常类型(throws)

  • 如果在编写方法体的代码时,某句代码可能发生某个编译时异常,不处理编译不通过,但是在当前方法体中可能不适合处理无法给出合理的处理方式,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。

image.png

  • 具体方式:在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。

二、throws基本格式

声明异常格式:在方法的声明处,使用"throws 异常类型1,异常类型2,..."

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

在throws后面可以写多个异常类型,用逗号隔开。

【举例1】

public void test() throws 异常类型1,异常类型2,.. {//可能存在编译时异常 (运行时异常不用管它)
}

【举例2】

public void readFile(String file)  throws FileNotFoundException,IOException {...// 读文件的操作可能产生FileNotFoundException或IOException类型的异常FileInputStream fis = new FileInputStream(file);//...
}

三、 throws 使用举例

(1)针对于编译时异常

1、案例1

以如下代码为例:

public class ThrowsTest {public void method1() {File file = new File("D:\\hello.txt");//可能报FileNotFoundException 文件找不到异常FileInputStream fis = new FileInputStream(file);  //流直接操作文件//把文件内容直接读到内存中输出int data = fis.read();    //可能报IOException 输入输出异常while (data != -1) {    //data为-1的时候退出,就是读完了System.out.print((char) data);data = fis.read();  //可能报IOException 输入输出异常}//资源关闭fis.close();  //可能报IOException 输入输出异常}
}

方法method1()中,是一段可能有异常的代码。

此时是编译时异常,必须要进行处理。

image.png

☕处理

在方法小括号后面加上throws,然后根据可能出现异常的类型,比如这里可能出现FileNotFoundExceptionIOException,那么就将它们写在throws后面即可,用逗号隔开。如下:

public class ThrowsTest {public void method1() throws FileNotFoundException, IOException {//...  ...}
}

可以看到现在编译器不报红了,这意味着已经将可能出现的异常处理了。

如下:
image.png


现在针对于method1()方法来说,已经处理了。

若是现在method1()方法在另一个方法method2()里面被调用了,发现此时报红了。如下:

image.png

这里相当于把异常抛给method1()的调用者了。

所以method2()需要处理一下:

public void method2() throws FileNotFoundException, IOException{method1();
}

可以看见,不报红了:

image.png

此时method2()又在method3()里面调用了,此时method3()也面临刚才同样的问题:

image.png

此时method3()觉得可以搞定这个问题,它就可以去解决啦:

public void method3() {try {method2();}catch (FileNotFoundException e){e.printStackTrace();}catch (IOException e){e.printStackTrace();}
}

若此时method3()在其他方法中也被调用了,比如在main方法中被调用了,此时不会出现异常,因为在method3()中将异常干掉了。
image.png

就相当于method1()那里面有一只狼,method1解决不了,就抛出来,向method2求助,method2也不行,就向method3求助,method3将狼干掉了。

所以main方法里面不再提示有异常要处理。

但是,若此时在main方法里面调用的是method2(),还是需要处理一下这个异常的,因为method2()抛出了异常。

image.png

到main方法就相当于到头了,此时最好不要再抛出去了,下面的代码处理方式就不可取:

public static void main(String[] args) throws FileNotFoundException, IOException{method3();method2();
}

此时就是将问题抛给虚拟机了,虚拟机也没有方案,程序就会挂掉。整个进程就停掉了。

所以实在不行,就只能这样处理了:

public static void main(String[] args){method3();try {method2();}catch (FileNotFoundException e){e.printStackTrace();}catch (IOException e){e.printStackTrace();}
}

不要在main方法里面写throws。

🌱代码

package yuyi02;import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;/*** ClassName: ThrowsTest* Package: yuyi02* Description:* 以编译时异常举例,运行时异常基本不做处理,直接改代码即可** @Author 雨翼轻尘* @Create 2024/1/13 0013 7:52*/
public class ThrowsTest {public static void main(String[] args){method3();try {method2();}catch (FileNotFoundException e){e.printStackTrace();}catch (IOException e){e.printStackTrace();}}public static void method3() {try {method2();}catch (FileNotFoundException e){e.printStackTrace();}catch (IOException e){e.printStackTrace();}}public static void method2() throws FileNotFoundException, IOException{method1();}public static void method1() throws FileNotFoundException, IOException {File file = new File("D:\\hello.txt");//可能报FileNotFoundException 文件找不到异常FileInputStream fis = new FileInputStream(file);  //流直接操作文件//把文件内容直接读到内存中输出int data = fis.read();    //可能报IOException 输入输出异常while (data != -1) {    //data为-1的时候退出,就是读完了System.out.print((char) data);data = fis.read();  //可能报IOException 输入输出异常}//资源关闭fis.close();  //可能报IOException 输入输出异常}
}

🎲问:是否真正处理了异常?

  • 编译是否能通过的角度看,看成是给出了异常万一要是出现时候的解决方案。此方案就是,继续向上抛出(throws)。
  • 但是,此throws的方式,仅是将可能出现的异常抛给了此方法的调用者。此调用者仍然需要考虑如何处理相关异常。从这个角度来看,throws的方式不算是真正意义上处理了异常。

2、案例2

package com.atguigu.keyword;public class TestThrowsCheckedException {public static void main(String[] args) {System.out.println("上课.....");try {afterClass();//换到这里处理异常} catch (InterruptedException e) {e.printStackTrace();System.out.println("准备提前上课");}System.out.println("上课.....");}public static void afterClass() throws InterruptedException {for(int i=10; i>=1; i--){Thread.sleep(1000);//本来应该在这里处理异常System.out.println("距离上课还有:" + i + "分钟");}}
}

(2)针对于运行时异常

throws后面也可以写运行时异常类型,只是运行时异常类型,写或不写对于编译器和程序执行来说都没有任何区别。如果写了,唯一的区别就是调用者调用该方法后,使用try…catch结构时,IDEA可以获得更多的信息,需要添加哪种catch分支。

package com.atguigu.keyword;import java.util.InputMismatchException;
import java.util.Scanner;public class TestThrowsRuntimeException {public static void main(String[] args) {Scanner input = new Scanner(System.in);try {System.out.print("请输入第一个整数:");int a = input.nextInt();System.out.print("请输入第二个整数:");int b = input.nextInt();int result = divide(a,b);System.out.println(a + "/" + b +"=" + result);} catch (ArithmeticException | InputMismatchException e) {e.printStackTrace();} finally {input.close();}}public static int divide(int a, int b)throws ArithmeticException{return a/b;}
}

四、 方法重写中throws的要求

(1)说明

方法重写时,对于方法签名是有严格要求的。复习:

1)方法名必须相同
(2)形参列表必须相同
(3)返回值类型
- 基本数据类型和void:必须相同
- 引用数据类型:<=4)权限修饰符:>=,而且要求父类被重写方法在子类中是可见的
(5)不能是staticfinal修饰的方法

当时说到方法的声明格式

权限修饰符 返回值类型 方法名(形参列表) [throws 异常类型] {//方法体 
}

方法在声明的时候,可能会抛出异常,关于父类被重写的方法和子类重写的方法,这里其实是有一个要求的:
子类重写的方法抛出的异常类型可以与父类被重写的方法抛出的异常类型相同,或是父类被重写的方法抛出的异常类型的子类


方法的重写的要求:(针对于编译时异常来说的)

子类重写的方法抛出的异常类型可以与父类被重写的方法抛出的异常类型相同,或是父类被重写的方法抛出的异常类型的子类

此外,对于throws异常列表要求:

  • 如果父类被重写方法的方法签名后面没有 “throws  编译时异常类型”,那么重写方法时,方法签名后面也不能出现“throws  编译时异常类型”。
  • 如果父类被重写方法的方法签名后面有 “throws 编译时异常类型”,那么重写方法时,throws的编译时异常类型必须 <= 被重写方法throws的编译时异常类型,或者不throws编译时异常。
  • 方法重写,对于“throws 运行时异常类型”没有要求。
package com.atguigu.keyword;import java.io.IOException;class Father{public void method()throws Exception{System.out.println("Father.method");}
}
class Son extends Father{@Overridepublic void method() throws IOException,ClassCastException {System.out.println("Son.method");}
}

(2)举例1

比如现在Son是Father的子类:

public class OverrideTest {}class Father{public void method1(){}
}class Son extends Father{@Overridepublic void method1() {}
}

假设父类Father中抛出了一个IOException的异常。对于子类Son来说,也可以抛出和父类被重写方法一样的异常。如下:

public class OverrideTest {}class Father{public void method1() throws IOException {}
}class Son extends Father{@Overridepublic void method1() throws IOException{}
}

一般开发中,都这样写成一样的。

其实,子类Son中,还可以抛一个IOException子类,比如FileNotFoundException。如下:

public class OverrideTest {}class Father{public void method1() throws IOException {}
}class Son extends Father{@Overridepublic void method1() throws FileNotFoundException {}
}

子类能抛出父类重写方法的异常的子类,而不能是父类重写方法的异常的父类。

image.png


🎲为什么会有上面那样的规则呢?

前面说过一个知识点叫做“多态性”,在多态性的场景下,比如现在声明一个变量f,在右边new了一个子类的对象,如下:

public class OverrideTest {public static void main(String[] args) {Father f=new Son();}
}

然后通过f来调用method1(),此时method1()可能存在编译时异常,需要处理。

image.png

这时候,我们加一个try,将有可能出现异常的代码放入try中:

public static void main(String[] args) {Father f=new Son();try {f.method1();}
}

然后要在catch中写上可能出现的异常,这时候要看父类中的可能异常,因为编译的时候它认为调用的是父类的方法。所以此时要看父类中的IOException

如下:

package yuyi02;import java.io.FileNotFoundException;
import java.io.IOException;/*** ClassName: OverrideTest* Package: yuyi02* Description:** @Author 雨翼轻尘* @Create 2024/1/13 0013 15:02*/
public class OverrideTest {public static void main(String[] args) {Father f=new Son();try {f.method1();}catch (IOException e){e.printStackTrace();}}
}class Father{public void method1() throws IOException {}
}class Son extends Father{@Overridepublic void method1() throws FileNotFoundException {}
}

真正运行的时候,实际调用的是子类的方法。调用了子类重写的method1()之后,抛出的异常不能比父类的大,要不然catch不了。
子类的方法在执行的时候抛出的异常父类异常的子类,那么子类的异常对象是可以进入try-catch中的,这其实也体现了多态性。

image.png

相当于实打实地new了一个FileNotFoundException的实例,这里认为是IOException,相当于赋给了IOException,也是多态。

所以子类最大抛出的异常要与父类的异常一样,也可以是父类异常的子类,但不能是父类异常的父类。

多态,编译看左边,运行看右边,但是想要调用子类独有的方法,需要将父类强转成子类才可以调用,俗称“向下转型”。

(3)举例2

若此时父类Father中有一个method2(),并没有声明throws异常。

然后子类Son要重写这个方法,里面有编译时异常,如下:

package yuyi02;public class OverrideTest {public static void main(String[] args) {Father f=new Son();try {f.method1();}catch (IOException e){e.printStackTrace();}}
}class Father{public void method2(){}
}class Son extends Father{@Overridepublic void method2() {}
}

这时候可以用throws吗?

不可以

因为在父类Father里面,它没有抛异常,或者是抛出的无穷小异常。所以子类里面也只能抛无穷小的异常,小到没有。

若此时试图去抛一个异常,就不可以,如下:

image.png

父类没有抛,子类就不能抛

若子类里面有异常,但是父类里面没有抛,此时子类就不可以抛异常出去,需要用try-catch-finally来解决。

🚗

以后会看到这样的场景,一个接口里面写了抽象方法,这个抽象方法后面还throws抛出了异常。既然抽象方法没有方法体,那怎么会有异常呢?

这是为了,当这个方法要被重写的时候,实现类重写这个方法可能要抛异常。

为了实现类可以抛异常,所以接口的抽象方法就抛了一个异常。

子类的异常不能大于父类的异常,父类没有抛异常子类就不能抛。


🍻补充1

现在说的异常都是编译器异常,父类没有抛,子类可以抛吗?

按道理是不能的。

但是,看下面的代码:

package yuyi02;import java.io.FileNotFoundException;
import java.io.IOException;class Father{public void method3(){}
}class Son extends Father{@Overridepublic void method3() throws RuntimeException{}
}

可以发现并没有报错:

image.png

因为对于“运行时异常”,写不写RuntimeException都无所谓。只有在运行的时候才会调用子类的方法。

编译时异常要是不处理,就会报错,比如:

image.png

所以需要处理一下这个编译时异常。若是代码中有运行时异常,在这里不会显示,不显示的话就不用处理。


🍻补充2

之前说“重写”的时候,提到过返回值类型,回顾一下:

关于返回值类型

  • 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型必须是void。
  • 父类被重写的方法的返回值类型是基本数据类型,则子类重写的方法的返回值类型必须与被重写的方法的返回值类型相同。
  • 父类被重写的方法的返回值类型是引用数据类型(比如类),则子类重写的方法的返回值类型可以与被重写的方法的返回值类型相同 或 是被重写的方法的返回值类型的子类。

比如,此时父类的返回值是Number,就是包装类里面数值类型的父类,如下:

class Father{public Number method4(){return null;}
}

子类的返回值,可以是Number,也可以是Number的子类,比如Integer,如下:

class Son extends Father{@Overridepublic Integer method4() {return null;}
}

此时用f调用method4,对于父类的方法来讲,返回的是Number,所以接收的时候用Number类型的来接收。实际执行的时候,却是子类重写的方法,这个方法可能返回的是方法子类的对象。

这里只是以一个多态的方式接收而已。(重写的方法里面不能是Number的父类,要不然接收不了)

如下:

public class OverrideTest {public static void main(String[] args) {Number n=f.method4();}
}

五、 两种异常处理方式的选择

前提:对于异常,使用相应的处理方式。此时的异常,主要指的是编译时异常。

🎲开发中,如何选择异常处理的两种方式?(重要、经验之谈)

  • 如果程序代码中,涉及到资源的调用(流、数据库连接、网络连接等),则必须考虑使用try-catch-finally来处理,保证不出现内存泄漏。
  • 如果父类被重写的方法没有throws异常类型,则子类重写的方法中如果出现异常,只能考虑使用try-catch-finally进行处理,不能throws。(父类没有throws,子类也不能throws)
  • 开发中,方法a中依次调用了方法b,c,d等方法,方法b,c,d之间是递进关系。此时,如果方法b,c,d中有异常,我们通常选择使用throws,而方法a中通常选择使用try-catch-finally

这篇关于Java异常处理--异常处理的方式2:throws的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/612585

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听