java:プリミティブ型とラッパークラスの暗黙の型変換

intやcharに代表されるプリミティブ型と、IntegerやCharacterに代用されるラッパークラスの間では暗黙の型変換が行われます。
プリミティブ型からラッパークラスへの暗黙の型変換をオートボクシング、ラッパークラスからプリミティブ型への暗黙の型変換をアンボクシングと呼びます。

例えば、intとIntegerを明示的に型変換すると以下のようになります。

【サンプルコード】

// 変数定義
int primitive = 1;
Integer wrapper = null;

// プリミティブ型からラッパークラスへの変換
wrapper = new Integer(primitive);

// ラッパークラスからプリミティブ型への変換
// (変換が正しく行われればif文の中に入り変数の中身を出力)
if (primitive == wrapper.intValue()) {
System.out.println(wrapper.intValue());
}

【結果】

1

しかし、以下のように、暗黙の型変換に頼ったコードでも通ります。

【サンプルコード】

// 変数定義
int primitive = 1;
Integer wrapper = null;

// プリミティブ型からラッパークラスへの変換
wrapper = primitive;

// ラッパークラスからプリミティブ型への変換
// (変換が正しく行われればif文の中に入り変数の中身を出力)
if (primitive == wrapper) {
System.out.println(wrapper);
}

【結果】

1


昔は明示的に型変換しなくても動くのを不安に感じていましたが、それを可能とする仕組みがjavaの仕様として組み込まれているのを知ってからは安心して使っています。
プログラミングでは型の違いで意図しない挙動になることがよくあるので、コンパイルエラーにならないから良しとするのではなく、本当に意図した挙動になるのか調べるべきだと思っています。

今回の記事で、オートボクシング・アンボクシングを知る方が増えれば幸いです。

ゾーン10進数とパック10進数のデータの持ち方

COBOLプログラムでの入出力が必要なデータ(ファイルや電文)を取り扱う際は、ゾーン10進数とパック10進数を意識する必要があります。
今回の記事では、ゾーン10進数とパック10進数について、どのようなデータの持ち方をしているのか(16進数のバイナリでどのようなデータが格納されるのか)を説明したいと思います。

【ゾーン10進数(符号無し)】

ゾーン10進数とは、簡単に言ってしまえば1バイト(8ビット)で1桁の数値を表現する形式です。
符号無しの場合は、通常の文字列と同じ形式で表現します。

表す数値と表現形式(文字コード)の一覧は以下の通りです。
表現形式については、メインフレームで良く使われるEBCDICと、Windows等のPCで良く使われるASCIIについて記載します。

数値表現形式(EBCDIC)表現形式(ASCII)
0F030
1F131
2F232
3F333
4F434
5F535
6F636
7F737
8F838
9F939

例えば、EBCDICで「123」を表現する時には、「F1 F2 F3」となります。

【ゾーン10進数(符号有り)】

符号有りの数値を表現する場合は、最後の桁の上位4ビットの値により符号を表現します。
表現形式はベンダ各社でまちまちなのですが、例としては以下のように表現します。

符号IBM互換機のメインフレーム(EBCDIC)opensource COBOL(ASCII)
符号無しF3
+C(Fの場合もあり)3
D7

例えば、IBM互換機のメインフレーム(EBCDIC)で「+123」を表現する時には、「F1 F2 C3」となります。
また、「-123」を表現する時には、「F1 F2 D3」となります。

【パック10進数】

パック10進数とは、4ビットで1桁の数値を表す形式です。4ビット(16進数)の値がそのまま1桁の数値になります。
また、最後の4ビットで符号を表し、符号無しの場合は F、+ の場合は C、-の場合は D が入ります。
最終的に1バイト(8ビット)の単位にする必要があるため、桁数が偶数の時(桁数×4ビットと符号4ビットの合計が8ビットの倍数にならない時)は、一番先頭の4ビットに 0 を入れます。

例えば、以下のように表現されます。
・「123」を表現する時…「12 3F」
・「+123」を表現する時…「12 3C」
・「-123」を表現する時…「12 3D」
・「1234」を表現する時…「01 23 4F」

【サンプル】

