ITお絵かき修行

3歩歩いても忘れないために

JavaDayTokyo2016メモ【2-A: Project Jigsawではじめるモジュール開発】

JavaDayTokyo2016に参加した。
下記セッションに参加したので、参加メモを書いておく。
※正確な情報・見解は後日公開される(であろう)登壇者のスライドや公式資料等を参照のこと。
www.oracle.co.jp

[2-A: Project Jigsawではじめるモジュール開発]

Java in the Box 櫻庭 祐一 氏

【概要】

Project Jigsawの導入背景、モジュールの作成方法・使用方法の紹介

【内容】

1.導入背景

(1)現状
・現在のJavaには「クラスパスが複雑」「rt.jarの肥大化」という2つの課題を抱えている。
・上記に関連し、「JAR HELL」と呼ばれるような.jarファイル間の依存関係の複雑さも発生している。

(2)(1)の原因
・クラスパス・JARのパッケージング機構における、
「依存関係」「バージョン」「公開範囲」をコントロールする仕組みが不足しているため。
・特にクラスのpublic属性のアクセス範囲が広すぎることが大きな原因とみられている。
 →『public is TOO public』

(3)歴史
・2005年頃から提言はされていた。
・JSR277、JSR294といったJSRを経てProject Jigsawが発足し、Java9で採用。

2.モジュールの作成

・モジュールを作成する際の定義内容は以下の3要素。

Module extends JAR{
    dependency …「依存関係」
    export   …「公開範囲」
    version   …「バージョン」 ※バージョンに対応する仕組みはまだない。今後どうなるか不明とのこと。
}

・モジュールはJARの拡張。JARと同じくクラスパスに追加しクラスを読み込ませることは可能。

・今までのpublicクラスは誰からでもアクセス可能だった。これからの(※モジュール内の)publicクラスは、公開範囲に対応するクラスからしかアクセスできなくなる。
 →今後はmainメソッドを含むクラスを明示的に公開しないと、アプリが起動できなくなる。

・モジュールの定義内容は「modules-info.java」に記述する。「modules-info.java」は「src」フォルダ直下に置く。(※読み込み効率、複数ファイル対応etcの為)
例:modules-info.java

module fxdemo {
    requires javafx.controls;
    requires javafx.graphics;
}
3.モジュールの使用

(1)コンパイル
・jarコマンドに「--create」などのオプションを付与して実行する。tarっぽいらしい。

(2)実行
・実行時はオプションを指定して実行する。
 「モジュールのみ使用」「モジュールとクラスパス」といった使い方が可能。
例:実行コマンドの先頭部

java -mp mods -addmods ・・・

・mainメソッドの実行コマンドは「モジュール名/クラス名」となる。

4.MISC about Module

・「jdeps」…自分たちでモジュール(modules-info.java)を作成する際に使用できるツール。クラス、jarファイルの依存関係を出力できる。
・「jlink」…JREのカスタマイズが可能となるツール。組み込み用のライブラリのみ抜き出す、コアライブラリのみ抜き出すといったことが可能となる。


[感想など]
・モジュールを使った構成管理のプロセスが、antやGradleといったビルドツールで実現可能なのかが気になった。
・発表資料は毎回JavaFXで作成されているとのこと。

Upgrade Java SE 7 to Java SE 8 OCP Programmer(1Z0-810)対策 第三章 ラムダ式を使用するコレクション(1)

第三章よりラムダ式を使用したstreamに対するフィルタリングを取り扱う。

■注意
正確な情報は下記リンクを参照してください。

■試験対策ページ
java.boot.by

■試験
Upgrade Java SE 7 to Java SE 8 OCP Programmer | Oracle Certification Exam

3.1. forEach()メソッドメソッド・チェーンを使用したコレクションの反復処理

繰り返し処理を制御するイテレータには一般的に2つの実装アプローチが存在する。

Activeな(外部)イテレータ
Activeなイテレータでは、イテレータの制御者が、次の要素へ処理が移ったことを伝えたり、各要素へアクセスした際に処理を行ったり等、制御者自身が生成した繰り返し処理を細かく制御できる。
Java1.0、1.1ではVectorとHashtableというコレクションクラスと、Iteratorデザインパターンを実装したEnumrationと呼ばれるクラスがあった。

Vector names = new Vector();
names.add("George");
names.add("Fred");
names.add("Ron");

Enumeration e = names.elements();
while (e.hasMoreElements()) {
    String name = (String) e.nextElement();
    System.out.println(name);
}

Java1.2ではIteratorデザインパターンIteratorというクラスに実装された。Iteratorでは未だに返却値のキャストが必要とされていた。Java1.2から1.4にかけて文字列の繰り返し処理はは下記のようなコードだった。

