Top > MyPage
 

酔っぱらいながらのお勉強

まあ、ノンビリ勉強でもしようか。

Java魂

final story

定数はいつ決まる?

Javaの定数を表しているようなfinalについて今日は復習しようと思います。

このfinalというキーワードは単に定数とは、少々違いそうです。ぶっちゃけ、どんなスコープで finalなのか、或いはfinalは変数のみにつけるわけじゃないので、少々話しが難しい。次のソースを見てください。

package oreilly.hcj.finalstory;

import java.awt.Color;

public class FinalReplacement {
	/** A string constant */
	public static final String A_STRING = "Java Hardcore";

	/** An int constant. */
	public static final int AN_INT = 5;

	/** A double constant. */
	public static final double A_DOUBLE = 102.55d;

	/** An array constant. */
	public static final int[] AN_ARRAY = new int[] { 1, 2, 3, 6, 9, 18, 36 };

	/** A color constant. */
	public static final Color A_COLOR = new Color(45, 0, 155);

	/** 
	 * A demonstration method.
	 */
	public void someMethod() {
		System.out.println(A_STRING);
		System.out.println(AN_INT);
		System.out.println(A_DOUBLE);
		System.out.println(AN_ARRAY);
		System.out.println(A_COLOR);
	}
}

                    

まずは、上記のソースで5個の定数を定義していますが、これをコンパイルすると、実際には以下のように置き換えられます。

	public void someMethod() {
		System.out.println("Hardcore Java");
		System.out.println(5);
		System.out.println(102.55d);
		System.out.println(AN_ARRAY);
		System.out.println(A_COLOR);
	}
                    

ここで問題なのは、変数の基本型とString型は置き換えられているが他はそうではない、ということではなく、値がコンパイル時に 決定するということです。

特に今回の上記のソースはpublicなので他のクラスから使われる可能性があります。 つまり、仮に上記のA_STRINGを書き換えて、コンパイルすれば、確かにその中の値は変わりますが、他のクラスで その定数を利用している場合は、その値までは変わってくれません。つまり他のクラスもコンパイルし直さないと駄目だ ということです。これは時々大きなバグになる可能性があります。

メソッドスコープのfinal変数

メソッドスコープの定数というのは、やや奇な感じがするかもしれませんが、これが結構意味があるのです。次のソースを見てください。


package oreilly.hcj.finalstory;

import javax.swing.JDialog;

public class FinalVariables {
	public static final void main(final String[] args) {
		System.out.println("Note how the key variable is changed.");
		someMethod("JAVA_HOME");
		someMethod("ANT_HOME");
	}

	public static String someBuggedMethod(final String environmentKey) {
		final String key = "env." + environmentKey;
		System.out.println("Key is: " + key);
		//key = new String("someValue"); // <= compiler error.  
		return (System.getProperty(key));
	}

	public static String someMethod(final String environmentKey) {
		final String key = "env." + environmentKey;
		System.out.println("Key is: " + key);
		return (System.getProperty(key));
	}

	public void buildGUIDialog(final String name) {
		final String instanceName;
		if (name == null) {
			// no problem here.
			instanceName = getClass()
				               .getName() + hashCode();
		} else {
			// no problem here as well.
			instanceName = getClass()
				               .getName() + name;
		}

		JDialog dialog = new JDialog();

		// .. Do a bunch of layout and component building. 
		dialog.setTitle(instanceName);

		// .. Do dialog assembly
		// instanceName = "hello";  // <= compiler error
	}
}


                    

上記のソースでmainメソッドでは、もちろん、エラーは起こりません。スコープがメソッドの場合は 毎回final String keyは初期化されるので、まるで変数のようですが、実際には、上記のsomeBuggedMethodの中の、 //key = new String("someValue"); // compiler error.のコメントをはずすと、コンパイルエラーが起こります。これが重要です。

つまり、コンパイルすることが出来るが、後にエラーになる可能性のあるもの(論理エラー)が一番厄介で、その前にコンパイルエラーで検出してしまえば 「あれ、なんでだあ?」という具合にすぐ見つかります。もし、コンパイルエラーにならず、数百のクラスができあがった後で、しかも、すでに納品済みになった後でエラーが 起こった場合は、相当の時間がかかることになります。すなわち論理エラーをなんとかコンパイルエラーに出来れば、みんな幸せになれるということです。

finalパラメータ

以前、eclipseのpluginのcheckstyleというコーディングのスタイルに対して、色々いちゃもんをつけるのですが、その中でもメソッドの引数にfinalを つけていないと、すぐにwarningするので、「うっとうしいなあ」とぐらいに思っていたことがありました。しかし、これは実際つけるべきだということです。次のソースを見てください。


package oreilly.hcj.finalstory;

import java.beans.PropertyVetoException;
import java.beans.VetoableChangeSupport;
import java.beans.PropertyChangeSupport;

public class PersonTEST {
  private String name = null;
  Object object = new Object();
	VetoableChangeSupport vetoableChangeSupport = 
			new VetoableChangeSupport(object);
	PropertyChangeSupport propertyChangeSupport = 
			new PropertyChangeSupport(object);
	
  public void setName(String name) throws PropertyVetoException {
    String oldName = this.name;
    vetoableChangeSupport.fireVetoableChange("name", oldName, name);
    name = name;
    propertyChangeSupport.firePropertyChange("name", oldName, name);
  }
}



                    

ここで一カ所間違いがあります。どこかというとname = name;です。単に接頭辞this を付け忘れただけです。これでは代入が行われません。もしsetName(String name)setName(final String name)となっていれば、そのミスはコンパイル時に わかります。実際eclipseだったら、試してみると、finalがないと黄色のwarning、finalがあると赤い、いつもの波線が 見られます。つまり、引数にもfinalをつけるべきだということでしょう。

by 知久和郎