おはようございます。
今回は、
java 8 gold資格試験の出題範囲からジェネリクスとコレクションについて解説します!
はじめに
【対象者】本記事は、Oracle Certified Java Programmer, Gold SE 8 認定資格試験の出題分野の解説をいたします。特に以下のような方には読んでいただければ幸いです。
- 当該試験を受ける方
- java 11 Goldの試験を受ける方で日付/時刻APIの理解を深めたい方
- 受験の有無にかかわらず、javaの日付/時刻APIをおさらいしたい方
- 普通にド忘れしたプログラマーの方
出題範囲の確認
公式ページの出題内容は以下の通りです。
- ジェネリクスクラスを作成および使用する
- ArrayList、TreeSet、TreeMap および ArrayDeque オブジェクトを作成および使用する
- java.util.Comparator およびjava.lang.Comparable インタフェースを使用する
参考(https://education.oracle.com/ja/java-se-8-programmer-ii/pexam_1Z0-809)
前回は上記の項目をまず読み解くのに必要なクラスの一部を紹介しました。
今回も引き続き残りのクラスを解説するとともに、上記の内容の意味するところを読み解いていければと思います!
なので、今回は
- 基礎知識と簡単なクラスの紹介(前半)
- 応用的なクラスの紹介(後半)
という流れで進めていきましょう!
それでは、内容をどうぞ。
内容
ジェネリッククラス、ジェネリックインタフェースとは
ジェネリクス とは、ある型Aやメソッドが別の型Bを扱うものである場合に、 その型Bをコンパイラに対して明示する仕組みです。 これによるメリットは、大きく分けて以下の2点です。
- 型に汎用性を持たせることができる(同種の振る舞いをするものであれば複数の型に対して操作を行える)
- 実行時の型変換の安全性を高める
ジェネリクス が利用できる宣言の型を「総称型」、 ジェネリッククラス、ジェネリックインタフェースと呼びます。
また、このジェネリクス がよく使われるのがコレクションです。
配列と似て、複数のデータを一つのインスタンスにまとめて扱うことができます。
よく試験の概念系問題で出されるキーワードとして、java EEの技術であり範囲ではないものですが、以下のようなものと並べられたりします。
- O/R Mapping(RDBのテーブルとオブジェクトを結びつける技術)
- アスペクト指向プログラミング(クラスの共通処理を分離する考え方)
型パラメータ/型引数/型変数
ジェネリッククラスの宣言では、型パラメータリストの記述が必要になります。
これは、<>で型名を囲んであるような記述のことです。
型パラメータは、以下のような構成で記述されます
<アノテーション(省略可) 型変数 型境界(省略可)>
型変数には、javaの識別子になる有効な任意の文字列が使えます。
ジェネリッククラスの使用の際には、この型パラメータにあたる箇所に、型引数という、実際に使用する型名を記述して使う必要があります。
List list = new ArrayList(); ではなく、(一応コンパイルエラーにはなりませんが、過去のバージョンとの互換性を保つためなので推奨はされず、警告が出ます)
List<String> list = new ArrayList<String>()
や
List<String> list = new ArrayList<>()
といった省略系などが使用例です。
ちなみに、
Test<TypeTest> test = new Test<TypeTest>(){ 実装};
のように匿名クラスを宣言する際にはこの型引数は省略できないので注意が必要です。
また、この型引数としては、プリミティブ型を記述することはできず、参照型しかかけません。しかし、プリミティブ型の配列は参照型なので記述が可能です。
また、ジェネリッククラスの実装では、以下のようなコードの記述ができません
- static修飾子付きの型変数
- 型変数によるインスタンス生成
- 型変数を要素に指定する配列の生成
- instanceofで型を判定する操作
- 型変数に対する.classの参照
ややこしいですが、何ができて何ができない、の境界線は試験になりやすいところです。覚えておきましょう。
ジェネリックメソッド
クラスやインタフェースがジェネリックでないとしても、 メソッドだけジェネリクス 使用表記にすることはできます。 その際の書き方は、
<型パラメータ> 戻り値 名前(引数){}
といった形で頭に記述される形になります。
どういった使われ方をするかと言うと、
一般的にはそのメソッドのスコープ内で自由に使うことはできますが、 だいたいは以下のような2通りの使い方です。
メソッドの引数の型宣言に使用する
public <T> String meth(T t){ 実装 }
メソッドの戻り値の型宣言に使用する
public <T> T meth(String str){ 実装 }
型境界
型パラメータの中の一つとして出てきましたが、これは型変数の種類を限定するための、ある種ルール付のようなものです。
Extends 型名
や、
super 型名
といった記法で書かれます。
extends 型名
は、 その型を継承しているクラスのみ、というルールで、
super 型名
は、その型のスーパークラスのみ、と言うルールです。
extendsの使い道のわかりやすいものとしては、
class TypeTest<T, U extends T> { }
といった形で、複数の型が書かれるような場合に、かかれる型を限定することで型安全性を向上させる、といったことができます。
ここまでで、型変数 と 型引数 の違いのイメージは分かったでしょうか? ちょっとごちゃ混ぜになりやすいところだと思うのでこの後の話のためにはっきりと違いを述べると、
型変数は ジェネリッククラスやジェネリックメソッドそのものの宣言時に書くもの、 型引数は それらの型の変数を宣言する際に書くもの、という意味合いで言います。
ここで、型変数は好きにTだのUだの付けれることはジェネリッククラスの説明のところで行ったと思います。 しかし、汎用ライブラリやクラス群を作成するような場合に、 型引数を描こうとするけれど何が入ってくるかはわからない、、、と言うときに曖昧にしておく方法はまだ説明していませんでした。ここで紹介すると、
その書き方が、
”?”
です。 はてなマークです。文字化けではないので安心してください。
ちなみに、以下のようにインスタンス生成の際に使うことはできず、あくまで変数宣言のところまでしか使うことはできません。
また、型引数の記述のルールとして、 ジェネリッククラスもしくはインタフェースを継承する際には、 そのジェネリッククラスに書かれる型変数は、型引数によってパラメータ化、つまり決定づけられている必要があります。
コードは以下のような感じになりますが、
class TypeTest<T> U<T>
class TypeTest<T> U<String>
class TypeTest<String> U<String>
class TypeTest U<String>
以下のようなのは決定づけられていない、とみなされてコンパイルエラーです。
class TypeTest U<T>
java.util.List
Listインタフェースを実装しているクラスは以下のようなものがあります。
- ArrayList
- 配列的な扱い方のリスト。
- 新たな要素を追加するときには、新しい配列を作成した上で元の要素をコピーし、残り一つの秋要素に新しい要素を入れる、と言うような方法を取る。しかも配列の要素をひとつひとつコピーしてずらさないといけない。
- 先頭に近い場所への追加や巨大なリストでは性能劣化が激しい
- LinkedList
- データと次要素のアドレス、といった形でつながっているリスト
- 要素の追加は簡単。新しい要素を入れて、隣接した要素の参照先アドレスを変えるだけ。
- 要素を辿る必要があるため、真ん中に近い要素に対してはアクセスするだけで配列に比べて時間がかかる。
- Vector
- java 1.0の頃からある、最も古いList系クラスになります。
- 基本的な使い方はArrayListと変わらず、単純な使用の際にはArrayListが使われる方がいいことがほとんど
- 複数スレッドから同時にデータ操作が入ったりするような場合にもデータの整合性を保つ(誰かが見たりいじったりしてるときに他の人が内容を変えたり、といったことが起こらない)作り、というメリットはありますが、常にこの動作なので処理は遅いです。
- 整合性保持のための仕組みとしては今では同期化ラッパーと呼ばれる方式で上記ArrayListを宣言するような使い方の方が一般的なので、正直あまり使われるところはありません。。。
- CopyOnWriteArrayList
- 並列処理の分野で解説してあります。
java.util.Arrays
配列から上記で解説したList型のインスタンスを作る操作が可能なクラスです。
他にも多彩なメソッドがあり、対象のデータ型が多いためドキュメントを一通りめくるのも一苦労ですが、
fill (指定の要素で指定の範囲を埋める)
parralellSort (要素を並び替える)
といった、配列操作に関して便利なメソッドが用意されています。
上記でも言いましたが、以下のように定義されるメソッド、
Public static <T> List<T> Arrays.asList(T … a)
によって配列的な宣言から、 リストを作成することができます。
ここで注意すべきなのが、配列を変換してコピーしている、というよりはリストのメソッドを用いてアクセスすることができているだけで、 実態は元の配列のデータを読み書きできるに過ぎません。
つまり、リストへの変更は元配列にも反映されます。 また、純粋なリストではなく多少いびつなことをしているので、
既存の要素の参照や書き換えといった操作はできても、要素の追加や削除など、構成そのものを変えてしまうようなListのメソッドは利用できません
使用しようとすると、UnSupportedExceptionがスローされます。
ArrayListやLinkedListの特徴を持たせたい
またここでややこしいのですが、Arrays.asList()を用いてArrayListを作成したい、と言うような場合があります。 その場合には、戻り値をそのままArrayListなどのコンストラクタに入れてしまいましょう。するとインスタンスを作成できます。
そこで、、、ここでできるインスタンスはあくまでArrayListのコンストラクタが生成したものであるので、先ほどのリストオブジェクトのように何かに紐づいたものではありません。なので要素追加などもできます。
まとめ
今回はjava 8 Gold試験対策として、ジェネリクスとコレクションの分野について解説しました。
一度では理解しにくい点も多いと思いますので、読み返したり、実際に手を動かして知識の定着を図っていただければと思います!
それでは、次回またお会いしましょう〜。
コメント