List names = new ArrayList();
names.add("George");
names.add("Fred");
names.add("Ron");

Iterator i = names.iterator();
while (i.hasNext()) {
    String name = (String) i.next();
    System.out.println(name);
}

Java5ではジェネリクスが導入され、Iteratorインタフェースクラスとforループの実装が改修された。

// Java SE 5 !!!
package java.lang;

import java.util.Iterator;

public interface Iterable<T> {
    public Iterator<T> iterator();
}
package java.util;

public interface Iterator<E> {
    public boolean hasNext();
    public E next();
    public void remove();
}

メモ:forループの内部では Iterator.hasNext() と Iterator.next()が呼ばれている。そのためActiveなイテレータとみなす事ができる。

List<String> names = new ArrayList<String>();
names.add("George");
names.add("Fred");
names.add("Ron");

for (String name : names) {
    System.out.println(name);
}


Passiveな(内部)イテレータ
Passiveなイテレータではイテレータ自身が繰り返し処理を制御する。イテレータの制御者は基本的に、コレクション内の要素に対するいくつかの操作をイテレータに行うよう指示します。Java8ではこのアプローチが利用可能である。

Java8ではjava.lang.IterableインタフェースクラスがforEachメソッドを持つ

package java.lang;

import java.util.Iterator;
import java.util.function.Consumer;

public interface Iterable<T extends Object> {

    public Iterator<T> iterator();

    public default void forEach(Consumer<? super T> cnsmr) {
        // ...
    }

    ...
}

java.util.Collectionインタフェースクラスはjava.lang.Iterableクラスを継承している。そのためjava.util.List もしくは java.util.Setインタフェースクラスの実装クラスは自動的にforEachメソッドを持っている。
java.util.Mapインタフェースクラスはjava.util.Collectionインタフェースクラスを継承していない。しかしJava8からは一貫性のためにforEachメソッドを持つ。
このメソッドはひとつのパラメータをとる関数型インタフェースである。そのため実体のパラメータを通すforEachメソッドラムダ式の候補となる。

List<String> names = new ArrayList<>();
names.add("George");
names.add("Fred");
names.add("Ron");

names.forEach(name -> System.out.println(name));

もしくはメソッド参照を使用する。

...
names.forEach(System.out::println);
...

上記のPassiveなイテレータと前セクションのActiveなイテレータとの差異を述べる。
Activeなイテレータのリスティング処理では、イテレーションのループ構造がイテレーションを制御し、ループを通過する度にループ内のオブジェクトはリストより回収され、文字列を出力する。
Passiveなイテレータのリスティング処理では、明確なループ処理は存在しない。単純に、リスト内におけるオブジェクトの処理(─今回の場合は単純な文字列の出力)をforEachメソッドにて呼び出す。
イテレーションの制御はforEachメソッドに依存する。

java.util.ListクラスにおけるforEachメソッドシグネチャ

package java.util;

public interface List<E extends Object> extends Collection<E> {

    /**
     * Performs the given action for each element of the Iterable until all elements have been
     * processed or the action throws an exception.
     */
    public default void forEach(Consumer<? super T> cnsmr) {
        ...
    }

    ...
}

java.util.SetインタフェースクラスにおけるforEachメソッドシグネチャ

package java.util;

public interface Set<E extends Object> extends Collection<E> {

    /**
     * Performs the given action for each element of the Iterable until all elements have been
     * processed or the action throws an exception.
     */
    public default void forEach(Consumer<? super T> cnsmr) {
        ...
    }

    ...
}


java.util.MapインタフェースクラスにおけるforEachメソッドシグネチャ

package java.util;

public interface Map<K extends Object, V extends Object> {

    /**
     * Performs the given action for each entry in this map until all entries have
     * been processed or the action throws an exception.
     */
    public default void forEach(BiConsumer<? super K, ? super V> bc) {
        ...
    }

    ...
}

Upgrade Java SE 7 to Java SE 8 OCP Programmer(1Z0-810)対策 第二章 ラムダ式を使用する事前定義済みの型の使用

第二章よりJava8で追加された関数型インタフェースの各クラス、メソッドの仕様を追っていく。
各インタフェースの詳細まで追うのは辛いので取り扱わない…

■注意
正確な情報は下記リンクを参照してください。

■試験対策ページ
java.boot.by

■試験
Upgrade Java SE 7 to Java SE 8 OCP Programmer | Oracle Certification Exam

2.1. java.util.function パッケージのインタフェースについて説明する

