このページではJavaの演習問題をいくつか用意した。
Java特有の文法の知識があれば解けるような演習問題たちだ。Javaを勉強中の方はチャレンジしてみてほしい。
アルゴリズムを考えて解く演習問題は下記に用意している。こちらも参考にしてほしい。
目次
Javaの演習問題:文法編
それでは早速演習問題に入っていこう。
解答方法
入力値は問題で定められた規則に従い、正しく入力されるものとし、入力ミスなどは考慮しなくてよい。
問題1(起動パラメータ)
起動パラメータに指定された引数をすべて表示するプログラムを作成しなさい
- 最初に、引数の個数を出力する。
- 引数は一つ以上指定されるものとする。
実行例:
java Man ABC あいうえお 12345
出力例:
1 2 3 4 |
引数は3個指定されています。 1番目の引数は「ABC」です。 2番目の引数は「あいうえお」です。 3番目の引数は「12345」です。 |
問題2(例外)
プログラム中で、以下の例外を発生させる処理を記述し、その例外の詳細情報は出力しなさい。
- Null参照によるNullPointerException
- 配列の範囲外参照によるArrayIndexOutOfBoundsException
- 互換性の無いオブジェクト型の変換によるClassCastException
ただし、プログラムは例外による異常終了はせず、正常に終了すること。
出力例:
1 2 3 4 5 6 |
java.lang.NullPointerException at Main.main(Main.java:8) java.lang.ArrayIndexOutOfBoundsException: 0 at Main.main(Main.java:14) java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer at Main.main(Main.java:21) |
問題3(static、ジェネリクス、アノテーション)
起動パラメータに指定された引数配列に対し、
- List型の変数に格納する
- List型の変数に格納された値を表示する
を行うプログラムを作成しなさい。
実行例:
java Man ABC あいうえお 12345
出力例:
1 2 3 |
ABC あいうえお 12345 |
いかがだっただろうか?
以下は、演習問題の回答になるので、解き終わってから確認してほしい。
問題1 解答/解説
解答例
1 2 3 4 5 6 7 8 |
public class Main { public static void main(String[] args) throws Exception { System.out.println("引数は" + args.length + "個指定されています。"); for(int idx = 0 ; idx < args.length; idx ++){ System.out.println((idx + 1 ) + "番目の引数は「" + args[idx] + "」です。"); } } } |
入力パラメータを数値に変換する。文字列を数値に変換する際、文字列が「-N」の形式の場合は、負の値として変換される。
まずは、javaプログラムを実行する際の基本的な問題だ。
コマンドラインからjavaプログラムを起動する事は、実はあまり多くは無いのだが、基礎知識として押さえておこう。
ポイントとしては
- main()メソッドの引数String[]には、コマンドライン引数が渡される。
- 引数String[]には、プログラムに渡されるコマンドライン引数のみが格納され、コマンドラインオプションや実行クラス名は含まれない。
というところだ。
この2つ目の「コマンドラインオプションや実行クラス名は含まれない」というところに「なんでわざわざそんなことを」と思われたかも知れない。
実は、プログラム言語によっては、起動引数の1番目に実行プログラム名が格納され、引数は2番目以降となるものがある。C言語などがそうだ。
引数だけが渡されることが当たり前ではないので注意してもらいたい。
問題2 解答/解説
解答例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class Main { public static void main(String[] args) throws Exception { try{ String name = null ; name = name.substring(0);//① }catch(Exception ex){ ex.printStackTrace(); } try{ String values[] = new String[2]; values[2] = "value";//② }catch(Exception ex){ ex.printStackTrace(); } try{ Object value = "value"; Integer number = (Integer)value;//③ }catch(Exception ex){ ex.printStackTrace(); } } } |
javaでは、try/catchという構文を利用して、例外の捕捉(ハンドリング)を行う。
まず、プログラム中に例外(Exception)が発生した際に、適切な処理継続(安全な終了処理を含む)のために、「例外の捕捉」を行う必要がある。
NullPointerExceptionの発生要因は、Nullオブジェクトに対するメソッドの呼び出しだ。
①では、変数nameは宣言後にnullで初期化され、その後実体の代入処理が無くsubstringメソッドが呼び出されている為にNullPointerExceptionが発生する。
この問題を回避するためには、宣言した変数がどのタイミングで値(オブジェクト)が代入されるのかを意識しながら全体の処理を記述する必要がある。
ArrayIndexOutOfBoundsExceptionの発生要因は、配列の範囲外の要素へのアクセスだ。
例えば、10個しか要素を用意していない配列なのに、11番目(添え字は10)の要素に代入したり参照したりすると発生する。
②では添え字「2」の要素(1番目の要素)に対して代入を行っているが、配列valuesを宣言した際に定義した要素数は「2」であり、添え字の最大番号は「1」(「0」から始まって2つ目は「1」)であり、添え字「2」に該当する要素は存在しないため、ArrayIndexOutOfBoundsExceptionが発生する。
この問題を回避するためには、配列の最大数(添え字の最大番号)は幾つになるのかを意識しながら全体の処理を記述する必要がある。
また、配列の場合はlengthプロパティを参集するなどして、要素数の変化に耐えうる処理を記述するよう心掛けると良いだろう。
ClassCastExceptionの発生要因は、互換性のオブジェクトの型変換(キャスト)だ。
③では、Object型として宣言された変数に、String型のオブジェクトが代入されたものを、互換性の無いInteger型変換しようとしているためにClassCastExceptionが発生する。
Object型はその名のとおりオブジェクト型の変数の値であればどのようなものでも代入可能で、String型のオブジェクトを代入する事も、Integer型にキャストする事も可能なため、コンパイルエラーにならないが、実行時に実際に格納されている型がString型のものをInteger型に変換するという事は、互換性の無い型同士での変換のため、Exceptionが発生してしまう。
この問題を回避するためには、そのオブジェクトが実態としてどんな型のオブジェクトを保持しているのかを意識しながら全体の処理を記述する必要がある。
また、ArrayListやHashMapのようなObject型を格納するオブジェクトには、ジェネリクスという型指定の考え方がある。
List<String> array = new ArrayList<String> ;
というように、変数の宣言時に扱う型を限定してしまうのだ。
これにより、実行時にClassCastExceptionが発生する可能性は低下するだろう。
ここで紹介した3つのExceptionは、どれもよく見かけるものなのだが、発生原因の多くは考慮不足によるものだ。
また、3つのExceptionはRuntimeExceptionの派生で、本来は発生しないものである。
Exceptionには大きく分けて
- 起こりうることが予想できるException
- 起こりうることが予想できず、多くの場合はプログラム中の問題である
の2つに分類され、後者はRuntimeExceptionを指している。
前者は、ファイルアクセス処理において、プログラムの実行時にファイルが見つからずに「IOException」が発生するケースなどが良い例だ。
プログラムとしては、処理を行うべきファイルが必ず存在する前提でプログラムを実行するのだが、プログラム外の問題でもあるので、「起こりうることが予想できるException」としてtry/catch文を利用して「Exceptionが発生したときの対処方法」を事前に明確にしておくべきだろう。
RuntimeExceptionでは無い例外の場合、必ずハンドリング処理を実装しなければならないようにjavaの言語仕様として定められている。
これに対し、RuntimeExceptionはあくまでも実行時の例外であり、事前に予測のできるものではない。しかしながら、プログラムの実行パターンを不足なく確認できれば、RuntimeExceptionの発生を抑えることは可能だ。
プログラム経験の浅いうちはRuntimeExceptionが多発するが、習熟度が上がるにつれてあまり見かけなくなるものでもある。
2つのExceptionの違いを最初から理解するのは難しいかも知れないが、少しずつ慣れていこう。
問題3 解答/解説
解答例
1 2 3 4 5 6 7 8 |
public class Main { public static void main(String[] args) throws Exception { List<String> params = Arrays.asList(args); for(String param : params){ System.out.println(param); } } } |
この問題は、何のひねりもない陳腐な問題と思われた方もいるだろう。
しかし、Javaにおけるいくつかの重要な要素を含んでいるので、解説をよく読んで理解を含めて欲しい。
static
プログラム中の以下の記述に注目してもらいたい。
List<String> params = Arrays.asList(args);
これはString配列をList型に変換する処理を行う、Java標準の機能を使用したものだ。
ここでは、「Arrays」というクラスの「asList()」というメソッドを呼び出しているが、Arraysは変数では無く、クラス名を指している。
このように、クラスの実体を生成する事無く、そのまま使用できるメソッドの事を「staticメソッド」と呼ぶ。
staticメソッドはJava標準機能でもいくつか用意されており、いずれも多くのプログラムで利用されるような汎用的な機能だ。
ジェネリクス
本来、リスト型の変数には、StringのみならずIntegerやDateなど、どのような型のオブジェクトも格納可能だ。
しかし、取り出す側のプログラムを記述する立場から見れば、どんな型のオブジェクトが入っているのかが分からないという曖昧さが生まれ、取り出した値の取り扱いに困ってしまう。
これを解決したのが「ジェネリクス」という考え方だ。
Listのように、どんなクラスのオブジェクトでも扱えるクラスであっても、変数の宣言時にその用途を限定することで、プログラムの曖昧さを抑制するものだ。
ジェネリクスの考え方を適用せずにこのプログラムを記述すると、以下のようになる。
1 2 3 4 5 6 7 8 9 |
public class Main { public static void main(String[] args) throws Exception { List params = Arrays.asList(args); for(int idx = 0 ; idx < params.size(); idx ++){ String param = (String)params.get(idx); System.out.println(param); } } } |
しかし、EclipseなどのIDEを使用してコンパイルを行うと、警告が出ていることが分かるだろう。実際にこのプログラムを作成し、実行すると正しく動作する。
これはList変数の宣言時に型を明示していないために出されている警告だ。
また、最初の解答例のように、拡張for記述を使用することも出来ず、コンパイルエラーとなる。
1 2 3 |
for(String param : params){ System.out.println(param); } |
これも、配列要素のオブジェクトの型が明示されていないからだ。
あえて拡張for記述を用いるとすれば
1 2 3 |
for(Object param : params){ System.out.println(param); } |
この場合、paramをStringとして使用するのであればと記述するしかない。
(String)param
と、キャスト(強制的な型変換)を行う必要がある。
アノテーション
上記のジェネリクスのケースで、どうしても警告が出て気になる場合には、警告の抑制を行うことができる。
1 2 3 4 5 6 7 8 9 10 |
public class Main { public static void main(String[] args) throws Exception { @SuppressWarnings("rawtypes") List params = Arrays.asList(args); for(int idx = 0 ; idx < params.size(); idx ++){ String param = (String)params.get(idx); System.out.println(param); } } } |
このように、
@SuppressWarnings("rawtypes")
という記述を追加するだけで、型定義の曖昧さに関する警告は抑制される。このように「@」で始まる記述を「アノテーション」と呼ぶ。
アノテーションは「注釈」と呼ばれ、様々なものがある。単にコメント的な意味を表すものや、使ってほしくないメソッドに対する警告的なものなど様々だ。
また、最新のJavaフレームワークであるJava EEでは、サーバーサイドプログラムの実行定義に関する命令を、アノテーションを使用して記述する。
このように、様々な目的でアノテーションが使用されているが、使う場面は熟慮すべきである。
例えば、先の
@SuppressWarnings("rawtypes")
のような記述は、極力利用すべきでは無い。
なぜなら、ジェネリクスによる型の明示は、プログラムの品質を高めるために考案されたものであるのに対し、@SuppressWarningsを記述することで、その警告を隠ぺいしてしまうことになるからだ。
あえて利用する場面としては、旧バージョンのJava(1.4以前)で作成した共通処理のソースコードを、そのまま利用して新バージョンのJava向けの開発に利用するようなケースだろうか。
1.4以前のJavaでは、ジェネリクスというものが存在しなかったため、型の明示はせずにあいまいなまま使用していた。
そのプログラムを、そのまま改変せずに新バージョンで使用すると、至る所で警告が発せられることになるだろう。
かといって、警告個所をジェネリクス記述に訂正する事で、不要なミスを呼び起こす危険も考えられる。
そんな場合であれば@SuppressWarningsを用いて、警告を隠ぺいしても良いだろう。
いずれにしても、アノテーションは適した場面で活用すれば、プログラム作成の簡素化や品質の向上につながるものだ。
是非とも活用してもらいたい。
まとめ
このページではJava特有の文法的な問題についてまとめてきた。
ぜひ解答だけ見ずに、実際にトライをしてみて、スキルを上げていただければと思う。