程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA編程入門知識 >> 深入Java7的一些新特性以及對腳本語言支持API的介紹

深入Java7的一些新特性以及對腳本語言支持API的介紹

編輯:JAVA編程入門知識

1.switch條件語句中可以加入字符串了,實現方法是利用了字符串的hashcode()值作業真正的值
2.增加了一種可以在字面量中使用的進制,二進制,通過在數字前面加“0b”或“0B”
3.在數字字面量中使用下劃線來分隔數字方便閱讀,不影響數值大小。基本原則是前後都是數字的才可以出現下劃線
4.java7對異常做了兩個改動:

  4.1.支持在一個catch子句中同時捕獲多個異常,另外一個是在捕獲並重新拋出異常時的異常類型更加精確。java7中Throwable類增加addSuppressed方法,當一個異常被拋出的時候,可能有其他異常因為該異常而被抑制住,從而無法正常拋出。這時可以通過addSuppressed方法把這些被抑制的方法記錄下來,被抑制的異常會出現在拋出的異常的堆棧信息中,也可以通過getSuppressed方法來獲取這些異常。這樣做的好處是不會丟失任何異常,方便開發人員測試。
代碼如下:

public class ReadFile(){
     public void read(String filename) throws IOException {
         FileInputStream input = null;
         IOException readException = null;
         try {
             input = new FileInputStream(filename);
         } catch (IOException ex) {
             readException = ex;
         } finally {
             if (input != null) {
                 try {
                     input.close();
                 } catch (IOException ex) {
                     if (readException != null) {
                         readException.addSuppressed(ex);
                     } else {
                         readException = ex;
                     }
                 }
             }
             if (readException != null) {
                 throw readException;
             }
         }
     }
 }

這種做法的關鍵在於把finally語句中產生的異常通過addSuppressed方法加到try語句產生的異常中。

java7改進了catch子句的語法,允許在其中指定多種異常,每個異常類型之間使用“|”來分隔。需要注意的是在catch子句中聲明捕獲的這些異常,不能出現重復的類型,也不允許其中的某個異常是另外一個異常參數的子類,否則會出現編譯錯誤(從小到大寫就沒問題)。如果在catch子句中聲明了多個異常,那麼異常參數的具體類型是所有這些異常類型的最小上界。

  4.2使用try(申請資源){業務處理}來自動釋放資源,能夠被try語句所管理的資源需要滿足一個條件,那就是其java類要實現java.lang.AutoCloseable接口,否則會出現編譯錯誤。當需要釋放資源的時候該接口的close方法會被自動調用。

5. 優化變長參數的方法調用:
j2se5.0中引入的一個新特性就是允許在方法聲明中使用可變長度的參數。一個方法的最後一個形式參數可以被指定為代表任意多個相同類型的參數。在調用的時候,這些參數是以數組的形式來傳遞的。在方法體中也可以按照數組的方式來引用這些參數。

6. java7引入了一個新的注解@SafeVarargs.如果開發人員確信某個使用了可變長度參數的方法,在與泛型類一起使用進不會出現類似的情況,就可以用這個注解進行聲明。@SafeVarargs注解只能用在參數長度可變的方法或構造方法上,且方法必須聲明為static或final,否則會出現編譯錯誤。一個方法使用@SafeVarargs注解的前提是,開發人員必須確保這個方法的實現中對泛型類型參數的處理不會引發類型安全問題。

7.java中在java虛擬機中支持一些腳本語言是通過腳本引擎。實際上腳本引擎管理器共支持三種查找引擎方式,分別通過名稱,文件擴展名和MIME類型來完成。如
代碼如下:

public void greet() throws ScriptException {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");
        if (engine == null) {
            throw new RuntimeException("找不到JavaScript語言執行引擎");
        }
        engine.eval("println('hello');");
    }

還可以通過getEngingByExtension("js")和getEngineByMimeType("text/javascript")來查找到。得到腳本引擎ScriptEngine的對象後,通過其eval方法可以執行一段代碼,並返回這段代碼的執行結果。

