私たちは日々の仕事やインタビューなどで「リフレクション」という知識ポイントに出会うことがあります。「リフレクション」によってオブジェクトの情報を動的に取得したり、オブジェクトのメソッドなどを柔軟に呼び出すことができると同時に、それを利用することになります。別の音、つまり「反射」の出現は非常に遅いため、使用は控えめにする必要があります。反映は本当に遅いですか?通常、オブジェクトを作成してメソッドを呼び出すときと比べて、どれくらい遅いのでしょうか? 多くの人はテストしたことがなく、単に「聞いた」だけだと思います。いくつかのテストケースを通して、「振り返り」を直接体験してみましょう。
Text
テスト オブジェクトの準備
以下では、id 属性と name 属性、およびその getter/setter メソッドのみを持つテスト クラス TestUser を定義します。 a self-sayHi メソッドを定義します。
public class TestUser { private Integer id; private String name; public String sayHi(){ return "hi"; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
100 万個のオブジェクトを作成するテスト
// 通过普通方式创建TestUser对象@Testpublic void testCommon(){ long start = System.currentTimeMillis(); TestUser user = null; int i = 0; while(i<1000000){ ++i; user = new TestUser(); } long end = System.currentTimeMillis(); System.out.println("普通对象创建耗时:"+(end - start ) + "ms"); }//普通对象创建耗时:10ms
// 通过反射方式创建TestUser对象@Testpublic void testReflexNoCache() throws Exception { long start = System.currentTimeMillis(); TestUser user = null; int i = 0; while(i<1000000){ ++i; user = (TestUser) Class.forName("ReflexDemo.TestUser").newInstance(); } long end = System.currentTimeMillis(); System.out.println("无缓存反射创建对象耗时:"+(end - start ) + "ms"); }//无缓存反射创建对象耗时:926ms
上記の 2 つのテスト方法で、作成者はそれぞれ 5 回テストし、消費した時間を平均して出力しました。結果として、1 つは 10 ミリ秒、もう 1 つは 926 ミリ秒となり、100 万個のオブジェクトが作成されると、実際には反射は約 90 倍遅くなります。なんと?ギャップはそんなに大きいですか?反映は本当に遅いですか?次に、著者は反省姿勢を変えてテストを続け、結果はどうなるか?
// 通过缓存反射方式创建TestUser对象@Testpublic void testReflexWithCache() throws Exception { long start = System.currentTimeMillis(); TestUser user = null; Class rUserClass = Class.forName("RefleDemo.TestUser"); int i = 0; while(i<1000000){ ++i; user = (TestUser) rUserClass.newInstance(); } long end = System.currentTimeMillis(); System.out.println("通过缓存反射创建对象耗时:"+(end - start ) + "ms"); }//通过缓存反射创建对象耗时:41ms
実際、コードを通して、Class.forName メソッドの方が時間がかかることがわかります。実際には、このメソッドはローカル メソッドを呼び出し、JVM はこのメソッドを通じて、指定されたクラスを検索してロードするように求められます。そのため、プロジェクト内で使用する場合は、Class.forName で返された Class オブジェクトをキャッシュしておき、次回使用するときにキャッシュから直接取得できるため、Class の取得効率が大幅に向上します。同様に、コンストラクター、メソッド、その他のオブジェクトを取得するときに、それらを使用するためにキャッシュして、使用するたびに時間のかかる作成を避けることもできます。
リフレクション呼び出しメソッドのテスト
@Testpublic void testReflexMethod() throws Exception { long start = System.currentTimeMillis(); Class testUserClass = Class.forName("RefleDemo.TestUser"); TestUser testUser = (TestUser) testUserClass.newInstance(); Method method = testUserClass.getMethod("sayHi"); int i = 0; while(i<100000000){ ++i; method.invoke(testUser); } long end = System.currentTimeMillis(); System.out.println("反射调用方法耗时:"+(end - start ) + "ms"); }//反射调用方法耗时:330ms
@Testpublic void testReflexMethod() throws Exception { long start = System.currentTimeMillis(); Class testUserClass = Class.forName("RefleDemo.TestUser"); TestUser testUser = (TestUser) testUserClass.newInstance(); Method method = testUserClass.getMethod("sayHi"); int i = 0; while(i<100000000){ ++i; method.setAccessible(true); method.invoke(testUser); } long end = System.currentTimeMillis(); System.out.println("setAccessible=true 反射调用方法耗时:"+(end - start ) + "ms"); }//setAccessible=true 反射调用方法耗时:188ms
ここでは、sayHi メソッドを 1 億回リフレクトして呼び出します。method.setAccessible(true) を呼び出した後、半分近く高速になっていることがわかりました。 API を見ると、取得フィールドの設定時やメソッドの呼び出し時に jdk がセキュリティ アクセス チェックを実行することがわかりますが、そのような操作には時間がかかるため、 setAccessible(true) を通じてセキュリティ チェックをオフにすることができます。反射効率が向上します。
究極のリフレクション
上記の方法に加えて、リフレクションを極限まで活用する方法はあるでしょうか?ここでは、高性能リフレクション ツールキットである ReflectASM を紹介します。バイトコード生成により実装されたリフレクション機構で、Java リフレクションとの性能比較は以下の通りです。
結論
最後にまとめると、リフレクションをより効果的に使用するには、プロジェクトの開始時にリフレクションに必要な関連構成とデータをメモリにロードする必要があります各ステージは、リフレクション操作のためにキャッシュからこれらのメタデータをフェッチします。リフレクションを恐れる必要はありません。仮想マシンは常に最適化されています。適切な方法を使用している限り、「噂」ほど遅くはありません。パフォーマンスを究極に追求する場合、次のことを考慮できます。サードパーティのパッケージを使用してコードを直接操作します。
関連チュートリアル: Java ビデオ チュートリアル
以上がJavaリフレクションの使用効率を向上させる方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。