java:ミュータブルな参照型変数の初期化の注意点

ミュータブルな参照型変数を初期化する場合、初期化の方法を間違えると他の変数も一緒に初期化してしまいます。
この記事では、ミュータブルな参照型変数の初期化方法を説明します。

【基本データ型変数と参照型変数】

変数は大きく分けて、基本データ型変数(プリミティブ型変数、値型変数)と参照型変数(オブジェクト型変数、クラス型変数)の2つに分けることができます。

基本データ型変数とは、以下の8つの内の何れかの型で定義された変数であり、オブジェクトを持ちません。
・byte
・short
・int
・long
・float
・double
・boolean
・char

参照型変数は以上の8つの型以外の型で定義された変数であり、オブジェクトを持ちます。
実際には、メモリ領域の番地を指し示すポインタのようなものが格納され、ポインタを辿って値を参照します。
C言語のポインタの概念を理解しているとこの概念も理解できます。C言語のポインタについては以下のページをご参照ください。

C言語:ポインタの概念の図解

【イミュータブルとミュータブル】

参照型変数は、更にイミュータブルとミュータブルの2つに大別することができます。

イミュータブルは、日本語で言うと「不変」という意味であり、オブジェクトの中の値を変更することができない変数を指します。
setter等でオブジェクトの中の値を変更できないようにする、メンバ変数をprivateで定義してオブジェクトの中の値を変更できないようにする、等の条件を満たすと、イミュータブルな型となります。
イミュータブルな型となる条件はかなり例外的であり、自分でクラスを作成する場合は意識的に条件を満たそうとしなければイミュータブルな型にはならないはずです。
標準で用意されている型の中では、String型がイミュータブルな型として有名です。
イミュータブルな参照型変数はオブジェクトの中の値を変更できないため、中の値を変更する場合は新たにメモリ領域を確保してオブジェクトを作り直すような動きになります。今回取り上げる記事上は、基本データ型変数と同じような動きになります。

ミュータブルはその逆で、オブジェクトの中の値を変更することができる変数のことであり、多くの参照型変数はこちらに該当します。
String型も、配列にした場合はミュータブルな型になります(配列がミュータブルなので)。
そして、注意が必要なのはこのミュータブルな参照型変数です。

【ミュータブルな参照型変数の初期化の注意】

参照型変数の中には、ポインタが格納されています。

例えば、

とした場合、fugaには「{“あ”,”い”,”う”}」が入るわけではなく、fugaのアドレスが格納されます。
つまり、hogeとfugaは同じ位置を指し示すことになります。

ここで、fugaのみ初期化(値を変更)しようとしたとします。
String型のようにイミュータブルな型であれば、fugaの値を変更された時点でメモリ領域が新たに確保されるので、hogeとfugaは別々の値となり、何の問題もありません。
しかし、String[]型のようにミュータブルな型の場合は、hogeとfugaが同じ位置を示しており、新たにメモリ領域が確保されることもないので、hogeが示す値も一緒に書き変わってしまいます。

例えば、

のようにfugaを全角スペースで初期化しようとすると、fugaだけでなくhogeも初期化されてしまいます。

これを防ぐためには、以下の2つの手順を踏む必要があります。
1.初期化したい変数にnullを代入してメモリ領域への参照を切る
2.newで初期化したい変数用のメモリ領域を新たに確保する

以下、サンプルコードで動きをまとめたいと思います。

【サンプルコード】

【実行結果】


「参照型」や「イミュータブル」といった概念は、プログラム経験の浅い人には難しいと思います
私の場合はC言語でポインタの概念を理解してからこれらの概念を学んだのですが、それでも初めの内はjava独特の仕様に戸惑いました。
しかし、概念を理解すればコーディングする上で便利だと感じることもあるので、この記事を通して理解を深めていただければ幸いです。

今後も、つまずきやすいポイントを記事にしていきたいと思います!

java:Unicodeの絵文字をjavaで取り扱う

メールや掲示板等の文章を見ていると時々「🗿」のような絵文字が出てきますが、これはUnicodeにより定義されています。
このような絵文字を取り扱うには少々知識が必要なので、この記事を通して必要な知識をお伝えしたいと思います。
また、絵文字を含む文字列を1文字ずつ切り出すjavaのサンプルコードも作りました。

【絵文字の定義と背景】

絵文字は符号化文字集合「Unicode」で定義されており、Unicodeの文字符号化方式である「UTF-8」「UTF-16」等で使用することができる。
(「符号化文字集合」とは「文字」と「文字に割り当てた番号」の対応表、「文字符号化方式」とは「文字に割り当てた番号」と「実際にコンピュータが扱う数字」の対応表のことである)
なお、「UTF-16」は狭義の「Unicode」として呼ばれることもある。

Unicodeは元々1~2バイト文字を定義していたが、世界中の文字を取り扱いたいという要望に応えるために4バイト文字を拡張領域として定義した。
絵文字は、この拡張領域に含まれる。
4バイト文字は、「上位サロゲート(2バイト)+下位サロゲート(2文字)」の組み合わせで定義される。
「上位サロゲート」は0xD800~0xDBFF(1024通り)、「下位サロゲート」は0xDC00~0xDFFF(1024通り)で定義され、何れも2バイト文字では使用しないコードであるため、表現が衝突することはない。

4バイト文字は定義当時は実際に扱われることが少なかったが、日本の携帯の絵文字の普及により一般的に使われるようになり、Webシステム等では無視できない存在となった。

【サンプルコード】

・ソースコード(UTF-8で作成)

・出力


文字の16進数表現は奥深いです。
先日公開したゾーン10進数・パック10進数もそうですが、文字が16進数でどのように表現されているのかを意識しないとコーディングに支障をきたすこともあります。
新人の時は16進数をあまり意識しておらず、エラーが出てもその理由がわからなかったりして色々苦労しました…。

今後も、文字の16進数表現でお伝えできることがあれば、記事にしていきたいと思います!

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の知識が必要になることがあります。

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