怒Mは思いつきでモノを言う

やったことメモなどなど

JJUG ナイトセミナー 「Java SE 10 / JDK10リリース特集」に行ってきた。

※下書きで書いてたのを忘れていたため供養投稿

Java10がリリースされましたね。 まだ、Java9もインストールしていなかったのに・・・ 仕事ではJava6からJava8になったばっかりだし。

JDKリリースモデル

Oracle JDKについては、Oracleとの契約が必要になるようですね。

OpenJDK Oracle JDK
サポート 無償 有償
サポート期間 STS、LTS※ LTS
機能 Oracle JDKと同じ
機能リリース 6ヶ月単位のリリース 6ヶ月単位のリリース
メンテナンス 3ヶ月単位のパッチ 3ヶ月単位のパッチ

※2018/5/22追記
きしだなおき氏のJavaのサポートについてのまとめ を見まして、OpenJDKのサポートについてちゃんと理解していなかったなーと考え、 追記しました。
団体ごとにサポートポリシーは違うんですね。

Java SE 10

2018年3月21日にリリースされました。
けど、2018年9月にはJava SE 11がリリース予定。
Java SE 10のサポートは Java SE 11までの短い寿命だから、 開発でお試し程度にして使ってね。

var:型推論が実装 JEP 286

ローカル変数の型推論が実装されました。
読みやすくなる。短くなる。やったー。

なぜ「ローカル変数」だけ?
フィールドやメソッドの引数に適用すると、影響範囲が複数のファイルにまたがってしまう。

varの制限

varには制限がある。

// 以下は全部error
var x;
var x[] = new int[i];
var x = 1, y = 1;
var x = {1, 2, 3};
var x = null;

Java予約語・キーワード

  • 予約語:識別子として使える
  • キーワード:言語の中で役割がある

varはキーワードで、class名に使えない。

int x = 3900; // OK
var var = var(); // OK
class var {} // NG

原則・ガイドライン

下記のOracle Blogを参照するのがいいと思います。

orablogs-jp.blogspot.jp

終わりに

仕事で使う時にはJava SE 11になっているだろうなー。

Enum型を勉強してみた。

現場で役立つシステム設計の原則 ~変更を楽で安全にするオブジェクト指向の実践技法を読んでいます。
「Chapter 2. 場合分けのロジックを整理する」でJavaの列挙型について書いてあったので調べてみました。

Enum型の使い方(1)

public enum MeatEnum {
    BEEF,
    PORK,
    CHICKEN,
    LAMB;
}

こんな感じで定数を定義します。 BEEFPORKなどは列挙子と呼びます。
Java言語仕様では "Enum Constant" と書かれていますね。 Enumは"enumeration" から来ているのですね。
「列挙」という意味の他に「一覧」とか「目録」という意味に訳せるようで、
プログラミング未経験の新人には、列挙型よりもEnum型を示す表現としては分かりやすい気がします。

public class Test {

    public static void main(String[] args) {

        String something = args[0];
        
        if(MeatEnum.BEEF.toString().equals(something)) {
            System.out.println("BEEF");            
        } else if(MeatEnum.PORK.toString().equals(something)) {
            System.out.println("PORK");            
        } else if(MeatEnum.CHIKEN.toString().equals(something)) {
            System.out.println("CHICKEN");            
        } else if(MeatEnum.LAMB.toString().equals(something)){
            System.out.println("LAMB");            
        } else {
            System.out.println("Something's Meat");
        }
    }
}

↑みたいにif文で使えますが、こういう使い方はしないですよね。

public class Test2 {

    public static void main(String[] args) {
        
        // 標準入力で BEEF/CHICKEN/PORK/LAMB が入力されレバー
        // 文字列に合致した定数で返してくれます。
        // 指定された文字列に合致しない場合は、IllegalArgumentExceptionが発生。
        MeatEnum meat = MeatEnum.valueOf(args[0]);
        
        // switch文だと以下のような感じで、シンプルに書けます。
        switch(meat) {
        case BEEF:
            System.out.println(meat);
            break;
        case PORK:
            System.out.println(meat);
            break;
        case CHICKEN:
            System.out.println(meat);
            break;
        case LAMB:
            System.out.println(meat);
            break;
        default:
            System.out.println("Something's Meat");
            break;
        }
    }
}

