Java – staticブロック

staticブロックは、クラスが呼ばれるタイミング、コードが実行される前に実行される。インスタンス生成時に実行される初期化ブロックよりも早いタイミングで、クラススコープの変数の初期化などに用いることができる。

上記のコードの実行結果は以下のとおり。

実行時の流れは以下のとおり。

  1. MainClassが読み込まれる
    MainClassstaticブロック実行
  2. MainClassmain()メソッド(static)実行
  3. main()メソッド内で
    1. MainClassと同じソース内のClass1のインスタンス生成
      Class1staticブロック実行
    2. 別のソースのOuterClassのインスタンス生成
      OuterClassstaticブロック実行
    3. MainClassのインスタンス生成
    4. MainClassのインスタンスのmethod()メソッド(public)実行
    5. method()メソッド内で
      1. MainClassと同じソース内のClass2のインスタンス生成
        Class2staticブロック実行

MainClassの動作を見ると、staticブロックが実行されるのはインスタンス生成時ではなく、クラスが読み込まれるタイミングとなっている。

その他のクラスはインスタンス生成時にクラスが呼ばれ、staticブロックが実行されている(宣言のみではstaticブロックは実行されない)。

インナークラスではstaticブロックは書けない(コンパイルエラー)。

 

Java – assert

概要

  • assert文にはコードを書く際に想定している前提条件を指定する
  • コード実行時、VMの引数に-enableassertionsを指定する(指定しないと無視される)
  • 前提条件に合わない場合、AssertionErrorが発生する
  • エラー時のメッセージを追加することができる
  • privateメソッドで想定している引数チェックなど、開発時のデバッグに用いる
  • publicメソッドの引数チェックのような、運用時の動作保証に対しては用いるべきではない

基本形

assertの後に想定条件を書く。実行時引数に-enableassertionsが設定されていて想定条件がfalseの場合はAsertionErrorが発生する。

assert 想定条件;

このコードのquotient()メソッドは引数dividendがゼロでないことを想定しているが、これを呼び出すメソッド側でゼロを指定している。

まずこのコードを実行時引数になにも指定しないで通常通り実行すると、以下の様に表示される。

次にこのコードの実行時引数に-enableassertionを指定して実行すると、以下のAssertionErrorが投げられる。

メッセージ指定形式

以下の形式で、assertが実行されたときのAssertionErrorにメッセージが付加される。

assert 想定条件 : メッセージ;

たとえば先のコードのassert文を以下の様に変更する。

そして実行時引数に-enableassertionsを指定して実行すると、エラー表示が以下の様になる。

実行時引数の指定

Eclipseで実行する場合は、実行→実行構成のダイアログ→引数タブ→VM引数に-enableassertionsを指定する。

コマンドラインから実行する場合は、コンパイル後のクラスファイルを実行する際に-enableassertionsを指定する。

活用シーン

assertは分散開発時に担当している処理の前提条件を明確化(通常はコメントにより明示するなど)。

デバッグ時に-enableassertionsを指定してエラー補足・対応、運用時にはオプションを外して実行。

publicメソッドの引数チェックなど運用時の仕様に関する条件は、assertではなく条件分岐や例外処理を用いるべき。

 

Java – 文字列リテラル

基本形

文字列を扱うStringjava.lang.Objectを継承したクラスだが、リテラルの書き方が用意されている。

