文章插圖

文章插圖
前言
Java 反編譯,一聽可能覺得高深莫測,其實反編譯并不是什么特別高級的操作,Java 對于 Class 字節碼文件的生成有著嚴格的要求,如果你非常熟悉 Java 虛擬機規范,了解 Class 字節碼文件中一些字節的作用,那么理解反編譯的原理并不是什么問題 。甚至像下面這樣的 Class 文件你都能看懂一二 。
恰好最近工作中也需要用到 Java 反編譯,所以這篇文章介紹目前常見的的幾種 Java 反編譯工具的使用,在文章的最后也會通過編譯速度、語法支持以及代碼可讀性三個維度,對它們進行測試,分析幾款工具的優缺點 。
Procyon
Github 鏈接:https://github.com/mstrobel/procyon
Procyon 不僅僅是反編譯工具,它其實是專注于 Java 代碼的生成和分析的一整套的 Java 元編程工具 。主要包括下面幾個部分:
Core FrameworkReflection FrameworkExpressions FrameworkCompiler Toolset (Experimental)Java Decompiler (Experimental)
可以看到反編譯只是 Procyon 的其中一個模塊,Procyon 原來托管于 bitbucket,后來遷移到了 GitHub,根據 GitHub 的提交記錄來看,也有將近兩年沒有更新了 。不過也有依賴 Procyon 的其他的開源反編譯工具如** decompiler-procyon**,更新頻率還是很高的,下面也會選擇這個工具進行反編譯測試 。
使用 Procyon
<!--https://mvnrepository.com/artifact/org.jboss.windup.decompiler/decompiler-procyon--><dependency><groupId>org.jboss.windup.decompiler</groupId><artifactId>decompiler-procyon</artifactId><version>5.1.4.Final</version></dependency>寫一個簡單的反編譯測試 。【java反編譯工具有哪些apktool java反編譯工具有哪些reflector】
packagecom.wdbyte.decompiler;importjava.io.IOException;importjava.nio.file.Path;importjava.nio.file.Paths;importjava.util.Iterator;importjava.util.List;importorg.jboss.windup.decompiler.api.DecompilationFailure;importorg.jboss.windup.decompiler.api.DecompilationListener;importorg.jboss.windup.decompiler.api.DecompilationResult;importorg.jboss.windup.decompiler.api.Decompiler;importorg.jboss.windup.decompiler.procyon.ProcyonDecompiler;[email protected]:[email protected]/05/15*/publicclassProcyonTest{publicstaticvoidmain(String[]args)throwsIOException{Longtime=procyon("decompiler.jar","procyon_output_jar");System.out.println(String.format("decompilertime:%dms",time));}publicstaticLongprocyon(Stringsource,StringtargetPath)throwsIOException{longstart=System.currentTimeMillis();PathoutDir=Paths.get(targetPath);Patharchive=Paths.get(source);Decompilerdec=newProcyonDecompiler();DecompilationResultres=dec.decompileArchive(archive,outDir,newDecompilationListener(){publicvoiddecompilationProcessComplete(){System.out.println("decompilationProcessComplete");}publicvoiddecompilationFailed(List<String>inputPath,Stringmessage){System.out.println("decompilationFailed");}publicvoidfileDecompiled(List<String>inputPath,StringoutputPath){}publicbooleanisCancelled(){returnfalse;}});if(!res.getFailures().isEmpty()){StringBuildersb=newStringBuilder();sb.append("Faileddecompilationof"+res.getFailures().size()+"classes:");IteratorfailureIterator=res.getFailures().iterator();while(failureIterator.hasNext()){DecompilationFailuredex=(DecompilationFailure)failureIterator.next();sb.append(System.lineSeparator()+"").append(dex.getMessage());}System.out.println(sb.toString());}System.out.println("Compilationresults:"+res.getDecompiledFiles().size()+"succeeded,"+res.getFailures().size()+"failed.");dec.close();Longend=System.currentTimeMillis();returnend-start;}}Procyon 在反編譯時會實時輸出反編譯文件數量的進度情況,最后還會統計反編譯成功和失敗的 Class 文件數量 。....五月15,202110:58:28下午org.jboss.windup.decompiler.procyon.ProcyonDecompiler$3call信息:Decompiling650/783五月15,202110:58:30下午org.jboss.windup.decompiler.procyon.ProcyonDecompiler$3call信息:Decompiling700/783五月15,202110:58:37下午org.jboss.windup.decompiler.procyon.ProcyonDecompiler$3call信息:Decompiling750/783decompilationProcessCompleteCompilationresults:783succeeded,0failed.decompilertime:40599msProcyon GUI對于 Procyon 反編譯來說,在 GitHub 上也有基于此實現的開源 GUI 界面,感興趣的可以下載嘗試 。Github 地址:https://github.com/deathmarine/Luyten
CFR
GitHub 地址:https://github.com/leibnitz27/cfrCFR 官方網站:http://www.benf.org/other/cfr/(可能需要FQ)Maven 倉庫:https://mvnrepository.com/artifact/org.benf/cfr
CFR(Class File Reader) 可以支持 Java 9、Java 12、Java 14 以及其他的最新版 Java 代碼的反編譯工作 。而且 CFR 本身的代碼是由 Java 6 編寫,所以基本可以使用 CFR 在任何版本的 Java 程序中 。值得一提的是,使用 CFR 甚至可以將使用其他語言編寫的的 JVM 類文件反編譯回 Java 文件 。
CFR 命令行使用
使用 CFR 反編譯時,你可以下載已經發布的 JAR 包,進行命令行反編譯,也可以使用 Maven 引入的方式,在代碼中使用 。下面先說命令行運行的方式 。
直接在 GitHub Tags 下載已發布的最新版 JAR. 可以直接運行查看幫助 。
#查看幫助java-jarcfr-0.151.jar--help如果只是反編譯某個 class.#反編譯class文件,結果輸出到控制臺java-jarcfr-0.151.jarWindupClasspathTypeLoader.class#反編譯class文件,結果輸出到out文件夾java-jarcfr-0.151.jarWindupClasspathTypeLoader.class--outputpath./out反編譯某個 JAR.#反編譯jar文件,結果輸出到output_jar文件夾?Desktopjava-jarcfr-0.151.jardecompiler.jar--outputdir./output_jarProcessingdecompiler.jar(usesilenttosilence)Processingcom.strobel.assembler.metadata.ArrayTypeLoaderProcessingcom.strobel.assembler.metadata.ParameterDefinitionProcessingcom.strobel.assembler.metadata.MethodHandleProcessingcom.strobel.assembler.metadata.signatures.FloatSignature.....反編譯結果會按照 class 的包路徑寫入到指定文件夾中 。添加依賴這里不提 。
<!--https://mvnrepository.com/artifact/org.benf/cfr--><dependency><groupId>org.benf</groupId><artifactId>cfr</artifactId><version>0.151</version></dependency>實際上我在官方網站和 GitHub 上都沒有看到具體的單元測試示例 。不過沒有關系,既然能在命令行運行,那么直接在 IDEA 中查看反編譯后的 Main 方法入口,看下命令行是怎么執行的,就可以寫出自己的單元測試了 。packagecom.wdbyte.decompiler;importjava.io.IOException;importjava.util.ArrayList;importjava.util.HashMap;importjava.util.List;importorg.benf.cfr.reader.api.CfrDriver;importorg.benf.cfr.reader.util.getopt.OptionsImpl;[email protected]:[email protected]/05/15*/publicclassCFRTest{publicstaticvoidmain(String[]args)throwsIOException{Longtime=cfr("decompiler.jar","./cfr_output_jar");System.out.println(String.format("decompilertime:%dms",time));//decompilertime:11655ms}publicstaticLongcfr(Stringsource,StringtargetPath)throwsIOException{Longstart=System.currentTimeMillis();//sourcejarList<String>files=newArrayList<>();files.add(source);//targetdirHashMap<String,String>outputMap=newHashMap<>();outputMap.put("outputdir",targetPath);OptionsImploptions=newOptionsImpl(outputMap);CfrDrivercfrDriver=newCfrDriver.Builder().withBuiltOptions(options).build();cfrDriver.analyse(files);Longend=System.currentTimeMillis();return(end-start);}}JD-CoreGiHub 地址:https://github.com/java-decompiler/jd-coreJD-core 官方網址:https://java-decompiler.github.io/JD-core 是一個的獨立的 Java 庫,可以用于 Java 的反編譯,支持從 Java 1 至 Java 12 的字節碼反編譯,包括 Lambda 表達式、方式引用、默認方法等 。知名的 JD-GUI 和 Eclipse 無縫集成反編譯引擎就是 JD-core 。JD-core 提供了一些反編譯的核心功能,也提供了單獨的 Class 反編譯方法,但是如果你想在自己的代碼中去直接反編譯整個 JAR 包,還是需要一些改造的,如果是代碼中有匿名函數,Lambda 等,雖然可以直接反編譯,不過也需要額外考慮 。
使用 JD-core
<!--https://mvnrepository.com/artifact/org.jd/jd-core--><dependency><groupId>org.jd</groupId><artifactId>jd-core</artifactId><version>1.1.3</version></dependency>為了可以反編譯整個 JAR 包,使用的代碼我做了一些簡單改造,以便于最后一部分的對比測試,但是這個示例中沒有考慮內部類,Lambda 等會編譯出多個 Class 文件的情況,所以不能直接使用在生產中 。packagecom.wdbyte.decompiler;importjava.io.File;importjava.io.IOException;importjava.io.InputStream;importjava.nio.file.Files;importjava.nio.file.Path;importjava.nio.file.Paths;importjava.util.Enumeration;importjava.util.HashMap;importjava.util.jar.JarFile;importjava.util.zip.ZipEntry;importjava.util.zip.ZipFile;importorg.apache.commons.io.IOUtils;importorg.apache.commons.lang3.StringUtils;importorg.jd.core.v1.ClassFileToJavaSourceDecompiler;importorg.jd.core.v1.api.loader.Loader;importorg.jd.core.v1.api.printer.Printer;[email protected]:[email protected]/05/15*/publicclassJDCoreTest{publicstaticvoidmain(String[]args)throwsException{JDCoreDecompilerjdCoreDecompiler=newJDCoreDecompiler();Longtime=jdCoreDecompiler.decompiler("decompiler.jar","jd_output_jar");System.out.println(String.format("decompilertime:%dms",time));}}classJDCoreDecompiler{privateClassFileToJavaSourceDecompilerdecompiler=newClassFileToJavaSourceDecompiler();//存放字節碼privateHashMap<String,byte[]>classByteMap=newHashMap<>();/***注意:沒有考慮一個 Java 類編譯出多個 Class [email protected][email protected][email protected][email protected]*/publicLongdecompiler(Stringsource,Stringtarget)throwsException{longstart=System.currentTimeMillis();//解壓archive(source);for(StringclassName:classByteMap.keySet()){Stringpath=StringUtils.substringBeforeLast(className,"/");Stringname=StringUtils.substringAfterLast(className,"/");if(StringUtils.contains(name,"$")){name=StringUtils.substringAfterLast(name,"$");}name=StringUtils.replace(name,".class",".java");decompiler.decompile(loader,printer,className);Stringcontext=printer.toString();PathtargetPath=Paths.get(target+"/"+path+"/"+name);if(!Files.exists(Paths.get(target+"/"+path))){Files.createDirectories(Paths.get(target+"/"+path));}Files.deleteIfExists(targetPath);Files.createFile(targetPath);Files.write(targetPath,context.getBytes());}returnSystem.currentTimeMillis()-start;}privatevoidarchive(Stringpath)throwsIOException{try(ZipFilearchive=newJarFile(newFile(path))){Enumeration<?extendsZipEntry>entries=archive.entries();while(entries.hasMoreElements()){ZipEntryentry=entries.nextElement();if(!entry.isDirectory()){Stringname=entry.getName();if(name.endsWith(".class")){byte[]bytes=null;try(InputStreamstream=archive.getInputStream(entry)){bytes=IOUtils.toByteArray(stream);}classByteMap.put(name,bytes);}}}}}privateLoaderloader=newLoader()[email protected][]load(StringinternalName){returnclassByteMap.get(internalName);[email protected](StringinternalName){returnclassByteMap.containsKey(internalName);}};privatePrinterprinter=newPrinter(){protectedstaticfinalStringTAB="";protectedstaticfinalStringNEWLINE="n";protectedintindentationCount=0;protectedStringBuildersb=newStringBuilder();@OverridepublicStringtoString(){StringtoString=sb.toString();sb=newStringBuilder();returntoString;[email protected](intmaxLineNumber,intmajorVersion,intminorVersion)[email protected]()[email protected](Stringtext){sb.append(text);[email protected](Stringconstant){sb.append(constant);[email protected](Stringconstant,StringownerInternalName){sb.append(constant);[email protected](Stringkeyword){sb.append(keyword);[email protected](inttype,StringinternalTypeName,Stringname,Stringdescriptor){sb.append(name);[email protected](inttype,StringinternalTypeName,Stringname,Stringdescriptor,StringownerInternalName){sb.append(name);[email protected](){this.indentationCount++;[email protected](){this.indentationCount--;[email protected](intlineNumber){for(inti=0;i<indentationCount;i++)sb.append(TAB);[email protected](){sb.append(NEWLINE);[email protected](intcount){while(count-->0)sb.append(NEWLINE);[email protected](inttype)[email protected](inttype){}};}JD-GUIGitHub 地址:https://github.com/java-decompiler/jd-guiJD-core 也提供了官方的 GUI 界面,需要的也可以直接下載嘗試 。
GitHub 地址:https://github.com/skylot/jadxJadx 是一款可以反編譯 JAR、APK、DEX、AAR、AAB、ZIP 文件的反編譯工具,并且也配有 Jadx-gui 用于界面操作 。Jadx 使用 Grade 進行依賴管理,可以自行克隆倉庫打包運行 。
gitclonehttps://github.com/skylot/jadx.gitcdjadx./gradlewdist#查看幫助./build/jadx/bin/jadx--helpjadx-dextojavadecompiler,version:devusage:jadx[options]<inputfiles>(.apk,.dex,.jar,.class,.smali,.zip,.aar,.arsc,.aab)options:-d,--output-dir-outputdirectory-ds,--output-dir-src-outputdirectoryforsources-dr,--output-dir-res-outputdirectoryforresources-r,--no-res-donotdecoderesources-s,--no-src-donotdecompilesourcecode--single-class-decompileasingleclass--output-format-canbe'java'or'json',default:java-e,--export-gradle-saveasandroidgradleproject-j,--threads-count-processingthreadscount,default:6--show-bad-code-showinconsistentcode(incorrectlydecompiled)--no-imports-disableuseofimports,alwayswriteentirepackagename--no-debug-info-disabledebuginfo--add-debug-lines-addcommentswithdebuglinenumbersifavailable--no-inline-anonymous-disableanonymousclassesinline--no-replace-consts-don'treplaceconstantvaluewithmatchingconstantfield--escape-unicode-escapenonlatincharactersinstrings(withu)--respect-bytecode-access-modifiers-don'tchangeoriginalaccessmodifiers--deobf-activatedeobfuscation--deobf-min-minlengthofname,renamedifshorter,default:3--deobf-max-maxlengthofname,renamediflonger,default:64--deobf-cfg-file-deobfuscationmapfile,default:samedirandnameasinputfilewith'.jobf'extension--deobf-rewrite-cfg-forcetosavedeobfuscationmap--deobf-use-sourcename-usesourcefilenameasclassnamealias--deobf-parse-kotlin-metadata-parsekotlinmetadatatoclassandpackagenames--rename-flags-whattorename,comma-separated,'case'forsystemcasesensitivity,'valid'forjavaidentifiers,'printable'characters,'none'or'all'(default)--fs-case-sensitive-treatfilesystemascasesensitive,falsebydefault--cfg-savemethodscontrolflowgraphtodotfile--raw-cfg-savemethodscontrolflowgraph(userawinstructions)-f,--fallback-makesimpledump(usinggotoinsteadof'if','for',etc)-v,--verbose-verboseoutput(set--log-leveltoDEBUG)-q,--quiet-turnoffoutput(set--log-leveltoQUIET)--log-level-setloglevel,values:QUIET,PROGRESS,ERROR,WARN,INFO,DEBUG,default:PROGRESS--version-printjadxversion-h,--help-printthishelpExample:jadx-doutclasses.dex根據 HELP 信息,如果想要反編譯 decompiler.jar 到 out 文件夾 。./build/jadx/bin/jadx-d./out~/Desktop/decompiler.jarINFO-loading...INFO-processing...INFO-doneress:1143of1217(93%)FernflowerGitHub 地址:https://github.com/fesh0r/fernflowerFernflower 和 Jadx 一樣使用 Grade 進行依賴管理,可以自行克隆倉庫打包運行 。
?fernflower-master./gradlewbuildBUILDSUCCESSFULin32s4actionabletasks:4executed?fernflower-masterjava-jarbuild/libs/fernflower.jarUsage:java-jarfernflower.jar[-<option>=<value>]*[<source>]+<destination>Example:java-jarfernflower.jar-dgs=truec:mysourcec:my.jard:decompiled?fernflower-mastermkdirout?fernflower-masterjava-jarbuild/libs/fernflower.jar~/Desktop/decompiler.jar./outINFO:Decompilingclasscom/strobel/assembler/metadata/ArrayTypeLoaderINFO:...doneINFO:Decompilingclasscom/strobel/assembler/metadata/ParameterDefinitionINFO:...doneINFO:Decompilingclasscom/strobel/assembler/metadata/MethodHandle...?fernflower-masterllouttotal1288-rw-r--r--1darcystaff595K51617:47decompiler.jar?fernflower-masterFernflower 在反編譯 JAR 同時,默認反編譯的結果也是一個 JAR 包 。Jad反編譯速度
到這里已經介紹了五款 Java 反編譯工具了,那么在日常開發中我們應該使用哪一個呢?又或者在代碼分析時我們又該選擇哪一個呢?我想這兩種情況的不同,使用時的關注點也是不同的 。如果是日常使用,讀讀代碼,我想應該是對可讀性要求更高些,如果是大量的代碼分析工作,那么可能反編譯的速度和語法的支持上要求更高些 。為了能有一個簡單的參考數據,我使用 JMH 微基準測試工具分別對這五款反編譯工具進行了簡單的測試,下面是一些測試結果 。
測試環境
環境變量描述處理器2.6 GHz 六核Intel Core i7內存16 GB 2667 MHz DDR4Java 版本JDK 14.0.2測試方式JMH 基準測試 。待反編譯 JAR 1procyon-compilertools-0.5.33.jar (1.5 MB)待反編譯 JAR 2python2java4common-1.0.0-20180706.084921-1.jar (42 MB)
反編譯 JAR 1:procyon-compilertools-0.5.33.jar (1.5 MB)
BenchmarkModeCntScoreUnitscfravgt106548.642 ± 363.502ms/opfernfloweravgt1012699.147 ± 1081.539ms/opjdcoreavgt105728.621 ± 310.645ms/opprocyonavgt1026776.125 ± 2651.081ms/opjadxavgt107059.354 ± 323.351ms/op
反編譯 JAR 2: python2java4common-1.0.0-20180706.084921-1.jar (42 MB)
JAR 2 這個包是比較大的,是拿很多代碼倉庫合并到一起的,同時還有很多 Python 轉 Java 生成的代碼,理論上代碼的復雜度會更高 。
BenchmarkCntScoreCfr1413838.826msfernflower1246819.168msjdcore1Errorprocyon1487647.181msjadx1505600.231ms
語法支持和可讀性
如果反編譯后的代碼需要自己看的話,那么可讀性更好的代碼更占優勢,下面我寫了一些代碼,主要是 Java 8 及以下的代碼語法和一些嵌套的流程控制,看看反編譯后的效果如何 。
packagecom.wdbyte.decompiler;importjava.util.ArrayList;importjava.util.List;importjava.util.stream.IntStream;importorg.benf.cfr.reader.util.functors.UnaryFunction;[email protected]:[email protected]/05/16*/publicclassHardCode<A,B>{publicHardCode(Aa,Bb){}publicstaticvoidtest(int...args){}publicstaticvoidmain(String...args){test(1,2,3,4,5,6);}intbyteAnd0(){intb=1;intx=0;do{b=(byte)((b^x));}while(b++<10);returnb;}privatevoida(Integeri){a(i);b(i);c(i);}privatevoidb(inti){a(i);b(i);c(i);}privatevoidc(doubled){c(d);d(d);}privatevoidd(Doubled){c(d);d(d);}privatevoide(Shorts){b(s);c(s);e(s);f(s);}privatevoidf(shorts){b(s);c(s);e(s);f(s);}voidtest1(Stringpath){try{intx=3;}catch(NullPointerExceptiont){System.out.println("FileNotfound");if(path==null){return;}throwt;}finally{System.out.println("Fred");if(path==null){thrownewIllegalStateException();}}}privatefinalList<Integer>stuff=newArrayList<>();{stuff.add(1);stuff.add(2);}publicstaticintplus(booleant,inta,intb){intc=t?a:b;returnc;}//LambdaIntegerlambdaInvoker(intarg,UnaryFunction<Integer,Integer>fn){returnfn.invoke(arg);}//LambdapublicinttestLambda(){returnlambdaInvoker(3,x->x+1);//return1;}//LambdapublicIntegertestLambda(List<Integer>stuff,inty,booleanb){returnstuff.stream().filter(b?x->x>y:x->x<3).findFirst().orElse(null);}//streampublicstatic<YextendsInteger>voidtestStream(List<Y>list){IntStreams=list.stream().filter(x->{System.out.println(x);returnx.intValue()/2==0;}).map(x->(Integer)x+2).mapToInt(x->x);s.toArray();}//switchpublicvoidtestSwitch1(){inti=0;switch(((Long)(i+1L))+""){case"1":System.out.println("one");}}//switchpublicvoidtestSwitch2(Stringstring){switch(string){case"apples":System.out.println("apples");break;case"pears":System.out.println("pears");break;}}//switchpublicstaticvoidtestSwitch3(intx){while(true){if(x<5){switch("test"){case"okay":continue;default:continue;}}System.out.println("wowx2!");}}}此處本來貼出了所有工具的反編譯結果,但是礙于文章長度和閱讀體驗,沒有放出來,不過我在個人博客的發布上是有完整代碼的,個人網站排版比較自由,可以使用 Tab 選項卡的方式展示 。如果需要查看可以訪問 https://www.wdbyte.com 進行查看 。Procyon
看到 Procyon 的反編譯結果,還是比較吃驚的,在正常反編譯的情況下,反編譯后的代碼基本上都是原汁原味 。唯一一處反編譯后和源碼語法上有變化的地方,是一個集合的初始化操作略有不同 。
//源碼publicHardCode(Aa,Bb){}privatefinalList<Integer>stuff=newArrayList<>();{stuff.add(1);stuff.add(2);}//Procyon反編譯privatefinalList<Integer>stuff;publicHardCode(finalAa,finalBb){(this.stuff=newArrayList<Integer>()).add(1);this.stuff.add(2);}而其他部分代碼,比如裝箱拆箱,Switch 語法,Lambda 表達式,流式操作以及流程控制等,幾乎完全一致,閱讀沒有障礙 。裝箱拆箱操作反編譯后完全一致,沒有多余的類型轉換代碼 。
//源碼privatevoida(Integeri){a(i);b(i);c(i);}privatevoidb(inti){a(i);b(i);c(i);}privatevoidc(doubled){c(d);d(d);}privatevoidd(Doubled){c(d);d(d);}privatevoide(Shorts){b(s);c(s);e(s);f(s);}privatevoidf(shorts){b(s);c(s);e(s);f(s);}//Procyon反編譯privatevoida(finalIntegeri){this.a(i);this.b(i);this.c(i);}privatevoidb(finalinti){this.a(i);this.b(i);this.c(i);}privatevoidc(finaldoubled){this.c(d);this.d(d);}privatevoidd(finalDoubled){this.c(d);this.d(d);}privatevoide(finalShorts){this.b(s);this.c(s);this.e(s);this.f(s);}privatevoidf(finalshorts){this.b(s);this.c(s);this.e(s);this.f(s);}Switch 部分也是一致,流程控制部分也沒有變化 。//源碼switchpublicvoidtestSwitch1(){inti=0;switch(((Long)(i+1L))+""){case"1":System.out.println("one");}}publicvoidtestSwitch2(Stringstring){switch(string){case"apples":System.out.println("apples");break;case"pears":System.out.println("pears");break;}}publicstaticvoidtestSwitch3(intx){while(true){if(x<5){switch("test"){case"okay":continue;default:continue;}}System.out.println("wowx2!");}}//Procyon反編譯publicvoidtestSwitch1(){finalinti=0;finalStringstring=(Object)(i+1L)+"";switch(string){case"1":{System.out.println("one");break;}}}publicvoidtestSwitch2(finalStringstring){switch(string){case"apples":{System.out.println("apples");break;}case"pears":{System.out.println("pears");break;}}}publicstaticvoidtestSwitch3(finalintx){while(true){if(x<5){finalStrings="test";switch(s){case"okay":{continue;}default:{continue;}}}else{System.out.println("wowx2!");}}}Lambda 表達式和流式操作完全一致 。//源碼//LambdapublicIntegertestLambda(List<Integer>stuff,inty,booleanb){returnstuff.stream().filter(b?x->x>y:x->x<3).findFirst().orElse(null);}//streampublicstatic<YextendsInteger>voidtestStream(List<Y>list){IntStreams=list.stream().filter(x->{System.out.println(x);returnx.intValue()/2==0;}).map(x->(Integer)x+2).mapToInt(x->x);s.toArray();}//Procyon反編譯publicIntegertestLambda(finalList<Integer>stuff,finalinty,finalbooleanb){returnstuff.stream().filter(b?(x->x>y):(x->x<3)).findFirst().orElse(null);}publicstatic<YextendsInteger>voidtestStream(finalList<Y>list){finalIntStreams=list.stream().filter(x->{System.out.println(x);returnx/2==0;}).map(x->x+2).mapToInt(x->x);s.toArray();}流程控制,反編譯后發現丟失了無異議的代碼部分,閱讀來說并無障礙 。//源碼voidtest1(Stringpath){try{intx=3;}catch(NullPointerExceptiont){System.out.println("FileNotfound");if(path==null){return;}throwt;}finally{System.out.println("Fred");if(path==null){thrownewIllegalStateException();}}}//Procyon反編譯voidtest1(finalStringpath){try{}catch(NullPointerExceptiont){System.out.println("FileNotfound");if(path==null){return;}throwt;}finally{System.out.println("Fred");if(path==null){thrownewIllegalStateException();}}}鑒于代碼篇幅,下面幾種的反編譯結果的對比只會列出不同之處,相同之處會直接跳過 。CFR
CFR 的反編譯結果多出了類型轉換部分,個人來看沒有 Procyon 那么原汁原味,不過也算是十分優秀,測試案例中唯一不滿意的地方是對 while continue 的處理 。
//CFR反編譯結果//裝箱拆箱privatevoide(Shorts){this.b(s.shortValue());//裝箱拆箱多出了類型轉換部分 。this.c(s.shortValue());//裝箱拆箱多出了類型轉換部分 。this.e(s);this.f(s);}//流程控制voidtest1(Stringpath){try{intn=3;//流程控制反編譯結果十分滿意,原汁原味,甚至此處的無意思代碼都保留了 。}catch(NullPointerExceptiont){System.out.println("FileNotfound");if(path==null){return;}throwt;}finally{System.out.println("Fred");if(path==null){thrownewIllegalStateException();}}}// Lambda 和 Stream 操作完全一致,不提 。// switch 處,反編譯后功能一致,但是流程控制有所更改 。publicstaticvoidtestSwitch3(intx){block6:while(true){//源碼中只有while(true),反編譯后多了block6if(x<5){switch("test"){case"okay":{continueblock6;//多了block6}}continue;}System.out.println("wowx2!");}}JD-CoreJD-Core 和 CFR 一樣,對于裝箱拆箱操作,反編譯后不再一致,多了類型轉換部分,而且自動優化了數據類型 。個人感覺,如果是反編譯后自己閱讀,通篇的數據類型的轉換優化影響還是挺大的 。
//JD-Core反編譯privatevoidd(Doubled){c(d.doubleValue());//新增了數據類型轉換d(d);}privatevoide(Shorts){b(s.shortValue());//新增了數據類型轉換c(s.shortValue());//新增了數據類型轉換e(s);f(s.shortValue());//新增了數據類型轉換}privatevoidf(shorts){b(s);c(s);e(Short.valueOf(s));//新增了數據類型轉換f(s);}// Stream 操作中,也自動優化了數據類型轉換,閱讀起來比較累 。publicstatic<YextendsInteger>voidtestStream(List<Y>list){IntStreams=list.stream().filter(x->{System.out.println(x);return(x.intValue()/2==0);}).map(x->Integer.valueOf(x.intValue()+2)).mapToInt(x->x.intValue());s.toArray();}Jadx首先 Jadx 在反編譯測試代碼時,報出了錯誤,反編譯的結果里也有提示不能反編 Lambda 和 Stream 操作,反編譯結果中變量名稱雜亂無章,流程控制幾乎陣亡,如果你想反編譯后生物肉眼閱讀,Jadx 肯定不是一個好選擇 。
//Jadx反編譯privatevoide(Shorts){b(s.shortValue());//新增了數據類型轉換c((double)s.shortValue());//新增了數據類型轉換e(s);f(s.shortValue());//新增了數據類型轉換}privatevoidf(shorts){b(s);c((double)s);//新增了數據類型轉換e(Short.valueOf(s));//新增了數據類型轉換f(s);}publicinttestLambda(){//testLambda反編譯失敗/*r2=this;r0=3r1=move-resultjava.lang.Integerr0=r2.lambdaInvoker(r0,r1)intr0=r0.intValue()returnr0*/thrownewUnsupportedOperationException("Methodnotdecompiled:com.wdbyte.decompiler.HardCode.testLambda():int");}//Stream反編譯失敗publicstatic<Yextendsjava.lang.Integer>voidtestStream(java.util.List<Y>r3){/*java.util.stream.Streamr1=r3.stream()r2=move-resultjava.util.stream.Streamr1=r1.filter(r2)r2=move-resultjava.util.stream.Streamr1=r1.map(r2)r2=move-resultjava.util.stream.IntStreamr0=r1.mapToInt(r2)r0.toArray()return*/thrownewUnsupportedOperationException("Methodnotdecompiled:com.wdbyte.decompiler.HardCode.testStream(java.util.List):void");}publicvoidtestSwitch2(Stringstring){// switch 操作無法正常閱讀,和源碼出入較大 。charc=65535;switch(string.hashCode()){case-1411061671:if(string.equals("apples")){c=0;break;}break;case106540109:if(string.equals("pears")){c=1;break;}break;}switch(c){case0:System.out.println("apples");return;case1:System.out.println("pears");return;default:return;}}FernflowerFernflower 的反編譯結果總體上還是不錯的,不過也有不足,它對變量名稱的指定,以及 Switch 字符串時的反編譯結果不夠理想 。
//反編譯后變量命名不利于閱讀,有很多var變量intbyteAnd0(){intb=1;bytex=0;bytevar10000;do{intb=(byte)(b^x);var10000=b;b=b+1;}while(var10000<10);returnb;}//switch反編譯結果使用了hashCodepublicstaticvoidtestSwitch3(intx){while(true){if(x<5){Stringvar1="test";bytevar2=-1;switch(var1.hashCode()){case3412756:if(var1.equals("okay")){var2=0;}default:switch(var2){case0:}}}else{System.out.println("wowx2!");}}}總結五種反編譯工具比較下來,結合反編譯速度和代碼可讀性測試,看起來 CFR 工具勝出,Procyon 緊隨其后 。CFR 在速度上不落下風,在反編譯的代碼可讀性上,是最好的,主要體現在反編譯后的變量命名、裝箱拆箱、類型轉換,流程控制上,以及對 Lambda 表達式、Stream 流式操作和 Switch 的語法支持上,都非常優秀 。根據 CFR 官方介紹,已經支持到 Java 14 語法,而且截止寫這篇測試文章時,CFR 最新提交代碼時間是在 11 小時之前,更新速度很快 。
文中部分代碼已經上傳 GitHub 的 niumoo/lab-notes 倉庫 的 java-decompiler 目錄 。
- java校驗日期格式的正則 Java校驗日期格式
- kindle死機沒反應 kindle 死機 長按不能重啟
- java怎樣運行cmd cmd中如何運行java
- java與c語言哪個好 java語言好還是c好
- 單反拍星空的參數設置 單反拍星空參數怎么設置
- 命令行查看java版本 查看JAVA版本
- mac電腦連接了打印機卻打印不出來 mac連接打印機沒反應
- python和java有什么關系 JAVA和Python的區別
- 女生的這四個生理反應和動作 男生喜歡你的肢體動作
- 什么是正向設計,什么是反向設計 反向設計法
