この記事ではコレクションとジェネリクスを使いこなせるようになるための基礎知識をまとめています。ラムダ・ストリーム・オプショナルを使いこなすためですね。
目次
Javaでは提供されている、複数のデータを扱うためのクラスのこと。配列よりも柔軟に複数のデータを扱える。配列は作成された時点で要素数が固定され、状況に応じて柔軟に要素数を変更したい処理には適さない。コレクションを利用すれば、要素数の変更にも柔軟に対応できる。 この記事では集合オブジェクトという言葉には を含めます。上記の通り、コレクションと配列を区別します。 コレクションといえば下記で紹介するものになります。 柔軟な配列みたいな List 、PHPの連想配列みたいな Map 、 Setは重複を除外できる。 引用元:Javaちょこっとリファレンス コレクションクラスの比較 - Javaちょこっとリファレンス コレクションクラスには大きく分けてList、Map、Setの3種類あります。そして、さらに性質の異なるクラスに分かれています。各コレクションクラスの特徴は以下のようになっています。 Listは、配列に非常によく似ている。 要素の数が固定的に決まっている場合
アクセス速度や処理の軽さが極端に求められる場合 要素の数が処理によって変わる場合
→基本的には(Array)Listを使い、
要素数が将来にわたって完全に固定である場合のみ
配列を使うとよい。 違い まずインターフェースとは、具体的な処理の内容の記述は無く、実装先のクラスで定義できるようにメソッドの型や変数を記述したもの。そのため、インターフェース自体のメソッドを用いて機能を呼び出すことはできません。それに対しArrayListはインターフェースであるListをもとに実装化したクラスであるため、機能を呼び出し使用する事ができます。 先述ではListインターフェースを直接用いて機能を利用することはできないと記述しましたが、宣言側の型としてListを使用する事は可能です。この場合のインスタンスは実際はArrayListでありながら表面的にはあくまでListとして捉えられるよう、多様性や拡張性を考慮して意図的に生成されたかたちとなります。
しかし、Listはあくまでインターフェースであり、実装されたクラスではないため、以下のような宣言はおこなう事ができません。 ArrayList は List インターフェイスの実装クラスであり、配列と同じように 0 開始の連番で各要素を管理できるコレクション。要素数は状況に応じて柔軟に増減させることができる。 <構文 ArrayList のオブジェクトの生成> ArrayList のオブジェクトだから、ArrayList 型の変数を宣言するかと思いきや実際に ArrayList を使用する場合、List インターフェイスに定義してあるメソッドだけで十分な場合がほとんど。
List 型で定義することにより、ArrayList 以外の List インターフェイスの実装クラスを代入することもできる。そのため、ArrayList を使用する場合は、一般的に List 型の変数に代入することが多いらしい。 →List 型で宣言された変数には、必要であれば、ArrayList だけでなく、LinkedList といった他の List インターフェースを継承したオブジェクトを代入出来るようになるため拡張性が広がる。 使用したい場合はArrayList型へのキャストをしなければいけない。 メリット デメリット ArrayList固有メソッドの使用が予想される場合以外は、List型宣言で良さそう。
みなさんどう思いますか?? ところで、List は上記で紹介しましたが「重複した要素を含むことができる順序の付けられたコレクション」です。 順序が不要であれば本来は List を使うべきではないと思うのですが、その辺りまで加味した運用が意識されているのを今のところ見た事がありません。 参考:インタフェースList 結局は、本来の意図と違っても多数の共通認識に沿った運用が良いんだろうなーとか思ったり。 ArrayList も配列と同様に扱うデータの型を指定。その際に使用するのが、ジェネリクス(< >)。総称型とも呼ばれ、扱う型を指定するための機能。「<>:ダイヤモンド演算子」内に型を記述することにより、記述された型のデータを ArrayList 内で扱うことができる。 というか、指定された型しか取り扱う事が出来なくなった、のほうがいいのかな? ジェネリクス以前のリストには、何でも格納できました。Object型の参照の集合をリストは扱います。全てのクラスはObjectクラスを暗黙的に継承しています。なので文字列、数値、キャラクタなんでもかんでも放り込めます。そして、このリストから何かを取り出して使うには、キャストしなきゃいけんかったり、型を保証するリストを実現するにはそれ専用のクラスをいちいち作らなくてはいけなかったみたいです。 型のパラメータ化
ジェネリクスはクラスの型を変数のようにパラメータとして扱い、型パラメータと呼びます。よく見る このクラスをインスタンス化する際に、型パラメータ 上記のクラス Test をジェネリクス化されたクラスと呼びます。 コレクションはどんな型でも扱えるがゆえにコレクション内に想定外の型の値が混ざっても、ジェネリクス以前はコンパイルエラーを出せず、実行してみないと例外が発生しませんでした。実行しないとエラーを検出でいないのはかなりまずいですよね(ClassCastException) ジェネリクスは、コレクションのインスタンス生成時に扱える型の制限・指定をし扱える型に制限をかける事で型安全を保証する仕組みです。型の制限によってバインドされた型以外を代入した場合コンパイルエラーが発生するようになり、実行前に不具合を検出可能になりました。 この型の指定による制限にはいくつかの方法があります。 引用元:【Java】ジェネリックス型の不変、共変、反変とは何か Object が String のスーパータイプであるとき、 と定義されているのです。 これらは、ジェネリックス型同士の継承についての性質です。継承関係にあるサブクラスをジェネリクス型で宣言し、そのインスタンスにスーパクラスの型の参照型を代入させてもたらあかん、ということです。これはジェネリクス登場前のコレクションで起きていたことです。 共変・反変を許容するとジェネリクスでインスタンス生成時に型を指定し制限した意味がなくなってしまいます。いろんな肩がごちゃ混ぜになった時、それを実行時 ClassCastException が発生させないこと、つまり実行しないとエラーが検出できないではなく、コンパイル時に型チェックでエラーを検出するのがジェネリクスの目的なのです。実際に下記のような書き方はコンパイルは通りませんが通ったとしましょう。 ここでは本来、Object型から String型へのダウンキャストによる実行時例外が発生します。実行しなくても事前に検査するためのジェネリクスでした。 この時点でジェネリクスによる型チェックでコンパイルエラーが起きます。Objectクラスと Stringクラスは継承から見た際の上下関係と型の互換性がありますが、ジェネリクスではその関係性は成立しません。 ジェネリクスで型を指定する際に、単一の型だけでは制限がきつすぎて使い勝手が悪いという事が起こるそうです。型パラメータに継承関係の制約を持たせたい時に使用するのが境界型パラメータです。 これは Numberクラスのサブクラスを実型パラメータに指定します。Integer / Double など。
この境界型パラメータを利用して、型パラメータの継承関係の制限をコントロールする事ができます。 前述したようにジェネリクス型は不変ですが実は、境界型パラメータとワイルドカードを使用する事で不変から共変・反変へ性質を変化させ、その度合いをコントロールする事ができます。 境界型パラメータとワイルドカードを合わせて使用する際は「境界型ワイルドカード」とよびます。
境界型ワイルドカードは以下のように表現できます。 上限境界型 下限境界型 Effective Javaでは「API の柔軟性向上のために境界ワイルドカードを使用する」という記述があります。不変による型安全の制約が、APIの柔軟性を損ねる場合がありその場合は境界型ワイルドカードを使えと言っています。ぶっちゃけよく分かりまん。 PECS(Producer-Extends、Consumer-Super) Producer、すなわちオブジェクトを生成するジェネリックス引数は extends(上限境界型ジェネリックス)を使うべきである。 Consumer、すなわちオブジェクトを消費するジェネリックス引数は super、(下限境界型ジェネリックス)を使うべきある。 プロデューサーとは関数内で、何らかの値を生成(提供)する引数。コンシューマーとは関数内で、何らかの値を消費(利用)する引数のこと。関数型インターフェイスあたりでよく聞く言葉ですね。 これで、標準ライブラリのジェネリクスの仮型パラメータとか見ても混乱しないですみそうですね。
ラムダ・ストリーム・オプショナル使いこなしたいですね。 ちなみに、HashMapの「HashMap<K,V>」はK、Vという2つの型(この場合であれば、キーと値の型)を受け取れることを意味します。
型パラメータは、慣例的に大文字一文字で表すのが普通です。E(要素)、K(キー)、V(値)などが良く使われるそうです。コレクションとは?
コレクションの種類
ArrayList LinkedList HashMap TreeMap HashSet TreeSet インタフェイス List List Map Map Set Set 要素の重複 ○ ○ × × × × null値の要素 ○ ○ ○ × ○ × 自動ソート × × × ○ × ○ それぞれの特徴
List
//ArrayList型で宣言する場合
ArrayList<型> 変数名 = new ArrayList<型>();
//List型で宣言する場合
List<型> 変数名 = new ArrayList<型>();
Set
Set<Integer> store = new HashSet<>();
Map
Map<string, Integer> maps new HashMap<>();
配列とListの使い分け
ListとArrayList
List<型> 変数名 = new List<型>();
インターフェース
説明
java.util.List
順序通りに並んだ要素の集まりを管理するデータ構造
実装元インターフェース
実装クラス
説明
java.util.List
java.util.ArrayList
配列と同様に0開始の連番で要素を管理
ArrayList とは
ArrayListのオブジェクト生成:ArrayList型でなくList型が鉄板?
List<型名> 変数名 = new ArrayList<>();
List型で宣言するメリット
List型で宣言するデメリット
ArrayList型で宣言する場合のメリット・デメリット
List型とArrayList型のまとめ
型の指定 ジェネリクス
ジェネリクスの注意点
var
を使用した場合、ジェネリクスによる型推論ができない
ジェネリクスが解決してくれた事 ~型安全~
ジェネリクスは何をしているのか?
<T>
とか <S>
とかですね。
class Test <T> {
private final T testValue;
public Test(T testValue) {
this.testValue = testValue;
}
public T get() {
return testValue;
}
}
<T>
にIntegerクラスをバインドします。new Test<Integer>(123);
// 型パラメータに Integerクラスをバインド
Test <Integer> test = new Test<Integer>(123);
// Integer クラスにバインドしたので型を検査する必要がない
Integer number = test.get();
<T>
を仮型パラメータ、バインドされた後の型パラメータを実型パラメータといいます。仮引数と実引数と同じ考えですね。
境界型パラメータや境界ワイルドカードと言います。共変・不変・反変
public class Main {
public static void main(String[] args) {
List<Object> list = new ArrayList<String>();
list.add(new Object);
list<String> stringList = list;
for (String str : stringList) {
System.out.println(str);
}
}
List<Object> list = new ArrayList<String>();
境界型パラメータとワイルドカード
public class NumberDeal <T extends Number> {
.....
具体例:List<? extends Number>
Numberクラス以下のサブクラスを扱える、Number より上のクラスは扱えない
共変性を持たせる
値の取得のみが可能であり、書き込むことはできない
「? extends Object」という「共変」化したリストの目的は、「Object以下の要素を持つリストから値を取得して何かする」ためのもので、書き込みは禁止されている。
値を追加するのは不変なジェネリックで行い受け取る時には境界ワイルドカードで幅広く受け取る。
具体例:List<? super Number>
Numberクラスのスーパークラスを扱える、Number より下のクラスは扱えない
反変性を持たせる
「? extends Integer」という反変化したリストの目的は「取得はできないが、書き込みはできる」オブジェクト作成
いつ使うんだ??List<Number> uppers = ArrayList<>(); // → uppers は不変の性質
List<? extends Number> uppers1 = new ArrayList<Object>(); // → コンパイルエラー
List<? extends Number> uppers2 = new ArrayList<Number>(); // → 問題なし
List<? extends Number> uppers3 = new ArrayList<Integer>(); // → 問題なし
// → 上限境界型の使用で共変性を持たせる
List<Number> lowers = ArrayList<>(); // → lowers は不変の性質
List<? super Number> lowers1 = new ArrayList<Object>(); // → 問題なし
List<? super Number> lowers2 = new ArrayList<Number>(); // → 問題なし
List<? super Number> lowers3 = new ArrayList<Integer>(); // → コンパイルエラー
// → 下限境界型の使用で反変性を持たせる
GET & PUT 原則(PECS)