7.1 語言綁定:
腳本語言支持api的一個很大優勢在於它規范了java語言與腳本語言之間的交互方式,使java語言編寫的程序可以與腳本之間進行雙向的方法調用和數據傳遞。數據傳遞是通過語言綁定對象來完成的。所謂的語言綁定對象就是一個簡單的哈希表,用來存放和獲取需要共享的數據。所有數據都對應這個哈希表中的一個條目,是簡單的名值對。接口javax.script.Bingings定義了語言綁定對象的接口,它繼承自java.util.Map接口。一個腳本引擎在執行過程中可能會使用多個語言綁定對象。不同語言綁定對象的作用域不同。在默認情況下,腳本引擎會提供多個語言綁定對象,用來存放在執行過程中產生的全局對象等。ScriptEnging類提供了put和get方法對腳本引擎中特定使用哉的默認語言綁定對象進行操作。程序可以直接使用這個默認的語言綁定對象,也可以使用自己的語言綁定對象。在腳本執行過程中,可以將語言綁定對象看成是一個額外的變量映射表。在解析變量值的時候,語言綁定對象中的名稱也會被考慮在內。腳本 執行過程中產生的全局變量等內容,會出現在語言綁定對象中。通過這種方式,就完成了Java與腳本語言之間的雙向數據傳遞。
如通過ScriptEngine的put方法向腳本引擎默認的語言綁定對象中添加了一個名為“name”的字符串,接著在腳本中直接根據名稱來引用這個對象。同樣,在腳本中創建的全局變量“message”也可以通過ScriptEnging的get方法來獲取。這樣就實現了Java程序與腳本之間的雙向數據傳遞。數據傳遞過程中的類型轉換是由腳本引擎來完成的,轉換規則取決於具體的語言的語法
代碼如下:

public void useDefaultBinging() throws ScriptException {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");
        // ScriptEngine engine = getJavaScriptEnging();
        engine.put("name", "World");
        engine.eval("var message = 'hello,'+name;");
        engine.eval("println(message)");
        Object obj = engine.get("message");
        System.out.println(obj);
    }

在大多數情況下,使用ScriptEnging的put和get方法就足夠了。如果僅使用put和get方法,語言綁定對象本身對於開發人員來說是透明的。在某些情況下,需要使用程序自己的語言綁定對象,比如語言綁定對象中包含了程序自己獨有的數據。如果希望使用自己的語言綁定對象,可以調用腳本引擎的creatBingings方法或創建一個javax.script.SimpleBingings對象,並傳遞給腳本引擎的eval方法如:
代碼如下:

public void useCustomBinding()throws ScriptException
    {
    ScriptEngine engine = getJavaScriptEngine();
    Bindings bindings = new SimpleBindings();
    bindings.put("hobby","play games");
    engine.eval("println('I like'+hobby);",bindings);
    }

通過eval方法傳遞的語言綁定對象,僅在當前eval調用中生效,並不會改變引擎默認的語言綁定對象

7.2 腳本執行上下文
與腳本引擎執行相關的另外一個重要接口是javax.script.ScriptContext,其中包含腳本引擎執行過程中的相關上下文信息,可以與JavaEE中servlet規范中的javax.servlet.ServletContext接口來進行類比。腳本引擎通過引上下文對象來獲取與腳本執行相關的信息,也允許開發人員通過此對象來配置腳本引擎的行為。該上下對象中主要包含以下3類信息。
7.2.1 輸入與輸出
首先介紹與腳本輸入和輸出相關的配置信息,其中包括腳本在執行中用來讀取數據輸入的java.io.Reader對象以及輸出正確內容和出錯信息的java.io.Writer對象。在默認情況下,腳本的輸入輸出都發生在標准控制台中,如果希望把腳本的輸出寫入到文件 中,可以使用如下代碼。通過setWriter方法把腳本的輸出重定向到一個文件 中。通過ScriptContext的setReader和setErrorWriter方法可以分別設置腳本執行時的數據輸入來源和產生錯誤時出錯信息的輸出 目的。
代碼如下:

public void scriptToFile()throws IOException,ScriptException{
        ScriptEngine engine = getJavaScriptEngine();
        ScriptContext context = engine.getContext();
        context.setWriter(new FileWriter("output.txt"));
        engine.eval("println('hello world!');");
        }

7.2.2 自定義屬性

