[???] / [Java FAQ] / [S038]
S038: Java言語一般(その他)

[S038-Q01]
Back
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]
Back
あるアプリケーションが使用するすべてのクラスのリストを得たいのですが? [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]
Back
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]
Back
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]
Back
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]
Back
オブジェクト o は System.identityHashCode(o) で識別できますか? つまり、 (o == p) == (System.identityHashCode(o) == System.identityHashCode(p)) は常に成り立ちますか? [S038-A06]  成り立たないと考えたほうが良いでしょう。オブジェクトを 0x100000000 個 以上作れるような環境では、identityHashCode の戻り値が等しくても、異なる オブジェクトである可能性があります。

Back
contributor: koto
コメントの送り先 Java FAQ BBS