Ch5. クラス定義とオブジェクト

インスタンス変数をセットするときの注意点

メソッドの引数として、インスタンス変数と同じ名前を設定してしまうと、メソッドの中ではその引数の変数名のスコープが適用されるので、インスタンス変数としては代入されない。 これは警告表示がされても、コンパイルエラーにはならない。

JavaScriptでと同様thisを使ってインスタンス変数であるかローカル変数であるか明示できるので、これを利用する方が確実に思える。

  int  params;
  public void setVariable(int params){
    params = params; //警告: The assignment to variable params has no effect
  }

  public void setVariable2(int givenParams){
    params = givenParams;
  }

  public void setVariable3(int params){
    this.params = params;
  }

ここで見たthisの使い方の他に、thisにはコンストラクタで利用される方法がある。 オーバーロードした2つのコンストラクタからもう片方のコンストラクタを呼び出すとき、this();を使う。 this();は全ての処理の前に置かなくてはならず(つまり初期化の前に何かを実施することはできない)、他のコンストラクタの呼び出しの前に何かを行おうとするとエラーになる。

java - コンストラクタの this() super()はなぜ先頭にしか記述出来ないか - スタック・オーバーフロー (特に特別な理由があるわけではなさそう。)

class newClass{
  newClass(){
    System.out.println("newClass");
  }

  newClass(String s){
    this();
    System.out.println("newClass from different constractor");
    //this(); //'Constructor call must be the first statement in a constructor'
  }
}

コンストラクタのルール

コンストラクタに戻り値は設定できない。 また戻り値としてvoidを設定することもできない。(voidを設定した時点でそのメソッドはコンストラクタではなくなる。) voidを設定してもコンパイルエラーにはならず、クラス名と同じ通常のインスタンスメソッドが出来るだけである点には注意。

java "void" and "non void" constructor - Stack Overflow

コンストラクタを明示的に定義しない場合、空の処理が挿入されたデフォルトコンストラクが設定される。 これはコンパイル時に自動で追加されるもので、実行時にJVMが何かしている訳でもない。

また、public static void main()にも引数を設定することは出来なかった。 実行時に戻り値をIntegerで設定したりするとバッチ処理などで実行の成功・不成功がわかりやすいと思うが、残念ながらそのような機能は無かった。

public class Ch5_1 {
    public static int main(String args[]){ return 1; } //実行は可能。ただしエントリーポイントとしてみなされない。
}

これをやると、実行が出来なくなる。(エントリーポイントとしてみなされないだけで、コンパイルには成功した。)

リターンコードを返すには代わりにSystem.exitメソッドが存在する。

System (Java Platform SE 8)

コンストラクタをオーバーロードして宣言する場合、複数のコンストラクタに共通させたい処理が発生することがある。 これは【初期化ブロック】という機能で実現できる。 後述するstaticイニシャライザインスタンスメソッド版 なのでセットで覚えておく。

class newClass{
  newClass(){
    System.out.println("newClass");
  }

  {
    System.out.println("初期化ブロックです。");
  }

  newClass(String s){
    this();
    System.out.println("newClass from different constractor");
    //this(); //'Constructor call must be the first statement in a constructor'
  }
}

オーバーロード

(変数名・引数の数・引数の順序・引数の型が同じで) 戻り値のみが異なるオーバーロードはできない。 このような、オーバーロードを宣言しようとするとコンパイルエラーとなる。

static変数とstaticメソッド

JavaRubyと同じくクラス変数はオブジェクト間で共有されるので、下記のようなコードで[クラスが何回インスタンス化されたか]を確認することが出来る。

public class Ch5_1 {

    public static void main(String args[]){
    TestClass t1 = new TestClass();
    TestClass t2 = new TestClass();
    TestClass t3 = new TestClass();
    TestClass t4 = new TestClass();

    System.out.println(t1.howManyTimes()); //インスタンスからも呼び出せる。(警告: The static method howManyTimes() from the type TestClass should be accessed in a static way)
    System.out.println(TestClass.howManyTimes()); //'4'

  } 
}

class TestClass {
  static int initializedTimes = 0;

  TestClass(){
    initializedTimes++;
  }
  
}

上記の例でクラスメソッドをインスタンス名を付けて呼び出すことは出来たが、逆は出来ない。 つまり、 クラス名.インスタンスメソッド という呼び出し方は出来ない。

さらにstaticイニシャライザというメソッドも存在する。 Classファイルがロードされたときに実行できるメソッドで、public static void main()よりも早く実行することが出来る。 また、staticイニシャライザはstatic変数にもアクセスすることが出来る。

  public class Ch5_1 {
    static {
      System.out.println("[1]static method.");
    }

    public static void main(String args[]){
      System.out.println("[2]main methid: Entry Point!!!");
   }
}

class TestClass {
  static int initializedTimes = 0;

  static{
    System.out.println("[3]This is Static Initializer. This is called when this file is loaded.");
    System.out.println(initializedTimes);//'0'
  }
}

//出力
//[1]static method.
//[2]main methid: Entry Point!!!
//[3]This is Static Initializer. This is called when this file is loaded.
//0

