Java – キーボード入力

InputStream

標準入力(System.in)を引数に渡して入力ストリームを生成する。

実行するとプロンプト(>)が表示されて入力待ちになり、文字列を入力してEnterを押すとその内容がオウム返しに表示される。

Scanner

java.util.Scannerの引数に標準入力(System.in)を渡してインスタンスを生成し、next()などのメソッドでキーボードからの入力を得る。

next()

以下のコードは、文字列”end”が入力されるまで、入力された文字列をオウム返しに表示し続ける。

実行例。

nextInt()

実行例。

全角数字も扱える。

3桁ごとのカンマ区切りは通る。

仕様ではInteger正規表現にマッチし、かつ範囲内である必要がある。数値と認識されない文字列に対しては、例外が投げられる。

以下は数値として扱えない。

  • 3桁区切りでないカンマ区切り
  • 全角の漢数字

closeするとSystem.inが使えなくなる

先のコードで、new Scanner(System.in)でインスタンスを生成している行に警告が出る(リソース・リーク: 'scanner' が閉じられることはありません)。

そこで、Scannerインスタンス使用後にいったんclose()してみると、再度インスタンスを生成しようとしたときに例外が投げられる。scannerをクローズしたときに標準入力System.inが閉じられてしまうため。

実行結果。

 

Java – ブロックスコープ

ブロック内でのみ有効

ブロック内で宣言された変数はそのブロック内でのみ有効で、ブロックを出るときに破棄される。

ブロック内で宣言された変数は、ブロック外では存在していない。

ブロック外で宣言可能

ブロック内スコープの変数はブロック外では存在しないので、ブロック外で同じ名前で宣言できる。

ブロック外宣言はブロック内で有効

ブロックの前で宣言された変数は、その後のブロック内で使用可能。以下の例では、ブロックの前で宣言された変数sがその後のブロックの中で書き換えられている。

したがって、ブロックの前で宣言された変数をブロック内ローカル変数として定義することはできない。定義しようとすると重複定義エラーになる。

 

Java – 初期化ブロック

staticブロックはクラスの読み込み時に実行されるが、初期化ブロックはクラスのインスタンス生成時に実行される。

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

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

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

 

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型はイミュータブルであると言える。

 

Java – 演算子 – 文字列結合

文字列同士の結合

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

文字列以外との結合

数値との結合

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

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

char型との結合

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

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

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

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

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

 

 

Java – 演算子 – 代入演算子

数値に対する代入演算子

a = b bの値をaに代入する
a += b a = a + bと等価
a -= b a = a ― bと等価
a *= b a = a * bと等価
a /= b a = a / bと等価
a %= b a = a % bと等価

+=など演算と代入を同時に行う演算子を複合代入演算子(compound assignment operator)と呼ぶ。複合代入演算子と対比する場合は、=を単純代入演算子(simple assignment operator)と呼ぶ。

代入演算子の左辺に置けるのは変数のみで、リテラルや式は置けない。

宣言文で使えるのは=のみで、+=などの複合代入演算子は使えない。

文字列に対する代入演算子

a = b aの内容をbに代入する
a += b aの文字列とbの文字列を結合した結果をaに代入する

左辺が変数でなければならない点、宣言文で+=が使えない点は数値に対する場合と同じ。

なお、左辺はString型の変数でなければならないが、右辺はString型のほかchar型でもよい。