switch文で、こういう使い方は見ることがあります。
どちらも標準入力で適切な文字列を渡した場合は、どちらも同じ結果が出力されます。

Enum型の使い方(2)

public enum MeatEnumKai {
    BEEF(286),   //肩ロース、脂身付き
    PORK(386),   //豚バラ、脂身付き
    CHICKEN(200), //鶏モモ、皮付き
    LAMB;   //ラム肉、子羊の背肉

    // コンストラクタは private だけ宣言できます。
    // public, protected を指定するとコンパイルエラーとなります。
    private MeatEnumKai() {
        System.out.println("ラム肉は0Kcal!");
    }

    // 当然コンストラクタは複数定義できます。 
    private MeatEnumKai(int calorie) {
        this.calorie = calorie;
    }    

    private int calorie; // 単位:Kcal

    // Enum型はクラスなので、メソッドやフィールドを定義できます。
    public int getCalorie() {
        return calorie; 
    }
}

Enum型はクラスですので、コンストラクタもフィールドも、メソッドも定義できます。
Enum型には、その列挙子で定義されたもの以外のインスタンスは持ちません。
Enum型を明示的にインスタンス化するとコンパイルエラーとなります。

MeatEnumKai meat = new MeatEnumKai(); // コンパイルエラー

MeatEnumKaiを実際に使ってみます。

public class Test3 {

    public static void main(String[] args) {
        
        MeatEnumKai chicken = MeatEnumKai.CHICKEN;
        MeatEnumKai lamb = MeatEnumKai.LAMB;
        
        System.out.println("鶏肉は、" + chicken.getCalorie() + "Kcal");
        System.out.println("ラム肉は無敵の、" + lamb.getCalorie() + "Kcal");
    }
}
ラム肉は0Kcal!
鶏肉は、200Kcal
ラム肉は無敵の、0Kcal

コンストラクタやメソッドを定義することで、Enum型の使い方に幅ができますね。

Enum型の使い方(3)

「現場で役立つシステム設計の原則」に書いていた使い方です。
Enum型を使いこなせていなかったなーと反省です。

public interface MeatDish {
    public String cooking();
}

こんなインターフェースを用意します。
で、実装します。

public class BeefDish implements MeatDish {
    @Override
    public String cooking() {
        return "牛肩ロースの赤ワイン煮";
    }
}

public class PorkDish implements MeatDish {
    @Override
    public String cooking() {
        return "豚の生姜焼き";
    }
}

public class ChickenDish implements MeatDish {
    @Override
    public String cooking() {
        return "親子丼";
    }
}

public class LambDish implements MeatDish {
    @Override
    public String cooking() {
        return "ジンギスカン";
    }
}

肉料理を返すメソッドです。
これを使う、Enum型を定義します。

public enum MeatEnumHyper {
    BEEF ( new BeefDish() ),   //肩ロース、脂身付き
    PORK ( new PorkDish() ),   //豚バラ、脂身付き
    CHICKEN( new ChickenDish() ), //鶏モモ、皮付き
    LAMB ( new LambDish() );   //ラム肉、子羊の背肉

    private MeatDish dish;

    // BEEF,PORK,CHICKEN,LAMB のコンストラクタ
    private MeatEnumHyper(MeatDish dish) {
        this.dish = dish;
    }
    
    public String offerDish() {
        return dish.cooking(); 
    }
}

使ってみましょう。

public class Test4 {

    public static void main(String[] args) {

        // 標準入力で BEEF/CHIKEN/PORK/LAMB が入力されレバー
        MeatEnumHyper meat = MeatEnumHyper.valueOf(args[0]);

        System.out.println(meat.offerDish());
    }
}