ScriptContext中也有與ServletContext中類似的獲取和設置屬性的方法,即setAttribute和getAttribute.所不同的是,ScriptContext中的屬性是有作用域之分的。不同作用域的區別在於查找時的順序不同,每個作用域都以一個對應的整數表示其查找順序。該整數值越小,說明查找時的順序越優先。優先級高的作用域中的屬性會隱藏優先級低中的同名屬性。因此,設置屬性時需要顯式指定所在的作用域。在獲取屬性的時候,即可選擇指定的作用域中查找,也可以選擇根據作用域優先級自動進行查找。
不過腳本執行上下文實現 中包含的作用域是固定的。開發人員不能隨意定義自己的作用域。通過ScriptContext的getScopes方法可以得到所有可用的作用域列表。SciptContext中預先定義了兩個作用域:常量ScriptContext.ENGINE_SCOPE表示的作用域對應的是當前的腳本引擎,而ScriptContext.GLOBAL_SCOPE表示的作用域對應的是從同一引擎工廠中創建出來的所有腳本引擎對象。前者的優先級較高。如下例:
代碼如下:

public void scriptContextAttribute(){
        ScriptEngine engine = getJavaScriptEnging();
        ScriptContext context = engine.getContext();
        context.setAttribute("name","World",ScriptContext.GLOBAL_SCOPE);
        context.setAttribute("name","Bob",ScriptContext.ENGINE_SCOPE);
        context.getAttribute("name");//值為Bob
        }

7.2.3 語言綁定對象

腳本執行上下文中的最後一類信息是語言綁定對象。語言綁定對象也是與作用域相對應的。同樣的作用域優先級順序對語言綁定對象也適用。這樣的優先級順序會對腳本執行時的變量解析產生影響。如下例:
代碼如下:

public void scriptContextBindings()throws ScriptException
    {
    ScriptEngine engine = getJavaScriptEnging();
    ScriptContext context = engine.getContext();
    Bindings bindings1 = engine.createBindings();
    bindings1.put("name","World");
    context.setBindings(bindings1,ScriptContext.GLOBAL_SCOPE);
    Bindings bindings2 = engine.createBindings();
    bindings2.put("name","Bob");
    context.setBindings(bindings2,ScriptContext.ENGINE_SCOPE);
    engine.eval("println(name);");//結果為Bob
    }

還可以Bindings bindings = context.getBindings(ScriptContext.ENGINE_SCOPE);

bindings.put("name","World")
engine.eval("println(name);");

7.3 腳本的編譯:
腳本語言一般是解釋執行的。腳本引擎在運行時需要先解析腳本之後再執行。一般來說,通過解釋執行的方式運行腳本的速度比編譯之後再運行會慢一些。當一段腳本需要被多次重復執行時,可以先對腳本進行編譯。編譯之後的腳本執行時不需要重復解析,可以提高執行效率。不是所有腳本引擎都支持對腳本進行編譯。如果腳本引擎支持這一特性,它會實現javax.script.Compilable接口來聲明這一點。腳本引擎的使用者可以利用這個能力來提高需要多次執行的腳本的運行效率。Java SE中自帶的JavaScript腳本引擎是支持對腳本進行編譯的。
如下代碼中,Compilable接口的compile方法用來對腳本代碼進行編譯,編譯的結果用javax.script.CompiledScript來表示。由於不是所有的腳本引擎都支持Compilable接口,因此這裡需要用instanceof進行判斷。在run方法中,通過CompiledScript的eval方法就可以執行腳本。代碼中把一段腳本重復執行了100次,以此說明編譯完的腳本在重復執行時的性能優勢。
代碼如下:

public CompiledScript compile(String scriptText) throws ScriptException {
        ScriptEngine engine = getJavaScriptEngine();
        if (engine instanceof Compilable) {
            CompiledScript script = ((Compilable) engine).compile(scriptText);
        }
        return null;
    }

    public void run(String scriptText) throws ScriptException {
        CompiledScript script = compile(scriptText);
        if (script == null) {
            return;
        }
        for (int i = 0; i < 100; i++) {
            script.eval();
        }
    }

CompiledScript的eval方法所接受的參數與ScriptEngine的eval方法是相同的。

