[???] /
[Java FAQ] / [S038]
S038: Java言語一般(その他)
[S038-Q01]
Java Application で、クラスの .class ファイルの場所を得たいのですが?
[S038-A01]
残念ながら、確実に得る方法はありません。
クラスローダーは、任意の位置のクラスファイルからクラスをロードすること
ができますが、クラスとクラスローダーからクラスファイルを特定する汎用的な
方法はありません。
もし、クラスファイルのある場所に、設定などを記録したファイルを作成した
いのであれば、System.getProperty("user.home") で得られるディレクトリ(フ
ォルダ)に、ピリオドで始まるユニークな(他のアプリケーションと重ならないよ
うな)名前のファイルを作成するのが良いでしょう。あるいは、ファイルの代わ
りにディレクトリ(フォルダ)を作成しても構いません。
import java.io.File;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.io.BufferedOutputStream;
import java.util.Properties;
public class PropertySaver {
public static void main(String[] args) {
try {
// 保存したい設定を作る
Properties p = new Properties();
p.put("time", new java.util.Date().toString());
// 「ホームディレクトリ」を得る。
File home = new File(System.getProperty("user.home"));
// ホームディレクトリの下に、設定を記憶するファイルを作る
File file = new File(home,
".JavaFAQ_Sample_Property_File");
// 保存する。
System.out.println("saving to '" + file + "'");
OutputStream os = new BufferedOutputStream(
new FileOutputStream(file));
try {
p.store(os, "JavaFAQ Sample");
} finally {
os.close();
}
} catch (java.io.IOException e) {
e.printStackTrace();
}
}
}
クラスが特定のパスからロードされることが分かっているならば、次のプログ
ラムのようにして、近いことはできます。しかしこのプログラムでは、nested
class や、ファイル名がクラス名に ".class" を付加した名前でない場合、クラ
スファイルの場所を得ることはできません。
import java.util.StringTokenizer;
import java.util.jar.JarFile;
import java.io.File;
public class ClassFileFinder {
public static void main(String[] args) {
if (args.length != 1) {
System.out.println("usage: java ClassFileFinder <class name>");
return;
}
String classNameToFind = args[0];
File result = find(System.getProperty("java.class.path"),
classNameToFind);
if (result == null)
System.out.println(classNameToFind + " not found.");
else
System.out.println(result);
}
// クラスパスから、classNameToFind に示されるクラスのファイルを探す。
public static File find(String classPath, String classNameToFind) {
// クラスパスを順次検索し、最初にヒットしたものを返す。
StringTokenizer st = new StringTokenizer(classPath,
System.getProperty("path.separator"));
File result = null;
while (st.hasMoreTokens() && result == null)
result = getFile(new File(st.nextToken()), classNameToFind);
return result;
}
// classPath から、classNameToFind に示されるクラスのファイルを探す。
private static File getFile(File classPath, String classNameToFind) {
File result = null;
if (classPath.isFile()) {
// jar/zip ファイルかもしれない
try {
JarFile jar = new JarFile(classPath, false);
String name = classNameToFind.replace('.', '/') + ".class";
if(jar.getEntry(name) != null)
result = classPath;
jar.close();
} catch (java.io.IOException x) {
}
} else if(classPath.isDirectory()) {
String name = classNameToFind.replace('.', File.separatorChar)
+ ".class";
File file = new File(classPath.getPath(), name);
if (file.exists())
result = file;
}
return result;
}
}
参考:
クラスの検索方法
http://java.sun.com/j2se/1.3/ja/docs/ja/tooldocs/findingclasses.html
[S038-Q02]
あるアプリケーションが使用するすべてのクラスのリストを得たいのですが?
[S038-A02]
SUN の Java2 SDK 1.3 であれば、アプリケーションを実行するときに、-verbose:class オプションを指定してください。ロードされた時点で [Loaded
クラス名 ...]の形式でクラス名が出力されます。
例:
public class Test {
public static void main(String[] args) {
System.out.println("test");
}
}
% javac Test.java
% java -verbose:class Test
[Opened C:\Program Files\JavaSoft\JRE\1.3\lib\rt.jar]
[Opened C:\Program Files\JavaSoft\JRE\1.3\lib\i18n.jar]
[Opened C:\Program Files\JavaSoft\JRE\1.3\lib\sunrsasign.jar]
[Loaded java.lang.Object from C:\Program Files\JavaSoft\JRE\1.3\lib\rt.jar]
[Loaded java.io.Serializable from C:\Program Files\JavaSoft\JRE\1.3\lib\rt.jar]
[Loaded java.lang.Comparable from C:\Program Files\JavaSoft\JRE\1.3\lib\rt.jar]
[Loaded java.lang.String from C:\Program Files\JavaSoft\JRE\1.3\lib\rt.jar]
[Loaded java.lang.Class from C:\Program Files\JavaSoft\JRE\1.3\lib\rt.jar]
:
:
:
[Loaded java.lang.RuntimePermission from C:\Program Files\JavaSoft\JRE\1.3\lib\rt.jar]
[Loaded java.security.cert.Certificate from C:\Program Files\JavaSoft\JRE\1.3\lib\rt.jar]
[Loaded Test]
test
[Loaded java.lang.Shutdown$Lock from C:\Program Files\JavaSoft\JRE\1.3\lib\rt.jar]
参考:
JavaTM 2 SDK ツールとユーティリティ
http://java.sun.com/j2se/1.3/ja/docs/ja/tooldocs/tools.html
[S038-Q03]
static {...} とは何ですか?
[S038-A03]
{ } の中に、クラスがロードされたときに1度だけ実行されるプログラムを記述
します。Static Initializer と呼ばれます。
例を示します。
class A {
static {
System.out.println("A loaded");
}
A() {
System.out.println("new A");
}
}
public class Test {
static {
System.out.println("Test loaded 1");
}
public static void main(String[] args) {
System.out.println("start");
new A();
new A();
new A();
}
static {
System.out.println("Test loaded 2");
}
}
Test を実行すると、次が出力されます。
Test loaded 1
Test loaded 2
start
A loaded
new A
new A
new A
1.まず、Test クラスがロードされ、Test クラスの Static Initializer が、プ
ログラムに現れた順に実行され、"Test loaded 1" と "Test loaded 2" が出
力されます。
2.続いて、Test クラスの main メソッドが呼び出され、"start" が出力されます。
3.続いて、new A を実行するために、A クラスがロードされます。A クラスの
Static Initializer が実行され、"A loaded" が出力されます。
4.new A() が実行され "new A" が出力されます。
参考:
8.7 Static Initializers, JLS 2nd Ed.
http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#39245
[S038-Q04]
Character#hashCode() は、Unicode の値を int を返すので、Unicode の値を
int で得るのに使えますか?
[S038-A04]
それは、良いアイディアではありません。確かに実装はそうなっているかもし
れませんが、そのことは、明確に仕様で定められていません(具体的には、Java
API Specification の Character#hashCode に Unicode の値を返すと書かれて
いません)。現在のバージョンではたまたまそのような実装になっているだけで、
将来のバージョンで実装が変更されるかもしれません。なによりも、そのプログ
ラムを hashCode の実装を知らない人が読んだ場合、かなり混乱します。
単純に int にキャストしましょう。
public class Test {
public static void main(String[] args) {
char ch = 'あ';
Character character = new Character(ch);
// int code = character.hashCode(); …良くない
int code = (int)ch;
System.out.println(ch + " " + code);
}
}
参照:
Character#hashCode
http://java.sun.com/j2se/1.3/ja/docs/ja/api/java/lang/Character.html#hashCode()
[S038-Q05]
hashCodeとは何ですか?
[S038-A05]
2つのオブジェクトが「等しい」とき、そのハッシュコードもまた等しくなる
ようなコードです。すなわち、オブジェクト o と p について、o.equals(p) な
らば、o.hashCode() == p.hashCode() でなければなりません。逆に、o.hashCode()
== p.hashCode() であっても、o.equals(p)とは限りません。
ハッシュコードはオブジェクトの検索を高速に行うために使われます。 java.
util.Map のような、オブジェクトからオブジェクトへのマッピングを行うクラ
スを書くことを考えてみましょう。もっとも単純な実装は、
[例]
import java.util.List;
import java.util.LinkedList;
public class SimpleMap
{
List keys = new LinkedList();
List values = new LinkedList();
void put(Object key, Object value) {
keys.add(key);
values.add(value);
}
Object get(Object key) {
for (int i=0; i<keys.size(); i++) {
if (keys.get(i).equals(key))
return values.get(i);
}
return null;
}
// ... その他のメソッド
}
のように、リストを使うことです。しかし、この方法では、項目数が増えたとき
(keys.size() が大きくなったとき)に、key によっては、keys から該当する
キーを探すのに時間がかかります。この問題を解決するために、ハッシュテーブ
ルと呼ばれるテクニックが考え出されました。
import SimpleMap;
public class HashingMap
{
SimpleMap[] hashCodeToSubmap = new SimpleMap[100];
// 100 は規模に応じて適切な値にする
void put(Object key, Object value) {
int index = key.hashCode() % hashCodeToSubmap.length;
if(hashCodeToSubmap[index] == null)
hashCodeToSubmap[index] = new SimpleMap();
hashCodeToSubmap[index].put(key, value);
}
Object get(Object key) {
int index = key.hashCode() % hashCodeToSubmap.length;
if(hashCodeToSubmap[index] == null)
return null;
return hashCodeToSubmap[index].get(key);
}
// ... その他のメソッド
}
ハッシュテーブルには様々なバリエーションがありえますが、ここでは先ほど
の SimpleMap を利用した、二段階のマッピングを考えます。
HashingMap は、オブジェクトが与えられたときに、そのハッシュコードを元
にして、0 から 99 までの整数を得ます。この整数は、同じ(equals)オブジェク
トに対しては、同じ値になります。この整数をキーとして、hashCodeToSubmap
によって第一段階のマッピングを行い、SimpleMap を得ます。第一段階のマッピ
ングでは、ハッシュコードやそれから整数を得る計算は、比較的(SimpleMap 単
体でサイズの大きなマッピングを扱う時のコストに比べれば)短時間で行うこと
ができます。
続いて第二段階のマッピングを行います。第一段階で得られた SimpleMap に
対し、オブジェクトをキーとしたマッピングを行います。SimpleMap に含まれる
オブジェクトの数が十分小さくなるように hashCodeToSubmap.length を調節す
ることによって、第二段階のマッピングも、比較的短時間で解決することができ
ます。
以上のようにして、ハッシュコードを利用したマッピングでは、SimpleMap の
ような単純な方法に比べ、サイズの大きなマッピングでも、短時間で処理するこ
とができます。第一段階でハッシュコードを用いて大雑把に分類するというのが、
ハッシュテーブルの基本的な考え方です。
ハッシュテーブルが正常に動作するためには、equals な2つのオブジェクト
が、同じハッシュコードを持たなければなりません。
また、ハッシュテーブルが効率的に動作するために、なるべくハッシュコード
がばらばらにならなければなりません。例えば、String のハッシュコードを、
先頭の1文字の Unicode の値を返すようにしたとします。フルパスのファイル
名をキーとするマッピングを考えると、多くのファイルは、Unix の場合はが '/
'、Windows の場合は 'C' で始まるはずです。この場合、第一段階の大雑把な分
類で、ほとんどのキーは1つのカテゴリに分類されてしまい、第二段階の検索で
時間がかかるようになってしまいます。
参考:
Object#hashCode()
http://java.sun.com/j2se/1.3/ja/docs/ja/api/java/lang/Object.html#hashCode()
[S038-Q06]
オブジェクト o は System.identityHashCode(o) で識別できますか?
つまり、
(o == p) == (System.identityHashCode(o) == System.identityHashCode(p))
は常に成り立ちますか?
[S038-A06]
成り立たないと考えたほうが良いでしょう。オブジェクトを 0x100000000 個
以上作れるような環境では、identityHashCode の戻り値が等しくても、異なる
オブジェクトである可能性があります。
contributor: koto
コメントの送り先 Java FAQ BBS