1
1

クリリンでもわかるJava入門(後編)

Last updated at Posted at 2021-01-17

前回のドラゴンボール(前編)

オブジェクト指向の魅力的な機能から始めていく

カプセル化(オブジェクト指向3大機能その①)

オブジェクト指向の3大機能(カプセル化・継承・多様性)の1つ。
アクセス制御を行うことでミスを防ぐ

4種のアクセス修飾子
制限度 名前 コードで指定方法 許可範囲
厳しい private private 自分自身のクラスのみ
public private 何も書かない 自分と同じパッケージのクラスのみ
protected protected 自分と同じパッケージor自分を継承したクラス
緩い public public 全てのクラス
一般的にフィールドは private、メソッドは publicにする。
※クラスはpublic か public privateしか使えない。

フィールドがprivateだと他クラスから変更ができない。変更するときは getterメソッドと setterメソッドというものを用意する。
getterで取得、setterで上書き。
基本的なメソッドの命名は
「get/set + フィールドの一文字目を大文字()」となる。
getter()のみを使えばRead only(読み込みのみ、上書き不可)のフィールドを実現できたりメリットがある。

sample.java
public class Hello{
    private String name;

    public String getName(){
        return this.name;
    }

    public void setName(String name){
        this.name = name;
    }
}

↓setterでフィールドへの上書き検査もできる。

sample.java
private String name;

