在一般的Java應用開發(fā)過程中,開發(fā)人員使用Java的方式比較簡單。打開慣用的IDE,編寫Java源代碼,再利用IDE提供的功能直接運行Java 程序就可以了。這種開發(fā)模式背后的過程是:開發(fā)人員編寫的是Java源代碼文件(.java),IDE會負責調用Java的編譯器把Java源代碼編譯成平臺無關的字節(jié)代碼(byte code),以類文件的形式保存在磁盤上(.class)。Java虛擬機(JVM)會負責把Java字節(jié)代碼加載并執(zhí)行。Java通過這種方式來實現(xiàn)其“編寫一次,到處運行(Write once, run anywhere)” 的目標。Java類文件中包含的字節(jié)代碼可以被不同平臺上的JVM所使用。Java字節(jié)代碼不僅可以以文件形式存在于磁盤上,也可以通過網絡方式來下載,還可以只存在于內存中。JVM中的類加載器會負責從包含字節(jié)代碼的字節(jié)數(shù)組(byte[])中定義出Java類。在某些情況下,可能會需要動態(tài)的生成 Java字節(jié)代碼,或是對已有的Java字節(jié)代碼進行修改。這個時候就需要用到本文中將要介紹的相關技術。首先介紹一下如何動態(tài)編譯Java源文件。
創(chuàng)新互聯(lián)建站,為您提供重慶網站建設公司、成都網站制作、網站營銷推廣、網站開發(fā)設計,對服務成都廣告推廣等多個行業(yè)擁有豐富的網站建設及推廣經驗。創(chuàng)新互聯(lián)建站網站建設公司成立于2013年,提供專業(yè)網站制作報價服務,我們深知市場的競爭激烈,認真對待每位客戶,為客戶提供賞心悅目的作品。 與客戶共同發(fā)展進步,是我們永遠的責任!
動態(tài)編譯Java源文件
在一般情況下,開發(fā)人員都是在程序運行之前就編寫完成了全部的Java源代碼并且成功編譯。對有些應用來說,Java源代碼的內容在運行時刻才能確定。這個時候就需要動態(tài)編譯源代碼來生成Java字節(jié)代碼,再由JVM來加載執(zhí)行。典型的場景是很多算法競賽的在線評測系統(tǒng)(如PKU JudgeOnline),允許用戶上傳Java代碼,由系統(tǒng)在后臺編譯、運行并進行判定。在動態(tài)編譯Java源文件時,使用的做法是直接在程序中調用Java編譯器。
JSR 199引入了Java編譯器API。如果使用JDK 6的話,可以通過此API來動態(tài)編譯Java代碼。比如下面的代碼用來動態(tài)編譯最簡單的Hello World類。該Java類的代碼是保存在一個字符串中的。
01 public class CompilerTest {
02 public static void main(String[] args) throws Exception {
03 String source = "public class Main { public static void main(String[] args) {System.out.println(\"Hello World!\");} }";
04 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
05 StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
06 StringSourceJavaObject sourceObject = newCompilerTest.StringSourceJavaObject("Main", source);
07 Iterable extends JavaFileObject fileObjects = Arrays.asList(sourceObject);
08 CompilationTask task = compiler.getTask(null, fileManager, null,null, null, fileObjects);
09 boolean result = task.call();
10 if (result) {
11 System.out.println("編譯成功。");
12 }
13 }
14
15 static class StringSourceJavaObject extends SimpleJavaFileObject {
16
17 private String content = null;
18 public StringSourceJavaObject(String name, String content) ??throwsURISyntaxException {
19 super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension), Kind.SOURCE);
20 this.content = content;
21 }
22
23 public CharSequence getCharContent(boolean ignoreEncodingErrors) ??throws IOException {
24 return content;
25 }
26 }
27 }
如果不能使用JDK 6提供的Java編譯器API的話,可以使用JDK中的工具類com.sun.tools.javac.Main,不過該工具類只能編譯存放在磁盤上的文件,類似于直接使用javac命令。
另外一個可用的工具是Eclipse JDT Core提供的編譯器。這是Eclipse Java開發(fā)環(huán)境使用的增量式Java編譯器,支持運行和調試有錯誤的代碼。該編譯器也可以單獨使用。Play框架在內部使用了JDT的編譯器來動態(tài)編譯Java源代碼。在開發(fā)模式下,Play框架會定期掃描項目中的Java源代碼文件,一旦發(fā)現(xiàn)有修改,會自動編譯 Java源代碼。因此在修改代碼之后,刷新頁面就可以看到變化。使用這些動態(tài)編譯的方式的時候,需要確保JDK中的tools.jar在應用的 CLASSPATH中。
下面介紹一個例子,是關于如何在Java里面做四則運算,比如求出來(3+4)*7-10的值。一般的做法是分析輸入的運算表達式,自己來模擬計算過程??紤]到括號的存在和運算符的優(yōu)先級等問題,這樣的計算過程會比較復雜,而且容易出錯。另外一種做法是可以用JSR 223引入的腳本語言支持,直接把輸入的表達式當做JavaScript或是JavaFX腳本來執(zhí)行,得到結果。下面的代碼使用的做法是動態(tài)生成Java源代碼并編譯,接著加載Java類來執(zhí)行并獲取結果。這種做法完全使用Java來實現(xiàn)。
01 private static double calculate(String expr) throws CalculationException {
02 String className = "CalculatorMain";
03 String methodName = "calculate";
04 String source = "public class " + className
05 + " { public static double " + methodName + "() { return " + expr +"; } }";
06 //省略動態(tài)編譯Java源代碼的相關代碼,參見上一節(jié)
07 boolean result = task.call();
08 if (result) {
09 ClassLoader loader = Calculator.class.getClassLoader();
10 try {
11 Class? clazz = loader.loadClass(className);
12 Method method = clazz.getMethod(methodName, new Class?[] {});
13 Object value = method.invoke(null, new Object[] {});
14 return (Double) value;
15 } catch (Exception e) {
16 throw new CalculationException("內部錯誤。");
17 }
18 } else {
19 throw new CalculationException("錯誤的表達式。");
20 }
21 }
上面的代碼給出了使用動態(tài)生成的Java字節(jié)代碼的基本模式,即通過類加載器來加載字節(jié)代碼,創(chuàng)建Java類的對象的實例,再通過Java反射API來調用對象中的方法。
Java字節(jié)代碼增強
Java 字節(jié)代碼增強指的是在Java字節(jié)代碼生成之后,對其進行修改,增強其功能。這種做法相當于對應用程序的二進制文件進行修改。在很多Java框架中都可以見到這種實現(xiàn)方式。Java字節(jié)代碼增強通常與Java源文件中的注解(annotation)一塊使用。注解在Java源代碼中聲明了需要增強的行為及相關的元數(shù)據(jù),由框架在運行時刻完成對字節(jié)代碼的增強。Java字節(jié)代碼增強應用的場景比較多,一般都集中在減少冗余代碼和對開發(fā)人員屏蔽底層的實現(xiàn)細節(jié)上。用過JavaBeans的人可能對其中那些必須添加的getter/setter方法感到很繁瑣,并且難以維護。而通過字節(jié)代碼增強,開發(fā)人員只需要聲明Bean中的屬性即可,getter/setter方法可以通過修改字節(jié)代碼來自動添加。用過JPA的人,在調試程序的時候,會發(fā)現(xiàn)實體類中被添加了一些額外的 域和方法。這些域和方法是在運行時刻由JPA的實現(xiàn)動態(tài)添加的。字節(jié)代碼增強在面向方面編程(AOP)的一些實現(xiàn)中也有使用。
代碼1:int static b;
代碼2:this.a = 10;
代碼3:Ca.b = 10;
代碼4:m1();
代碼5:new Ca().m2();
代碼6:System.out.println(Ca.b);
代碼7:System.out.println(s1.b);
代碼8:System.out.println(s2.b);
代碼2賦值問題,在靜態(tài)的m1()方法中,不能直接調用非靜態(tài)的變量,可以調用自身this對象。
在靜態(tài)的方法中,可以直接調用靜態(tài)的變量和方法,其它的要實例后再調用。希望回答對你有所幫助。
嗯,你說的沒錯,可以看一下java8的官方文檔
forEach(Consumer? super E action)
Performs the given action for each element of the Iterable
until all elements have been processed or the action throws an
exception.
::
作者:青虹
鏈接:
來源:知乎
著作權歸作者所有。商業(yè)轉載請聯(lián)系作者獲得授權,非商業(yè)轉載請注明出處。
代碼如下:
package lamda;
import java.util.function.Function;
import java.util.function.IntToDoubleFunction;
public class Recursive {
public static void main(String[] args) {
IntToDoubleFunction[] foo = {null};
foo[0] = x - (x == 0) ? 1 : x * foo[0].applyAsDouble(x - 1);
System.out.println(foo[0].applyAsDouble(10));
// fuck generics
FunctionInteger, Integer[] fuckRecusive = new Function[1];
fuckRecusive[0] = (x) - x == 1 ? 1 : x * (fuckRecusive[0]).apply(x - 1);
System.out.println(fuckRecusive[0].apply(10));
}
}
為了支持函數(shù)式編程,Java 8引入了Lambda表達式.
在Java 8中采用的是內部類來實現(xiàn)Lambda表達式.具體實現(xiàn)代碼,可以通過debug看, 同時通過字節(jié)碼查看工具及反編譯工具來驗證.
自從lambda表達式成為Java語言的一部分之后,Java集合(Collections)API就面臨著大幅變化。而JSR 355(規(guī)定了Java lambda表達式的標準)的正式啟用更是使得Java集合API變的過時不堪。
盡管我們可以從頭實現(xiàn)一個新的集合框架(比如“Collection II”),但取代現(xiàn)有的集合框架是一項非常艱難的工作,因為集合接口滲透了Java生態(tài)系統(tǒng)的每個角落,將它們一一換成新類庫需要相當長的時間。因此,我們決定采取演化的策略(而非推倒重來)以改進集合API: 為現(xiàn)有的接口(例如Collection,List和Stream)增加擴展方法;
在類庫中增加新的流(stream,即java.util.stream.Stream)抽象以便進行聚集(aggregation)操作;
改造現(xiàn)有的類型使之可以提供流視圖(stream view);
改造現(xiàn)有的類型使之可以容易的使用新的編程模式,這樣用戶就不必拋棄使用以久的類庫,例如ArrayList和HashMap
(當然這并不是說集合API會常駐永存,畢竟集合API在設計之初并沒有考慮到lambda表達式。我們可能會在未來的JDK中添加一個更現(xiàn)代的集合類庫)。
除了上面的改進,還有一項重要工作就是提供更加易用的并行(Parallelism)庫。盡管Java平臺已經對并行和并發(fā)提供了強有力的支持,然而開發(fā)者在實際工作(將串行代碼并行化)中仍然會碰到很多問題。因此,我們希望Java類庫能夠既便于編寫串行代碼也便于編寫并行代碼,因此我們把編程的重點從具體執(zhí)行細節(jié)(how computation should be formed)轉移到抽象執(zhí)行步驟(what computation should be perfomed)。