質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.70%

コンストラクタが例外を投げることについて

解決済

回答 3

投稿

  • 評価
  • クリップ 0
  • VIEW 5,422

chankane

score 134

前提・実現したいこと

こんにちは。毎度お世話になっております。
現在 Java でボンバーマンを作っていますが、発生しているエラーの処理の仕方がわかりません。
聞きたいことは3つです。

  1. 例外をどうやって補足するのか
  2. コンストラクタが例外を throw することはそもそも好ましくないのか
  3. その他修正すべきところ。

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

発生している問題・エラーメッセージ

Player.java:8: エラー: クラス GameObjectのコンストラクタ GameObjectは指定された
型に適用できません。
        Player(int x, int y){
                            ^
  期待値: String,int,int
  検出値: 引数がありません
  理由: 実引数リストと仮引数リストの長さが異なります
Player.java:11: エラー: superの呼出しはコンストラクタの先頭文である必要がありま
す
                        super("player.png", x, y);
                             ^
Player.java:41: エラー: クラス GameObjectのコンストラクタ GameObjectは指定された
型に適用できません。
        Bomb(){
              ^
  期待値: String,int,int
  検出値: 引数がありません
  理由: 実引数リストと仮引数リストの長さが異なります
Player.java:44: エラー: superの呼出しはコンストラクタの先頭文である必要がありま
す
                        super("bomb.png", -GameObject.SIDE, -GameObject.SIDE);
                             ^
エラー4

該当のソースコード1

// Player.java
public class Player extends GameObject{
    private static final int MAX_CAPACITY = 8;

    private Bomb[] bomb;
    private int capacity;

    Player(int x, int y){
        //super("player.png", x, y);
        try{
            super("player.png", x, y);
        }catch(Exception e){
            e.printStackTrace();
        }
        this.capacity = 1;
        try{
            this.bomb = new Bomb[MAX_CAPACITY];
            for(Bomb b : bomb){
                // 最初は画面外に配置
                b = new Bomb();
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    void putBomb(){
        if(capacity == 0){
            return;
        }
        this.bomb[capacity-1].put(this.x, this.y);
        this.capacity--;
    }
}

class Bomb extends GameObject{
    private static final int TIME_LIMIT = 1000;

    private int time_left;

    Bomb(){
        //super("bomb.png", -GameObject.SIDE, -GameObject.SIDE);
        try{
            super("bomb.png", -GameObject.SIDE, -GameObject.SIDE);
        }catch(Exception e){
            e.printStackTrace();
        }
        this.time_left = 0;
    }

    void put(int x, int y){
        this.x = x;
        this.y = y;
        this.time_left = this.TIME_LIMIT;
    }

    void countDown(){
        this.time_left--;
    }
}

該当のソースコード2

// GameObject.java
import javax.swing.JPanel;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import java.io.IOException;

class GameObject{
    static final int SIDE = 40;

    int dx, dy;
    protected int x, y;
    private Image image;

    GameObject(String file_name, int x, int y) throws IOException{
        BufferedImage bi = ImageIO.read(new File(file_name));

        if(bi.getWidth() != this.SIDE || bi.getHeight() != this.SIDE)
            throw new IOException("画像サイズは" + this.SIDE + "*" + this.SIDE + "でなければなりません. : " + file_name);
        this.image = bi;
        this.x = x;
        this.y = y;
        this.dx = 0;
        this.dy = 0;
    }

    int getX(){
        return this.x;
    }

    int getY(){
        return this.y;
    }

    void setLocation(int x, int y){
        this.x = x;
        this.y = y;
    }

    void move(){
        this.x += this.dx;
        this.y += this.dy;
    }

    void draw(Graphics g, JPanel io){
        int x0 = this.x - this.SIDE/2;
        int y0 = this.y - this.SIDE/2;

        g.drawImage(this.image, x0, y0, io);
    }
}

試したこと

ソースコード1に対してコメントアウトしている位置に super クラスのコンストラクタの記述を移動させたが、今度は例外が補足できない。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+2

やりたいことは、親クラス GameObject のコンストラクタがチェック例外を投げると宣言されている

GameObject::GameObject() throws IOException

この時、その子クラス Player のコンストラクタの中でチェック例外を補足して、例外を投げないコンストラクタとしたい

Player::Player()

ということですね。

結論から言うと、そのようなことはできません。

通常のメソッドをオーバーライドする場合は、親クラスのメソッド呼び出しで発生する例外を子クラスの実装側で補足することができますが、コンストラクタではできません。Javaの言語設計上の制約ですが、理由はちょっとわかりません。

よくある回避策は、Playerに static なファクトリメソッドを追加して、その中で例外を処理します。

public static Player makePlayer() {
    try {

        return new Player(...);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/05/14 09:33

    ご回答ありがとうございました。そのような回避策があったのですね。初知りです Σ(・ω・ノ)ノ!
    早速使用させていただきます。
    勝手ながらフォローさせていただきます。

    キャンセル

+2

「コンストラクタで例外を発生させる」というのは手法としてはありです。そして、「superした親クラスのコンストラクタで例外が発生する」ということは、親クラスとして正しく初期化されていないということなので、子クラスで続けられることはありません。newを呼んだ側へ例外を素通しする、以外の選択肢はありません。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/05/14 09:39

    ご回答ありがとうございました。コンストラクタが例外を投げるのはありということですか。よくわかりました。なにか特別な事情がない限りは例外を素通ししたいと思います。今回は例外を上へ投げたくないということを記述するのを忘れていました。すみません。

    キャンセル

+1

他の回答者さんの回答のとおりと思うのですが「なぜ」について・・・

Javaにせよその他のクラスベースのオブジェクト指向言語にせよ次のような想定があると思います。

  • 派生クラスのインスタンスは必ず基底クラスのインスタンスでなければならない
    これは、派生クラスのインスタンスに対して「必ず」基底クラスのインスタンスに対する操作(メソッド)が行えることを保証する目的と言えると思います。

  • 基底クラスのいずれかのコンストラクターが正常に呼び出せれば「よし」という仕様が達成できるか?
    もし基底クラスのあるコンストラクター呼び出しで例外が発生してもそれを捕捉できるようにするなら、基底クラスの別のコンストラクターを必ず呼び出して「正常に基底クラスの初期化」を行えるようにする必要があります。例えば

class Derived extends Base {
  Derived(int param1, int param2) {
    try {
      super();
    } catch (Exception e) {
      try {
        super(param1);
      } catch (Exception e2) {
        super(param1, param2);
      }
    }
    ... Derivedの初期化処理
  }
}

このように書けたとして、コンパイラーは「基底クラスのコンストラクター呼び出しが成功するまでは、派生クラスのいかなる操作も許さない」ようにしなければならいないことでしょう。なぜなら「派生クラスの任意の操作は基底クラスとしての初期化が大前提であるから」です。そうしなければ最初の前提が崩れてしまいます。このような言語仕様にすることも可能だとは思います。しかしそれは言語仕様を大変複雑なものにするだけであってそれほどの価値はありません。
なぜなら「派生クラスが、基底クラスのどのコンストラクターで初期化すべきものか」は派生クラスを設計する際に分かっていると仮定することは充分合理的だからです。「どう初期化すればいいか実行時にやってみないとわからない」というような状況は考えにくいわけです。

こうした理由で言語設計者は「コンストラクター内で基底クラスのコンストラクター呼び出しの例外を捕捉する必要性はない」と判断することが一般的なのだと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/05/15 19:30

    こんにちは、毎度お世話になっております。
    ごめんなさい。じっくり読んでいたので返信が遅くなりました。
    そうですよね、初期化が大前提ですよね、そう考えると、基底クラスのコンストラクタを最初に記述しなければならない理由もよくわかります。
    また、その大前提が崩れると言語として複雑になる。
    だから、派生クラス内で例外を補足する必要はなく、むしろ補足するほうがその後の処理に関してデメリットが大きい。
    ということですね。おおざっぱではありますが、理解できたと思います。
    なんでコンストラクタだけ無理なんや!!と思っていたモヤモヤが解決いたしました。ありがとうございました(^^)
    またお世話になるとおもいます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.70%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る