標準入力で肉がちゃんと指定されれば、それぞれの肉料理が呼び出されます。
呼び出し側はMeatEnumHyperにマトンや鴨肉が追加されても意識することなく、
肉料理を表示することができます。

Enum型はもっと使いようがある

他にも、EnumSetEnumMapを使うことで、できることの幅ができます。
「現場で役立つシステム設計の原則」にもありますが、
Enum型を使うことによって、業務ロジックでよくある区分判断で複雑になりがちな、
if文やswitch文を簡素な表現にすることができそうです。

Javaのコンストラクタの挙動について確認した。

この記事はJava Advent Calendar 2017の18日目です。

はじめに

Javaについて勉強を初めて数ヶ月が経ちました。

私の所属している会社では1年目の社員はOracle Certified Java Programmer, Silver SE 8 認定資格を取得します。

新人の頃に受験し合格しましたが、Sun Certified Programmer for the Java 2 Platform 1.4です。

私のところにも新人が配属されるようになり、試験勉強の面倒を見る機会が増えてきている中でちゃんと教えれる必要があるなーと感じ、先日Oracle Certified Java Programmer, Silver SE 8 認定資格を取得してみました。

合格はできましたが試験結果のレポートには間違った問題の傾向をレポートで記載されていたので、その中でコンストラクタの動作についての項目がありました。

今回、自分自身の復習の意味を込めてコンストラクタの仕様について確認したことを残します。

確認方法

コンストラクタの仕様の確認方法は「The Java® Language Specification Java SE 8 Edition」を参照しています。

日本語訳のドキュメントは無いんですね・・・英語成績2の身としてはコンストラクタの章を読むのにも一苦労で、Google翻訳と英和辞典が大活躍です。高校の時の英語教師にテスト返却時に「なめてんのか」と言われたのはトラウマいい思い出です。

その他、手元にある書籍「Java本格入門」「Javaエンジニア養成読本」とか参考になりそうな箇所を探しながら、Google検索しながらです。

Java SE 9 Edition の内容は確認していません。

コンストラクタ(Constructor)

コンストラクタとはクラスのオブジェクトを作成するときに実行される処理のことです。

コンストラクタ宣言

コンストラクタ宣言にはいくつかの決まりがあります。


  1. コンストラクタの名前はクラス名と同一であること
  2. コンストラクタ宣言では戻り値は定義しないこと
  3. コンストラクタはオーバーロードできる
  4. コンストラクタのアクセス修飾子はpublic,protected,private,修飾子なし(パッケージプライベート)を指定できる
  5. コンストラクタにはthrowsを定義できる
  6. コンストラクタは継承できない

abstract public class Meat {

    // 1. コンストラクタの名前はクラス名と同一であること
    // 2. コンストラクタ宣言では戻り値は定義しないこと
    public Meat() { 
        System.out.println("Constructor Meat()");
    }

    // 3. コンストラクタはオーバーロードできる
    public Meat(int num) {
        System.out.println("Constructor Meat(int num)");
    }

    // 4. コンストラクタのアクセス修飾子は指定できる
    // public, protected, private, 修飾子なし
    protected Meat(int num1, int num2) {
        System.out.println("Constructor Meat(int num1, int num2)");
    }

    // 5. コンストラクタにはthrowsを定義できる
    Meat (String s1, String s2) throws RuntimeException {
        System.out.println("Constructor Meat(String s1, String s2)");
    }

    // 6. コンストラクタは継承できない(抽象化できないということで良い?)
    // コンパイルエラー
    abstract Meat(String s);
}

とりあえず、こんな感じでしょうか。

コンストラクタを1つも作らなかった場合は、コンパイラが自動でデフォルトコンストラクタを追加してくれます。 デフォルトコンストラクタは引数なし・処理なしのコンストラクタです。

