java:シリアルバージョンUIDの用途

実務で使用するjavaには、シリアルバージョンUIDが使われていることが良くあります。
シリアルバージョンUIDを知らなくともテストが通ってしまうことが多いと思うのですが、それだと思わぬ失敗をしかねないので、今回の記事で紹介したいと思います。

【シリアルバージョンUIDの用途】

javaではインスタンスをバイナリファイルとして保存することができます。
しかし、インスタンスのクラスの設計を変更した場合、前の設計のバイナリファイルを読み込むと、現在の設計のクラスとの整合性が取れずに処理結果が不正になる恐れがあります。

その際に、クラスにシリアルバージョンUIDを定義しておくことで、前の設計のバイナリファイルを読み込んだ際に例外を吐き出すことができるようになります。
シリアルバージョンUIDはバイナリファイルに保存されるのですが、現在のクラスのシリアルバージョンUIDとバイナリファイルのシリアルバージョンUIDが異なる場合に例外として判定されます。
そのため、クラスの設計を変更する度にシリアルバージョンUIDを変更していれば、前の設計のバイナリファイルを読み込んだ際に例外となります。

【テストコード】

以下、実際に挙動を確認した結果です。

■Item.java

■MainInput.java

■MainOutput.java

【実行結果】

■当初のクラス設計(①を有効、②をコメントアウト)

・MainInputを実行

インスタンスがファイルとして保存される。

・MainOutputを実行

インスタンスを読み込めていることを確認できる。

■クラス設計変更(①をコメントアウト、②を有効)

・MainOutputを実行(変更前のクラス設計時のファイルを読み込んでみる)

インスタンス内に保存されたシリアルバージョンUIDが現在のクラスのシリアルバージョンUIDと異なるため、現在のクラスに対応したインスタンスではないと判断して異常終了する。
(シリアルバージョンUIDを変更しないと、現在のクラスに対応したインスタンスでなくても読み込み時にエラーにならずに後続に進んでしまう。不正なインスタンスを用いることで後続処理の処理結果が不正になる可能性が出てしまう。)

・MainInputを実行→MainOutputを実行

新しいクラスでインスタンスを作り直せば正常に処理できる。


今回取り扱ったシリアルバージョンUIDもそうですが、研修で習うことは少なくても実務での使用頻度は高い、という文法は少なくありません。
そのような文法を見つけたら、また紹介したいと思います。

これからも、実務で役に立つ情報をお伝えできればと思います。
では、また来週!

java:インターフェースを用いることでクラス毎の重複した記述を無くす

javaにはインターフェースという機能があります。
インターフェースとは、メソッドの仕様(メソッド名、戻り値、引数)のみを定義したものです。
インターフェース単独では処理を実行できませんが、そのインターフェースを実装したクラスを定義することで処理を実行可能になります。

インターフェースを利用するメリットとしては、重複した記述を無くせることがあります。
オブジェクトを参照する際にインターフェース名を指定することで、そのインターフェースを実装している全てのクラスを指すことができます。一つの記述で複数のクラスに対応させることができるので、クラスごとに同じ記述を行う必要が無くなり、保守性が向上します。

言葉だと通じにくいと思うので、サンプルコードを用意しました。

【サンプルコード】

まず、インターフェースとして、金融商品インターフェース(FinancialProductsインターフェース)を定義します。
このインターフェースは、1ヶ月後の商品価格を計算するメソッドを持ちます。

・FinancialProducts.java

 

次に、金融商品インターフェースを実装します。
単利商品クラス(SingleInterestProductsクラス)と、複利商品クラス(ComplexInterestProductsクラス)を実装します。
この2つのクラスは、共に1ヶ月後の商品価格を計算するメソッドを実装していますが、その実装内容(計算ロジック)は異なります。

・SingleInterestProducts.java

・ComplexInterestProducts.java

 

そして、単利商品クラスと複利商品クラスを使用するメインクラスです。
メインクラス内で定義している2ヶ月後の商品価格計算メソッドにて、金融商品インターフェースを参照することで、単利商品クラスと複利商品クラスを同じ記述で使用することができているのがポイントです。

・Main.java

【実行結果】


いかがでしたでしょうか。

インターフェースはjavaの基礎的な文法の一つです。
しかし、インターフェースは様々な形で応用されています。
例えば、配列のソートで独自定義を行う時にインターフェースの理解が必要になりますし、デザインパターンやフレームワークといったオブジェクト指向の設計手法でもインターフェースは頻出です。
そのため、単純に読める・コンパイルエラーにならないコードを書ける、というレベルの理解ではなく、これを使うと何が嬉しいのか、というレベルで理解するのが望ましい、と個人的には思います。

これからも、実務で役に立つ情報をお伝えできればと思います。
では、また来週!

java:暗黙の型変換による意図しない小数点以下切り捨て

原因がわかるまでに手間取ってしまったので、記事として残しておきます。
double型やBigDecimal型の変数の初期値を分数(例:2/3)で定義する際、小数点を入れないと「int型変数/int型変数」と判断されてしまい、小数点以下が切り捨てられた状態で変数に格納されてしまいます。
「2.0/3.0」なら可なのですが、「2/3」は不可です。

以下、サンプルコードです。

【失敗例】

・ソースコード

・実行結果

【成功例】

・ソースコード

・実行結果

java:javaからのOSコマンド呼び出しと注意点

javaのプログラムからOSのコマンドを実行したい場合は、Runtimeクラスのexecメソッドで実現できます。
 
しかし、OSのコマンドは別プロセスで立ち上がるので注意が必要です。
ProcessクラスのwaitForメソッドでプロセスの終了を待たないと、処理が前後してしまい意図しない挙動となることがあります。
また、destroyメソッドでプロセスを明確に終了させ、資源を回収することも重要です。
 
以下は、プロセスの終了を待たないと意図しない挙動となることを確認するためのテストコードです。
javaのプログラムでは、OSのコマンドを使用して、3秒のスリープ後に hoge.txt を aフォルダ から bフォルダ へ移動させています。
waitForメソッドを使用しなかった場合、javaのプログラムが終了した時点で hoge.txt の bフォルダ への移動が完了していない(javaのプログラムが立ちあげたプロセスが終了していない)ことを確認できます。
逆に、waitForメソッドを使用した場合は、javaのプログラムが終了した時点で hoge.txt の bフォルダ への移動が完了している(javaのプログラムが立ちあげたプロセスが終了している)ことを確認できます。
 

【テストコード】

・RuntimeTest.java

【実行用バッチ】

・RuntimeTest.bat

【「p.waitFor();」と「p.destroy();」をコメントアウトした場合の実行結果】

【「p.waitFor();」と「p.destroy();」を有効にした場合の実行結果】

java:色々なfor文の書き方

新人にfor文を教えるために書いたコードです。
5通りの書き方をしており、全て同じ挙動を示します。
これらの書き方を使い分けることで、様々なループ条件を記述できます。

【サンプルコード】

【実行結果】