以下は、opensource COBOL(ASCII)を用いて実際にどのように値が表現されているのかを表したものです。
「1234567890」という値について、「ゾーン10進数(符号無し)」「ゾーン10進数(+)」「ゾーン10進数(-)」「パック10進数(符号無し)」「パック10進数(+)」「パック10進数(-)」の順番に出力しています。

・サンプルコード

IDENTIFICATION DIVISION.
PROGRAM-ID. SIGNTEST.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
 SELECT F1 ASSIGN TO “C:\tmp\a.txt”.
*
DATA DIVISION.
FILE SECTION.
FD F1.
01 F1R.
 03 F1-REC PIC X(60).
*
WORKING-STORAGE SECTION.
01 F1-REC-WORK.
 03 F1-REC1 PIC 9(10) VALUE 1234567890.
 03 KAIGYO1 PIC X(02) VALUE X”0D0A”.
 03 F1-REC2 PIC S9(10) VALUE +1234567890.
 03 KAIGYO2 PIC X(02) VALUE X”0D0A”.
 03 F1-REC3 PIC S9(10) VALUE -1234567890.
 03 KAIGYO3 PIC X(02) VALUE X”0D0A”.
 03 F1-REC4 PIC 9(10) COMP-3 VALUE 1234567890.
 03 KAIGYO4 PIC X(02) VALUE X”0D0A”.
 03 F1-REC5 PIC S9(10) COMP-3 VALUE +1234567890.
 03 KAIGYO5 PIC X(02) VALUE X”0D0A”.
 03 F1-REC6 PIC S9(10) COMP-3 VALUE -1234567890.
 03 KAIGYO6 PIC X(02) VALUE X”0D0A”.
*
PROCEDURE DIVISION.
*
* 各種処理の呼び出し
*
000-CONTROLLER-S.
 PERFORM 100-START-S THRU 100-START-E.
 PERFORM 200-MAIN-S THRU 200-MAIN-E.
 PERFORM 300-END-S THRU 300-END-E.
 STOP RUN.
000-CONTROLLER-E.
*
* 前処理
*
100-START-S.
 OPEN OUTPUT F1.
100-START-E.
*
* 主処理
*
200-MAIN-S.
 MOVE F1-REC-WORK TO F1-REC.
 WRITE F1R.
200-MAIN-E.
*
* 後処理
*
300-END-S.
 CLOSE F1.
300-END-E.

・出力結果


金融系だと、仮に自社でCOBOLを使用していなくても、接続先のシステムがCOBOLを使用しているということ良くあります。
データ表現も接続先システムのCOBOLのシステムに合わせなければならないこともあるので、このようなCOBOLの知識が必要になることがあります。

来週も、実務で役立つ情報を提供していきたいと思います!

java:文字コード・改行コードを指定してファイルを出力する

javaでは、実行環境に応じたデフォルトの文字コード・改行コードを用いてファイルを出力するようにコーディングすることができます。
そのことにより、実行環境毎でコーディングを変更せずとも、実行環境に合わせた文字コード・改行コードを採用することができます。

しかし、他の環境向けのファイルを出力するような場合は、実行環境のデフォルトの文字コード・改行コードが採用されると困ることがあります。
そのような場合、FileOutputStreamクラスを用いれば、指定した文字コード・改行コードを採用することができます。

なお、FileWriterクラスを用いる場合は、setPropertyで実行環境のデフォルトを変更しても文字コードは変更できませんでした。
公式ドキュメント(https://docs.oracle.com/javase/jp/8/docs/api/java/io/FileWriter.html)では「文字ファイルを書き込むための簡易クラスです。このクラスのコンストラクタは、デフォルトの文字エンコーディングとデフォルトのbyteバッファのサイズが許容できることを前提としています。」と書かれており、まさにその通りの挙動となっています。

【テストコード】

【標準出力】

【ファイル出力結果】


入門書では文字コードや改行コードについて触れられることは少ないと思うのですが、システム間でファイル連携を行う場合は必ずと言って良いほど文字コードや改行コードを意識する必要があります。
システム間連携でなくとも、モジュールとモジュールの間でファイルを連携する時にも意識する必要がある場合があります。
入門書には出てこなくとも実務での重要性は高いと思います。

来週からも、実務で役立つ情報を中心に提供していきたいと思います!

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

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