Java中父类和子类抛出异常的处理

版权声明:本文为博主原创文章,转载请注明出处,冷血之心的博客。 https://blog.csdn.net/qq_25827845/article/details/85109390

(尊重劳动成果,转载请注明出处:https://blog.csdn.net/qq_25827845/article/details/85109390冷血之心的博客)

背景:

        这篇博客的灵感来自于我在实际工作中,发现 Runnable接口的run方法不可以在方法上抛出异常,如果有编译时异常,那么只能在方法内部进行 try-catch ,这个知识点成功引起了我的注意。于是,这篇简单的总结性博客就诞生了。

正文:

      我们先来创建一个任务类DataTask,如下所示:

public class DataTask implements Runnable {
    @Override
    public void run() {
        
    }
}

然后,我们在run方法中,调用Thread.sleep(1000),那么会有一个编译时异常出现。我们快捷键Alt +Enter看看IDE的提示:

如果我们在一个普通方法中,调用Thread.sleep(1000) ,再来看以下IDE提供的解决办法:

普通方法中的异常我们可以选择在方法内部catch处理,也可以在方法上进行抛出处理。究竟有哪些不同呢?我们来看一下Runnable接口的源码:

/*
 * Copyright (c) 1994, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package java.lang;

/**
 * The <code>Runnable</code> interface should be implemented by any
 * class whose instances are intended to be executed by a thread. The
 * class must define a method of no arguments called <code>run</code>.
 * <p>
 * This interface is designed to provide a common protocol for objects that
 * wish to execute code while they are active. For example,
 * <code>Runnable</code> is implemented by class <code>Thread</code>.
 * Being active simply means that a thread has been started and has not
 * yet been stopped.
 * <p>
 * In addition, <code>Runnable</code> provides the means for a class to be
 * active while not subclassing <code>Thread</code>. A class that implements
 * <code>Runnable</code> can run without subclassing <code>Thread</code>
 * by instantiating a <code>Thread</code> instance and passing itself in
 * as the target.  In most cases, the <code>Runnable</code> interface should
 * be used if you are only planning to override the <code>run()</code>
 * method and no other <code>Thread</code> methods.
 * This is important because classes should not be subclassed
 * unless the programmer intends on modifying or enhancing the fundamental
 * behavior of the class.
 *
 * @author  Arthur van Hoff
 * @see     java.lang.Thread
 * @see     java.util.concurrent.Callable
 * @since   JDK1.0
 */
@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

该接口定义了一个抽象方法run( ),并且没有抛出任何的异常。

敲黑板!敲黑板!我们本篇博客的第一个重点就来了:

一个类如果实现了抽象类或者接口中的抽象方法,那么实现该方法能否抛出异常由抽象方法决定。

也就是说,在接口Runnable中的抽象方法run上,JDK并没有让其抛出异常,那么Runnable所有的实现的run方法同样不可以抛出异常。

我们来做一个验证:

abstract class Super{
    public abstract void test();
}
class Sub extends Super{

    @Override
    public void test() throws Exception {

    }
}

这个程序是会报异常的如下:

IDE在告诉我们,因为在父类中的该方法没有抛出异常,所以这里也不可以抛出异常。Alt +Enter 解决办法也是有两个:

解决方法就是在父类中的该方法上抛出异常,或者在子类中remove去掉该异常。将该抽象类改为接口,效果也是一样的,如下所示:

说到这里,我们就了解了run方法中为什么不可以抛出异常。接下来,我们看看如果涉及到方法的重写,抛异常有什么特定的限制没?

我们先来回顾一下何为方法的重写

当子类需要修改父类的一些方法进行扩展,增大功能,程序设计者常常把这样的一种操作方法称为重写,也叫称为覆盖。

可以这么理解:重写就是指子类中的方法与父类中继承的方法有完全相同的返回值类型、方法名、参数个数以及参数类型。这样,就可以实现对父类方法的覆盖。

我们来看一个案例来阐述子类和父类关于方法重写中抛异常的限制:

class Super{
    public void test(){

    };
}
class Sub extends Super{

    @Override
    public void test()throws IOException {

    }
}

上边这个代码编译不通过:

我们将IOException换成NullPointerException ,则编译通过,如下:

为什么下边换成空指针异常就可以在方法重写的时候抛出异常呢?我们通过一幅图先来看看这两个异常的区别:

很明显:IOException属于编译时异常,而NullPointerException 则属于运行时异常。所以,我们的第二个知识点就出来了。

方法重写的时候,如果父类没有抛出任何异常,那么子类只可以抛出运行时异常,不可以抛出编译时异常。

如果父类的方法抛出了一个异常,那么子类在方法重写的时候不能抛出比被重写方法申明更加宽泛的编译时异常。

子类重写方法的时候可以随时抛出运行时异常,包括空指针异常,数组越界异常等。

例如: 父类的一个方法申明了一个编译时异常IOException,在重写这个方法是就不能抛出Exception,只能抛出IOException的子类异常,可以抛出非检查异常。

我们再来看一个涉及到多态的案例吧,代码如下:

class A {
    public void method() throws Exception {
        System.out.println("A");
    }
}

class B extends A {
    @Override
    public void method() {
        System.out.println("B");
    }
}

class Test {

    public static void main(String[] args) {

        A a = new A();
        A b = new B();
        B c = new B();

        try {
            b.method();
        } catch (Exception e) {
            e.printStackTrace();
        }

        c.method();
    }
}

这个地方的疑问是:

       为什么改变了引用的类型,catch的Exception也会跟着改变?输出任然为B,说明调用的是B.method,但是B.method并没有抛出异常,这里捕获了A.method的异常,这是为什么?

关于A b = new B(); 我们定义了b的表面类型是A,但是其实际类型却是B。在编译的时候,IDE会认为b就是类A的对象,那么调用的自然就是 A.method,当然需要catch Exception了。但是在真正执行的时候,由于子类B重写了A类中的方法method,所以会调用子类中的method,输出 B

关于多态的详细介绍,请参考我的博文:Java三大特性:封装,继承与多态

总结:

       在这篇博客中,我们较为详细的学习了各个情况下是否能够抛异常的知识点,都是一些边角小知识,容易被大家忽略,这里主要是和大家一起温习知识。

如果对你有帮助,记得点赞哦~欢迎大家关注我的博客,我会持续更新,如果有什么问题,可以进群824733818一起交流学习哦~

猜你喜欢

转载自blog.csdn.net/qq_25827845/article/details/85109390