多くの再利用可能な機能要件について、関数型インタフェースとラムダ式は取り込むことができる。
Java8の設計者は一般的なユースケースを取り込み、それらのためのライブラリを実装した。
java.util.functionと呼ばれる新しいパッケージは、一般的なユースケースに対する機能を提供する。

※WARNING※
このセクションで提示するインタフェースのリストは完全ではない。最も重要な部分の関数型メソッドシグネチャのみ列挙する。

java.util.function.Predicate
パラメータの型: T
戻り値の型  : boolean
メソッドの詳細: 1つの引数の述語(boolean値関数)を表す。

package java.util.function;

@FunctionalInterface
public interface Predicate<T extends Object> {

    public boolean test(T t);

}


java.util.function.Consumer
パラメータの型: T
戻り値の型  : void
メソッドの詳細: 単一の入力引数を受け取って結果を返さないオペレーションを表す。

package java.util.function;

@FunctionalInterface
public interface Consumer<T extends Object> {

    public void accept(T t);

}


java.util.function.Function
パラメータの型: T
戻り値の型  : R
メソッドの詳細: 1つの引数を受け取って結果を生成する関数を表す。

package java.util.function;

@FunctionalInterface
public interface Function<T extends Object, R extends Object> {

    public R apply(T t);

}


java.util.function.Supplier
パラメータの型: (なし)
戻り値の型  : T
メソッドの詳細: 結果のサプライヤを表す。サプライヤを呼び出すたびに新規もしくは異なる結果が返される必要はない。

package java.util.function;

@FunctionalInterface
public interface Supplier<T extends Object> {

    public T get();
}


java.util.function.ToDoubleFunction
パラメータの型: T
戻り値の型  : double
メソッドの詳細: double値の結果を生成する関数を表す。これは、Functionに対して、doubleを生成するようプリミティブ化を行ったもの。

package java.util.function;

@FunctionalInterface
public interface ToDoubleFunction<T extends Object> {

    public double applyAsDouble(T t);
}


java.util.function.BiPredicate
パラメータの型: T,U
戻り値の型  : boolean
メソッドの詳細: 2つの引数の述語(boolean値関数)を表す。これは、Predicateを引数を2個取るようにしたもの。

package java.util.function;

@FunctionalInterface
public interface BiPredicate<T extends Object, U extends Object> {

    public boolean test(T t, U u);

}


java.util.function.UnaryOperator
パラメータの型: T
戻り値の型  : T
メソッドの詳細: 単一のオペランドに対する操作に作用して同じ型の結果を生成する演算を表す。これは、オペランドと結果の型が同じである場合においてFunctionを特殊化したもの。
メモ:Functionクラスを拡張しており、Function.apply(Object)メソッドを引き継いでいる。

package java.util.function;

@FunctionalInterface
public interface UnaryOperator<T extends Object> extends Function<T, T> {

}

【参考】
java.util.function (Java Platform SE 8)

Upgrade Java SE 7 to Java SE 8 OCP Programmer(1Z0-810)対策 第一章 ラムダ式(3)

(1)(2)に引き続き、ラムダ式の章を進める。

■試験対策ページ
java.boot.by

■注意
全文が意訳です。正確な情報は下記リンクを参照してください。

■試験
Upgrade Java SE 7 to Java SE 8 OCP Programmer | Oracle Certification Exam

1.3. ラムダ式について説明する 匿名インナークラスを使用したコードをラムダ式を使用するようにリファクタリングする 型推論とターゲット型付けについて説明する

匿名インナークラスとラムダ式で置き換える
開発者のコードの一部には無名インナークラスが含まれており、時として読みづらい。
開発者は匿名インナークラスを、読みやすさとメンテナンスしやすさのために置き換えるべきである。

匿名インナークラスとラムダ式での置き換えによって、定型化したコードの何行かのコードを書くよりも開発時間を短縮できるだろう。
一般的なSwingアプリケーションでは匿名インナークラスをアプリケーションに機能追加する際に必要とする。
例えば匿名クラスはボタンにactionを追加するよく使われる方法である。ここでの問題は内部クラスは読みにくいということであり、多くの定型化したコードを書く必要があることである。

下記のコードはボタンのactionの実装として匿名インナークラスの実装を試している。
まずは下記のラムダ式で置き換える前のコードを見てほしい。

JButton button = ...
JLabel comp = ...

button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        comp.setText("Button has been clicked");
    }
});

例えばActionListenerインタフェースクラスの具象クラスを実装する。このインタフェースはactionPerformedという1つのメソッドを保持している。
actionPerformedメソッドはユーザーが実際に画面上のボタンを押下した時に、buttonインスタンスより呼び出されている。
匿名インナークラスはメソッドの実装を提供する。