7.4 腳本中方法調用
在腳本中,最常見的和最實用的就是方法。有些腳本引擎允許使用者單獨調用腳本中某個方法。支持這種方法調用方式的腳本引擎可以實現javax.script.Invocable接口。通過Invocable接口可以調用腳本中的頂層方法,也可以調用對象中的成員方法。如果腳本中頂層方法或對象中的成員方法實現了Java中的接口,可以通過Invocable接口中的方法來獲取腳本中相應的Java接口的實現對象。這樣就可以在Java語言中定義接口,在腳本中實現接口。程序中使用該接口的其他部分代碼並不知道接口是由腳本來實現的。與Compilable接口一樣,ScriptEngine對於Invocable接口的實現也是可選的。
下面代碼通過Invocable接口的invokeFunction來調用腳本中的頂層方法,調用時的參數會被傳遞給腳本中的方法。因為JavaSE自帶的JavaScript腳本引擎實現了Invocable接口,所以這裡省去了對引擎是否實現了Invocalbe接口的判斷
在java中調用腳本頂層方法的示例:
代碼如下:

public void invokeFunction()throws ScriptException,NoSuchMethodException{
        ScriptEngine engine = getJavaScriptEngine();
        String scriptText ="function greet(name) { println('hello,'+name );}";
        engine.eval(scriptText);
        Invocable invocable=(Invocable)engine;
        invocable.invokeFunction("greet","World");
        }

如果被調用方法是腳本中對象的成員方法,就需要使用invokeMethod方法,如下面代碼中所示,代碼中getGreeting方法是屬於對象obj的,在調用的時候需要把這個對象作為參數傳遞進去。

//在Java中調用腳本對象的成員方法的示例
代碼如下:

public void inokeMethod()throws ScriptException,NoSuchMethodException
    {
    ScriptEngine engine = getJavaScriptEngine();
    String scriptText = "var obj={ getGreeting:function(name){return 'Hello,'+name;}};";
    engine.eval(scriptText);
    Invocable invocable = (Invocable)engine;
    Object scope = engine.get("obj");
    Object result = invocable.invokeMethod(scope,"getGreeting","Alxx");
    System.out.println(result);
    }

方法invokeMethod與方法invokeFunction用法差不多,區別在於invokeMethod要指定包含待調用方法的對象。

7.5 腳本中實現java接口

在有些腳本引擎中,可以在Java語言中定義接口,並在腳本中編寫接口的實現,這樣程序中的其他部分可以只同Java接口交互,並不需要關心接口是由什麼方式來實現的。在下面代碼中Greet是用Java定義的接口,其中包含一個getGreeting方法。在腳本中實現這個接口,通過getInterface方法可以得到由腳本實現的這個接口的對象,並調用其中的方法。
代碼如下:

public void useInterface() throws ScriptException {
        ScriptEngine engine = getJavaScriptEngine();
        String scriptText = "function getGreeting(name){return 'Hello,'+name;}";
        engine.eval(scriptText);
        Invocable invocable = (Invocable) engine;
        Greet greet = invocable.getInterface(Greet.class);
        System.out.println(greet.getGreeting("World"));
    }

上面中的接口的實現是由腳本中的頂層方法來完成的。同樣的,也可以由腳本中對象的成員方法來實現。對於這種情況,getInterface方法另外一種重載形式可以接受一個額外的參數來指定接口實現所在的對象。

由於腳本語言的語法簡單和靈活,非常適用於沒有或只有少量編程背景的用戶來使用,這些用戶可以通過腳本語言來定制程序的業務邏輯和用戶界面等,通過腳本語言可以在程序的易用性和靈活性之間達到一個比較好的平衡。比如腳本語言Lua就被廣泛應用在游戲開發中,用來對游戲的內部行為和用戶界面進行定制。

8. 反射API在為Java程序帶來靈活性的同時,也產生了額外的性能代價,由於反射API的實現機制,對於相同的操作,比如調用一個方法,用反射API來動態實現比直接在源代碼中編寫的方式大概慢一到兩個數量級。隨著Java虛擬機實現的改進,反射API的性能已經有了非常大的提升。但是這種性能的差距是客觀存在的,因此,在某些對性能要求比較高的應用中,要慎用反射API。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved