目的
stream apiを学ぶ上では匿名クラスの設計及び実装の考え方の理解が不可欠だと感じた.
メソッド参照,関数型インターフェース,ラムダ式,ジェネリクスを用いた設計(インスタンススコープ及びメソッドスコープ,PECS原則に基づいた設計)学習前の入口となる匿名内部クラスについて学ぶ.
入れ子クラス
ブロック({})内で宣言されたクラスを「入れ子クラス」(Nasted Class)と呼ぶ
public class NastedClazzSample {
static class StaticClazz {} //static member class
class MemberClazz {} //menber class (inner class)
void method() {
class LocalClazz {} //local class (inner class)
}
}
メンバー・クラスとして宣言された入れ子クラスにはアクセス修飾子を付けることが出来る
public class NastedClazzSample {
static protected class StaticClazz {} // OK
private class MemberClazz {} // OK
void method() {
public class LocalClazz {} // NG
}
}
ローカル・クラス LocalClazz の修飾子が正しくありません。abstract または final だけが許可されています NastedClassSample.java /...(path) 行 11 Java 問題
何故か.
ローカル・クラスのスコープはローカル変数と同様
例ではmethod()を介してLocalClazzにアクセス
つまりmethod()のアクセッサーと同様になる為記述が出来ない仕様になっている
内部クラス
上記の通り,staticメンバー・クラス以外の入れ子クラス(非staticメンバー・クラス)を内部クラスと言う
内部クラスの特徴
- ①staticメンバを所持できない
- ②外部クラスで定義したインスタンス変数にアクセス可能
①staticメンバを所持できない
非staticなメンバはインスタンスと関連
ローカル変数はスタック領域で管理され,対象クラスがインスタンス化される時にヒープ領域に配置される
public class InnerClazzSample {
public class Inner {
String foo;
}
public static void main(String... args) {
Inner inner = new InnerClazzSample().new Inner();
inner.foo = "bar";
System.out.println(inner.foo);
}
}
bar
インスタンス化せずに使用した場合
public class InnerClazzSample {
public class Inner {
String foo;
}
public static void main(String... args) {
Inner.foo = "bar";
}
}
非 static フィールド Inner.foo を static 参照できません InnerClazzSample.java /~(path) 行 9 Java 問題
これはどういうことか.
まず,JVMはアプリケーションに使用されるクラスを読み込みメモリ上に展開する
この時staticメンバは非staticメンバとは異なるメモリ領域(クラス領域)に配置される
JVM起動時ブートストラップ・クラス・ローダーが呼び出されるが,すべてのクラスを読み込むのではなくアプリケーションからのクラス参照があった時にロードされる
また,-Xbootclasspathを変更するとロードできるクラス・セットの範囲を変更できる
staticメンバーはクラスと関連している.
メモリに展開されるタイミングはJVMの実装に左右される.
②外部クラスで定義したインスタンス変数にアクセス可能
public class Outer {
private int foo;
public class Inner {
public int bar;
private void method() {
this.foo = Outer.this.bar; // OK
}
}
private void method() {
this.foo = bar; // NG
}
}
また,非static内部クラスのインスタンス優先順位は
- 呼び出し元ローカル変数
- 内部クラスのインスタンス変数
- 外部クラスのインスタンス変数
public class Outer {
String foo = "outerclass-a";
String bar = "outerclass-b";
String baz = "outerclass-c";
public class Inner {
String foo = "innerclass-a";
String bar = "innerclass-b";
public void thread() {
String foo = "thread-c";
System.out.println(foo);
System.out.println(bar);
System.out.println(baz);
System.out.println(this.foo);
System.out.println(Outer.this.foo);
}
}
public static void main(String... args) {
new Outer().new Inner().thread();
}
}
thread-c
innerclass-b
outerclass-c
innerclass-a
outerclass-a
匿名クラス(無名クラス,匿名内部クラス)
入れ子クラスを利用する際,名前を持たないクラスとして利用することができる
そのような入れ子クラスを「匿名クラス」という
主に以下の目的の為に使用される
- ① 処理の再利用性が低くその場でのみ必要となる場合
- ② 処理コードが短いがカスタマイズが豊富にある場合
new 型名() {
抽象メソッドの実装
}
型名 インスタンス名 = new 型名() {
抽象メソッドの実装
}
まずは匿名クラスを利用せず,インターフェースの抽象メソッドの実装を行う.
interface FooInterface { //interface
void method(); //abstract method
}
class BarClazz implements FooInterface { //implemented class
public void method() {
System.out.println("Did Implementation"); //implemented abstract method
}
}
public class Sample{ //runner
public static void main(String... args) {
new BarClazz().method(); //new instance
}
}
匿名クラスを利用して抽象メソッドの実装を行うと
interface FooInterface {
void method();
}
//nothing inplemented class
public class Sample {
public static void main(String... args) {
FooInterface foo = new FooInterface() { //anonymous class
public void method() {
System.out.println("Did Implementation"); //implemented abstract method
}
}; //semi-coron
foo.method(); //instance.method()
}
}
Did Implementation
匿名クラスの実用例
匿名クラスの実用例を考えてみる
① 処理の再利用性が低くその場でのみ必要となる場合
顧客情報を管理するクラスを定義
public class ManagementClientInformation {
public static class Client {
private int id;
private String name;
Client(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return this.id;
}
public String getName() {
return this.name;
}
}
public static void main(String... args) {
List<Client> clientList = new ArrayList<>();
clientList.add(new Client(1, "matsuda"));
clientList.add(new Client(2, "tanaka"));
clientList.add(new Client(3, "neko"));
clientList.stream()
.forEach(x -> System.out.println(x.getId() + " " + x.getName()));
}
}
1 matsuda
2 tanaka
3 neko
以下の事象について考えてみる
あなたはシステム・エンジニアです
保守・改修中に顧客からの要望がありました
ある画面について顧客idの逆順に並び替えてほしいとの要望が
尚,システム影響懸念・肥大化の為Clientクラスに手を加えることは禁止とする
⇒匿名クラスを使用
public class ManagementClientInformation {
public static class Client {
private int id;
private String name;
Client(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return this.id;
}
public String getName() {
return this.name;
}
}
public static void main(String... args) {
List<Client> clientList = new ArrayList<>();
clientList.add(new Client(1, "matsuda"));
clientList.add(new Client(2, "tanaka"));
clientList.add(new Client(3, "neko"));
Collections.sort(clientList, new Comparator<Client>() {
public int compare(Client c1, Client c2) {
return Integer.compare(c2.getId(), c1.getId());
}
});
clientList.stream()
.forEach(x -> System.out.println(x.getId() + " " + x.getName()));
}
}
3 neko
2 tanaka
1 matsuda
#終わりに
初めてStream apiに触れた時,内容が全く分かりませんでした.
もんもんしているうちにメソッド参照⇒ラムダ式⇒ジェネリクス設計と逆順に学習していくうちに,少しずつ理解が深まっていきました.
基礎的な部分をしっかりと理解した上で応用したいと思います.