匿名インナークラスはJavaプログラマーがコード内でデータを受け渡しやすいように設計されている。
だが不幸なことに、それらはコードをわかりにくくしている。ここでは1行の重要なコードを呼び出すために4行の定型化されたコードが必要となる。

定型化されたコードの問題は1つではない。このコードは開発者の意図が明確でなく非常に読みづらい。
開発者が行いたいことはいくつかの振る舞いを実行するすることであり、(メソッドに)オブジェクトを渡したくない。
Java8ではこのコードをラムダ式で書くことができる。下記にサンプルを示す。

JButton button = ...
JLabel comp = ...

button.addActionListener(e -> comp.setText("Button has been clicked"));

・インタフェースを実装したオブジェクトを渡す代わりに、匿名の関数(ブロック化されたコード)を渡している。
・"e"(Javaの修飾子名以外が指定可能)は匿名インナークラスのパラメータのようなパラメータである。
・"->"はラムダ式の関数部からパラメータを分離する。ユーザがボタンをクリックした際に動作する。
・他の匿名インナークラスを使用していたときとの違いは変数eの宣言である。以前は明確に ActionEvent e と型を宣言していた。
 今回の例(※ラムダ式を使った場合)開発者に型となるクラスが渡されていないが、このクラスはコンパイルすることができる。
 内部で何が起こっているかというと、変数eに対してaddActionListenerメソッドシグネチャより型推論が行われている。
 これは、開発者は明確にパラメータの型を記述する必要がないということを意味する。

※WARNING※
いくつかの場面ではJavaコンパイラが型を推論できない場合、変数の型やパラメータの型を明確に記述する必要がある。

下記の例はラムダ式の引数に対して、明示的に型を記述する場合。

JButton button = ...
JLabel comp = ...

button.addActionListener((ActionEvent e) -> comp.setText("Button has been clicked"));

ラムダ式の構文
ラムダ式は下記の構文から構成される。

  • 括弧で囲まれたカンマ区切りのリスト

メモ:ラムダ式のパラメータの型は省略することができる。加えて、パラメータがひとつの場合は括弧を省略することができる。例えば下記のラムダ式は正しい。

s -> s.getAge() >= 18
(Student s) -> s.getAge() >= 18
(s) -> s.getAge() >= 18
  • メソッド本文は「1つの式」もしくは「ブロック」から構成される。
... -> s.getAge() >= 18

1つの式しか記述しない場合、Javaの実行環境は式を戻り値の評価を行う。代わりに「return」句を使うことができる。

... -> { return s.getAge() >= 18; }

return句に型はない。ラムダ式では、式を中括弧{}で囲む必要がある。しかし、voidメソッドは中括弧で囲む必要はない。例えば、下記のラムダ式は正しい。

email -> System.out.println(email)

Upgrade Java SE 7 to Java SE 8 OCP Programmer(1Z0-810)対策 第一章 ラムダ式(2)

(1)に引き続き、ラムダ式の章を進める。

■注意
全文が意訳です。正確な情報は下記リンクを参照してください。

■試験対策ページ
java.boot.by

■試験
Upgrade Java SE 7 to Java SE 8 OCP Programmer | Oracle Certification Exam

1.2. 関数型インタフェースクラスの実装

関数型インタフェースクラスはラムダ式メソッド参照のための型を提供する。
各関数型インタフェースクラスは関数メソッドと呼ばれる単一の抽象メソッド(single abstract method (SAM))を持ち、ラムダ式のパラメータと戻り値に対応あるいは合致する。
JDK8において、既に存在するRunnabl、Callable、 Comparator、ActionListenerのような、単一のメソッドを持つインタフェースクラスは、現在の関数型インタフェースとラムダ式となる。これらは単一の抽象メソッドが使われているインタフェースクラスにて使用することができる。


@FunctionalInterface アノテーション
Java8では@FunctionalInterfaceという新しいアノテーションを関数型インタフェースに付与するよう紹介している。関数型インタフェースであるという意図の伝達とコンパイラにいくつかの追加のチェックを許可するためである。

※WARNING※
@FunctionalInterfaceはオプションである。Javaコンパイラに対するヒント句である。

例えば下記のインタフェースはコンパイルに成功する。

public interface MyInterface {
}

しかし関数型インタフェースであることを示した場合(コンパイルに失敗する)

// Java 8 compiler fails
@FunctionalInterface
public interface MyInterface {
}

抽象メソッドが無かった場合、コンパイラは"MyInterface is not a functional interface" や "no abstract method was found"といったエラーを発生させる。
2つ目のメソッドを追加した場合もエラーを発生させる。

// Java 8 compiler fails !
@FunctionalInterface
public interface MyInterface {
    void doIt();
    void doItNow();
}

