Open35
Effective Javaメモ
項目22 型を定義するためだけにインターフェースを使う
項目21 将来のためにインターフェースを設計する
項目20 抽象クラスよりもインタフェースを選ぶ
項目88 防御的にreadObjectメソッドを書く
項目60 正確な答えが必要ならば、floatとdoubleを避ける
項目61 ボクシングされた基本データよりも基本データ型を選ぶ
項目62 他の型が適切な場所では、文字列を避ける
- 文字列は貧弱
- 他の値型に対する代替として
- 数値だったらint, float, BigInteger
- 適切な型がなかったら適切な値型を書くべき
- 列挙型に対する代替として
- enum使おう
- 集合型に対する代替として
- 複数の構成要素を持つ集合のキーで文字列で表現するのは、だめ
- private staticのメンバークラスを書く
- ケイパビリティに対する代替として
- 独自実装のThreadLocalの例
- 「二つの独立したクライアントが、それぞれのスレッドローカル変数に同じ名前を使うと決めたら、両方のクライアントは変数を意図せずに共有してしまい、一般には両方のクライアントがエラーになります。」がわからない。
- 独自実装のThreadLocalの例
- 他の値型に対する代替として
項目45 ストリームを注意して使う
-
ストリームパイプライン
- ソースのストリーム、それに続く0個以上の中間操作、その後に一つの終端操作から構成される
- 遅延して評価される
- 評価は終端操作が呼び出されるまで開始されない
- 終端操作を完了させるために必要のないデータ要素は計算されない
- デフォルトで順次実行される
- 並行実行する方法はparallelメソッドを呼び出す
- そうするのが適切なことはめったにありません(項目48 ストリームを並列化するときは注意を払う)
- 並行実行する方法はparallelメソッドを呼び出す
-
ストリームAPI
- 流れるようなAPI
- パイプラインを構成するすべての呼び出しをつなげて単一の式となるように設計されている
- 十分に汎用性があるので、事実上はストリームを使ってどのような計算もできる
- いつストリームを使うかに関して鉄則はないが、発見的な規則はある
- 流れるようなAPI
- アナグラムのプログラム例
- 入力例
listen silent enlist inlets banana nabana cat act tac dog god
- 出力例
4: [enlist, inlets, listen, silent] 3: [act, cat, tac] 2: [banana, nabana] 2: [dog, god]
- マップへのキー挿入で
computeIfAbsent
を使っている- キーに複数の値を関連付けするマップの実装を単純にしている
- 入力例
- ストリーム乱用の例
- alphabetizeメソッドの処理も単一の式となっている
- ストリームパイプラインの可読性にとっては以下が重要
- 明示的な型がないので、ラムダのパラメータを注意深く命名すること
- ヘルパーメソッドを使うこと
- char値にストリームを使うのは控えるべき
- ストリームを使うことに意味がある場合にだけ使うこと
- 関数オブジェクトでできない、かつコードブロックでできること
- スコープ内のローカル変数を読みだしたり修正できる
- return, break, continue, 例外スローができる
- ストリームに適した処理
- ストリームで行うのが困難
- パイプラインの複数ステージから対応する要素を同時にアクセスすること
- 値を他の値にマッピングしてしまうと、元の値は失われる
- 回避策: ペアオブジェクト 、マッピングを逆にする
- 関数オブジェクトでできない、かつコードブロックでできること
- メルセンヌ素数の例(マッピングを逆にするの例)
- メルセンヌ素数とは、メルセンヌ数の中で素数になるもの
- メルセンヌ数とは、2のべき乗から1を引いた数
- n=0のとき、0
- n=1のとき、1
- n=2のとき、3(メルセンヌ素数)
- n=3のとき、7(メルセンヌ素数)
- n=4のとき、15
- n=5のとき、31(メルセンヌ素数)
- 最初の20個のメルセンヌ素数を表示するプログラムの例
- Stream.iterateで2を開始点として、その次の素数を生成し続ける
- filterで素数判定、limitで20個に制限
- 指数も表示させたい
- メルセンヌ数の指数はバイナリ表現のビット数→マッピングを逆にする
項目46 ストリームで副作用のない関数を選ぶ
- 純粋関数: 結果が入力だけに依存している関数
- 「これを達成するには、中間操作と終端操作の両方のストリーム操作に渡される関数オブジェクトには副作用がないべき」
- なんか日本語がおかしい気がする。達成したいんだったら純粋関数でなければいけないのでは。
-コード中の悪臭 - 状態を更新するラムダ(副作用あり、純粋関数でない)
- ストリームが行った計算結果を表示する以外の処理を行っているforEach操作
- なんか日本語がおかしい気がする。達成したいんだったら純粋関数でなければいけないのでは。
- forEach操作
- 最も力を持たない終端操作の一つ
- 最もストリーム向きではない
- 明示的なループであり、その結果並列化に適していない
- ストリームの計算結果を報告するためだけに使い、計算を行うために使うべきではない
- 計算結果を既存のコレクションへ追加するといった他の目的のために使うことには意味がある
- (コメント)大半は戻り値ないことの言い換え
- (コメント)順序を持つものに対してはその順序通りループする仕様のことを明示的なループとよんでいる?
- Collectors
- https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/stream/Collectors.html
- リダクション戦略: ストリームのまとめ方
- ストリームをまとめた結果できるのがコレクションなので、まとめ方がコレクター
- toList()
- toSet()
- toCollection(collectionFactory)
- 任意のコレクション型への終端処理が可能
- staticインポートするのが慣習
- ストリームパイプラインが読みやすくなる
- topTenの処理
- toMapの使い方
- 第一引数: ストリームをキーにマッピングする関数, 第二引数: ストリームを値へマッピングする関数
- ストリームの複数の要素が同一のキーにマッピングされると、パイプラインはIllegalStateExceptionをスロー
- 第三引数: マージ関数 or 最後の書き込みを優先する方針を強制する
- 第四引数: 特定のマップ実装の指定(EnumMap, TreeMap)
- toConcurrentMapもあるよ
- 第一引数: ストリームをキーにマッピングする関数, 第二引数: ストリームを値へマッピングする関数
- groupByの使い方
- 第一引数: 分類関数
-分類関数は要素を受け取り、それが分類されるカテゴリを返す - 第二引数: ダウンストリームコレクター
- カテゴリー内のすべての要素を含むストリームから値を生成する
- (コメント)集約操作の際に、集約されたグループ内に処理を行うコレクターのこと
- 例
- toSet()
- toColletction(collectionFactory)
- counting()
- 他にもいっぱいある
- Streamのコレクターにもあるダウンストリームコレクター
- counting, summing, averaging, summarizingで始まる名前の9個のメソッド、reducingメソッドのすべてのオーバーロード、filtering,,,,,,,
- それ以外のCollectorsのメソッド
- minBy, maxBy, joining
- Streamのコレクターにもあるダウンストリームコレクター
- 他にもいっぱいある
- カテゴリー内のすべての要素を含むストリームから値を生成する
- 第三引数: マップファクトリ
- 第二引数に先行している
- 例: 値がTreeSetであるTreeMapを返す
Map<Integer, TreeSet<String>> result = list.stream() .collect(Collectors.groupingBy( String::length, // 第1引数: グループ化するキー Collectors.toCollection(TreeSet::new), // 第2引数: ダウンストリームコレクター TreeMap::new // 第3引数: TreeMapを生成 ));
- 例: 値がTreeSetであるTreeMapを返す
- 第二引数に先行している
- groupingByConcurrentもあるよ
- partitioningByもあるよ
- 第一引数: 分類関数
項目31 APIの柔軟性向上のために境界ワイルドカードを使う
この項目は、共変・反変の関係を使ってAPIの柔軟性を表現しよう。Javaだとその手段は境界ワイルドカードと言っている。
- パラメータ化された型は不変
-
String <: Object
,Integer <: Number
は成立する -
List<String> <: List<Object>
,List<Integer> <: List<Number>
は成立しない - (脱線)不変とは
- 共変でも反変でもないもの
- 共変:
A <: B
のとき、T<A> <: T<B>
の関係になるT - 反変:
A <: B
のとき、T<A> :> T<B>
の関係になるT
- 共変:
- 共変でも反変でもないもの
-
以下はEが不変のため変換できない。
public void pushAll(Iterable<E> src) {
for (E e : src)
push(e);
}
Stack<Number> numberStack = new Stack<>();
Iterable<Integer> integers = ....;
numberStack.pushAll(integers);
public void popAll(Collection<E> dst) {
while (!isEmpty())
dst.add(pop());
}
Stack<Number> numberStack = new Stack<Number>();
Collection<Object> objects = ....;
numberStack.popAll(objects);
- 境界ワイルドカード型
-
Iterable<? extends E>
- EのなんらかのサブタイプのIterable
-
Collection<? super E>
- Eのなんらかのスーパータイプのコレクション
-
- PECS
- Producer -extends
- pushAllの例でEインスタンスを生産
- (補足)共変の関係、E読み取り専用としたいときに使う
- Consumer -super
- popAllはEインスタンスを消費
- (補足)反変の関係、データ追加や書き込み操作を行うときに使う
- TypeScriptの関数の関係に似ている
- 関数の引数は反変(Consumerのイメージ)、関数の返り値は共変(Producerのイメージ)
- TypeScript4.7から型引数に変性アノテーション(in: 反変, out:共変)指定可
- 例
- Producer -extends
- (脱線)Scalaすごい
- ジェネリクスを使うとき、共変クラス・反変クラス・非変クラスを定義できる
- 変位指定
- 「Javaでは、クラスの抽象性を使う時に変位指定アノテーションが利用側のコードから与えられます(使用時の変位指定)」の言わんとしていること
- ジェネリクスを使うとき、共変クラス・反変クラス・非変クラスを定義できる
- unionの例
- 項目30(ジェネリックメソッド)の例を改善
- 戻り値として境界ワイルドカード型を使うな
- クライアントのコードでワイルドカード型を使うことを強制する
- Java8より前は目的型が必要だった
- maxの例
- 型パラメーターに対してワイルドカードを適用した例
- Comparable<? super T>とするのはその型もしくはその型のスーパータイプという範囲で比較可能である表現をするため
- 何か恩恵をもたらすのでしょうか。はい、もたらします。(なぜなら、以下なので)
- ScheduledFutureがComparable<ScheduledFuture>を実装していない。
-
ScheduledFuture <: Delayed
かつComparable<Delayed>
が実装されている。
- 型パラメーターに対してワイルドカードを適用した例
- 型パラメータとワイルドカードの二重性
- swapメソッドの2つの宣言
- (公開する宣言において、)型パラメータがメソッド宣言中に一度しか現れないなら、それをワイルドカードで置き換えてください
- 理由: 単純だから
- ワイルドカードのままだとコンパイルできない
- ワイルドカードキャプチャのためのprivateヘルパーメソッドをジェネリックメソッドを使って実装する