文字列リテラルは文字列をダブルクォート(")で囲む。

文字列は1文字でもよく、1文字も含まない空文字列(長さゼロで内容がない文字列)でもよい。

エスケープ

文字列リテラル中でバックスラッシュ(\、¥記号)は特別な意味を持ち、その直後の文字でエスケープ文字の種類があらわされる。

たとえば\tはタブ、\nは改行。

\uhhhhはユニコードエスケープ。

シングルクォート(')は文字列リテラル中でそのまま使えるが、ダブルクォート(")はエスケープする必要がある。

エスケープ記号自体を使いたいときは\でエスケープする(2つ並べる)。

変数展開・値の埋め込み

Javaには多言語のように文字列中で変数展開する機能は備えられていない。

変数の値を文字列に入れるには、文字列結合の演算子(+)を使うか、String.format()メソッドを使う。

 

Java – ミュータブル/イミュータブル

概要

“immutable”の説明として、「オブジェクト生成後にその内容を変えることができないオブジェクトを”イミュータブルである”」というらしい。いま一つわかり辛かったので、以下の様に考えてみた。

オブジェクトへの参照値が変わらないのにその内容が変わり得るのがミュータブル、オブジェクトの参照値が変わらなければその内容が変わり得ないのがイミュータブル、というのはどうだろうかと考えた。

これをJavaのint型、配列、String型で確認してみる。

プリミティブ型はイミュータブル

以下のコードで考える。

  • i2i1をそのまま代入しているだけなので、==比較はtrue
    • そのi2インクリメントしてもi1は影響を受けず、i1i2==比較はfalseになる
  • i3i1と同じ値をリテラルで設定していて、i1との==比較はtrue
  • i4i1と異なる値を代入していて、i1との==比較はfalse
  • i5i4と同じ値を計算した上で代入しており、i4 == i5

int型などのプリミティブ型は、変数ごとにスタック上に内容が保存されるため、オブジェクトのような参照をしない。その挙動から「プリミティブ型はイミュータブルである」ともいえる。

これをラッパークラスのIntegerで試しても同じ結果になる。

配列はミュータブル

配列はミュータブルなオブジェクトで、参照している内容を直接変更することができる。

以下の様に、2つの変数が同じインスタンスを参照している場合、一方でその内容を変更すると他方でもそれが反映される。

ただし同じ内容でもリテラルで初期化した場合は別のインスタンスとして保持され、一方への変更が他方へは影響しない。

Stringはイミュータブル

String型はイミュータブルなオブジェクトで、詳細はこちらにまとめた

String型の文字列インスタンスはヒープ上に保存され参照される。リテラルの内容が同じ場合は1つのインスタンスを共有してこれを参照する。

参照されている実体の内容が直接操作され変更されることはなく、変数同士で参照値を代入した場合に一方への変更が他方に影響することはない。

 

 

Java – String – イミュータブル

概要

JavaのStringの挙動について整理してみた。

同一性と同値性

String型のオブジェクトの同一性と同値性(等価性)について、以下のコードで確認した。

  • s2s1の参照値を代入しているので、s1s2は同一であり同値
  • その後にs2の内容を変更すると、s1とは内容も参照値も異なるようになる
  • s3には別のリテラルでs1と同じ内容を代入しているが、s1s3は同一であり同値

すなわち同じ内容のStringリテラルを用いると、それらリテラルは1つのインスタンスとして共有される。

同値なオブジェクトと同一性

以下のコードで、同値なオブジェクトが常に同一になるか(共有されるか)確認した。

  • s5は2つのStringリテラルからs4と同じ値のリテラルを作り出していて、s4s5は同一であり同値
  • s6は内容が"ABC"の変数s1Stringリテラルからs4と同じ値のリテラルを作り出しているが、s4s6は同値ではあるが同一ではない

すなわち以下の様に整理される。

  • リテラルから生成された内容が同じ場合は1つのインスタンスとして共有される
  • 変数を含めた操作で生成されたインスタンスは、同じ内容のものがあっても別のインスタンスとして扱われる

Stringのイミュータビリティー

以下の様にStringはイミュータブルと言える。

  • 異なる内容のStringリテラルは異なるインスタンスとして生成され、参照される
  • Stringクラスで変更を伴うメソッドはStringを戻り値としており、インスタンスの内容を直接は変更できない
  • Stringクラスはfinal宣言されており継承はできない(クラスを継承して自身を変更することはない)

なおStringに関しては、同値であっても同一でない(インスタンスが異なる)場合がある。

  • リテラル同士の結合結果が他のリテラルに等しい場合、その内容は1つのインスタンスとして共有される(同一であり同値)
  • String変数を含む結合結果が他のリテラルに等しい場合でも、それらは別のインスタンスとして扱われる(同値だが同一ではない)

リテラルと変数での挙動の違いは、コンパイルレベルで値を同じかどうかを判断しているためか。

これらの場合があるとしても、一度設定されたオブジェクトの内容を変更することはできないため、String型はイミュータブルであると言える。

 

MySQL – リレーションを持つCSVのインポート

概要

参照関係を持つテーブルにCSVデータをインポートすることを考える。

  • 親テーブルの主キーにAUTO_INCREMENTが設定されていて、子テーブルがそのキーを参照するリレーションを対象とする
  • データの追加ではなく、空のテーブルに新たにデータを登録するケースを対象とする

テーブル構成

以下のようなテーブル構造とする。

  • branchesテーブルは支社に関する情報を記録する
    • AUTO_INCREMENTの支社ID(プライマリーキー)
    • 支社名
  • warehousesテーブルは支社に属する倉庫に関する情報を記録する
    • AUTO_INCREMENTの倉庫ID(プライマリーキー)
    • 倉庫が属する支社を参照する支社ID(外部キー)
    • 倉庫名
  • customersテーブルは倉庫から出荷される先の顧客に関する情報を記録する
    • AUTO_INCREMENTの顧客ID(プライマリーキー)
    • 顧客を担当する倉庫のID(外部キー)
    • 顧客名

ExcelでCSVファイルの作成

元データの入力

以下の3つのテーブルデータを別のシートに入力する。

<branchesテーブル>

id name
1 関東支社
2 関西支社

<warehousesテーブル>

id branch_id name
1 1 千葉倉庫
2 1 八王子倉庫
3 2 東大阪倉庫
4 2 神戸倉庫

<customersテーブル>

id warehouse_id customer
1 1 山田商会
2 1 田中屋
3 2 中川商店
4 2 川井製作所
5 3 井戸商店
6 3 戸田商会
7 4 田村屋
8 4 村山製作所

CSVファイルの保存

3つのシートそれぞれをCSVファイルで保存する。

  • 名前を付けて保存
  • ファイルの種類は”CSV UTF-8 (コンマ区切り)”
  • シートごとに別々のCSVファイルに保存

BOMなしへの変換

ExcelのUTF-8はBOM付きで保存されるので、これをBOMなしのUTF-8に変換する。

たとえば、Windowsのメモ帳で読み込んでUTF-8(BOM付きではない方)で保存し直すなど。

ファイルの提供

外部サーバーの場合、FTPで所定の場所にCSVファイルをアップロード。

Vagrant仮想環境の場合、保存されたCSVファイルを共有ディレクトリーにコピーまたは移動。

MySQLでの操作

ローカルファイルを許可してログイン

MySQLにログインするのに、--enable-local-infileを指定してローカルファイルからの入力を許可。

MySQL内でローカルファイルの入力許可

@@local_infile変数の内容をチェックする。0の場合はローカルファイル入力が許可されていないので、SET GLOBALで許可する。

テーブルの準備

CREATE TABLEでCSVデータを読み込むテーブルを準備する。

作成したテーブルの構造。

CSVファイルの読み込み

アップロードまたは共有されたCSVファイルをLOAD DATA LOCAL INFILEクエリーで読み込む。

  • カンマ区切り→FILES TERMINATED BY ','
  • 改行コード→LINES TERMINATED BY '\r\n'
  • 1行目が見出し行の場合→IGNORE 1 LINES

読み込み結果の確認

各テーブルデータの確認

参照関係の確認

外部キーによる参照関係の確認。各外部キーと親テーブルのキーでテーブルを結合。

実行結果。

新規追加の確認

たとえば新たな支社を1つ追加し、AUTO_INCREMENTが機能していることを確認。

外部結合してみる。

 

Vagrant – PHPインストール – FreeBSD

さくらのレンタルサーバーに合わせた仮想環境にPHPをインストール。bootstrap済みのpkgで簡単にインストールできた。

 

Vagrant – MySQLインストール – FreeBSD

pkgによるインストール

FreeBSDのバージョンが古いと言われたが、ミスマッチを無視することができるようなので無視して続行。

インストール直後の主要ファイルの存在状況は以下の通り。

/usr/local/etc/mysql/my.cnf → 存在
$HOME/.mysql_secret → 存在しない
/root/.mysql_secret → 存在しない
/usr/local/libdata/ldconfig/mysql57-server → 存在
/usr/local/share/mysql/mysql.server → 存在

 

MySQLサーバー起動

/etc/rc.confに以下の1行を追加してvagrant reload

サーバーの実行状況を確認。

サーバーを再起動する場合。

サーバーを起動すると.mysql_secretファイルが作成される。

初回ログインとパスワード変更

初期パスワードの確認

rootユーザーの初期パスワードは/root/.mysql_secretファイルに書かれている。

初回ログイン

パスワードを変更

SET PASSWORDクエリーでパスワードを変更。

日本語の設定

さくらサーバーでの日本語の扱い

インストールしたままの状態だと、ロケールやエンコーディングの設定が日本語の扱いに適していないので変更する必要がある。さくらサーバーのMySQLでの扱いは以下のようになっている。

OSのロケール

ロケールは日本(ja_JP)で文字コードはUTF-8で設定されている。

MySQLの文字コード

systemがutf8、databaseとserverがutf8mb4で設定されている。

MySQLの照合順序

databaseとserverの照合順序はutf8mb4_general_ciに設定されている。

Vagrantの設定

さくらサーバー上のMySQLの設定に合わせる。ロケール、文字コード、照合順序の詳細については以下を参照。

ロケール設定

/etc/profileに以下2行を追加してvagrantをリロード。

※’=’の前後にはスペースを入れない。

<変更後のロケール>

文字コードと照合順序の設定

/usr/local/etc/mysql/my.cnf[mysql]セクションと[mysqld]セクションに各々以下を追加してMySQLをリロード(sudo /usr/local/etc/rc.d/mysql-server restart)。

<変更後の文字コード>

<変更後の照合順序>

 

Vagrant – 環境構築 – さくらサーバー/FreeBSD

概要

さくらレンタルサーバーの環境をローカルのVagrantで構築。使用はできるだけ本家に近づけるようにした(2021年6月時点)。

ホストOS
Windows10
ゲストOS/Box
FreeBSD 11.2-RELEASE-p10 (GENERIC)
bento/freebsd-11.2
MySQL
mysql Ver 14.14 Distrib 5.7.34, for FreeBSD11.4 (amd64) using EditLine wrapper
PHP
PHP 7.4.20 (cli) (built: Jun 8 2021 01:11:43) ( NTS )

特にMySQLのインストールで行きつ戻りつして数日かかった。

FreeBSD仮想環境の構築

Boxのダウンロード

bento/freebsd-11.2のボックスを追加。

仮想環境のディレクトリー準備

Windowsで既作成の\vagrantディレクトリー下にsakuraディレクトリーを作成して移動。

イニシャライズ

仮想環境ディレクトリーを初期化。FreeBSDの仮想環境が構築され、vagrantfileが作成される。

仮想環境の起動

仮想サーバーの起動

vagrant upコマンドでFreeBSDの仮想サーバーを起動する。

仮想サーバーへの接続

vagrant sshで仮想サーバーにログインする。

タイムゾーン設定

初期状態はUTC。

タイムゾーンはtzsetupにタイムゾーンのファイルを指定して設定する。

まずtzsetupの場所を確認。

次にタイムゾーンファイルの場所を確認。

tzsetupTokyoを与えて実行。タイムゾーンファイルを使うかと尋ねられるのでYes。

タイムゾーン設定後

ファイル共有設定

Vagrantfile

以下の行のコメントを外して修正。

1つ目の引数

  • ホスト側の共有ディレクトリーのパスで、仮想環境を起動したディレクトリーからの相対パス
  • この設定ではC:\vagrant\sakura\shareがホスト側の共有ディレクトリーになる
  • このディレクトリーはホスト側で作成する必要がある

2つ目の引数

  • ゲスト側の共有ディレクトリーの絶対パス
  • この場合は~/shareがゲスト側の共有ディレクトリーになる
  • このディレクトリーは自動作成される

ホスト側の共有ディレクトリー作成

ホスト側のWindowsで共有ディレクトリーを作成

Vagrant起動/再起動

vagrant up/reloadでvagrantfileの内容を仮想マシンに反映させる。その結果、vagrantfileで指定したパスで仮想マシンにshareディレクトリーが作成される。

pkgのブートストラップ

MySQLやPHPなどをインストールするのにpkgによるバイナリーパッケージ管理を利用する。このため、pkgをブートストラップする。

 

 

Java – 演算子 – 文字列結合

文字列同士の結合

+演算子の左右のオペランドが文字列の時、演算子は文字列結合として機能する。

文字列以外との結合

数値との結合

左右のオペランドの何れか1つが文字列の時も文字列結合として機能し、他方のオペランドは文字列に変換された後に結合される。

以下は文字列と数値を+演算子で結んだ場合で、数値が文字列に変換されて結合される。左右とも数値の場合は、当然数値の加算演算子として機能する。

char型との結合

char型の場合も文字列として結合される。左右ともchar型の場合はchar同士の値の加算(以下の例では’a’ + ‘b’ = $61 + $62 = $C3 = 195)。

一般的のオブジェクトとの結合

+演算子のオペランドの1つが文字列で他方がそうでないとき、演算子は文字列でない方のオペランドの文字列表現を結合する。

たとえば配列同士の+演算子は定義されていないが、文字列と配列を+演算子で結ぶと配列の方が文字列表現(ハッシュ)になり、もう1法の文字列と結合される。

以下の例はtoString()が実装されたクラスのオブジェクトと文字列の結合例で、Listオブジェクトが文字列化された後に結合されている。