インタフェースにおけるデフォルトメソッド
Java8のインタフェースクラスではデフォルトメソッドとstaticメソッドをサポートする。
デフォルトメソッドインスタンスメソッドの先頭に「default」キーワードを付けて定義する。またデフォルトメソッドはコードの本文を提供する。
デフォルトメソッドを含むインタフェースの具象クラスは、デフォルトメソッドとその他のメソッドをオーバーライドすることができる。
関数型インタフェースクラスは単一の抽象メソッドを必要とする。下記のコードはコンパイルに失敗する、無効な関数型インタフェースクラスである。

// Java 8 compiler fails !
@FunctionalInterface
public interface MyDefInterface {
    default void doIt() { /* cool implementation */ }
}


しかし下記の関数型インタフェースクラスはコンパイルに成功する。

@FunctionalInterface
public interface MyDefInterface {
    default void doIt() { /* cool implementation */ }
    void doItNow(); // Single Abstract Method (SAM)
}

インタフェースにおけるstaticメソッド
staticメソッドは定義したクラスと結びついている、というよりもクラスのどのオブジェクトでも使用できる。
各クラスのインスタンスは、クラス内でstaticメソッドを共有している。
Java8ではインタフェースに定義したstaticメソッドをデフォルトメソッドで補足することができる。

staticメソッドのようにインタフェースにstaticメソッドを定義したい時はメソッドシグネチャの先頭に「static」を付与する。
インタフェースクラスの全てのメソッド宣言は「public」となるので「public」キーワードは省略することができる。

staticメソッドを含むインタフェースクラスを実装したい時、staticメソッドはまだインタフェースクラスの一部であり、実装クラスの一部ではない。
理由はクラス名をメソッドの接頭辞に付加できないため。staticメソッドの呼び出し時はインタフェースクラス名を付記する必要がある。

関数型インタフェースは単一の抽象メソッドを定義する必要がある。下記のコードはコンパイルに失敗する、無効な関数型インタフェースである。

// Java 8 compiler fails !
@FunctionalInterface
public interface MyStatInterface {
    static void doIt() { /* cool implementation */ }
}

しかし下記の関数型インタフェースはコンパイルに成功する。

@FunctionalInterface
public interface MyStatInterface {
    static void doIt() { /* cool implementation */ }
    void doItNow();
}

java.lang.Objectクラスの関数型インタフェース
インタフェースクラスがjava.lang.Objectクラスの抽象メソッドのひとつをオーバーライドした場合、関数型インタフェースの抽象メソッドとしてカウントしない。
例えば java.util.Comparatorは関数型インタフェースであるが、2つの抽象メソッドを有している。

package java.util;

@FunctionalInterface
public interface Comparator<T> {

    int compare(T o1, T o2);

    boolean equals(Object obj);

    ...
}

(関数型インタフェースである)理由は、equals() メソッドjava.lang.Objectクラスの持つpublicメソッドシグネチャと一致するため。

