コアJava 6日目

スタティックフィールドとスタティックメソッド

クラスフィールド(スタティックフィールド)

staticとしてフィールドを定義する場合、そのフィールドはクラスに1つだけ存在することになる。

class Employee
{
   ...
   private int id;                      // インスタンスフィールド
  private static int nextId = 1; // クラスフィールド
} 

すべての従業員オブジェクトは独自のidフィールドを持つが、nextIDフィールドは1つだけで、クラスのすべてのインスタンス間で共有される。Employeeクラスのオブジェクトが1000個あれば、各オブジェクトに1つずつ1000個のidフィールドがあるが、nextIdは1つだけ。スタティックフィールドはクラスに属しており、個々のオブジェクトには属さない。

スタティック定数
public class Math
{
  ...
  public static final double PI = 3.14150265...
  ...
}

プログラムの中ではこの定数に Math.PI としてアクセスできる。
static を省略すると、PIはMathクラスのインスタンスフィールドになる。つまり、PIにアクセスするには、Mathクラスのオブジェク必要になる。

クラスに属しているってそういうことね!

スタティックメソッド

オブジェクトを使用しないメソッド。たとえば、Mathクラスのpow()メソッド。

Math.pow(x、y)

計算にはMathオブジェクトは使用されない。つまり、暗黙的な引数は受け取らない。したがって、スタティックメソッドはthisの引数を持たないメソッドとみなすことができる。
スタティックメソッドはオブジェクトに対して動作するわけではないので、インスタンスフィールドにアクセスできない。しかし、スタティックメソッドはそのクラスのスタティックフィールドにはアクセスできる。

public static int getNextId()
{
  return nextId; // スタティックフィールドを返す
}

このメソッドを呼び出すには、クラス名は指定する。

int n = Employee.getNextId);

こんなときにスタティックメソッドを使用します
  1. 必要な引数がすべて明示的な引数として指定されるので、メソッドがオブジェクトの状態にアクセスする必要がない(Math.pow()など)
  2. メソッドが単にクラスのスタティックフィールドにアクセスするだけ(Employee.getNextId()など)

staticに関してはとりあえず、クラス名.メソッドでアクセス出来るようになるってイメージをもっとこう。
もっと細かい話は置いておいて。

ファクトリメソッド

NumberFormat.getNumberInstance()
NumberFormat.getCurrencyInstance()

これらのメソッドはそれぞれNumberFormat型のオブジェクトを返す。その例。

NumberFormat formatter = NumberFormat.getCurrencyInstance();
System.out.println(formatter.format(salary)); // 通貨記号を付けて給与を出力する

クラス名.メソッドから、スタティックメソッドとわかる。しかし、これらのメソッドの目的は同じクラスのオブジェクトを生成することである。このようなメソッドをファクトリメソッドと呼ぶ。

なぜ、コンストラクタを使用しないのか
  1. コンストラクタには名前を付けることができない。名前はクラス名と同じ。ここでは、取得する数値オブジェクトと通貨フォーマットオブジェクトに対して、2つの異なる名前を付けることを意味する。
  2. ファクトリメソッドはNumberFormat型のオブジェクト、またはNuberFormatoを継承するサブクラスのオブジェクトを返すことがきでる。
main()メソッド

クラスはすべて、main()メソッドを持つことができる。これは、クラス単位でテストを行う場合に便利。

メソッドの引数
  • 値渡し

呼び出し側が提供する値だけをメソッドが受け取ることを意味する。

  • 参照渡し

呼び出し側が提供する変数の場所を受け取ることを意味する。

メソッドは、参照渡しの変数に格納される値を変更できるが、値渡しの変数は変更できない。Javaでは、メソッドは常に値渡しである。つまり、メソッドはすべての引数の値のコピーを取得する。特に、メソッドは渡された引数の変数の内容をまったく変更できない。

具体的にみてみる
// 動作しない
public static void tripleValue(double x)
{
   x = 3 * x;
}

このメソッドを呼び出してみる。

