IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

Effective Java 読書会 6 日目 「ジェネリクス!」

はじめに

今日からついにジェネリクスに突入しました!

今日読んだところ

101 ページ〜 125 ページ

関数オブジェクト

インスタンスメソッドを一つだけ持っているクラスのオブジェクト。
C++ の関数ポインタのような、 JavaScriptクロージャのような使われ方をする。

ストラテジーパターンで

ストラテジーだけを渡すときに使う。

関数オブジェクトのインスタンスは複数いらない

以下のように、シングルトンにしたり、 public static final なフィールドに入れられたりする。

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class Main {

    public static void main(final String[] args) {
        List<String> list = Arrays.asList("aaaa", "baaabaaaaaaa", "cacaca");
        Collections.sort(list, CountAComparator.INSTANCE);
        System.out.println(list);
    }
}

 enum CountAComparator implements Comparator<CharSequence> {

    INSTANCE;
    
    @Override
    public int compare(CharSequence cs1, CharSequence cs2) {
        return countA(cs1) - countA(cs2);
    }

    private int countA(CharSequence cs) {
        int count = 0;
        for (int i = 0; i < cs.length(); i++) {
            char c = cs.charAt(i);
            if (c == 'a' || c == 'A') {
                count++;
            }
        }
        return count;
    }
}

static メンバクラスに戦略を保持する

そういうやりかたもする

class Klass {
    pravate static class Comparator extends Comparator<String> {
        // snip
    }
    // snip
}

無名クラスとして使われたりもする

クロージャ

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class Main {

    public static void main(final String[] args) {
        List<Integer> list = Arrays.asList(1, 10, 100);
        
        final int base = 30;
        
        // 30 から近い順にソート
        Collections.sort(list, new Comparator<Integer>() {

            @Override
            public int compare(Integer o1, Integer o2) {
                return Math.abs(base - o1.intValue()) - Math.abs(base - o2.intValue());
            }
        });
        System.out.println(list);
    }
}

ネストしたクラスの 3 種類

  • static メンバクラス
  • 非 static メンバクラス
  • 無名クラス
    • インタフェースや抽象クラスをそのまま new して中に書く
    • インスタンスメソッド内で new されればエンクロージングインスタンスを持つ
    • static なメンバを持てない
  • ローカルクラス
    • 無名クラスのように、メソッド内に書く
    • 違いは、繰り返し使用できること

それぞれの用途まとめ

  • static メンバクラス
    • クラスの構成要素を作る
  • 非 static なメンバクラス
  • 無名クラス
    • 関数オブジェクトやプロセスオブジェクト
  • ローカルクラス
    • あまり使わない

エンクロージングインスタンス

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;

public class Main {
    public static void main(final String[] args) {
        JFrame f = new JFrame();
        JButton b = new JButton("click");
        f.getContentPane().add(b);
        f.pack();
        f.setVisible(true);
        
        b.addActionListener(new Hoge().createListener());
    }
}

class Hoge {

    public ActionListener createListener() {
        return new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println(data);
                // この場所で、
            }
        };
    }

    // ここの this.data にアクセスできている!!
    // ここの this をエンクロージングインスタンスという
    private final String data = "hoge";
}

修飾された this

エンクロージングインスタンスはクラス名.this (修飾されたthis)で取得できる

Hoge enclosingInstance = Hoge.this;

ジェネリック型の原型は使わない

せっかくのコンパイル時の型チェックが台無しになっちゃうので><

for-each ループすごい!

for (String s : list) { } みたいな書き方できるんだ!すげー!

原型を使う 2 パターン

  • クラスリテラル
    • List.class, Set.class
  • instanceof
    • obj instanceof List, obj instanceof Set

境界型ワイルドカード、非境界型ワイルドカード

? は非境界型ワイルドカード ? extends CharSequence, ? super String は境界型ワイルドカード

非境界型ワイルドカード

  • null 以外なにも入れられない
  • オブジェクトとしてしか受け取れない
import java.util.Arrays;
import java.util.List;

public class Main {

    public static void main(String[] args) {
        foo(Arrays.asList(""));
    }

    public static void foo(List<?> e) {
        Object o = e.get(0);
        e.add(o); // コンパイルエラー
    }
}

境界型ワイルドカード

import java.util.Arrays;
import java.util.List;

public class Main {

    public static void main(String[] args) {
        foo(Arrays.asList(""));
    }

    public static void foo(List<? extends CharSequence> e) {
        // CharSequence で受け取れる
        CharSequence o = e.get(0);
    }
}
import java.util.Arrays;
import java.util.List;

public class Main {

    public static void main(String[] args) {
        foo(Arrays.asList(""));
    }

    public static void foo(List<? super String> e) {
        // String で受け取れる
        e.add("");
    }
}

無検査警告を取り除く

@SuppressWarnings("unchecked")

ジェネリック型が配列に出来ない理由

もともと、配列が共変クラスだったことが問題。ジェネリックス型からは、それができなくなる。
リストを使うことでその問題を解決できる。

まとめ

明日は、予定があるのでまとめてる時間ないかもなー><