如何提高使用Java反射的效率?

前言

在我們平時(shí)的工作或者面試中,都會(huì)經(jīng)常遇到“反射”這個(gè)知識(shí)點(diǎn),通過“反射”我們可以動(dòng)態(tài)的獲取到對(duì)象的信息以及靈活的調(diào)用對(duì)象方法等,但是在使用的同時(shí)又伴隨著另一種聲音的出現(xiàn),那就是“反射”很慢,要少用。難道反射真的很慢?那跟我們平時(shí)正常創(chuàng)建對(duì)象調(diào)用方法比慢多少? 估計(jì)很多人都沒去測(cè)試過,只是”道聽途說“。下面我們就直接通過一些測(cè)試用例來直觀的感受一下”反射“。

正文

準(zhǔn)備測(cè)試對(duì)象

下面先定義一個(gè)測(cè)試的類TestUser,只有idname屬性,以及它們的getter/setter方法,另外還有一個(gè)自定義的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;

}

}

測(cè)試創(chuàng)建100萬個(gè)對(duì)象

// 通過普通方式創(chuàng)建TestUser對(duì)象

@Test

public 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("普通對(duì)象創(chuàng)建耗時(shí):"+(end - start ) + "ms");

}

//普通對(duì)象創(chuàng)建耗時(shí):10ms

// 通過反射方式創(chuàng)建TestUser對(duì)象

@Test

public 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("無緩存反射創(chuàng)建對(duì)象耗時(shí):"+(end - start ) + "ms");

}

//無緩存反射創(chuàng)建對(duì)象耗時(shí):926ms

在上面這兩個(gè)測(cè)試方法中,筆者各自測(cè)了5次,把他們消耗的時(shí)間取了一個(gè)平均值,在輸出結(jié)果中可以看到一個(gè)是10ms,一個(gè)是926ms,在創(chuàng)建100W個(gè)對(duì)象的情況下,反射居然慢了90倍左右。wtf?差距居然這么大?難道反射真的這么慢?下面筆者換一種反射的姿勢(shì),繼續(xù)測(cè)試一下,看看結(jié)果如何?

// 通過緩存反射方式創(chuàng)建TestUser對(duì)象

@Test

public 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("通過緩存反射創(chuàng)建對(duì)象耗時(shí):"+(end - start ) + "ms");

}

//通過緩存反射創(chuàng)建對(duì)象耗時(shí):41ms

咦?這種操作只需要41ms了,大大提高了反射創(chuàng)建對(duì)象的效率。為什么會(huì)快這么多呢?

其實(shí)通過代碼我們可以發(fā)現(xiàn),是Class.forName這個(gè)方法比較耗時(shí),它實(shí)際上調(diào)用了一個(gè)本地方法,通過這個(gè)方法來要求JVM查找并加載指定的類。所以我們?cè)陧?xiàng)目中使用的時(shí)候,可以把Class.forName返回的Class對(duì)象緩存起來,下一次使用的時(shí)候直接從緩存里面獲取,這樣就極大的提高了獲取Class的效率。同理,在我們獲取Constructor、Method等對(duì)象的時(shí)候也可以緩存起來使用,避免每次使用時(shí)再來耗費(fèi)時(shí)間創(chuàng)建。

測(cè)試反射調(diào)用方法

@Test

public 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("反射調(diào)用方法耗時(shí):"+(end - start ) + "ms");

}

//反射調(diào)用方法耗時(shí):330ms

@Test

public 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 反射調(diào)用方法耗時(shí):"+(end - start ) + "ms");

}

//setAccessible=true 反射調(diào)用方法耗時(shí):188ms

這里我們反射調(diào)用sayHi方法1億次,在調(diào)用了method.setAccessible(true)后,發(fā)現(xiàn)快了將近一半。查看API可以了解到,jdk在設(shè)置獲取字段,調(diào)用方法的時(shí)候會(huì)執(zhí)行安全訪問檢查,而此類操作會(huì)比較耗時(shí),所以通過setAccessible(true)的方式可以關(guān)閉安全檢查,從而提升反射效率。

極致的反射

除了上面的手段,還有沒有什么辦法可以更極致的使用反射呢?這里介紹一個(gè)高性能反射工具包ReflectASM。它是通過字節(jié)碼生成的方式來實(shí)現(xiàn)的反射機(jī)制,下面是一個(gè)跟java反射的性能比較。


這里就不介紹它的用法了,有興趣的朋友那就點(diǎn)個(gè)關(guān)注唄,只收藏不點(diǎn)關(guān)注的都是在耍流氓!

關(guān)注并私信我“架構(gòu)”,免費(fèi)送一些Java架構(gòu)資料,先到先得!

結(jié)語

最后總結(jié)一下,為了更好的使用反射,我們應(yīng)該在項(xiàng)目啟動(dòng)的時(shí)候?qū)⒎瓷渌枰南嚓P(guān)配置及數(shù)據(jù)加載進(jìn)內(nèi)存中,在運(yùn)行階段都從緩存中取這些元數(shù)據(jù)進(jìn)行反射操作。大家也不用懼怕反射,虛擬機(jī)在不斷的優(yōu)化,只要我們方法用的對(duì),它并沒有”傳聞“中的那么慢,當(dāng)我們對(duì)性能有極致追求的時(shí)候,可以考慮通過三方包,直接對(duì)字節(jié)碼進(jìn)行操作。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容