カプセル化レベル(アクセス修飾子)

メソッドのカプセル化

Javaのアクセス修飾子は4種類存在する。 public private protected そしてデフォルトがある。

  • デフォルトは【同一パッケージ内の同・別クラスから】のみ
  • protectedは継承先と【同一パッケージ内の同・別クラスから】+【別パッケージの継承先子クラスから】のみ

当然ではあるが、ローカル変数にはアクセス修飾子を付けることが出来ない。(コンパイルエラーが発生する箇所を問われる問題などで引っ掛かる) ローカル変数に着けられる修飾子はfinalだけである。

  public void method3(String parameter){
    public int a = 100; //コンパイルエラー
    final  int b = 200;
    parameter = parameter + " !!!";
  }

利用可能である。 Rubyではprotectedがほぼ使われないので、気づかなかったがアクセスレベルとしてはprotected>defalutである。 protectedの方が、パッケージを跨いだアクセスが出来る分よりアクセス範囲が広くなる。

コンストラクタのカプセル化

コンストラクタはprivateにすることもデフォルトにすることもできる。 コンストラクタをprivateにするタイミングはすぐには思いつかないが、実はこのような例がある。

Java コンストラクタをprivateにするとき | エンジニアすみきちのブログ

クラスのカプセル化

クラスはprotectedprivateにすることが出来ない。 (そもそも、継承関係やカプセル化の話をしているので意味をなさない。)

  • クラスにpublicを付けない場合、同一パッケージ内からしかアクセスできなくなる。
  • また、publicに出来るメソッドは一つのクラスファイルつき一つと定められている。
  • 更に、publicを指定したクラス名はパッケージ名と同じにすることが定められている。

ガベージコレクション

明示的にオブジェクトをガベージコレクションの対象にするには以下の2つの方法がある。

  1. 変数にnullを代入する
  2. どの変数からも参照されていないオブジェクトにする。

また、GCによってオブジェクトが破棄される直前に実施したい処理は全てfinalize()というメソッドに書くことが出来る。 これは本来Objectクラスで定義されているメソッドで、Objectクラスでprotectedに指定されているため、protected以下のカプセル化制限を課すことが出来ない。

  • 継承元でprotectedが指定されていた場合[Cannot reduce the visibility of the inherited method from Object] となり、private・デフォルトのアクセス修飾子にすることが出来ない。

パッケージ

パッケージに入っていないクラスは無名パッケージに所属しているものとして扱われる。 また、階層構造をもつパッケージのクラスは、その階層構造を持つディレクトリ構造の中にクラスファイルが存在している必要がある。

とあるパッケージに所属している全てのクラスをimportしたいとき、アスタリスク*が利用できる。 パッケージが所属する階層に対して*を利用して、全てのパッケージを利用することはできない。

参照型の引数とプリミティブ型の引数

メソッドを呼び出し、引数を設定する場合は呼び出し側から渡す引数が参照型プリミティブ型・ラッパークラスかを確認する必要がある。 プリミティブ型であれば、呼び出し先に渡されるのは【値のコピー】に過ぎないことを確認する。

参照型はString Array Classしか存在しない。 Integerとかはラッパークラスではあれど、参照型ではない。

Stringもimmutableなオブジェクトなので、たとえ参照値を渡されて呼び出し先で文字列加工が発生しても呼び出し元には影響を及ぼさない。 配列の場合だけ気を付ければよさそうだ。

class newClass{
  public void method1(int parameter){
    parameter += 100;
  }
  public void method2(Integer[] parameter){
    parameter[0] += 100;
  }

  public void method3(String parameter){
    parameter = parameter + " !!!";
  }
}

public class Ch5_2 {
  public static void main(String[] args){
    int primitive = 66;
    Integer[] arrayOfInt = {66};
    Integer instanceOfInt = 66;
    String  instanceOfStr = "HelloWorld";

    newClass n = new newClass();
    n.method1(primitive);
    System.out.println(primitive);//'66'

    n.method1(instanceOfInt);
    System.out.println(instanceOfInt);//'66'

    n.method2(arrayOfInt);
    System.out.println(arrayOfInt[0]);//'166'

    n.method3(instanceOfStr);
    System.out.println(instanceOfStr);//'HelloWorld' Stringはimmutable!
  }
  
}

戻り値とreturn

戻り値がvoidのメソッドに対してnullが返却されることを期待して変数による代入を設定することはできない。

また、returnは戻り値voidのメソッドに対しても設定できる。 returnには戻り値を返却する意味だけでなく、【制御を呼び出し元に戻す】という意味もある。

ただし、returnの後ろに絶対に実行されない到達不可能コードが存在すると、コンパイルエラーが発生する。

可変長引数

スクリプト言語お家芸だと思っていた可変長引数もJava SE5から利用可能になったらしい。

Rubyと違い可変長とする引数は必ず前に置かなくてはならない。 また静的型付け言語なので当然だが、可変長とする引数は1つの型の配列として処理される。

利用方法は以下の通り。