public void setName(String name){
    //名前がnullなら中断
    if(name == null){
        throw new IllegalArgumentException("名前がnullの為、処理中断")
        this.name = name;
    }

継承(オブジェクト指向3大機能その②)

extends で他クラスを継承し、内容を継ぐことができる。

.java
public class Angel extends Human {
}

つまり、Angelクラスをインスタンス化したとき Humanクラスのメソッド(&フィールド)が使える。

.java
Angel Ag = new Angel();
Ag.run(); //Humanクラスで宣言したrunメソッド

このクラスの継承関係では元となった親クラスHumanをスーパークラスという。
継承済みAngelを他クラスに継承させることもできる。
※ただし、1クラスで複数同時継承はできない(1回1個まで)。

オーバーライド

↑に記述したAngelクラスで継承したHumanクラスの一部を変更したいときは、
再定義することで上書きできる。
※Humanクラスに影響はない。

Angel.java
public void run(){
    System.out.println("runメソッドの内容が上書きされた")
}

子クラスでオーバーライド(上書き)されたくないときは
「public final class」のようにfinalを付ける。

上書き前のrunメソッドも使いたいという場合は、
superを利用しスーパークラス(親)を呼び出せる。

.java
super.run(); //メソッド
super.name; //フィールド

※「親-子-孫」 の継承関係の場合、孫から子へはsuperで呼べるが、孫から親へはsuperでは呼べない。親をsuperで呼びたいなら、子にオーバーライドさせず、superで子を呼べば結果親の処理を呼ぶことになる。

継承とコンストラクタ

インスタンス化(new)すればコンストラクタが自動で呼ばれるが、
継承しているとスーパークラスのコンストラクタから処理される。
例えば、Humanクラスを継承したAngelクラスをインスタンス化したとき、
Angelのコンストラクタ等の処理の前に、Humanにコンストラクタがあればそちらが優先処理される。

実はAngelクラスでHumanクラスのコンストラクタを「super()」で呼んでいる。
書かなくても自動で暗黙のsuper()が呼ばれる。

Angel.java
public class Angel extends Human {
    //省略できるが暗黙的に呼ばれる
    public Angel(){ //Angelのコンストラクタ
        super(); //Humanのコンストラクタを呼ぶ
    }
}

※super()はthis()同様、コンストラクタの先頭行にしか記述できない。

正しい継承

正しく継承をするには「is-a」(子は親の一種である)を守る。

例:3つ

  • スーパークラス:Human(人型) サブクラス:Angel

 〇 Angelは人型の一種である。

  • スーパークラス:Food サブクラス:Sushi

 〇 SushiはFoodの一種である。

  • スーパークラス:Car サブクラス:Engine

 × EngineはCarの一種である。
  より具体的な車(スーパーカー、ベンツ等)なら〇

親から子へは具体的に特化していき、
子から親へは抽象的に汎化していく。

間違った継承をするとクラスに矛盾が生じたり、オブジェクト指向の1つ「多様性」を利用できなくなる。

高度な継承

他の開発者向けにスーパークラスを作るとなれば、様々な不都合が出てくる。
3つの不都合に対する解決法を挙げていく。

①詳細未定(抽象)メソッドの宣言

他に開発者2人が 悟空クラスと ベジータクラスを作ろうとしている。
気の色や髪色、似たフィールドがある為、継承用にスーパーサイヤ人クラスを作ってあげる。
かめはめ波メソッドを作ろうとしたが、それぞれで威力が違う為定義できない。

SuperSaiyajin.java
public class SuperSaiyajin {
    public void kamehameha (Saibaiman s){
        s.hp -= ??;
    System.out.println("サイバイマンのHPが??減った!")
    }
}

とりあえず、オーバーライドして上書きしてもらう事にして、
かめはめ波メソッドの処理を削除すれば良いと考えるが、
何も行わないメソッドだと思われてしまうのを避ける為に「abstract(アブストラクト)」を使う。
日本語で「抽象的・あいまい」という意味。
「{}」を付けずに「;」を付ける。
abstractの付いたメソッドを抽象メソッドという。

.java
public abstract void kamehameha (Saibaiman s);

②抽象メソッドを含むクラスの宣言

1つでも abstract を含むメソッドがるときクラスにも abstract を付ける必要がある。(無いとエラー)

SuperSaiyajin.java
public abstract class SuperSaiyajin {
    public abstract void kamehameha (Saibaiman s);
}

abstract の付いたクラスは抽象クラスと呼ばれ、new(インスタンス化)できない。(するとエラー)
継承(extends)用に作成したスーパーサイヤ人クラスが間違ってインスタンス化されないようになる。

③オーバーライドの強制

悟空クラスでかめはめ波メソッドのオーバーライド(上書き)を忘れる可能性がある。
しかし、上の①②を実践していればエラーが起こるので忘れることがなくなる。
なぜなら、スーパーサイヤ人を継承しているということは「抽象メソッドのかめはめ波メソッド」を含む。
抽象メソッドを含むクラスは abstract が必要。
オーバーライドに気づき、かめはめ波メソッドを抽象化せずオーバーライドすればエラーは消える。

アクセス修飾子 protected について

protected の許可範囲は自分と同パッケージか、自分を継承したクラス。
自分のパッケージだけなら public で良いわけで、違うパッケージかつ継承クラスに protected を
使うことになるので利用局面は少ないといえる。

高度な継承をまとめると、

各自で絶対にオーバーライドして欲しいメソッドは抽象メソッド(abstract)にすると良いということ。
オーバーライドしなかったときのエラー文
Goku is not abstract and does not override abstract method kamehameha() in SuperSaiyajin

インタフェース

特に抽象度が高いクラスをインタフェースという。
インタフェースにできる2条件
①すべて抽象メソッドである。
②基本的にフィールドを持たない。
↑のSuperSaiyajin.javaはインタフェースにできる。
インタフェースのメソッドは自動で「public abstract」になる為、省略可能。

SuperSaiyajin.java
public interface class SuperSaiyajin {
    //public abstract は省略可能
    void kamehameha (Saibaiman s);
}

基本的にフィールドを持たないと書いたが、「public static final(定数)」だけ宣言が許可されている。
これもメソッド同様、自動で保管(public static final(定数)扱い)されるので省略可能。
例:int power = 1000000;

※もちろんインタフェースも抽象的に変わりない為インスタンス化不可。

インタフェースの実装

インタフェースの継承は extends ではなく、implements を使う。
implementsは日本語で「実装する」という意味。
インタフェース(抽象的で未確定)をオーバーライドで実装確定するイメージ。

Gokuu.java
public class Gokuu implements SuperSaiyajin {
}

インタフェースの必要性

継承は複数クラス同時にできないと言いました。
理由は例えば、
かめはめ波メソッドを持つ悟空クラスと、チチクラスを
継承時点で悟飯クラスは威力が違うどっちのかめはめ波メソッドを使えばいいのか衝突し問題になる。

そこで両親がインタフェース(=抽象メソッドしか存在しない)なら衝突せず、悟飯自身でオーバーライドしてかめはめ波の威力を決定できる。
よってインタフェースは多重継承が可能。

Gohan.java
public class Gohan implements Gokuu, Chichi{
}

インタフェースとextendsの使い分け

悟空クラスをインタフェースとして次を見てください。

Gohan.java
public interface class Gohan extends Gokuu {
}

悟空はインタフェースだから implements で継承じゃないの?となる。
実は implements を使うのはインタフェースを継承したクラスでオーバーライド(実装)するときだけ。
この悟飯クラスはよく見たらインタフェースだ。
悟空インタフェース(未確定)⇒悟飯インタフェース(未確定)なので確定(実装)にはなっていない。
よってただの継承(extends)である。

implements を使うのは未確定から確定になるときのみ。
言い換えると、クラスがインタフェースを継承するときのみ。
インタフェース⇒クラス はimplements

extendsとimplementsは同時に使える

例:public class Gohan extends SuperSaiyajin implements Gokuu, Chichi{

多様性(オブジェクト指向3大機能③)

多様性(polymorphism)はポリフォーフィズムとも呼ばれる。
カプセル化は private、継承は extends と、文法があるが多様性にはない。
ここでの多様性とは曖昧に捉えることでメリットを得られる方法のこと。

スーパーサイヤ人クラスを親とする子クラス「悟空クラス」をインスタンス化すると(継承関係)

.java
Gokuu g = new Gokuu();

↑のようになるが、スーパーサイヤ人型にもできる。

.java
SuperSaiyajin g = new Gokuu();

悟空はスーパーサイヤ人クラスを継承している為、これが可能になる。
つまり、親の型でインスタンス化ができる。
インタフェースのような抽象クラスはインスタンス化できないと言ったが、
ここでは型として利用しているだけで問題はない。

悟空はスーパーサイヤ人の一種である。
これが逆だと矛盾が生じる為、正しく継承する必要があった。(項目:正しい継承)

型を曖昧にしたときの変化

悟空クラスを親クラス(スーパーサイヤ人)にしたときどう変化するのか。

.java
//1
public abstract class SuperSaiyajin {
    public abstract void kamehameha(); //スーパーサイヤ人はかめはめ波を使える。
}
//2
public class Gokuu extends SuperSaiyajin {
    public void kamehameha(){ //かめはめ波のオーバーライド
        System.out.println("100ダメージ");
    }

    public void kaiouken(){  //悟空は界王拳を使える。
    }
}
//3
public class Main{
    public static void main (String[] args){
        Gokuu g = new Gokuu();
        SuperSaiyajin s = g; //悟空インスタンスをスーパーサイヤ人型へ代入

        s.kamehameha();
        s.kaiouken();
    }
}

↑のs.kamehameha()は成功するが、s.kaiouken()はエラーになる。
スーパーサイヤ人クラスにかめはめ波はあるが、界王拳はない為使えない。
実は以下のようなルールがある。

型とインスタンス

  • 使えるメソッドは型で決まる。

  • メソッドの処理は中身(インスタンス)で決まる。

つまり、スーパーサイヤ人クラスで適当に界王拳メソッドを作れば、
呼び出すことが可能になり、処理は悟空クラスでオーバーライドした内容で動作しエラーは起こらない。

型を途中で変える

↑のソースで、スーパーサイヤ人は誰でも界王拳が使えるわけじゃないし
やっぱり悟空クラスにだけ界王拳を使えるようにしたい!となる場合がある。
そんなときはキャストを使う。

.java
SuperSaiyajin s = new Gokuu();
Gokuu g = (Gokuu) s; //スーパーサイヤ人型を悟空型へ変換し代入

今回のような「曖昧な型を厳密な型に代入」することをダウンキャストとって失敗が危惧される。
例えば、悟空インスタンスのスーパーサイヤ人型をチチ型へ変換し代入したときもコンパイル時にエラーが出ない。
しかし、動作させた瞬間にClassCastExceptionというエラーが発生する。
キャストによる強制代入の内容が間違っているという意味のエラー。

このエラーを確実に回避するには、ある演算子を使用する。

instanceof演算子
変数 instanceof 型名 (変数を型名に代入可能ならばtrue)

.java
SuperSaiyajin s = new Gokuu();
if (s instanceof Gokuu){
    Gokuu g = (Gokuu) s;
}

より詳細な説明は以下参照。

多様性のメリット

多様性を用いてインスタンスを曖昧に捉えることで
呼び出せるメソッドが減ったりとメリットがないようだが、
具体的なコードで説明していく。
スーパーサイヤ人1名、ナメック星人2名のPT(パーティー)が仙豆で100HP回復するプログラム。

.java
public class Main {
    public static void main (String[] args){
        SuperSaiyajin s1 = new SuperSaiyajin();
        Namekkuseijin n1 = new Namekkuseijin();
        Namekkuseijin n2 = new Namekkuseijin();

        //仙豆で回復
        s1.setHp( s1.getHp() + 100);
        n1.setHp( n1.getHp() + 100);
        n2.setHp( n2.getHp() + 100);
    }
}
//前提:スーパーサイヤ人もナメック星人も抽象クラス「Character」を継承している。
//Characterクラスはhpフィールドとそのgetter,setterを持つとする。

このプログラムの回復処理は2つの問題がある。

  • コードに重複が多い ⇒ 記述が面倒。変数名を間違えそう。

  • 将来的に多くの修正が必要 ⇒ PT人数の増加による行追加。インスタンス名が変更による変数名修正。

これは多様性と配列の組み合わせで解決可能。

.java
public class Main {
    public static void main (String[] args){
        Character[] c = new Character[3]; 
        c s1 = new SuperSaiyajin();
        c n1 = new Namekkuseijin();
        c n2 = new Namekkuseijin();

        //仙豆で回復
        for (Character ch : c){ //拡張for文
            ch.setHp( ch.getHp() + 100);
        }
    }
}

次は、引数を曖昧にした活用例を紹介
悟空がサイバイマンと魔人ブウに攻撃するプログラム。
以下のように処理を分けて書くはず。

Gokuu.java
public clss Gokuu extends SuperSaiyajin {
    public void kamehameha (Saibanman s){}; //サイバイマンにかめはめ波を撃つ処理

    public void kamehameha (Majinbuu m){}; //魔人ブウにかめはめ波を撃つ処理
}

しかし、敵が増えるごとにコードの追加が必要になる。
これも多様性で敵(Enemy)を曖昧にすれば、以下で済む。

Gokuu.java
public clss Gokuu extends SuperSaiyajin {
    public void kamehameha (Enemy e){}; //敵にかめはめ波を撃つ処理
}

これでEnemyクラスを継承している敵なら誰にでもかめはめ波を撃てる。
引数の渡し方は↓のような感じで良い。

.java
Gokuu g = Gokuu();
Saibaiman s = Saibaiman();
Majinbuu m = Majinbuu();

g.kamehameha(s); //サイバイマンにかめはめ波
g.kamehameha(m); //魔人ブウにかめはめ波

多様性の最大のメリット

多様性の真価は、
異なるインスタンスを曖昧に捉えることでまとめて使えることと
動作は型でなくインスタンスのメソッドで決まることを組み合わせたときに発揮される。

例.java
public class Main {
    public static void main(String[] args){
        SuperSaiyajin [] superSaiyajin  = new SuperSaiyajin[3];
        superSaiyajin  [0] = new Gokuu(); //悟空は奥義メソッドは身勝手の極意
        superSaiyajin  [1] = new Beji-ta(); //ベジータの奥義メソッドはファイナルフラッシュ
        superSaiyajin  [2] = new Bejitto(); //ベジットの奥義メソッドはベジットソード

        for (SuperSaiyajin su : superSaiyajin ){
            su.ougi(); //それぞれ所有する奥義を使う。
        }
    }
}
実行結果.
悟空は奥義:身勝手の極意を使用した。
ベジータは奥義:ファイナルフラッシュを使用した。
ベジットは奥義:ベジットソードを使用した。

全体から多様性の構図と本質を見る。
指示側は「サイヤ人達、とりま奥義使え」といい加減なのに対し、
一方、キャラクター(サイヤ人)側は「奥義使え」と言われただけで独自の奥義を使えている。
このように、呼び出し側は相手を同一視し、同じように呼ぶのに、
呼び出される側は、決められた動きをするという特性から「多様性」と名付けられた。

Javaの標準クラス

日時を扱う基本形式2つ

  • 形式1:long型の数値

  基準日時である1970年1月1日0時0分0秒(エポックと呼ばれる)から経過したミリ秒数で表現する方法。
  long値:1316622225935 は2011年9月22日・・・を意味する。
  しかし、人間では日付が読めないことや、longは単に数字の格納にも使われ中身が日時情報と判断がつかない。
  System.currentTimeMiilis()で現在時刻取得。

  • 形式2:Date型のインスタンス

  long型の課題克服で広く用いられているのが java.util.Dateクラス。
  内部でlong値を保持しているだけだが、Date型の変数は日時情報であると判断がつく為、Javaで最も利用される

Date例.java
import java.util.Date;
public class Main {
    public static void main(String[] args){
        Date now = new Date(); //現在時刻取得
        System.out.println(now);
        System.out.println(now.getTime());
        Date p = new Date(1316622225935L); //long型なので「L」が必要
        System.out.println(p);
    }
}
実行結果.
Thu Jan 14 07:25:22 JST 2021 👈この行はリアルタイム次第
1610576722675
Thu Sep 22 01:23:45 JST 2011

人間が扱いやすい2つの形式

  • 形式3:String型のインスタンス

  SimpleDateFormatクラスで書式の指定ができる。(例:yyyy年MM月dd日も可能)

SimpleDateFormat例.java
import java.text.SimpleDateFormat;
import java.util.Date;
public class Main {
    public static void main(String[] args){
        Date now = new Date(); //現在時刻取得
        SimpleDateFormat f = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); //書式指定
        String s = f.format(now); //フォーマットをあてる
        System.out.println(s);
        //逆に指定日時(String型)からDate型にもできる。
        Date d = f.parse("2020/01/14 01:01:01");
    }
}
  • 形式4:6つのint形式

  月や秒、それぞれを指定できる。
  変数.set(年,月,日,時,分,秒)or変数.set(Calendar.〇〇, 値)で指定。
  ※Calendarで月の指定には注意。0始まりなので1月ならset(Calendar.MONTH, 2)となる。

Calendar例.java
import java.util.Calendar;
import java.util.Date;
//public ~割愛
Date now = new Date(); //現在時刻取得
Calendar c = Calendar.getInstance();
c.setTime(now); //setTime()でDate型をリアルタイムセット
c.set(2030,8); //set()で年・月を上書き
c.set(Calendar.YEAR, 2020) //set()で年を上書き
int s = c.get(Calendar.SECOND) //get()で秒を取得

暗黙の継承

メソッドもフィールドも一切定義していないクラスでtoString()を呼び出せるのは、
全てのクラスはjava.lang.Objectを継承しているからだ。
実質:public class 〇〇 extends Object

Objectクラスの存在価値は
最低限必要なメソッド(toString()、.equals())が使える他に、
多様性が利用できる利点がある。

.java
public class Main {
    public static void main(String[] args){
        Object o1 = new Gokuu(); //全インスタンスはオブジェクトの一種であると言える。
    }
}

ラッパークラス

Javaの型には基本型と参照型がある。基本型(String以外)では数値や文字の格納、値を使った演算処理が行える。
しかし、値に対する操作を行う手段(メソッド)は用意されていない。また、複雑な処理をより簡易に行えるAPIなどを利用しようとした場合、基本型は引数の対象外となるため使用できない。

そこで基本データ型の値をラップ(=包み込む)してオブジェクトとして利用できるようにするクラスが用意されている。
それらをラッパークラスと呼ぶ。

例えば「数値を文字列に変換したい」といった場合、ラッパークラスを利用して変換、といったことも行える。

基本データ型:int の ラッパークラス:java.lang.Integer

.java
//ラッパークラスを利用するパターン
int i = 84;
Integer o = new Integer(i);
String str = o.toString(); //toStringはint(基本型)には使用できないが変数oはオブジェクトだ
//利用しないパターン
int i = 84;
String str = String.valueOf(i);

2つの違いは、valueOfは値がnullならnullを返すが、toStringはエラーが起こる為注意!
(※ラッパークラスはByte,Shot,Integer,long,Float,Double,Boolean等がある。)

エラーの種類と対応策

とりあえず動作するプログラムを作るのは簡単だが、エラーを起こさないプログラムを作るのは難しい。
いかなる場合も不具合のないプログラムを目指す必要がある。

Javaでの不具合は主に3種

  • ①文法エラー(syntax error)

  文字・文法間違え、セミコロン抜け等。
  ⇒エラー文の箇所を修正。

  • ②実行時エラー(runtime error)

  文法に問題がない為、コンパイル時は成功するが、実行中にエラーが出る。
  ⇒対策処理を記述しておく。

  • ③論理エラー(logic error)

  文法に問題がなく、強制終了することもないが内容がおかしい。(例:電卓ソフトの計算結果がおかしい)
  ⇒自力で探しコードを修正。

例外的状況

①と③はテスト時のコード修正で予防できるが、②実行時エラーはそうはいかない。
そもそも実行時エラーは想定外の事態の発生で起こる。
この「想定外の事態」を**例外的状況(exceptional situation)又は例外(exception)**という。

例外パターン例
・パソコンのメモリ不足
  開発環境では問題なく、本番環境の動作中にメモリ不足。
・存在すべきファイルがない
  誤って消されていた。
・nullが入っている変数のメソッドを呼び出した。
  ユーザーの想定外の操作により、本来入る予定のないnullが変数に入り、その変数を使用するメソッドを呼び出した。
  (if文でnullチェックはできるが、全ての処理に対しチェックするのは現実的ではない。)

これら全て開発時では例外的状況の発生を予防できない。
しかし、例外的状況発生後の対策は可能。

例外処理

例外的状況に陥ったときに備えた対策を例外処理という。

従来型の例外処理
処理①に対してif文でチェック
処理②に対してif文でチェック
と、1つの処理毎にチェックが必要で面倒。本来の処理の行もわかりづらい。

try-catch文

try-catch文でまとめてチェックできる。
tryは通常通り流れ、例外が発生したところでcatchブロックに移る。

.java
try{
    //処理①
    //処理②
}catch(・・・ 変数){
    //・・・という例外処理が発生すれば動作する。
}

↑のコード「・・・」には例外的状況を表すクラス「例外クラス」が入る。

例外の種類

  • ①Error 系例外

  catchする必要なし。
  回復の見込みがない致命的な状況。
  例:OutOfMemoryError(メモリ不足)、ClassFormatError(クラスファイル破損)

  • ②Exception 系例外

  catchすべき。
  java.lang.Excepton(RunTimeException以外)を親に持つ。
  想定し対処を考える必要がある例外的状況。
  例:IOException(ファイル等が読み書き不可)、ConnectException(ネットに接続できない)

  • ③RunTimeException 系例外

  catchしなくてもよい。
  java.lang.RunTimeExceptionを親に持つ。
  必須とは言えない例外的状況。
  例:NullPointerException(変数等がNull)、ArrayIndexOutBoundsException(配列の添え字不正)

②のException 系例外の発生可能性がある箇所はtry-catch文で囲んでいないと、
コンパイル時に「例外 java.io.IOException は報告されません。~」と怒られる。
これをチェック例外という

発生する例外の調べ方

APIに含まれるクラスは例外が発生する可能性があるメソッドが多くある。
発生する例外はAPIリファレンスに掲載されている。
例:
APIが FileWriter なら「throws IOException」と書かれている為、
try-catch文でcatch(IOException e)としてやる。

↑1行上の「変数e」にはエラー情報が詰まっている(文字はeである必要はない)。
例外インスタンスと呼ばれ、e.getMessage()のようにも使える。

例外インスタンスが必ず備えているメソッド

メソッド 意味
getMessage() エラーメッセージの取得
printStackTrace() スタックトレースを画面に出力
※スタックトレース:プログラムの起動内容や例外内容等が記録された情報

様々なcatch構文

try-catch-catchのように複数の例外をキャッチすることも可能。
また、抽象的な例外クラスを指定することもできる。
Exception 系例外なら親に java.lang.Exceptonを持つので
catch(Exception e)でException 系例外全てをキャッチできる。

後付け処理の対応

次に、ファイルを開いて⇒文字を書いて⇒ファイルを閉じるプログラムがある。

.java
try {
    FileWriter fw = new FileWriter("data.txt");
    fw.write("hello!");
    fw.close();
} catch (Exception e) {
    System.out.println("エラー発生");
}

これには問題点がある。
ファイルは開いたら閉じるのが決まり。
write()で上書きした後に、もしディスク容量が不足した場合に例外が発生し
catchへ移動するのでclose()が飛ばされる。

その対策でclose()をtry-catch構文の外へ移動し、
try-catch後に閉じれば問題ないかと思うが、NullPointerException等のcatchしていない例外が発生すれば
その時点で処理が止まり、close()が流れない。

必ず最後に実行しなければならない処理には
try-catch-finallyを使う。

.java
} finally {
    fw.close(); //例外が起こっても行う処理
}

