java编译器jdk版本_以编程方式确定Java类的JDK编译版本
java編譯器jdk版本
當(dāng)需要確定使用哪個(gè)JDK版本來(lái)編譯特定的Java .class文件時(shí), 通常使用的方法是使用javap并在javap輸出中查找列出的“主要版本”。 我在我的博客文章Autoboxing,Unboxing和NoSuchMethodError中引用了這種方法,但是在繼續(xù)以編程方式實(shí)現(xiàn)此方法之前,請(qǐng)先在此進(jìn)行詳細(xì)說(shuō)明。
以下代碼段演示了如何對(duì)commons-configuration-1.10.jar包含的Apache Commons Configuration類(lèi)ServletFilterCommunication運(yùn)行javap -verbose 。
我在上面顯示的屏幕快照中圈了“主要版本”。 在“主要版本:”之后列出的數(shù)字(本例中為49)表示用于編譯此類(lèi)的JDK版本是J2SE 5 。 Java類(lèi)文件的Wikipedia頁(yè)面列出了與每個(gè)JDK版本相對(duì)應(yīng)的“主要版本”數(shù)字:
| 52 | Java SE 8 |
| 51 | Java SE 7 |
| 50 | Java SE 6 |
| 49 | J2SE 5 |
| 48 | JDK 1.4 |
| 47 | JDK 1.3 |
| 46 | JDK 1.2 |
| 45 | JDK 1.1 |
這是確定用于編譯.class文件的JDK版本的簡(jiǎn)便方法,但是對(duì)目錄或JAR文件中的多個(gè)類(lèi)執(zhí)行此操作可能會(huì)變得很乏味。 如果我們可以以編程方式檢查此主要版本,以便可以編寫(xiě)腳本,則會(huì)更容易。 幸運(yùn)的是,Java確實(shí)支持這一點(diǎn)。 Matthias Ernst發(fā)布了“ 代碼段:以編程方式調(diào)用javap ”,其中他演示了如何使用JDK工具JAR中的JavapEnvironment以編程方式執(zhí)行javap功能,但是有一種更簡(jiǎn)便的方法來(lái)標(biāo)識(shí).class文件中指示字節(jié)用于編譯的JDK版本。
博客文章“ 從類(lèi)格式主要/次要版本信息中識(shí)別Java編譯器版本 ”和StackOverflow線程“ Java API來(lái)找出要為類(lèi)文件編譯的JDK版本? ”演示了如何使用DataInputStream從Java .class文件中讀取相關(guān)的兩個(gè)字節(jié)。
對(duì)用于編譯.class文件的JDK版本的基本訪問(wèn)
下一個(gè)代碼清單演示了訪問(wèn).class文件的JDK編譯版本的簡(jiǎn)約方法。
final DataInputStream input = new DataInputStream(new FileInputStream(pathFileName)); input.skipBytes(4); final int minorVersion = input.readUnsignedShort(); final int majorVersion = input.readUnsignedShort();該代碼在感興趣的(假定的) .class文件上實(shí)例化FileInputStream ,并且該FileInputStream用于實(shí)例化DataInputStream 。 有效.class文件的前四個(gè)字節(jié)包含數(shù)字,指示該數(shù)字是有效的Java編譯類(lèi),因此被跳過(guò)。 接下來(lái)的兩個(gè)字節(jié)被讀為無(wú)符號(hào)的short,代表次要版本。 在那之后是最重要的兩個(gè)字節(jié)。 它們也以未簽名的縮寫(xiě)形式讀入,代表主要版本。 這個(gè)主要版本與JDK的特定版本直接相關(guān)。 Java虛擬機(jī)規(guī)范的 第4章 (“類(lèi)文件格式”)中描述了這些有效字節(jié)(magic,minor_version和major_version)。
在上面的代碼清單中,為方便理解,僅跳過(guò)了“魔術(shù)” 4個(gè)字節(jié)。 但是,我更喜歡檢查這四個(gè)字節(jié)以確保它們是.class文件所期望的。 JVM規(guī)范解釋了對(duì)這前四個(gè)字節(jié)的期望。 它的值為0xCAFEBABE。” 下一個(gè)代碼清單將修改前面的代碼清單,并添加檢查以確保所討論的文件在Java編譯的.class文件中。 請(qǐng)注意,該檢查專(zhuān)門(mén)使用十六進(jìn)制表示形式CAFEBABE以提高可讀性。
final DataInputStream input = new DataInputStream(new FileInputStream(pathFileName)); // The first 4 bytes of a .class file are 0xCAFEBABE and are "used to // identify file as conforming to the class file format." // Use those to ensure the file being processed is a Java .class file. final String firstFourBytes =Integer.toHexString(input.readUnsignedShort())+ Integer.toHexString(input.readUnsignedShort()); if (!firstFourBytes.equalsIgnoreCase("cafebabe")) {throw new IllegalArgumentException(pathFileName + " is NOT a Java .class file."); } final int minorVersion = input.readUnsignedShort(); final int majorVersion = input.readUnsignedShort();在檢查了其中最重要的部分之后,下面的代碼清單提供了我稱(chēng)為ClassVersion.java的Java類(lèi)的完整清單。 它具有main(String[])函數(shù),因此可以從命令行輕松使用其功能。
ClassVersion.java
import static java.lang.System.out;import java.io.DataInputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map;/*** Prints out the JDK version used to compile .class files. */ public class ClassVersion {private static final Map<Integer, String> majorVersionToJdkVersion;static{final Map<Integer, String> tempMajorVersionToJdkVersion = new HashMap<>();tempMajorVersionToJdkVersion.put(45, "JDK 1.1");tempMajorVersionToJdkVersion.put(46, "JDK 1.2");tempMajorVersionToJdkVersion.put(47, "JDK 1.3");tempMajorVersionToJdkVersion.put(48, "JDK 1.4");tempMajorVersionToJdkVersion.put(49, "J2SE 5");tempMajorVersionToJdkVersion.put(50, "Java SE 6");tempMajorVersionToJdkVersion.put(51, "Java SE 7");tempMajorVersionToJdkVersion.put(52, "Java SE 8");majorVersionToJdkVersion = Collections.unmodifiableMap(tempMajorVersionToJdkVersion);}/*** Print (to standard output) the major and minor versions of JDK that the* provided .class file was compiled with.** @param pathFileName Name of (presumably) .class file from which the major* and minor versions of the JDK used to compile that class are to be* extracted and printed to standard output.*/public static void printCompiledMajorMinorVersions(final String pathFileName){try{final DataInputStream input = new DataInputStream(new FileInputStream(pathFileName));printCompiledMajorMinorVersions(input, pathFileName);}catch (FileNotFoundException fnfEx){out.println("ERROR: Unable to find file " + pathFileName);}}/*** Print (to standard output) the major and minor versions of JDK that the* provided .class file was compiled with.** @param input DataInputStream instance assumed to represent a .class file* from which the major and minor versions of the JDK used to compile* that class are to be extracted and printed to standard output.* @param dataSourceName Name of source of data from which the provided* DataInputStream came.*/public static void printCompiledMajorMinorVersions(final DataInputStream input, final String dataSourceName){ try{// The first 4 bytes of a .class file are 0xCAFEBABE and are "used to// identify file as conforming to the class file format."// Use those to ensure the file being processed is a Java .class file.final String firstFourBytes =Integer.toHexString(input.readUnsignedShort())+ Integer.toHexString(input.readUnsignedShort());if (!firstFourBytes.equalsIgnoreCase("cafebabe")){throw new IllegalArgumentException(dataSourceName + " is NOT a Java .class file.");}final int minorVersion = input.readUnsignedShort();final int majorVersion = input.readUnsignedShort();out.println(dataSourceName + " was compiled with "+ convertMajorVersionToJdkVersion(majorVersion)+ " (" + majorVersion + "/" + minorVersion + ")");}catch (IOException exception){out.println("ERROR: Unable to process file " + dataSourceName+ " to determine JDK compiled version - " + exception);}}/*** Accepts a "major version" and provides the associated name of the JDK* version corresponding to that "major version" if one exists.** @param majorVersion Two-digit major version used in .class file.* @return Name of JDK version associated with provided "major version."*/public static String convertMajorVersionToJdkVersion(final int majorVersion){return majorVersionToJdkVersion.get(majorVersion) != null? majorVersionToJdkVersion.get(majorVersion): "Unknown JDK version for 'major version' of " + majorVersion;}public static void main(final String[] arguments){if (arguments.length < 1){out.println("USAGE: java ClassVersion <nameOfClassFile.class>");System.exit(-1);}printCompiledMajorMinorVersions(arguments[0]);} }下一個(gè)屏幕快照演示了如何針對(duì)自己的.class文件運(yùn)行此類(lèi)。
如PowerShell控制臺(tái)的最后一個(gè)屏幕快照所示,該類(lèi)的版本是使用JDK 8編譯的。
有了這個(gè)ClassVersion ,我們就能使用Java告訴我們何時(shí)編譯特定的.class文件。 但是,這比簡(jiǎn)單地使用javap并手動(dòng)尋找“主要版本”要容易得多。 使它更強(qiáng)大和更易于使用的是在腳本中使用它。 考慮到這一點(diǎn),我現(xiàn)在將重點(diǎn)放在利用此類(lèi)的Groovy腳本中,以識(shí)別用于編譯JAR或目錄中的多個(gè).class文件的JDK版本。
下一個(gè)代碼清單是可以使用ClassVersion類(lèi)的Groovy腳本的示例。 該腳本演示了用于編譯指定目錄及其子目錄中的所有.class文件的JDK版本。
displayCompiledJdkVersionsOfClassFilesInDirectory.groovy
#!/usr/bin/env groovy// displayCompiledJdkVersionsOfClassFilesInDirectory.groovy // // Displays the version of JDK used to compile Java .class files in a provided // directory and in its subdirectories. //if (args.length < 1) {println "USAGE: displayCompiledJdkVersionsOfClassFilesInDirectory.groovy <directory_name>"System.exit(-1) }File directory = new File(args[0]) String directoryName = directory.canonicalPath if (!directory.isDirectory()) {println "ERROR: ${directoryName} is not a directory."System.exit(-2) }print "\nJDK USED FOR .class COMPILATION IN DIRECTORIES UNDER " println "${directoryName}\n" directory.eachFileRecurse { file ->String fileName = file.canonicalPathif (fileName.endsWith(".class")){ClassVersion.printCompiledMajorMinorVersions(fileName)} } println "\n"接下來(lái)顯示的是剛剛列出的腳本生成的輸出示例。
接下來(lái)顯示另一個(gè)Groovy腳本,該腳本可用于標(biāo)識(shí)用于編譯指定目錄或其子目錄之一中的任何JAR文件中的.class文件的JDK版本。
displayCompiledJdkVersionsOfClassFilesInJar.groovy
#!/usr/bin/env groovy// displayCompiledJdkVersionsOfClassFilesInJar.groovy // // Displays the version of JDK used to compile Java .class files in JARs in the // specified directory or its subdirectories. //if (args.length < 1) {println "USAGE: displayCompiledJdkVersionsOfClassFilesInJar.groovy <jar_name>"System.exit(-1) }import java.util.zip.ZipFile import java.util.zip.ZipExceptionString rootDir = args ? args[0] : "." File directory = new File(rootDir) directory.eachFileRecurse { file->if (file.isFile() && file.name.endsWith("jar")){try{zip = new ZipFile(file)entries = zip.entries()entries.each{ entry->if (entry.name.endsWith(".class")){println "${file}"print "\t"ClassVersion.printCompiledMajorMinorVersions(new DataInputStream(zip.getInputStream(entry)), entry.name)}}}catch (ZipException zipEx){println "Unable to open file ${file.name}"}} } println "\n"接下來(lái)顯示針對(duì)本文前面部分使用的JAR運(yùn)行此腳本的輸出的早期部分。 JAR中包含的所有.class文件都具有JDK的版本,它們針對(duì)打印到標(biāo)準(zhǔn)輸出而編譯。
其他想法
剛剛顯示的腳本演示了一些實(shí)用程序,這些實(shí)用程序是通過(guò)能夠以編程方式訪問(wèn)用于編譯Java類(lèi)的JDK版本而實(shí)現(xiàn)的。 這里是增強(qiáng)這些腳本的其他一些想法。 在某些情況下,我使用了這些增強(qiáng)功能,但此處并未顯示它們以保持更好的清晰度并避免使發(fā)布時(shí)間更長(zhǎng)。
- ClassVersion.java可能是用Groovy編寫(xiě)的。
- ClassVersion.java返回單個(gè)信息而不是將其打印到標(biāo)準(zhǔn)輸出,則其功能將更加靈活。 同樣,即使返回返回的字符串,也比假設(shè)調(diào)用者希望將輸出寫(xiě)入標(biāo)準(zhǔn)輸出更為靈活。
- 這將是容易鞏固上述腳本指示用于編譯個(gè)體JDK版本.class以及在目錄中直接訪問(wèn)文件.class包含在JAR文件的文件從相同的腳本。
- 演示腳本的一種有用的變體是返回在特定版本的JDK之前或之后,使用特定版本的JDK編譯的所有.class文件的腳本。
結(jié)論
這篇文章的目的是演示以編程方式確定用于將Java源代碼編譯為.class文件的JDK版本。 文章演示了基于JVM類(lèi)文件結(jié)構(gòu)的“主要版本”字節(jié)確定用于編譯的JDK版本,然后演示了如何使用Java API讀取和處理.class文件,以及識(shí)別用于編譯它們的JDK版本。 最后,用Groovy編寫(xiě)的幾個(gè)示例腳本演示了以編程方式訪問(wèn)此信息的價(jià)值。
翻譯自: https://www.javacodegeeks.com/2015/02/programmatically-determining-java-classs-jdk-compilation-version.html
java編譯器jdk版本
總結(jié)
以上是生活随笔為你收集整理的java编译器jdk版本_以编程方式确定Java类的JDK编译版本的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: primefaces_PrimeFace
- 下一篇: quasar 异步回调_Java IO基