文章目录
- 一、方式2:声明抛出异常类型(throws)
- 二、throws基本格式
- 三、 throws 使用举例
- (1)针对于编译时异常
- 1、案例1
- 2、案例2
- (2)针对于运行时异常
- 四、 方法重写中throws的要求
- (1)说明
- (2)举例1
- (3)举例2
- 五、 两种异常处理方式的选择
一、方式2:声明抛出异常类型(throws)
- 如果在编写方法体的代码时,某句代码可能发生某个
编译时异常
,不处理编译不通过,但是在当前方法体中可能不适合处理
或无法给出合理的处理方式
,则此方法应显示地
声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
- 具体方式:在方法声明中用
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()
中,是一段可能有异常的代码。
此时是编译时异常,必须要进行处理。
☕处理
在方法小括号后面加上throws
,然后根据可能出现异常的类型,比如这里可能出现FileNotFoundException
和IOException
,那么就将它们写在throws
后面即可,用逗号隔开。如下:
public class ThrowsTest {
public void method1() throws FileNotFoundException, IOException {
//... ...
}
}
可以看到现在编译器不报红了,这意味着已经将可能出现的异常处理了。
如下:
现在针对于method1()
方法来说,已经处理了。
若是现在method1()
方法在另一个方法method2()
里面被调用了,发现此时报红了。如下:
这里相当于把异常抛给method1()
的调用者了。
所以method2()
需要处理一下:
public void method2() throws FileNotFoundException, IOException{
method1();
}
可以看见,不报红了:
此时method2()
又在method3()
里面调用了,此时method3()
也面临刚才同样的问题:
此时method3()
觉得可以搞定这个问题,它就可以去解决啦:
public void method3() {
try {
method2();
}catch (FileNotFoundException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
}
若此时method3()
在其他方法中也被调用了,比如在main方法中被调用了,此时不会出现异常,因为在method3()
中将异常干掉了。
就相当于method1()那里面有一只狼,method1解决不了,就抛出来,向method2求助,method2也不行,就向method3求助,method3将狼干掉了。
所以main
方法里面不再提示有异常要处理。
但是,若此时在main方法里面调用的是method2()
,还是需要处理一下这个异常的,因为method2()
抛出了异常。
到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)不能是static,final修饰的方法
当时说到方法的声明格式:
权限修饰符 返回值类型 方法名(形参列表) [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{
@Override
public 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{
@Override
public void method1() {
}
}
假设父类Father中抛出了一个IOException
的异常。对于子类Son来说,也可以抛出和父类被重写方法一样的异常。如下:
public class OverrideTest {
}
class Father{
public void method1() throws IOException {
}
}
class Son extends Father{
@Override
public void method1() throws IOException{
}
}
一般开发中,都这样写成一样的。
其实,子类Son中,还可以抛一个IOException
的子类,比如FileNotFoundException
。如下:
public class OverrideTest {
}
class Father{
public void method1() throws IOException {
}
}
class Son extends Father{
@Override
public void method1() throws FileNotFoundException {
}
}
子类能抛出父类重写方法的异常的子类,而不能是父类重写方法的异常的父类。
🎲为什么会有上面那样的规则呢?
前面说过一个知识点叫做“多态性”,在多态性的场景下,比如现在声明一个变量f,在右边new了一个子类的对象,如下:
public class OverrideTest {
public static void main(String[] args) {
Father f=new Son();
}
}
然后通过f来调用method1()
,此时method1()
可能存在编译时异常,需要处理。
这时候,我们加一个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{
@Override
public void method1() throws FileNotFoundException {
}
}
真正运行的时候,实际调用的是子类的方法。调用了子类重写的method1()
之后,抛出的异常不能比父类的大,要不然catch不了。
若子类的方法在执行的时候抛出的异常是父类的异常的子类,那么子类的异常对象是可以进入try-catch
中的,这其实也体现了多态性。
相当于实打实地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{
@Override
public void method2() {
}
}
这时候可以用throws吗?
不可以。
因为在父类Father里面,它没有抛异常,或者是抛出的无穷小异常。所以子类里面也只能抛无穷小的异常,小到没有。
若此时试图去抛一个异常,就不可以,如下:
父类没有抛,子类就不能抛。
若子类里面有异常,但是父类里面没有抛,此时子类就不可以抛异常出去,需要用try-catch-finally
来解决。
🚗
以后会看到这样的场景,一个接口里面写了抽象方法,这个抽象方法后面还throws
抛出了异常。既然抽象方法没有方法体,那怎么会有异常呢?
这是为了,当这个方法要被重写的时候,实现类重写这个方法可能要抛异常。
为了实现类可以抛异常,所以接口的抽象方法就抛了一个异常。
子类的异常不能大于父类的异常,父类没有抛异常子类就不能抛。
🍻补充1
现在说的异常都是编译器异常,父类没有抛,子类可以抛吗?
按道理是不能的。
但是,看下面的代码:
package yuyi02;
import java.io.FileNotFoundException;
import java.io.IOException;
class Father{
public void method3(){
}
}
class Son extends Father{
@Override
public void method3() throws RuntimeException{
}
}
可以发现并没有报错:
因为对于“运行时异常”,写不写RuntimeException
都无所谓。只有在运行的时候才会调用子类的方法。
编译时异常要是不处理,就会报错,比如:
所以需要处理一下这个编译时异常。若是代码中有运行时异常,在这里不会显示,不显示的话就不用处理。
🍻补充2
之前说“重写”的时候,提到过返回值类型,回顾一下:
关于返回值类型
- 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型必须是void。
- 父类被重写的方法的返回值类型是基本数据类型,则子类重写的方法的返回值类型必须与被重写的方法的返回值类型相同。
- 父类被重写的方法的返回值类型是引用数据类型(比如类),则子类重写的方法的返回值类型可以与被重写的方法的返回值类型相同 或 是被重写的方法的返回值类型的子类。
比如,此时父类的返回值是Number
,就是包装类里面数值类型的父类,如下:
class Father{
public Number method4(){
return null;
}
}
子类的返回值,可以是Number,也可以是Number的子类,比如Integer
,如下:
class Son extends Father{
@Override
public 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
。