double percent = 10;
tripleValue(percent);

これは動作しない。メソッドを呼び出した後も、percentの値は10のまま。
メソッドは、基本型の引数を変更できないが、オブジェクト型の引数の場合にはメソッドを簡単に実装できる。

// 従業員のメソッドを3倍にするメソッド
public static void triplesSalary(Employee x) 
{
  x.raiseSalary(200);
}

呼び出す。

harry = new Employee(...);
tripleSalary(harry);

こんな処理が行われます
  1. xはharryの値のコピー(つまりオブジェクトの参照)で初期化される
  2. raiseSalary()メソッドをそのオブジェクトの参照に適用する。xとharryの両方が参照するEmployeeオブジェクトの給与は、3倍に上げられる。
  3. メソッドが終了すると、引数の変数xは使用できない。もちろん、オブジェクト変数のharryは、3倍になったオブジェクトを引き続き参照する。
オブジェクト型の引数の状態を変更するメソッドを実装する理由

簡単。メソッドはオブジェクトの参照のコピーを取得し、元の参照とコピーの参照はどちらも同じオブジェクトを参照するからである。

Javaでメソッドの引数を使用して、実行可能な処理と不可能な処理
  • メソッドは基本型の引数(つまり数値とブール値)を変更できない
  • メソッドはオブジェクト型の引数の状態を変更できる
  • メソッドはオブジェクト型の引数に新しいオブジェクトを参照させることはできない。
プログラムで実際にみてみる
public class ParamTest
{
	public static void main(String[] args)
	{
	/*
	 * テスト1:メソッドは引数の値を変更できない
	 */
	System.out.println("Testing tripleValu:");
	double percent = 10;
	System.out.println("Before: percent =" + percent);
	tripleValue(percent);
	System.out.println("After: percent=" + percent);
	
	/*
	 * テスト2:メソッドは引数のオブジェクトの状態を変更できる
	 */
	System.out.println("\nTesting tripleSalary;");
	Employee harry = new Employee("Harry", 50000);
	System.out.println("Before: salary=" + harry.getSalary());
	tripleSalary(harry);
	System.out.println("After: salary=" + harry.getSalary());
	
	/*
	 * テスト3:メソッドは新しいオブジェクトをオブジェクト型の引数に参照させることはできない
	 */
	System.out.println("\nTesting swap:");
	Employee a = new Employee("Alice", 70000);
	Employee b = new Employee("Bob", 60000);
	System.out.println("Before: a=" + a.getName());
	System.out.println("Before: b=" + b.getName());
	swap(a,b);
	System.out.println("After: a=" + a.getName());
	System.out.println("Agter: b=" + b.getName());
	}
	
	public static void tripleValue(double x)
	{
		x = 3 * x;
		System.out.println("End of method: x = " + x);
	}
	
	public static void tripleSalary(Employee x)
	{
		x.raiseSalary(200);
		System.out.println("End of method: salary=" + x.getSalary());
	}
	
	public static void swap(Employee x, Employee y) 
	{
		Employee temp = x;
		x = y;
		temp = y;
		System.out.println("End of method: x=" + x.getName());
		System.out.println("End of method: y=" + y.getName());
	}
}

class Employee
{
	public Employee(String n, double s)
	{
		name = n;
		salary = s;
	}
	
	public String getName()
	{
		return name;
	}
	
	public double getSalary()
	{
		return salary;
	}
	
	public void raiseSalary(double byPercent)
	{
		double raise = salary * byPercent / 100;
		salary += raise;
	}
	
	private String name;
	private double salary;
}

結果

Testing tripleValu:
Before: percent =10.0
End of method: x = 30.0
After: percent=10.0

Testing tripleSalary;
Before: salary=50000.0
End of method: salary=150000.0
After: salary=150000.0

Testing swap:
Before: a=Alice
Before: b=Bob
End of method: x=Bob
End of method: y=Bob
After: a=Alice
Agter: b=Bob

参照渡しはできません。