【参考】
Java関数型インターフェースメモ(Hishidama's Java8 Functional Interface Memo)

Upgrade Java SE 7 to Java SE 8 OCP Programmer(1Z0-810)対策 第一章 ラムダ式(1)

Upgrade Java SE 7 to Java SE 8 OCP Programmer(1Z0-810)を受けるための勉強メモ。
下記サイトが詳しそうな雰囲気だったので訳しながら追ってゆく。

■注意
全文が意訳です。正確な情報は下記リンクより一次情報を参照してください。

■お世話になる試験対策っぽいページ
java.boot.by

githubにも別の対策サイトがあったので紹介
rahulsh1.github.io


■試験
Upgrade Java SE 7 to Java SE 8 OCP Programmer | Oracle Certification Exam

1.1. ネストしたクラス、静的クラス、ローカル・クラス、無名クラスなど、Javaの内部クラスを使用する

Javaでは4種類の内部クラスを実装することができる。下記に名称と実装例を示す。

staticな内部クラスコンパイルが通るよう修正済

public class Outer {
	static class Inner {
		void doIt() {
			System.out.print("Static nested class reference is: " + this);
		}
	}

	public static void main(String[] args) {
		Outer.Inner n = new Outer.Inner();
		n.doIt();
	}
}


●標準出力

Static nested class reference is: chap1.stat.Outer$Inner@2a139a55

内部クラスと似ているが、静的クラスとして宣言している。
エンクロージングクラスのインスタンスに依存しない。エンクロージングクラスのインスタンスのstaticメソッドとフィールドへのみアクセスすることができる。
「利用シーン」
・エンクロージングクラスのインスタンスと分離したい場合
・エンクロージングクラスのインスタンスより生存期間が長い場合


内部クラス(インナークラス)
内部クラスは明示的なインスタンスとして生成する。(メンバ変数として外部のクラス内で宣言される。)
内部クラスはエンクロージングクラスと内部クラスの全てのメソッド、フィールド、エンクロージングインスタンスのthisによる参照にアクセスすることができる。

public class Outer {

    private int secretVar = 1;

    public void makeInner() {
        Inner in = new Inner();
        in.seeOuter();
    }

    class Inner {
        public void seeOuter() {
            System.out.println("Outer 'secretVar' is " + secretVar);
            System.out.println("Inner class reference is " + this);
            System.out.print("Outer class reference is " + Outer.this);
        }
    }

    public static void main(String... args) {
        Inner i = new Outer().new Inner();
        i.seeOuter();
    }
}


●標準出力

Outer 'secretVar' is 1
Inner class reference is chap1.inner.Outer$Inner@2a139a55
Outer class reference is chap1.inner.Outer@15db9742


内部クラスはエンクロージングクラスとは別のスコープで定義される。
無名クラスであることがある。(次のセクションを参照)
外部・内部クラスはお互いのメソッドを直接参照することが可能(※private宣言されてる場合でも)
OuterクラスのメソッドはInnerクラスのインスタンスを生成することでInnerクラスを使用する。
内部クラスのインスタンスはエンクロージングクラスのインスタンスとは分離されている。
内部クラスのインスタンスはエンクロージングクラスのインスタンスよりインスタンス化する必要がある。
内部クラスはエンクロージングインスタンスの生成タイミングに縛られる。(※変更不可)


ローカル内部クラス(ローカルインナークラス)
ローカル内部クラスはブロック内で定義される。ブロックは0以上の要素で構成され互いに関係する。

インスタンスメソッドにおけるローカル内部クラス」

public class Outer {

    private String x = "outer";

    public void doStuff() {

        class MyInner {
            public void seeOuter() {
                System.out.println("x is " + x);
            }
        }

        MyInner i = new MyInner();
        i.seeOuter();
    }

    public static void main(String[] args) {
        Outer o = new Outer();
        o.doStuff();
    }
}

●標準出力

x is outer


「staticメソッドにおけるローカル内部クラス」

public class Outer {

    private static String x = "static outer";

    public static void doStuff() {

        class MyInner {
            public void seeOuter() {
                System.out.println("x is " + x);
            }
        }

        MyInner i = new MyInner();
        i.seeOuter();
    }

    public static void main(String[] args) {
        Outer.doStuff();
    }
}

●標準出力

x is static outer

内部クラスはエンクロージングクラスに対するアクセスを持つ。
加えて、内部クラスはエンクロージングクラス内の変数に対するアクセスを持つ。
JavaSE7以前のバージョンでは、内部クラスはエンクロージングクラスの「定数」に対するアクセスを持っている。
内部クラスがエンクロージングクラスの変数、パラメータへアクセスする時は他の変数やパラメータで保持する。

// Java SE 7 syntax
...
public void validatePhoneNumber(String phoneNumber) {

    final int numberLength = 10;

    class PhoneNumber {
        PhoneNumber(String phoneNumber) {
            if (phoneNumber.length() > numberLength) { ... }
        }
    }
}
...

※WARNING※
Java8からは、内部クラスがエンクロージングクラスの定数または"事実上の定数"へアクセスできる。
初期化後に一度も変更されていない変数、パラメータを"事実上の定数"とする。

// Java SE 8 - successfully compiles and runs;
// Java SE 7 - compilation failure
...
public void validatePhoneNumber(String phoneNumber) {

    int numberLength = 10; // no mandatory 'final' anymore, as long as variable not modified

    class PhoneNumber {
        PhoneNumber(String phoneNumber) {
            if (phoneNumber.length() > numberLength) { ... }
        }
    }
}
...

※WARNING※
下記の例はエンクロージングクラスの変数(numberLength)の代入文が内部クラス(PhoneNumberクラス)にある場合。
変数numberLengthは定数ではないため、Javaコンパイラは「Local variables referenced from an inner class must be final or effectively final」というエラーメッセージを生成する。

// Java SE 8 - compilation failure
...
public void validatePhoneNumber(String phoneNumber) {

    int numberLength = 10;

    class PhoneNumber {

        numberLength = 12;

        PhoneNumber(String phoneNumber) {
            if (phoneNumber.length() > numberLength) { ... }
        }
    }
}
...


匿名インナークラス
GUIアプリにおけるウィジェットに対するActionリスナーを追加するときによく利用する。

public class Outer {

    public void init() {
        JButton button = new JButton();
        JLabel comp = new JLabel();

        button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                comp.setText("Button has been clicked");
            }
        });
    }
}

