Trouble 18: 期待した通りの値が返りません
<Q18-1>Vectorクラスのcloneメソッドが、オブジェクトのコピーとはならず、参照のままとなってしまいます。
Cloneしたオブジェクトの内容を変更すると、オリジナルのオブジェクトの内容も変更されてしまいます。
| <発生環境> |
| OS |
Solaris2.5.1 |
| JDK |
JDK1.2.1 |
| Vender |
Sun |
- <A18-1>
- Vectorのcloneメソッドは、保持している要素オブジェクトのcloneの生成までは行わないので、cloneで生成されたVectorオブジェクトは、clone元のVectorと同じ要素オブジェクトを参照しています。
要素オブジェクトまでもコピーしたVectorを生成したい場合は、Vectorのcloneメソッドを使わず、要素オブジェクトをコピーして、新しいVectorオブジェクトに格納するプログラムを実装する必要があります。
これを不便だと思う人がいるかと思いますが、勝手にcloneを生成されてメモリを大量に使用する潜在バグの温床にもなりかねませんし、オブジェクトのコピーの深さ(シャローコピーなのかディープコピーなのか)をコントロール出来なくなるので、Vectorのcloneメソッドの振る舞いは正しいと考えます。
<Q18-2>Calenderクラスに、lenientを設定していても、異常な値をsetしても例外をthrowしません。
APIには例外をthrowするように書いてあるのですが、どうなのでしょうか?
| <発生環境> |
| OS |
Solaris2.5.1 |
| JDK |
JDK1.2.1 |
| Vender |
Sun |
- <A18-2>
- 異常な値をsetしても、直ちに例外がthrowされるのではなく、その後、何らかの操作を行った時点で例外がthrowされるため、set後に、例外補足処理を行ってください。
<Q18-3>配列から変換したListに要素を追加することができません。
Arrays.asList(Object[] a)で生成されたListに対して、add(Object a)をしようとしたところ、java.lang.UnsupportedOperationExceptionが発生します。
| <発生環境> |
| OS |
Any |
| JDK |
Any |
| Vender |
Any |
- <A18-3>
- java.util.Arrays#asList(Object[] a)で生成されるインスタンスは、java.util.Arraysのソースコードでprivateなクラスとして定義されているArrayListです。 このArrayListクラスはメソッドadd(Object a)を実装していないため要素を追加できません。
<Q18-4>ビットシフトを行ったところ、予期せぬ数値が返ってきます。
2つのbyte型(8bit)変数をそれぞれ上位バイト、下位バイトと見なします。
上位バイト(high=0x07) を8ビット左シフトし、下位バイト(low=0xb1) と足して 16進数表現を得ようとしました。
Integer.toHexString( (high << 8) + (low) );
0x07b1 という結果を期待したのですが、0x06b1 が返されました。
| <発生環境> |
| OS |
Any |
| JDK |
Any |
| Vender |
Any |
- <A18-4>
- この現象は シフト対象の値が負の場合のみ発生します。
負の値に正しくマスクをかけてから、計算する必要があります。
Javaのシフト演算は、オペランドが int 型より小さい場合(つまり、shortや byte 型の場合)、自動的に int 型に拡張されます。
この際の符号の取り扱いが原因。拡張の際は符号付(signed)の値として見なされるため、符号付表現で負の値は、intに拡張したときに拡張されたビッ トが全て 1 になってしまいます。
この例では、0x07 と 0xb1 は内部的には以下のように扱われます。
0x07 = 0000 0000 0000 0000 0000 0000 0000 0111
0xb1 = 1111 1111 1111 1111 1111 1111 1011 0001
よって、下位8ビットを抽出するマスクをかける必要があります。
Integer.toHexString( (high << 8) + (low & 0xff) );
<Q18-5>FileクラスのgetAbsolutePath()メソッドを実行しても、絶対パスが返って来ません。
カレントディレクトリを
C:\eclipse\workspace\JTS_Test
として、以下の親ディレクトリを出力するコードを実行しました。
File file = new File("..\\");
String absolutePath = file.getAbsolutePath();
System.out.println(absolutePath);
親ディレクトリとして
C:\eclipse\workspace
が出力されることを期待したのですが、
C:\eclipse\workspace\JTS_Test\..
と表示されます。
| <発生環境> |
| OS |
Any |
| JDK |
Any |
| Vender |
Any |
- <A18-5>
- "絶対パス"という言葉の意味の食い違いによると考えられます。
getAbsolutePath()メソッドで返って来るのは、
・UNIX :"/"で始まるパス
・Windows:ドライブ指示子で始まり、その後に"\\" が続くパス、または"\\"から始まるパス
となります。"."や".."などの変換は行いません。
"."や".."などの変換を含んだ一般的な意味での絶対パスを取得するには、getCanonicalPath()メソッドを
使用します。
<Q18-6>ResultSetに対してgetObject()でフィールド値を取得しようとしても、nullしか返ってきません。
| <発生環境> |
| OS |
WindowsNT 4.0 |
| JDK |
JDK1.3.1_02 |
| Vender |
Sun |
| DB |
Oracle8 Enterprise Edition Release 8.0.5.2.1 |
| JDBC Driver |
Oracle JDBC Driver 8.1.6.0.1 |
- <A18-6>
- getInt()などの型付メソッドを使用することで回避できます。
<Q18-7>HashSetで取得した値を変更すると、HashSetで値が正しく取得できなくなります。
HashSetで取得した値を以下のように変更すると、HashSetが正しく動作しなくなります。
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
class Foo {
public int id;
public Foo() {}
public int hashCode() {
return id;
}
public boolean equals(Object obj) {
if (obj instanceof Foo) {
return (this.id == ((Foo) obj).id);
}
return false;
}
}
class HashSetError {
public static void main(String[] args) {
Foo foo = new Foo();
foo.id = 1;
// HashSetにデータを入れる
Set<Foo> barList = new HashSet<Foo>();
barList.add(foo);
// trueと表示
System.out.println("foo contains? " + barList.contains(foo));
Iterator<Foo> it = barList.iterator();
while (it.hasNext()) {
Foo tmp = it.next();
tmp.id = 2;
}
// falseと表示
System.out.println("foo contains? " + barList.contains(foo));
}
}
※JDK1.5.0で動作確認を行いました。
- <A18-7>
- これはHashSetが各要素と紐付けているハッシュ値と、
要素のhashCode()を呼び出したときのハッシュ値が異なるために発生する問題です。
HashSetは要素を管理するために、要素と同時にそれに対応するハッシュ値を保存しています。
各要素のハッシュ値は、HashSet#add()を呼ばれた際にhashCode()を呼び出した結果となります。
上の例ではHashSetに追加されたfooはハッシュ値1とともに保存されます。
しかしHashSetが管理している要素に対してハッシュ値に影響を与える操作を行うとしても
HashSetはその操作によって要素のハッシュ値を変更することはありません。
つまり要素の値を変更すると、要素のハッシュ値とHashSetが管理するハッシュ値が
不一致になるのです。
上の例ではHashSetに追加されたfooは 「tmp.id=2;」 によってハッシュ値が2になりますが、
HashSetが登録されているハッシュ値は1のままです。
このため、ハッシュ値が変更になった要素をHashSetで検索すると、
HashSetはハッシュ値で検索を行うために登録されていないと判断されてしまいます。
これを解決するには、値を変更した要素を新しいHashSetに対してaddする必要があります。
また本来、HashSetに格納した内容を変更することは避けるべきです。
java.util.SetのJavaDocにも、以下の通りの記述があります。
注: 可変オブジェクトがセット要素として使用される場合は、
細心の注意が必要です。
オブジェクトがセット内の要素であるうちに equals 比較に影響する
方式でその値が変更された場合、セットの動作は保証されません。
この禁止事項の特例により、セットがそれ自体を要素として
持つことは許可されません。
参考URL:
http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/java/util/Set.html

注意:本文書の内容に誤りがあり、またこの文書によって不利益を被っても、
エスエムジー株式会社は一切関知いたしません。