1. 程式人生 > >Java中父類和子類丟擲異常的處理

Java中父類和子類丟擲異常的處理

 

(尊重勞動成果,轉載請註明出處: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一起交流學習哦~