finallyが必須の状況は、今回のようなファイルやデータベース接続、ネットワーク接続等

※java8からはfinallyを使う場合、
try-finallyよりもtry-with-resourcesを使うと良い。try-finallyよりもtry-with-resourcesを使おう

例外の伝搬

例外はcatchしないと強制終了することがわかる。
では、mianメソッドでaメソッドを呼び出し、aメソッドはbメソッドを呼び出したが
bメソッドで例外が発生した場合どういう流れで処理されるか。

main() ⇒ a() ⇒ b() ✖例外発生

b()でcatchできなければa()へ、a()でも無理ならmain()へ、それでも無理なら強制終了となる。
この現象を例外の伝播と呼ぶ。
(例えば、a()でb()を呼び出すとき例外が発生する想定をしcatch構文を記述していれば防げた。)

Exception 系例外はキャッチが必須の為、例外の伝播は基本発生しない。
しかし、スロー宣言を行えば呼び出し元へ例外を伝播できる。
throws 例外クラス1, 例外クラス2...(throw:意味 投げる)

.java
public static void b() throws IOException{
    //本当はtry-catch構文でIOExceptionを拾う必要があるが
    //例外発生した場合は、俺を呼び出したやつに投げるぜ!(責任丸投げ)
    FileWriter fw = new FileWriter("data.txt");
}

