0
0

More than 3 years have passed since last update.

匿名クラス(stream api 導入を目指して)

Last updated at Posted at 2020-01-23

目的

stream apiを学ぶ上では匿名クラスの設計及び実装の考え方の理解が不可欠だと感じた.
メソッド参照,関数型インターフェース,ラムダ式,ジェネリクスを用いた設計(インスタンススコープ及びメソッドスコープ,PECS原則に基づいた設計)学習前の入口となる匿名内部クラスについて学ぶ.

入れ子クラス

ブロック({})内で宣言されたクラスを「入れ子クラス」(Nasted Class)と呼ぶ

image.png 入れ子クラスのイメージ図.

NastedClazzSample.java
public class NastedClazzSample {
    static class StaticClazz {}     //static member class

    class MemberClazz {}            //menber class  (inner class)

    void method() {
        class LocalClazz {}         //local class   (inner class)
    }
}

メンバー・クラスとして宣言された入れ子クラスにはアクセス修飾子を付けることが出来る

NastedClazzSample.java
public class NastedClazzSample {
    static protected class StaticClazz {} // OK

    private class MemberClazz {}          // OK

    void method() {
        public class LocalClazz {}        // NG
    }
}
Error.java
ローカルクラス LocalClazz の修飾子が正しくありませんabstract または final だけが許可されています    NastedClassSample.java  /...(path)   11  Java 問題

何故か.
ローカル・クラスのスコープはローカル変数と同様

例ではmethod()を介してLocalClazzにアクセス
つまりmethod()のアクセッサーと同様になる為記述が出来ない仕様になっている

内部クラス

上記の通り,staticメンバー・クラス以外の入れ子クラス(非staticメンバー・クラス)を内部クラスと言う

内部クラスの特徴

  • ①staticメンバを所持できない
  • ②外部クラスで定義したインスタンス変数にアクセス可能
①staticメンバを所持できない

非staticなメンバはインスタンスと関連
ローカル変数はスタック領域で管理され,対象クラスがインスタンス化される時にヒープ領域に配置される

InnerClazzSample.java
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);
    }
}
実行結果.java
bar

インスタンス化せずに使用した場合

InnerClazzSample.java
public class InnerClazzSample {
    public class Inner {
        String foo;
    }

    public static void main(String... args) {
        Inner.foo = "bar";
    }
}
Error.java
 static フィールド Inner.foo  static 参照できません   InnerClazzSample.java   /~(path)     9   Java 問題

これはどういうことか.

まず,JVMはアプリケーションに使用されるクラスを読み込みメモリ上に展開する
この時staticメンバは非staticメンバとは異なるメモリ領域(クラス領域)に配置される
JVM起動時ブートストラップ・クラス・ローダーが呼び出されるが,すべてのクラスを読み込むのではなくアプリケーションからのクラス参照があった時にロードされる
また,-Xbootclasspathを変更するとロードできるクラス・セットの範囲を変更できる

staticメンバーはクラスと関連している.
メモリに展開されるタイミングはJVMの実装に左右される.

②外部クラスで定義したインスタンス変数にアクセス可能
Outer.java
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内部クラスのインスタンス優先順位は

  • 呼び出し元ローカル変数
  • 内部クラスのインスタンス変数
  • 外部クラスのインスタンス変数
Outer.java
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();
    }
}
実行結果.java
thread-c
innerclass-b
outerclass-c
innerclass-a
outerclass-a

匿名クラス(無名クラス,匿名内部クラス)

入れ子クラスを利用する際,名前を持たないクラスとして利用することができる
そのような入れ子クラスを「匿名クラス」という

主に以下の目的の為に使用される

  • ① 処理の再利用性が低くその場でのみ必要となる場合
  • ② 処理コードが短いがカスタマイズが豊富にある場合
匿名クラス-構文1.text
new 型名() {
    抽象メソッドの実装
}

匿名クラス-構文2.text

型名 インスタンス名 = new 型名() {
    抽象メソッドの実装
}

まずは匿名クラスを利用せず,インターフェースの抽象メソッドの実装を行う.

Sample.java
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
    }
}

匿名クラスを利用して抽象メソッドの実装を行うと

Sample.java


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()
    }
}
実行結果.java
Did Implementation

匿名クラスの実用例

匿名クラスの実用例を考えてみる

① 処理の再利用性が低くその場でのみ必要となる場合

顧客情報を管理するクラスを定義

ManagementClientInformation.java
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()));
    }
}
実行結果.java
1 matsuda
2 tanaka
3 neko


以下の事象について考えてみる

あなたはシステム・エンジニアです
保守・改修中に顧客からの要望がありました
ある画面について顧客idの逆順に並び替えてほしいとの要望が
尚,システム影響懸念・肥大化の為Clientクラスに手を加えることは禁止とする

⇒匿名クラスを使用

ManagementClientInformation.java
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()));
    }
}
実行結果.java
3 neko
2 tanaka
1 matsuda

終わりに

初めてStream apiに触れた時,内容が全く分かりませんでした.
もんもんしているうちにメソッド参照⇒ラムダ式⇒ジェネリクス設計と逆順に学習していくうちに,少しずつ理解が深まっていきました.
基礎的な部分をしっかりと理解した上で応用したいと思います.

0
0
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
0
0