Ch.1 Javaクラス設計

【復習】Switch文で使える型

  • intより小さい整数型とラッパークラス(int, short, byte, Integer, Short, Byte)
  • 文字と文字列(char, String)
  • 列挙型(enum)

JavaBeans

下記の規則を満たすJavaのクラスをJavaBeansといる。

  • メンバ変数をprivateとして、getterとsetterからしか値を取得できないようにする。
  • getterとsetterはpublicとする。命名規則getName()のように、getとsetを先に持ってくる。
  • boleanに対するgetterだけ``isActive()```にして良い。
  • getterで返却する型は対応するメンバ変数の型と同じ。引数はとらない。
  • setterはvoid型。引数は対応するメンバ変数の型と同じ。

setterとgetterを活用することで、メンバ変数がイミュータブルなJavaBeansを作ることが出来る。 その場合は以下のことに注意する。

  • getterのオーバーライドが出来ない様final宣言してしまう。或いはクラスの継承ごと禁じてしまう。
  • メンバ変数をfinal(定数)とする。なお、その場合インスタンス初期化時に値をセットしておく必要がある。
  • メンバ変数が参照型の場合、返却値を操作して値を変更されない様に注意する必要がある。

メンバ変数が参照型の場合は以下のように工夫したgetterを宣言する必要がある。

JavaBean.java

package Chapter1;

import java.util.ArrayList;
import java.util.List;

public class JavaBean {
    private final List<String> names;

    public JavaBean(){
        this.names = new ArrayList<String>();
        names.add("JapanAir");
        names.add("All Nippon");
        names.add("AirDo");
    }
    public final List<String> getNames() {
        List<String> copiedList = new ArrayList<String>();
        copiedList.addAll(names);
        return copiedList;
    }
}

StartPoint.java

package Chapter1;

public class StratPoint {

    public static void main(String[] args) {
        JavaBean testBean = new JavaBean();
        System.out.println(testBean.getNames());
        System.out.println(testBean.getNames().remove("AirDo"));
        System.out.println(testBean.getNames());
    }
}

結果

[JapanAir, All Nippon, AirDo]
true
[JapanAir, All Nippon, AirDo]

なお、final宣言をした変数はコンストラクタ、或いはイニシャライザ内で変数代入しておけば(変数宣言と同時の変数代入でなくても)問題ない。 final宣言を行ったクラス変数に関してはstaticイニシャライザ内でも初期化できる。

package Cahpter1.Static;

public class StaticVariable {

    static {
        variable = 100;
    }

    static final int variable; //static×final
    static int variable2;
    final int variable3;

    {
        variable3 = 300;
    }

    public static void main(String[] args) {
        //variable = 100;
        variable2 = 200;

        System.out.println("variable1 :" + variable);//'100'
        System.out.println("variable2 :" + variable2);//'200'

        StaticVariable object = new StaticVariable();
        System.out.println("variable3 :" + object.variable3);//'300'
    }
}

シングルトン

インスタンスをプログラムで一つだけにしたい場合のデザインパターン。 下記3点がポイント。

  1. プライベートなクラス変数にfinal宣言を行って、インスタンスを格納する。(初期化はstaticイニシャライザまたは変数宣言時に行う。)
  2. コンストラクタを宣言しておかないと、コンパイラが自動で生成してしまうため、プライベートなコンストラクタを設定して外部からnewされるのを防ぐ。
  3. インスタンスの返却は1.のクラス変数を返却する。常に同じインスタンスが何度でも返却される。
package Singleton;

public class Singleton {
    private static final Singleton instance;
    static {
        instance = new Singleton();
    }

    public int instanceVariable;
    private Singleton() {/* コンストラクタが自動生成されるのを防いでいるだけ。*/}
    public static Singleton getInstance() {
        return instance;
    }
}
package Singleton;

public class StartPoint {

    public static void main(String[] args) {
        // TODO 自動生成されたメソッド・スタブ
        Singleton.getInstance().instanceVariable = 666;
        System.out.println("InstanceVariable " + Singleton.getInstance().instanceVariable); //'666'
        System.out.println("InstanceVariable " + Singleton.getInstance().instanceVariable); //'666'
    }
}

列挙型

明示的なインスタンス化が出来ないが、宣言した定数とjava.lang.Enumから継承されたいくつかのメソッドを持つクラスである。 宣言した数だけ定数を持ち、クラス名.定数名で初期化できる。各定数にはenum型のインスタンスがセットされる。

package enumSample;

enum Airlines {
    NipponAir, AllNippon, AirDo, StarFlyer, JetStar
}

public class enumSample {
    public static void main(String[] args) {
        Airlines enumAirLines = Airlines.AirDo;
        System.out.println(enumAirLines);     //'AirDo'
        //System.out.println(Airlines.valueOf("JetS"));//'No enum constant enumSample.Airlines.JetS'
        System.out.println(Airlines.valueOf("JetStar"));//'JetStar'

        for(Airlines a: Airlines.values()) {
            System.out.print(a + " "); //'NipponAir AllNippon AirDo StarFlyer JetStar'
        }
    }
}

さらに各定数に値をセットしておくと、初期化時にインスタンス変数として代入され保持しておくことが出来る。 この場合、インスタンスに値をセットする作業が必要になるため、コンストラクタは自分で作成する必要がある。

package enumSample;

enum NewAirlines {
    NipponAir(101), AllNippon(102), AirDo(103), StarFlyer(104), JetStar(104);
    private int code;
    NewAirlines(int number) {
        this.code = number;
    }

    int getCode(){
        return code;
    }

    public String toString() {
        return this.name() + " Country: Japan";
    }
    //nameメソッドはfinalでオーバーライド出来ない。
}

public class enumSample2 {

    public static void main(String[] args) {
        NewAirlines company1 = NewAirlines.AllNippon;
        System.out.println("code :" + company1.getCode() + " name :" + company1.toString() + " order :" + company1.ordinal());//'code :102 name :AllNipponCountry: Japan order :1'
    }
}

Comparableインターフェースを実装しているためequalsが利用できる。 この際のequalsはユーザーで定義した各定数の値ではなく、宣言した順番が利用される。

package enumSample;

enum NewAirlines3 {
    NipponAir(100), AllNippon(100), AirDo(100), StarFlyer(100), JetStar(101);
    private int code;
    NewAirlines3(int number) {
        this.code = number;
    }

    int getCode(){
        return code;
    }
}

public class enumSample3 {

    public static void main(String[] args) {
        NewAirlines3 company1 = NewAirlines3.AllNippon;
        NewAirlines3 company2 = NewAirlines3.StarFlyer;
        System.out.println("name :" + company1.name() + " code :" + company1.getCode());
        System.out.println("name :" + company2.name() + " code :" + company2.getCode());
        System.out.println(company1.equals(company2));//'false'
    }
}

結果

name :AllNippon code :100
name :StarFlyer code :100
false

なお、順番を利用しているのならordinalメソッドをオーバーラードして同じ値を返却するようにすれば、期待通り動作しなくなる可能性がある。 そのようなことを見越してordinalメソッドはfinal宣言されている。

列挙型はクラスなので、抽象メソッドの実装も行える。 その場合のメソッドの実装は定数の宣言時に一緒に行うことになる。

package enumSample;

enum NewAirlines4 {
    NipponAir(100){
        String JapaneseName() {
            return "日本航空";
        }
    },
    AllNippon(100){
        String JapaneseName() {
            return "全日本空輸";
        }
    },
    AirDo(100){
        String JapaneseName() {
            return "北海道国際航空";
        }
    };
    private int code;
    NewAirlines4(int number) {
        this.code = number;
    }

    abstract String JapaneseName();

    int getCode(){
        return code;
    }
}

public class enumSapmleAbst {
    public static void main(String[] args) {
        NewAirlines4 company = NewAirlines4.AllNippon;
        System.out.println(company.JapaneseName());//'全日本空輸'
    }
}

equals()とhashCode()

Objectクラスではequals()と--は同じ動作をしてしまう。 異なるオブジェクトであっても、同じ値を保持している場合はequals()がTrueになってほしい場合があるので、その場合はhashCode()メソッドと一緒にオーバーライドする。

equlas()でTrueを返す場合は、必ず両方のオブジェクトのhashCode()も同じでなくてはならない。 一方、equals()でfalseを返す場合、必ずしもhashCode()が異なる値を返す必要はない。ただし、hashCode()が異なる場合は、equals()もfalseを返す必要がある。

言い換えると「hashCodeが同じ場合でもequals()がfalseになってもよい。」「hashCodeが異なる場合はequals()はfalseでなくてはならない。」

package equals;

class AirPlane{
    public String name;

    public int hashCode() {
        return 100;
    }
    /*hashCode()をオーバーライドしなくてもequalsはTrueを返す。
    ただし、HashMapなどで管理する際正常に作動しなくなる。*/

    public boolean equals(Object param) {
        if((param instanceof AirPlane) && (((AirPlane)param).name.equals(this.name))){
            return true;
        }else {
            return false;
        }
    }
}

public class Equals {

    public static void main(String[] args) {
        AirPlane B747 = new AirPlane();
        B747.name = "JamboJet";

        AirPlane A380 = new AirPlane();
        A380.name = "JamboJet";

        System.out.println(B747.equals(A380));
        System.out.println(B747.hashCode() == A380.hashCode());
    }

}

equalsメソッドをオーバーライドする際に必ずhashCodeを一緒にオーバーライドするのは、一種のお作法で上記のコードはhashCodeメソッドをコメントアウトしても正常に作動する。 ただし、hashMapなどで正常に機能しなくなることがあるようだ。この辺の記述はEffectivr Javaで解説されているようなので、受かったら読んでみる。

Effective Java 第3版

【復習】staticインポート

何のための機能なのかよくわかってないと思っていたら公式から(よほどのことが無い限り)使うなとお達しがあった。

static インポート