内部クラスは匿名クラスであり、エンクロージングクラスのインスタンスの一部となる。
内部クラスの一部の箇所でインスタンス化して使用したいときによく使用される。

SomeType x = new SomeType() {     // unnamed (i.e. anonymous) inner class
    // body of class goes here
};

・SomeTypeは存在するクラスまたはインタフェースでなければならない。
・SomeTypeがクラスの場合:提供されたクラスの拡張クラス
・SomeTypeがインタフェースの場合:提供されたインタフェースの実装クラス


シャドーイング
変数やパラメータ等の箇所にて宣言した型が独特なスコープ(内部クラスやメソッドの宣言部)にて、エンクロージングクラスのスコープ内の別の宣言と同じ名前があると、
エンクロージングクラスのスコープの宣言を隠蔽する。
覆い隠された宣言部へ対して、宣言の名前単独で参照することはできない。

public class Shadow {

    public int x = 0;

    class FirstLevel {

        public int x = 1;

        void methodInFirstLevel(int x) {
            System.out.println("x = " + x);
            System.out.println("this.x = " + this.x);
            System.out.println("Shadow.this.x = " + Shadow.this.x);
        }
    }

    public static void main(String... args) {
        Shadow s = new Shadow();
        Shadow.FirstLevel fl = s.new FirstLevel();
        fl.methodInFirstLevel(2);
    }
}

●標準出力

x = 2
this.x = 1
Shadow.this.x = 0

この例では3つのxという変数が定義されている。Shadowクラスのメンバ変数、内部クラスのFirstLevelのメンバ変数、methodInFirstLevelメソッドのパラメータである。
内部クラスのFirstLevelのメンバ変数xは、methodInFirstLevelメソッドのパラメータの変数を隠蔽する。
その結果、methodInFirstLevelメソッドのパラメータの変数を使用したい時は、メソッドのパラメータを参照するようにする。
内部クラスのFirstLevelのメンバ変数を参照するには、エンクロージングクラスのインスタンスのスコープを表すthisキーワードを使用する。

System.out.println("this.x = " + this.x);


参照するメンバ変数は、所属するクラスよりもより大きなスコープに属している。
例えば、下記の代入文はmethodInFirstLevelメソッドからShadowクラスのメンバ変数へアクセスする。

System.out.println("Shadow.this.x = " + Shadow.this.x);


