question2024
@question2024 (step1engineer)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Java Silver SE11 7章 例外処理 (継承,throws)

何についての問題?

java silver se11 7章(例外処理)にでてくる問題について質問です。
継承関係があるときのthrowsについてです。

問題文と回答

【問題】
コンパイル、実行した結果は?

【回答】
15行目でコンパイルエラーが発生する。

【解説(抜粋)】
スーパークラスのgetSpeed()メソッドは、throwsを指定していませんが、
サブクラス側でchecked例外であるIOExceptionを指定しているため。

 1  import java.io.*;
 2  public class Mycar extend Vehicle {
 3    int speed = 0;
 4    int year = 1960;
 5    int price = 0;
 6    public static void main(String[] args){
 7      System.out.println("There comes my car!");
 8    }
 9    int getPrice(){
10      return price;
11    }
12    int getYear() throws NullpointerExcetion{
13      return year;
14    }
15    int getSpeed() throws IOException{    // 15行目でエラー発生
16      return speed;
17    }
18  }
19  class Vehicle{
20    int getPrice() throws IOException{
21      return 500;
22    }
23    int getYear(){
24      return 1990;
25    }
26    int getSpeed(){
27      return 60;
28    }
29  }

躓いている箇所

15行目でエラーが出ることは、理由も含めて理解しています。
今回質問させていただきたい箇所は、getPriceについてです。
getPrice()は、
親クラス → int getPrice() throws IOException{
子クラス → int getPrice(){
となっています。
私の理解では、(解説にも記載がある通り)スーパークラスで定義したメソッドに
throwsで例外クラスを指定している場合で、その例外クラスがチェックされる例外
であるとき、サブクラスでそのメソッドをオーバーライドする場合には、
同じ例外クラスorその例外クラスのサブクラスをthrowsで指定する必要がある。
ということです。

getPrice()は親クラスでchecked例外のIOExceptionをthrowsしているので、
子クラス側でもIOExceptionをthrowsしなければいけないという認識ですが、
解説ではgetPrice(), getYear()のオーバーライドに問題はありません。
とありました。

オーバーライドには問題がないと理解するためには、私の認識をどう正すべきでしょうか。
サポートいただけますと助かりますm(_ _)m

よろしくお願いいたします。

0

1Answer

明確に禁止されている条件としては「上書きする方のメソッド定義では、上書きされる方のメソッドから投げられる可能性がないような検査例外を投げてはいけない」だからです。

上書きされる方のメソッドにおいて投げられる可能性のある検査例外が存在したとして、
上書きする方のメソッドでは必ずしもその例外を投げる必要はありません。

以下、もうちょっと具体的な説明です。

メソッド宣言の説明

例えば次の2つのメソッド定義があった時、それぞれの意味を表で示します。
int getPrice() throws IOException
int getPrice()
int getPrice() throws FileNotFoundException

項目
名前 getPrice getPrice getPrice
引数(型および順序) なし なし なし
戻り値の型 int int int
検査例外 IOException なし FileNotFoundException

このうち名前と引数は厳密に一致したもののみが同一シグネチャとされ、上書き対象になります。

実は上位の型で①のメソッドが宣言されていた場合、①②③いずれも上書き可能な宣言です(理由は後述)。

戻り値の型とチェック例外はシグネチャよりもやや緩いルールとなりますが
必要なのは、上書きするメソッドの定義に反しない=ルールを破らない(勝手にルールを増やさない)、という点です。

実行時のとりうる結果という観点からの説明

一般的にメソッドを実行した際のとりうる結果としては以下のいずれかになります。

  1. 正常終了:宣言に適合する型の値を返す
  2. 異常終了:非検査例外を投げる
  3. 異常終了:宣言に適合する検査例外を投げる

ここで前述の②③による①のメソッド上書きについて

int getPrice() throws IOExceptionメソッドの取りうる結果は以下です。

  1. 正常終了:intの値を返す
  2. 異常終了:非検査例外を投げる
  3. 異常終了:検査例外IOExceptionを投げる(※IOExceptionを継承した例外を投げても問題ない)

int getPrice()メソッドの取りうる結果は以下です。

  1. 正常終了:intの値を返す
  2. 異常終了:非検査例外を投げる

②がIOExceptionを投げる可能性は一切ないですが、どの結果になったとしても①の取りうる結果の範囲に収まるので問題ありません。

また③int getPrice() throws FileNotFoundExceptionメソッドの取りうる結果は以下です。

  1. 正常終了:intの値を返す
  2. 異常終了:非検査例外を投げる
  3. 異常終了:検査例外FileNotFoundExceptionを投げる

FileNotFoundExceptionはIOExceptionのサブクラスであるため、これは「検査例外IOExceptionを投げる」ルールに適合しています。
つまり③も、どの結果になったとしても①の取りうる結果の範囲に収まります。

上書きする際に許されないのは、
「より上位の型であるthrows java.lang.Exceptionにする」とか
「元の宣言と無関係な検査例外throws java.sql.SQLExceptionに変える」とか
「そもそも元で宣言されていない検査例外を追加してthrows IOException, java.sql.SQLExceptionにする」とかです。

例えば④int getPrice() throws IOException, java.sql.SQLExceptionメソッドを考えると、取りうる結果は以下となり明らかに①に存在しない結果が増えています。

  1. 正常終了:intの値を返す
  2. 異常終了:非検査例外を投げる
  3. 異常終了:検査例外IOExceptionを投げる
  4. 異常終了:検査例外SQLExceptionを投げる

質問文で挙げられている問題(何らかの書籍の問題?)の「15行目でコンパイルエラー」の理由がまさにコレです。

3Like

Comments

  1. @question2024

    Questioner

    PECMM 様
    コメントいただいてとても嬉しいです!
    ご丁寧に解説してくださっていて、感謝です。助かります!!
    これから内容を読ませていただき学習したいと思います。
    追加で分からないことが出てきましたら、またQA投稿しようと思います。
    ありがとうございます。m(_ _)m!!

Your answer might help someone💌