先日、オラクル認定資格のJava Bronze試験を受験しました。Javaの資格試験としてはもっとも難易度の低いものとして知られていますが、プログラミング初心者にとってはかなり難しいタイプの試験なのではないかと感じました。
今回の記事ではJava Bronze試験の学習経験を踏まえ、つまづきやすかったり、なかなか覚えられなかった文法項目を5つ取り上げてまとめました。
このJava Bronze試験にはバージョンがさまざまあるようなので、ご注意ください。
- 受験した試験:Java SE Bronze (試験コード1Z0-818)
1.使用した主な参考書や教材
Java Bronze試験の対策本としてはいくつか有名なものがあります。私は「黒本」と言われる「徹底攻略Java SE Bronze問題集[1Z0-818]対応」を5周ほどし、対策しました。参考書としては結局黒本のみを使用しました。
そのほか「紫本」という「オラクル認定資格教科書 Javaプログラマ Bronze SE(試験番号1Z0-818)」がありますが、比較的難易度が低いという評判を聞きます。まず紫本で基礎を押さえてから黒本に移るのも手かもしれません。
またJava入門書として「スッキリわかるJava入門 第3版 (スッキリわかる入門シリーズ) 」が有名です。初学者にもわかりやすいですが、分量が多く、Java Bronze試験を念頭に置いた書籍ではないため、試験対策としては使いづらいのではないかと思います。試験問題を解きながら理解が及ばなかった項目について、理解の助けとして利用する分には便利かもしれません。私も少しだけ読みました。
また、YouTubeを利用しての学習も行いました。項目ごとにわかりやすく解説した動画も豊富に上がっているため理解の助けになります。以下リンクのJava Bronze試験要点まとめ公式動画も2時間分ありますので、試験直前に理解度確認のために利用しました。
Oracle Certified Java Programmer, Bronze SE 7 / 8 認定資格試験 ポイント解説セミナー(1)
Oracle Certified Java Programmer, Bronze SE 7 / 8 認定資格試験 ポイント解説セミナー(2)
次節から早速、「Java Bronze試験つまずいた項目」5選の紹介をしていきます。
2.Java Bronze試験つまずいた項目」5選
①シグネチャの同じメソッドを持つインスタンスを別の型で扱う
スーパークラスとサブクラスが同じシグネチャのメソッドを持っている以下のような場合を考えます。
public class A {
public void speak() {
System.out.println("Hello.");
}
public class B extends A {
public void speak() {
System.out.println("Good morning.");
}
}
public class Main {
public static void main(String[] args) {
A a = new B();
a.speak();
}
}
この場合、Mainメソッドの出力結果として何が出てくるかが問題として問われます。BのインスタンスをA型で扱っているため、「Hello.」と「Good morning.」どちらが出力されるかが観点になってきます。
このパターンの問題は、どのクラスがnewされているかで考えればよいです。今回はBクラスがインスタンス化されているため、どんな型で扱われていようとspeakメソッドがB型のものでオーバーライドされることになります。すなわち、出力結果は"Good morning."となります。
サブクラスのインスタンスをスーパークラスの型で扱うということは、サブクラスの持つスーパークラスの要素部分のみ抜き出しインスタンスとして扱うということです。つまりサブクラス固有のメソッドなどが無視されます。今回は両クラスがシグネチャの同じメソッドを持っているため、Aクラスのspeakメソッドの内容がBクラスのメソッドの内容に上書きされます。
②例外が出てくるパターン
Java Bronzeでは、出題されるコードがなんかおかしいなと感じられた場合の正答は、多くの場合「コンパイルエラー」になります。しかし例外が発生するわずかなパターンが出題されることがありますので、ここだけ押さえておけば確実に得点につながります。
(参考サイト:例外とは)
そもそも例外とは、コンパイルしてjavaの文法的には正しかったものの、実際に動かしてみるとうまく行かなかった場合に発生するものです。一方のコンパイルエラーはそもそも文法が間違っているときに、コンパイル時に発生するものです。コンパイルエラーの例としては、カッコが足りないなどの単純な記述ミスや、他クラスのprivateフィールドに直接アクセスするなどのミスが該当します。ちなみに、カッコが足りないなどのレベルの、間違い探しのようなひっかけ問題は、本試験では基本的に出題されないようです。
それでは例外になるパターンとしてはどのようなものがあるのでしょうか?Javaの初歩的な例外パターンを洗い出したところ、以下の3つが挙げられました。
Ⅰ.配列で、要素数を超えるアクセスをした場合(初期化していない配列も含む)
これはごく単純なパターンです。例えば要素数が3個しかない配列の4番目の要素にアクセスしようとしたときに発生します。
ちなみに以下のような場合も同じ理由で例外が発生します。
//「java Main」というコマンドで以下のプログラムを実行
public class Main {
public static void main(String[] args) {
System.out.println(args[0]);
}
}
普段はあまり意識されませんが、mainクラスに「String[] args」が指定されていることからわかるように、mainクラスは文字列の配列を引数に受け取ることができます。プログラム実行時に「java Main 文字列, 文字列, ...」と指定すれば「args」という配列に文字列の値が入ってきます。
「java Main」というコマンドでプログラムを実行すると、「args」というインスタンスは生成されるものの、値が何も入っていない状態になります。この状態で0番目の要素にアクセスしようとすると例外が発生するわけです。
###Ⅱ.明示的なダウンキャスト
まずはダウンキャストについて、コンパイルエラーが発生するパターンを考えてみます。
public interface A {
//Aというインターフェース
}
public class B implements A {
//インターフェースAを実装したBクラス
}
public class C extends B {
//Bクラスを継承したCクラス
}
public class Main {
public static void main(String[] args) {
B b = new B(); //Bのインスタンスを生成する(AとBの差分を持っている)
A a = b; //Aの差分を持った変数aを宣言する(Bの差分がなくなり、Aの差分しか持っていない)
C c = a; //変数aをそのままC型に当て込もうとする。変数aはBやCの差分を持っていないためコンパイルエラーとなる
}
}
この例ではAというインターフェース、Aを実装したBクラス、Bクラスを継承したCクラス、Mainクラスが宣言されています。「 B b = new B();」でBクラスをインスタンス化することで、BクラスはBクラス自体の差分はもちろんのこと、実装元のインターフェースAの差分を持ちます。
「 A a = b;」で、先ほどインスタンス化したBクラスをA型で扱おうとしています。つまりBクラスのA型の部分だけ抜き出して変数aに格納しています。これによって元々あったBクラスの差分はなくなってしまいます。このように生成したインスタンスを継承元、実装元の上流の型に当て込むことをアップキャストと呼びます。
アップキャストの逆で、継承先、実装先の型でインスタンスを扱おうとすることをダウンキャストと呼びます。アップキャストするときは必ずインスタンスが継承元、実装元の差分を持っているため、自動的に問題なく行われます。一方でダウンキャストについてはインスタンスが継承先、実装先の差分を持っているとは限らないため、型に整合性があることを明示的に示す必要があります。
例の「 C c = a;」では、Aの差分のみを持っている変数aの値をC型で扱おうとしています。C型は、インターフェースAを実装しているBクラスを継承しているため、A、B、C全ての型の差分を持っていることになります。変数aの値をC型で扱ってしまうと、仮にB、C型の差分内容を使おうとなったときに不釣合いが生じてしまいます。このような現象を防ぐために、ダウンキャストする際は一律で明示的に型の整合性を示す必要があり、明示的に示していない今回の例はコンパイルエラーとなります。
それでは、mainクラスを以下のように書くとどうなるでしょうか。
public class Main {
public static void main(String[] args) {
B b = new B(); //Bのインスタンスを生成する(AとBの差分を持っている)
A a = b; //Aの差分を持った変数aを宣言する(Bの差分がなくなり、Aの差分しか持っていない)
C c = (C)a; //変数aをC型に変換してC型に当て込もうとする。
}
}
「C c = (C)a;」というように、変数aはC型で扱えるよと明示的に示しているため、文法的には問題ないと判断されます。しかし実際は変数aはAの差分しか持たないため、実行時に型の生合成がつかなくなってしまいます。こういった場合は実行時に例外が発生します。
ダウンキャストをする場合は、現在の型に整合性があるかどうかのみ考慮されます。そのため仮に、以下のように元々の型に整合性があるダウンキャストを行ったとしても例外が発生します。
public class Main {
public static void main(String[] args) {
B b = new B(); //Bのインスタンスを生成する(AとBの差分を持っている)
A a = b; //Aの差分を持った変数aを宣言する(Bの差分がなくなり、Aの差分しか持っていない)
B nextB = (B)a; //変数aをB型に変換してB型に当て込もうとする。変数aはすでにB型の差分を失っているため整合性が取れず、例外となる
}
}
###Ⅲ.予期せぬ型で値が入力される
このパターンについては問題集、本試験で出会ったことはありませんが、要求されている型とは違う型の引数が指定された場合などに例外が起こります。
(参考サイト:IllegalArgumentException IllegalStateExceptionの使い方)
③コンストラクタに関わるコンパイルエラーパターン
他のクラスを継承した場合のデフォルトコンストラクタ追加などについても紛らわしさがあるため、ここで整理してみます。
前提として、自クラスの別のコンストラクタを呼び出したいときには「this()」、継承元のスーパークラスのコンストラクタを呼び出したいときは「super()」キーワードを用います。
それでは以下で、コンパイルエラーになるパターンをいくつかみていきましょう。
###Ⅰ.コンストラクタ呼び出しが共存するパターン
コンストラクタはインスタンスの準備をするものです。そのためインスタンス準備の前に処理が発生すると困ってしまいます。コンストラクタ呼び出しの前に処理を書くことはできないので「this()」と「super()」いずれも、コンストラクタの一番最初でしか使用することが出来ないことになります。
このルールがあるため、以下のようにコンストラクタ内で並列して2つのコンストラクタ呼び出しを行っているパターンはコンパイルエラーになります。
this();
super();
###Ⅱ.引数ありのコンストラクタがあるのに、引数が指定されていないパターン
以下のように引数ありのコンストラクタが宣言されている場合、引数なしのデフォルトコンストラクタは自動追加されません。そのためインスタンス生成時に引数を渡さなかった場合はもちろんコンパイルエラーになります。
Sample s = new Sample() //引数指定なしでインスタンス生成
~省略~
class Sample{
public Sample(int a){
//処理
}
//処理
}
###Ⅲ.コンストラクタ呼び出しの引数としてstaticでないフィールドを指定している
コンストラクタはインスタンス生成時に最初に実行されるメソッドです。そのためstaticではないフィールドを指定すると、まだ準備がなされていないフィールドを参照することになります。staticフィールドはインスタンスを生成しなくても使えるので問題ありませんが、それ以外の場合はコンパイルエラーになってしまいます。
public class Sample {
public String argument = “example”;
public String name;
public Sample() { //引数なしのコンストラクタ
this(argument); //staticでない変数を指定して別の自クラスのコンストラクタ呼び出し
}
public Sample(String name) {
this.name = name;
}
}
④データ隠蔽と情報隠蔽の違い
どちらも「隠蔽」という言葉が使われている、かつ「データ」と「情報」の意味合いも似ているため混同しがちな項目でした。
(参考サイト:【Java Bronze学習】カプセル化・データ隠蔽・情報隠蔽の違い)
カプセル化とセットで使われるのがデータ隠蔽です(カプセルとデータどちらもカタカナ、というふうに覚えました)。
カプセル化というのは関連する変数やメソッドを同じクラスにカプセルのようにまとめてしまおうという考え方です。例えば以下のような二つのクラスを考えましょう。
public class employee {
private String department;
private String name;
public void introduce(String department, String name) {
System.out.println(department + “の” + name + “と申します。”);
}
}
public class product {
private int priceWithoutTax;
private double taxRate;
public int calculateConsideringTaxRate(int priceWithoutTax, double taxRate) {
return priceWithoutTax + priceWithoutTax * taxRate;
}
}
employeeクラスには従業員に関する変数と処理、productクラスには商品に関する変数と処理をまとめています。システムに改修が入った時、関係する要素が別々のクラスに散らばっていると調査に時間がかかってしまい、保守性が高いシステムとは言えません。関連する変数や処理をひとつにまとめて構造をわかりやすくするのがカプセル化です。
例えば従業員クラスの中で商品の価格を変えてしまうような処理があったとすると、無関係なクラスが勝手にクラスの内容に影響を及ぼすことになってしまいます。そのような事態を防ぐために変数にprivate修飾子をつけ、他クラスから勝手に参照できないようにします。これがデータ隠蔽という考え方です。カプセル化、データ隠蔽、プライベート、いずれもカタカナなので私はまとめて覚えました。
一方の情報隠蔽は抽象化の維持のために使われる考え方です。情報隠蔽、抽象化いずれも漢字表記なのでまとめて覚えました。
そもそもの抽象化とは、各クラスの共通部分に着目する考え方のことです。例えば、「殴る」という処理を持つ「人間」クラス、「噛む」という処理を持つ「ヘビ」クラス、「ひっかく」という処理を持つ「猫」クラスがあるとします。それぞれをこのまま扱おうとすると散らばった3つのクラスそれぞれについて考える必要が出てきてしまい、改修時などに時間がかかってしまいます。
これを一般化して「攻撃」処理を持つ「生き物」クラスというものをひとつ用意して、殴るや噛むなどの具体的な実装は「生き物」クラスを継承したそれぞれのクラスに委ねるというのが抽象化の考え方です。仮に「防御」処理を追加したくなった時、「生き物」クラスに「防御」処理を追加してそれぞれのクラスで具体的に実装することでまとめて扱うことができるようになります。
以下に引用した画像では「社員」という一般化したクラスを用意し、「働く」処理の具体的な内容の実装を各クラスに委ねています。ちなみにこの「社員」クラスに当たるものはインターフェースと呼ばれ、インターフェースでは抽象メソッドというものを定義し、継承先のクラスに具体的な実装を強制することができます。
⑤Javaプログラムの実行方式
java bronze試験のほとんどはソースコードを見て想定される動きを考えるものが多いですが、単純な知識を問うものもいくつか出題されます。このような知識問題はいっそ捨ててかかる方も多いようですが、基本的なところは得点しやすいので押さえておくべきかなと感じました。
プログラム言語の主な二つの実行方法として、「事前コンパイル方式」と、「インタープリタ方式」があります。しかしjavaはこの二者の中間的な実行方法を採用しており、それを「実行時コンパイル方式」と呼びます。
(参考サイト:インタープリタ方式とコンパイル方式)
インタープリタ方式では、インタープリタが実行時に人間向けのソースコードを読み込み、OS向けのネイティブコードに変換(コンパイル)します。こうすることでプログラムをどのようなOSでも実行できます。しかし実行速度が落ちてしまうというデメリットもあります。
事前コンパイル方式では人間向けのソースコードを事前に機械語の実行ファイルにコンパイルしておき、OSでそれを読み込んで実行する方式です。すでにコンパイルされたファイルを読み込むため実行速度が速いというメリットがあります。一方でOS依存の実行ファイルが作られてしまうというデメリットがあります。
javaではOSに依存しないかつ実行速度の速いプログラムを生成するために、実行時にコンパイルを行います。実行時にソースコードが中間コード(クラスファイル)にコンパイルされます。さらにその中間コードを、JVMという仮想的なコンピュータが読み込みます。このように事前にJVM用のコンパイルを行いつつ、OSごとにJVMを用意すればOS非依存にできるため、二つの実行方式のいいとこ取りができるというわけです。
(参考画像:JVM Architecture)
実行時コンパイル方式を含めた以上三種類の実行方式については、何度も実行の流れを確認して覚えていくしかなさそうです。
またjavaプログラムの実行に関連して、JVMやJREなど英語のアルファベット表記の用語がいくつか登場して混同しがちになりました。包含関係などをに簡単にまとめてみます。
名称 | 説明 |
---|---|
JVM | 実行時にクラスファイルを読み込む仮想的なコンピュータ |
JRE | javaプログラムの実行に必要なJVMやライブラリ、コンポーネントをまとめたもの |
JDK | JREに加えてコンパイラなどの開発ツールを含んでいる |
Hotspot VM | 実行時コンパイル方式を可能にするJVMの一種(参考記事) |
3.最後に
Java Bronze試験に合格したからといってプログラミングがスイスイ進むというわけではありません。しかし知識の部分では証明になりますし、上位試験を受ける際の演習にもなるため、手軽で良い試験だと思います。受験料も高額なため、万全の対策をして一発合格をしたいですね。
参考サイト:
◯勉強法について
Oracle認定資格のJava Bronzeレベルとは?勉強方法4つを解説
【2021/10 更新】Java Bronze SE を高確率で一発合格する方法
◯サンプル問題の載っているサイト
Java初心者の登竜門! Java Bronze試験で間違えやすい問題6選
Oracle Java Bronzeテスト対策!簡単なことなのに私がよく間違える問題 7問!
Oracle認定Javaプログラマ(OCJ-P)試験の練習問題(複数クラス編)
◯つまずきやすいポイント解説
Java Bronzeで引っ掛かりがちな問題8選!難易度や試験範囲も解説
【Java資格】Bronze(OCJ-P)の出題問題について徹底解剖
◯受験方法の説明
【Oracle認定Javaプログラマ】Bronze試験の申し込み方法と合格率を上げる勉強法
Java Bronze資格試験の申し込み手順がややこしすぎるぅぅぅぅ泣
【Java Bronze(ブロンズ)】試験の申し込みから対策法まとめ
◯その他本記事作成の参考としたもの
徹底攻略Java SE Bronze問題集[1Z0-818]対応
オラクル認定資格教科書 Javaプログラマ Bronze SE(試験番号1Z0-818)
スッキリわかるJava入門 第3版 (スッキリわかる入門シリーズ)
Oracle Certified Java Programmer, Bronze SE 7 / 8 認定資格試験 ポイント解説セミナー(1)
Oracle Certified Java Programmer, Bronze SE 7 / 8 認定資格試験 ポイント解説セミナー(2)
例外とは
IllegalArgumentException IllegalStateExceptionの使い方
【Java Bronze学習】カプセル化・データ隠蔽・情報隠蔽の違い
抽象クラス(Abstract Class)
インタープリタ方式とコンパイル方式
JVM Architecture
HotSpot VMについて