■参考
四種類の内部クラス - にょきにょきブログ
【改訂版】Eclipseではじめるプログラミング(17):あなたの知らない、4つのマニアックなJava文法 (1/3) - @IT
裏Javaメモ(Hishidama's Java Memo)

jol(Java Object Layout)を使ったオブジェクトのサイズ取得

jol(Java Object Layout)というOpenJDKに含まれるツールを使用して、オブジェクトのメモリサイズが取得できるらしいので試してみる。


OpenJDK: jol


■環境
Windows7 Home Premium 64bit
JDK 1.8.0_60-b27
Eclipse Luna SR1 (4.4.1)
・jol (0.4) (※jol-cli-0.4-full.jar)


■すること
jolを使用して何ができるのかを調べる。
主目的は、1処理におけるメモリ量の取得とする。
よって割とメイン所(と思われる)の機能のみ追いかける。


■クラス紹介
1.ClassLayoutクラス
jolを使用する上で基本となるクラス。クラス・クラスインスタンスのメモリ情報を取り扱う。

●処理対象特定系

通番 static 戻り値の型 メソッド 引数 メソッド概要
1 ClassLayout parseInstance Object インスタンスのメモリ情報を取得する。
2 ClassLayout parseInstance Object, Layouter インスタンスのメモリ情報を取得する。第二引数のLayouterで読み込み時の動作変更が可能
3 ClassLayout parseClass Object クラスのメモリ情報を取得する。
4 ClassLayout parseClass Object, Layouter クラスのメモリ情報を取得する。第二引数のLayouterで読み込み時の動作変更が可能

●サイズ取得系

通番 static 戻り値の型 メソッド 引数 メソッド概要
5 SortedSet< FieldLayout> fields - インスタンス内に含まれるフィールドのメモリ情報を取得する。
6 int headerSize - インスタンスのヘッダサイズ(int値)を取得する。
7 int instanceSize - インスタンスのサイズ(int値)を取得する。
8 String toPrintable - インスタンスのメモリ情報を整形して詳細情報を出力する。
9 String toString - インスタンスのメモリ情報を整形して出力する。


2.FieldLayoutクラス
フィールドのメモリ情報を取り扱う。

●サイズ取得系

通番 static 戻り値の型 メソッド 引数 メソッド概要
1 String name - フィールド名を取得する。
2 String offset - インスタンスが占めるサイズの先頭からのメモリ量を取得する。
3 int size - フィールドのサイズを取得する。
4 String typeClass - フィールドの型を取得する。
5 String toString - フィールドの詳細を取得する。


3.VMSupportクラス
JVMのメモリに関連する情報を取得する。

通番 static 戻り値の型 メソッド 引数 メソッド概要
1 String vmDetails - JVMのメモリ情報を出力する。


4.GraphLayoutクラス
インスタンス生成からGraphLayout#parseInstanceした時点までにおける、指定したインスタンスに対するメモリの状態を可視化する。

通番 static 戻り値の型 メソッド 引数 メソッド概要
1 GraphLayout parseInstance Object インスタンスのメモリ情報を取得する。
2 - toImage String 引数で指定したファイル名(~.png等)でグラフを生成する。


●HelloWorld的なやつ

package com.hhhhhskw.jol;

import java.util.SortedSet;

import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.info.FieldLayout;
import org.openjdk.jol.util.VMSupport;

public class HelloJol {

	private String HELLO_JOL_HOGE = "HOGEEEEEEEEEEEEEEEEE";
	private String HELLO_JOL_FUGA = "FUGAAAAAAAAAAAAAAAAA";


	public static void main(String[] args) {
		HelloJol hj = new HelloJol();
		System.out.println("■JVM情報■");
		System.out.println(VMSupport.vmDetails());

		System.out.println("■クラスインスタンスのメモリ情報■");
		System.out.println(ClassLayout.parseInstance(hj).instanceSize());
		System.out.println(ClassLayout.parseInstance(hj));
		System.out.println(ClassLayout.parseInstance(hj).toPrintable());

		SortedSet<FieldLayout> flSet = ClassLayout.parseInstance(hj).fields();

		flSet.stream().forEachOrdered(fl -> {
			System.out.println("■フィールドのメモリ情報■");
			System.out.println(fl.name());
			System.out.println(fl.size());
			System.out.println(fl.typeClass());
			System.out.println(fl);
		});

		System.out.println(flSet);
	}
}


●実行結果*1

■JVM情報■
WARNING: Unable to attach Serviceability Agent. You can try again with super-user privileges. Use -Djol.tryWithSudo=true to try with sudo.
WARNING: VM details, e.g. object alignment, reference size, compressed references info will be guessed.

Running 64-bit HotSpot VM.
Using compressed oop with 3-bit shift.
Using compressed klass with 3-bit shift.
Objects are 8 bytes aligned.
Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

■クラスインスタンスのメモリ情報■
24
HelloJol.HELLO_JOL_HOGE@12(4)
HelloJol.HELLO_JOL_FUGA@16(4)
size = 24

com.hhhhhskw.jol.HelloJol object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                    VALUE
      0     4        (object header)                01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                05 c0 00 f8 (00000101 11000000 00000000 11111000) (-134168571)
     12     4 String HelloJol.HELLO_JOL_HOGE        (object)
     16     4 String HelloJol.HELLO_JOL_FUGA        (object)
     20     4        (loss due to the next object alignment)
Instance size: 24 bytes (estimated, add this JAR via -javaagent: to get accurate result)
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

■フィールドのメモリ情報■
HELLO_JOL_HOGE
4
String
HelloJol.HELLO_JOL_HOGE@12(4)
■フィールドのメモリ情報■
HELLO_JOL_FUGA
4
String
HelloJol.HELLO_JOL_FUGA@16(4)
[HelloJol.HELLO_JOL_HOGE@12(4), HelloJol.HELLO_JOL_FUGA@16(4)]



【感想】
・継承関係にあるクラスのフィールドの中身までさかのぼってサイズ取得してくれるので便利。
・アプリケーション単位で特定の箇所でメモリサイズを取得する場合、インスタンスを適当に決めると、重複してサイズ取得する恐れがあるので、取得対象とするインスタンスは慎重に選ぶべき。
・VMSupport、GraphLayoutクラスを使用したサンプルは下記のソースを参照。
code-tools/jol: 4443d2696dcf /jol-samples/src/main/java/org/openjdk/jol/samples/


【参考】
[Java]オブジェクトの使用メモリを測る - Qiita

*1:「sa-jdi.jar」を入れ、VM引数に「-Djol.tryWithSudo=true」を指定することで警告を消せる可能性がある。警告は無視しても動作する。 ※手元の環境では、左記の対応を行ったうえで管理者権限で実行しても変化しなかった。。実行環境をOpenJDKにした方がよいかも。