これで、コンパイル時にtry-catchがないと怒られることはないが、
呼び出し側が(try-catchする)責任を取るので注意が必要。

例外を発生させる

例外を投げるとも言う。
throw new 例外クラス名("エラーメッセージ");
※スロー宣言は throw s で「s」があるから間違わないように。

.java
if ( name == null) {
    throw new NullPointerException("null発生!");
}

オリジナル例外クラスの定義

チェック例外(Exception 系例外)を継承すればオリジナルエラーメッセージの例外を作れる。
(非チェック例外で作成する状況はほとんどない。)

OrignalException.java
public class UnRecitedSpellException extends Exception {
    //エラーメッセージを受け取るコンストラクタ
    public UnRecitedSpellException(String msg) {
        super(msg); //作成したエラーメッセージを親に渡している。
    }
}
Main.java
public class Main {
    public static void main(String[] args){
        try {
            throw new UnRecitedSpellException("呪文が詠唱が間違っているぜ!")
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

List

可変長の配列を使いたいときはリストを使う。

基本形.java
List<String> list = new ArrayList<>();

ArrayListのデータ型はJava7から省略可能。
Listのデータ型も省略可能だが何でも代入できる曖昧なものになってしまうので非推奨。

ArrayListはListを継承している。
Listはインタフェースなのでインスタンスを生成できない。
✖ List<String> list = new List<>();

ArrayListにしかないcloneメソッドを使いたいときはArrayListで宣言する。
ArrayList<String> list = new ArrayList<>();

リストの使い方.java
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
system.iut.println(list.size() + "個ある1つ目の値:" + list.get(0));
初期値を設定する方法.java
List<String> list = new ArrayList<>(Arrays.asList("a","b"));
固定長にする方法.java
List<String> list = Arrays.asList("a","b");
//固定長なのでaddできない
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1