酔っぱらいながらのお勉強
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 知久和郎