注意点としては、1つでもコンストラクタを明示的に作っている場合はデフォルトコンストラクタは作られないということです。

コンストラクタの実装

コンストラクタの実装に関してもルールがあります。 試験問題で間違えていたのは、この辺りだったかと思います。


  1. クラス内の別のコンストラクタを呼び出す場合はthis()を使う(引数を渡せます)
  2. 子クラスから親クラスのコンストラクタを呼び出す場合はsuper()を使う(引数を渡せます)
  3. コンストラクタ内で別のコンストラクタを明示的に呼び出す場合は必ず一番最初に呼び出す
  4. 子クラスのコンストラクタ内で親クラスのコンストラクタを明示的に呼び出さない場合はsuper()が自動的に呼び出される
  5. コンストラクタは再帰的に呼び出せない

// 親クラス
abstract public class Meat {

    public Meat() {
        System.out.println("Constructor Meat()");
    }
    
    public Meat(int num) {
        System.out.println("Constructor Meat(int num)");
    }
    
    protected Meat(int num1, int num2) {
        System.out.println("Constructor Meat(int num1, num2)");
    }
    abstract public String toString();
}

// 子クラス
public class Beef extends Meat {

    public Beef() {
        // 4. 子クラスのコンストラクタ内で親クラスのコンストラクタを
        // 明示的に呼び出さない場合はsuper()が自動的に呼び出される
        System.out.println("Constructor Beef()");
    }
    
    public Beef(int num) {
        // 1. クラス内の別のコンストラクタを呼び出す場合は`this()`を使う(引数を渡せます)
        this();  
        System.out.println("Constructor Beef(int num)");
    }
    
    public Beef(int num1, int num2) {
        // 2. 子クラスから親クラスのコンストラクタを呼び出す場合はsuper()を使う(引数を渡せます)
        // 3. コンストラクタ内で別のコンストラクタを明示的に呼び出す場合は必ず一番最初に呼び出す
        super(10); 
        System.out.println("Constructor Beef(int num1, int num2)");
    }

    // 5. コンストラクタは再帰的に呼び出せない
    // コンパイルエラーになる
    public Beef(String s) {
        this(s);
    }

ルール3とルール4についての理解が足りていない感じです。
コメントをいただきまして、理解できました!(2017/12/24追記)

    public Beef(){
        super();
        this(10);  // ここでコンパイルエラー
    }

これはルール3に基づいているので理解はできます。

    public Beef(int num) {
        this();  
        System.out.println("Constructor Beef(int num)");
    }

けど、上記のコンストラクタの場合は、this()の前にルール4に基づきsuper()が自動的に呼び出されます。
上記のコンストラクタの場合は、this()の前にsuper()が呼び出されているように見えますが、
Beef()super()が呼ばれているだけなので、Beef(int num)super()が呼ばれているわけではありません。

実際の動きを確認します。

    public static void main(String[] args) {
        System.out.println("beef1 -------->");
        Beef beef1 = new Beef();
        System.out.println("beef2 -------->");
        Beef beef2 = new Beef(10);
        System.out.println("beef3 -------->");
        Beef beef3 = new Beef(30, 40);
    }
MacBook-Pro:bin $ java constructor.sample.Beef
beef1 -------->
Constructor Meat()
Constructor Beef()
beef2 -------->
Constructor Meat()
Constructor Beef()
Constructor Beef(int num)
beef3 -------->
Constructor Meat(int num)
Constructor Beef(int num1, int num2)

必ず、親クラスのコンストラクタが呼び出され、その後に子クラスのコンストラクタが呼び出されていることがわかります。

最後に

しかし、なぜthis()super()を2つ明示的に呼び出せないのか、その辺の仕様が記載されている箇所まで「The Java® Language Specification Java SE 8 Edition」を読めていません。・・・ちゃんと書いてあることを信じて、もう少し読み進めます。 理解できました!

今回、かなり中途半端に終わってしまったので、この記事は今後メンテしていく予定です。