1. 程式人生 > 其它 >jdk7新特性 JDK7 新特性

jdk7新特性 JDK7 新特性

JDK7 新特性

 

想更詳細的熟悉JDK7新特性可以瀏覽官方介紹

JDK7新特性的目錄導航:

  • 二進位制字面值
  • switch 語句支援 String
  • try-with-resources
  • catch 多個型別異常
  • 字面值中使用下劃線
  • 型別推斷
  • 改進泛型型別可變引數
  • 其它

二進位制字面值

在Java SE 7,整數型別(byte,short,int 和 long)也可以使用二進位制數。要指定二進位制,請新增字首0b或0B編號。以下示例顯示了二進位制:

// 一個 8-bit 'byte' 值:
byte aByte = (byte)0b00100001;

// 一個 16-bit 'short' 值:
short aShort = (short)0b1010000101000101;

// 一些 32-bit 'int' 值:
int anInt1 = 0b10100001010001011010000101000101;
int anInt2 = 0b101;
int anInt3 = 0B101; // B可以是大寫 或 小寫.

// 一個 64-bit 'long' 值。 注意 'L' 字尾:
long aLong = 0b1010000101000101101000010100010110100001010001011010000101000101L;

 二進位制可以使資料之間的關係比以十六進位制或八進位制更明顯。例如,以下陣列中的每個連續數字都移動一位:

public static final int [] phases = {
        0b00110001,
        0b01100010,
        0b11000100,
        0b10001001,
        0b00010011,
        0b00100110,
        0b01001100,
        0b10011000
}

在十六進位制中,數字之間的關係並不明顯:

public static final int[] phases = {
        0x31, 0x62, 0xC4, 0x89, 0x13, 0x26, 0x4C, 0x98
}

您可以在程式碼中使用二進位制常量來驗證一個規範文件,(例如假想的8位微處理器的模擬器)進行驗證:

public State decodeInstruction(int instruction, State state) {
        if ((instruction & 0b11100000) == 0b00000000) {
            final int register = instruction & 0b00001111;
            switch (instruction & 0b11110000) {
                case 0b00000000: return state.nop();
                case 0b00010000: return state.copyAccumTo(register);
                case 0b00100000: return state.addToAccum(register);
                case 0b00110000: return state.subFromAccum(register);
                case 0b01000000: return state.multiplyAccumBy(register);
                case 0b01010000: return state.divideAccumBy(register);
                case 0b01100000: return state.setAccumFrom(register);
                case 0b01110000: return state.returnFromCall();
                default: throw new IllegalArgumentException();
            }
        } else {
            final int address = instruction & 0b00011111;
            switch (instruction & 0b11100000) {
                case 0b00000000: return state.jumpTo(address);
                case 0b00100000: return state.jumpIfAccumZeroTo(address);
                case 0b01000000: return state.jumpIfAccumNonzeroTo(address);
                case 0b01100000: return state.setAccumFromMemory(address);
                case 0b10100000: return state.writeAccumToMemory(address);
                case 0b11000000: return state.callTo(address);
                default: throw new IllegalArgumentException();
            }
        }
    }

您可以使用二進位制來使點陣圖更具可讀性:

public static final short[] HAPPY_FACE = {
        (short)0b0000011111100000,
        (short)0b0000100000010000,
        (short)0b0001000000001000,
        (short)0b0010000000000100,
        (short)0b0100000000000010,
        (short)0b1000011001100001,
        (short)0b1000011001100001,
        (short)0b1000000000000001,
        (short)0b1000000000000001,
        (short)0b1001000000001001,
        (short)0b1000100000010001,
        (short)0b0100011111100010,
        (short)0b0010000000000100,
        (short)0b0001000000001000,
        (short)0b0000100000010000,
        (short)0b0000011111100000
}

 

switch 語句支援 String

在JDK7中,可以swicth表示式中使用String物件:

public String getTypeOfDayWithSwitchStatement(String dayOfWeekArg) {
    String typeOfDay;
    switch (dayOfWeekArg) {
        case "Monday":
            typeOfDay = "Start of work week";
            break;
        case "Tuesday":
        case "Wednesday":
        case "Thursday":
            typeOfDay = "Midweek";
            break;
        case "Friday":
            typeOfDay = "End of work week";
            break;
        case "Saturday":
        case "Sunday":
            typeOfDay = "Weekend";
            break;
        default:
            throw new IllegalArgumentException("Invalid day of the week: " + dayOfWeekArg);
    }
    return typeOfDay;
}

該switch語句將String其表示式中的物件與與每個case標籤關聯的表示式進行比較,就好像它使用該String.equals方法一樣;因此,程式碼中String物件的比較switch區分大小寫。Java編譯器通過switch使用String物件的 if-then-else 語句比鏈式語句生成通常更高效的位元組碼。

 

try-with-resources

try-with-resources可以自動關閉相關的資源(只要該資源實現了AutoCloseable介面,jdk7為絕大部分資源物件都實現了這個介面)

以下示例從檔案讀取一行。它使用一個BufferedReader從檔案中讀取資料的例項。BufferedReader是程式結束後必須關閉的資源:

static String readFirstLineFromFile(String path) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
}

在此示例中,在try-with-resources語句中宣告的資源是一個 BufferedReader。宣告語句出現在try關鍵字後面的括號內。該類BufferedReader在Java SE 7及更高版本中實現該介面java.lang.AutoCloseable。由於BufferedReader例項是在try-with-resource語句中宣告的,因此無論try語句是正常還是意外中斷(若BufferedReader.readLine丟擲一個IOException),它都將被關閉。

public interface Closeable extends AutoCloseable
public abstract class Reader implements Readable, Closeable 
public class BufferedReader extends Reader

如上是BufferedReader的繼承關係,他最終有實現AutoCloseable介面。

在JDK7之前,無論try語句是正常還是意外中斷,你都可以使用finally塊來確保資源已關閉。以下示例使用finally代替try-with-resources語句的塊:

static String readFirstLineFromFileWithFinallyBlock(String path) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        if (br != null) br.close();
    }
}

然而,在這個例子中,如果方法readLine和close兩者都丟擲異常,那麼readFirstLineFromFileWithFinallyBlock方法丟擲從finally塊丟擲的異常;從try塊中丟擲的異常被抑制。

相反,在本例中readFirstLineFromFile,如果try塊和try-with-resources語句都丟擲異常,則此方法丟擲該try塊丟擲的異常;從try-with-resources塊丟擲的異常被抑制。

 

您可以在try-with-resources語句中宣告一個或多個資源。以下示例:

public static void writeToFileZipFileContents(String zipFileName, String outputFileName)
        throws java.io.IOException {

    java.nio.charset.Charset charset = java.nio.charset.Charset.forName("US-ASCII");
    java.nio.file.Path outputFilePath = java.nio.file.Paths.get(outputFileName);

    // Open zip file and create output file with try-with-resources statement

    try (
            java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName);
            java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
    ) {

        // Enumerate each entry

        for (java.util.Enumeration entries = zf.entries(); entries.hasMoreElements();) {

            // Get the entry name and write it to the output file

            String newLine = System.getProperty("line.separator");
            String zipEntryName = ((java.util.zip.ZipEntry)entries.nextElement()).getName() + newLine;
            writer.write(zipEntryName, 0, zipEntryName.length());
        }
    }
}

在本例中,try-with-resources語句有兩個宣告ZipFile 和 BufferedWriter使用分號分隔。當跟隨它的程式碼塊正常結束或由於異常終止時,將按照BufferedWriter再ZipFile物件此順序自動呼叫close方法。請注意,close資源的方法按照其建立的相反順序進行呼叫。

 

以下示例使用try-with-resources語句自動關閉java.sql.Statement物件:

public static void viewTable(Connection con) throws SQLException {

    String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES";

    try (Statement stmt = con.createStatement()) {

        ResultSet rs = stmt.executeQuery(query);

        while (rs.next()) {
            String coffeeName = rs.getString("COF_NAME");
            int supplierID = rs.getInt("SUP_ID");
            float price = rs.getFloat("PRICE");
            int sales = rs.getInt("SALES");
            int total = rs.getInt("TOTAL");
            System.out.println(coffeeName + ", " + supplierID + ", " + price +
                    ", " + sales + ", " + total);
        }

    } catch (SQLException e) {
        JDBCTutorialUtilities.printSQLException(e);
    }
}

注意:try-with-resources語句可以像普通try語句一樣擁有catch和finally程式碼塊。在try-with-resources語句中,任何catch或finally塊在宣告的資源關閉後才執行。

 

抑制異常

可以在try-with-resources語句的程式碼塊中丟擲異常。在該示例中writeToFileZipFileContents,可以從該try塊中引發異常,並且try在嘗試關閉ZipFile和BufferedWriter物件時最多可以從 try-with-resources 語句中丟擲兩個異常。如果從try塊中丟擲異常,並且從try-with-resources語句丟擲一個或多個異常,那麼從try-with-resources語句丟擲的異常將被抑制,並且該塊引發的異常將被writeToFileZipFileContents方法丟擲。您可以通過Throwable.getSuppressed從該try塊引發的異常中呼叫方法來檢索這些抑制的異常。

實現AutoCloseable或Closeable介面的類

請參閱Javadoc AutoCloseable和Closeable介面以獲取實現這些介面之一的類的列表。該Closeable介面繼承了AutoCloseable介面。介面Closeable的close方法丟擲IOException異常,而介面AutoCloseable的close方法丟擲Exception異常。因此,AutoCloseable介面的子類可以重寫此close方法的行為來丟擲特殊的異常,例如IOException根本沒有異常。

 

catch 多個型別異常

這一章涵蓋兩個部分:Catch 多種型別異常 和 分析異常並重新丟擲精準異常

catch 多種型別異常

在JDK7,單個catch塊可以處理多種型別的異常。此功能可以減少程式碼重複並減少異常捕捉過度。

思考下面的例子,其中catch塊中的有重複程式碼:

catch (IOException ex) {
     logger.log(ex);
     throw ex;
catch (SQLException ex) {
     logger.log(ex);
     throw ex;
}

在JDK7之前的版本中,很難建立一個通用方法來消除重複的程式碼,因為該變數ex具有不同的型別。

以下示例在JDK7,可消除重複的程式碼:

catch (IOException|SQLException ex) {
    logger.log(ex);
    throw ex;
}

該catch塊指出可以處理的異常型別,每個異常型別用豎線(|)分隔。

注意:如果一個catch塊處理多個異常型別,則該catch引數是隱含的final。在這個例子中,catch引數ex是final,因此你不能在catch塊中為它分配任何值。

通過編譯處理多個異常型別的catch塊生成的位元組碼將會更小(因此優於)編譯許多隻處理一個異常型別的catch塊。處理多個異常型別的catch塊不會在編譯器生成的位元組碼中建立重複;位元組碼沒有重複的異常處理程式。

  

分析異常並重新丟擲精準異常

與早期版本的JDK相比,JDK7 編譯器對重新產生的異常執行更精確的分析。這使您可以在throws方法宣告的子句中指定更具體的異常型別。

思考下面例子:

static class FirstException extends Exception { }
static class SecondException extends Exception { }
public void rethrowException(String exceptionName) throws Exception {
    try {
        if (exceptionName.equals("First")) {
            throw new FirstException();
        } else {
            throw new SecondException();
        }
    } catch (Exception e) {
        throw e;
    }
}

這個例子的try塊可能會丟擲FirstException或者SecondException。假設你想在rethrowException方法中throws這些異常型別。在JDK 7之前的版本中,您不能這樣做。由於catch子句的異常引數e是Exception型別,並且catch塊重新丟擲異常引數e,所以只能在rethrowException方法丟擲Exception異常型別。

在JDK7中的編譯器可以通過catch塊確定FirstException和SecondException異常。即使該catch子句的異常引數e型別為Exception,編譯器也可以確定它是FirstException例項或者是SecondException例項:

  public void rethrowException(String exceptionName)
  throws FirstException, SecondException {
    try {
      // ...
    }
    catch (Exception e) {
      throw e;
    }
  }

如果catch引數分配給catch塊中的另一個值,則此分析將失效。但是,如果catch引數分配給另一個值,則必須在方法宣告Exception的throws子句中指定異常型別。

詳細地說,在JDK7及更高版本中,當您在catch子句中宣告一個或多個異常型別並重新丟擲由此catch塊處理的異常時,編譯器將驗證重新丟擲異常的型別是否滿足以下條件:

  • 該try塊可以throw它。
  • 沒有前面其他catch可以處理它。
  • 它是catch子句的異常引數之一的子型別或超型別。

JDK7編譯器允許在rethrowException方法throws丟擲指定Exception型別FirstException和SecondException。因為您可以重新丟擲一個throws宣告的任何型別的超型別。

在JDK7之前的版本中,您不能丟擲該catch子句的異常引數之一的超型別。在JDK7之前的編譯器Exception會在語句中生成錯誤未報告的異常;必須捕獲或宣告throw,編譯器檢查丟擲異常的型別是否可分配給rethrowException方法宣告的throws子句中宣告的任何型別。然而,捕捉引數的型別e是Exception,這是一種超型別,而不是一個子型別FirstException和SecondException。

  

字面值中使用下劃線

在JDK7中,任意數量的下劃線字元(_)可以出現在字面值的任意位置。這個特性使您能夠在字面值中分離數字組,這可以提高程式碼的可讀性。

例如,如果您的程式碼包含具有許多數字的數字,則可以使用下劃線字元以三個一組來分隔數字,這與使用逗號或空格等標點符號作為分隔符類似。

以下示例顯示了可以在字面值中使用下劃線的其他方法:

long creditCardNumber = 1234_5678_9012_3456L;
long socialSecurityNumber = 999_99_9999L;
float pi =     3.14_15F;
long hexBytes = 0xFF_EC_DE_5E;
long hexWords = 0xCAFE_BABE;
long maxLong = 0x7fff_ffff_ffff_ffffL;
byte nybbles = 0b0010_0101;
long bytes = 0b11010010_01101001_10010100_10010010;

您只能在數字之間放置下劃線; 你不能在下列地方放置下劃線:

  • 在數字的開頭或結尾
  • 與浮點數字中的小數點相鄰
  • 在F或L字尾之前
  • 在期望一串數字的位置(In positions where a string of digits is expected)

下面的例子演示了字面值中有效和無效的下劃線位置(突出顯示):

 

float pi1 = 3_.1415F;      // 無效; 不能在小數點附近加下劃線
float pi2 = 3._1415F;      // 無效; 不能在小數點附近加下劃線
long socialSecurityNumber1
        = 999_99_9999_L;         // 無效; 在L字尾之前不能加下劃線

int x1 = _52;              // 這是一個識別符號,而不是字面值
int x2 = 5_2;              // OK(十進位制文字)
int x3 = 52_;              // 無效; 不能在文字的末尾加上下劃線
int x4 = 5_______2;        // OK(十進位制文字)

int x5 = 0_x52;            // 無效; 不能在0x基數字首中加下劃線
int x6 = 0x_52;            // 無效; 不能在數字的開頭加下劃線
int x7 = 0x5_2;            // OK(十六進位制文字)
int x8 = 0x52_;            // 無效; 不能在數字的末尾加上下劃線

int x9 = 0_52;             // OK(八進位制文字)
int x10 = 05_2;            // OK(八進位制文字)
int x11 = 052_;            // 無效; 不能在數字的末尾加上下劃線

 

型別推斷

只要編譯器可以從上下文中推斷出型別引數,你就可以用一組空的型別引數(<>)來代替呼叫泛型類的建構函式所需的型別引數。這一對尖括號被非正式地稱為鑽石。

例如,請考慮以下變數宣告:

Map<String, List<String>> myMap = new HashMap<String, List<String>>();

在JDK7中,可以使用一組空的型別引數(<>)替換建構函式的引數化型別:

Map<String, List<String>> myMap = new HashMap<>();

請注意,為了在泛型類例項化過程中利用自動型別推斷,您必須指定菱形。在以下示例中,編譯器會生成未經檢查的轉換警告,因為HashMap()建構函式引用的是HashMap原始型別,而不是Map<String, List<String>>型別:

Map<String, List<String>> myMap = new HashMap(); //未經檢查的轉換警告

JDK7支援泛型例項建立的型別推斷; 如果建構函式的引數化型別在上下文中顯而易見,則只能使用型別推斷。例如,以下示例沒有編譯:

List<String> list = new ArrayList<>();
list.add("A");
// 以下語句應該失敗,因為addAll需要
// Collection<? extends String>
list.addAll(new ArrayList<>());

請注意,鑽石通常用於方法呼叫; 但是,建議您將菱形主要用於變數宣告。

相比之下,下面的例子會編譯:

// 以下語句會編譯:
List<? extends String> list2 = new ArrayList<>();
list.addAll(list2);

泛型和非泛型類的型別推斷和建構函式

請注意,建構函式在泛型和非泛型類中都可以是通用的(換句話說,宣告它們自己的正式型別引數)。思考下面的例子:

class MyClass<X> {
  <T> MyClass(T t) {
    // ...
  }
}

思考下面這個類的例項MyClass,它在JDK7和以前的版本中是有效的:

new MyClass<Integer>("")

該語句建立引數化型別的例項MyClass<Integer>;該語句明確指定了泛型類Integer的正式型別引數X的型別MyClass<X>請注意,此泛型類的建構函式包含一個正式的型別引數T。編譯器推斷這個泛型類的建構函式String的形式型別引數的型別T(因為這個建構函式的實際引數是一個String物件)

JDK7之前版本的編譯器能夠推斷泛型建構函式的實際型別引數,類似於泛型方法。但是,如果使用磚石(<>),JDK7中的編譯器可以推斷例項化的泛型類的實際型別引數<>。思考以下示例,該示例適用於JDK7及更高版本:

MyClass<Integer> myObject = new MyClass<>("");

在這個例子中,編譯器推斷泛型類Integer的形式型別引數X的型別MyClass<X>。它推斷這個泛型類的建構函式String的形式型別引數的型別T。

 

改進泛型型別可變引數

這一章涵蓋以下幾個部分:

  • 堆汙染
  • 可變引數方法和非具體化形式引數
  • 可變引數方法在傳遞非具體引數的缺點
  • 抑制可變引數方法在傳遞不具體形式引數的警告

堆汙染

大多數引數化型別(如ArrayList<Number>和List<String>)都是非具體化的型別。非具體化型別是一種在執行時不確定的型別。在編譯時,非具體化型別在此過程中經歷了一個稱為型別擦除的過程,擦除引數型別和型別引數相關的資訊。這確保了在泛型之前建立的Java庫和應用程式的二進位制相容性。因為型別擦除在編譯時從引數型別中刪除資訊,所以這些型別是非具體化的。

當引數化型別的變數引用不是該引數化型別的物件時,會發生堆汙染。這種情況只有在程式執行一些操作時才會發生,從而在編譯時產生不受約束的警告。一個未經檢查的警告如果產生,無論是在編譯時(在編譯時型別檢查規則的限制範圍內)或執行時。一個涉及引數化型別(例如,一次轉換或方法呼叫)的操作的正確性是無法被驗證的。

思考下面的例子:

List l = new ArrayList<Number>();
List<String> ls = l;       // 未經檢查的警告
l.add(0, new Integer(42)); // 另一個未經檢查的警告
String s = ls.get(0);      // 丟擲型別轉換異常 ClassCastException

在型別擦除時,ArrayList<Number>型別和List<String>型別分別變成ArrayList和List。

該ls變數具有引數化型別List<String>,當List引用l賦值給ls時,編譯器會生成未經檢查的警告;如果編譯器無法在編譯時確定,而且JVM也無法在執行時確定l它不是一個List<String>型別;因此,產生堆汙染。

因此,在編譯時,編譯器會在add語句處生成另一個未經檢查的警告。編譯器無法確定變數l是List<String>型別還是List<Integer>型別(以及另一種發生堆汙染的情況)。然而,編譯器不會在get語句中產生警告或錯誤。此宣告有效; 它呼叫List<String>的get方法來獲得一個String物件。相反,在執行時,get語句會丟擲ClassCastException。

詳細地說,當List<Number>物件l被賦值給另一個List<String>物件ls時,就會出現堆汙染情況,它有一個不同的靜態型別。然而,編譯器仍然允許這個賦值。它必須允許這個賦值來保證與不支援泛型的JDK版本相容性。因為型別擦除,List<Number>和List<String>變成List。因此,編譯器允許物件l的賦值個體ls物件,因為ls是一個List型別。

另外,l.add呼叫該方法時會發生堆汙染情況。該方法add第二引數應該是String,但實際引數是Integer。但是,編譯器仍然允許這種方法呼叫。因為型別擦除,add方法第二個引數(List<E>.add(int,E))變成Object。因此,編譯器允許這種方法呼叫。因為在型別擦除之後,該l.add方法可以新增任何型別,包括Integer型別物件,因為它Object的子類。

 

可變引數方法和非具體化形式引數

思考下面的ArrayBuilder.addToList方法。它將型別為T的elements可變引數,新增到List listArg引數中:

 1 import java.util.*;
 2 
 3 public class ArrayBuilder{
 4 
 5     public static <T> void addToList (List<T> listArg, T... elements) {
 6         for (T x : elements) {
 7             listArg.add(x);
 8         }
 9     }
10 
11     public static void faultyMethod(List<String>... l) {
12         Object[] objectArray = l;  // 有效
13         objectArray[0] = Arrays.asList(new Integer(42));
14         String s = l[0].get(0);    // 丟擲ClassCastException異常
15     }
16 
17 }
 1 import java.util.*;
 2 
 3 public class HeapPollutionExample {
 4 
 5     public static void main(String[] args) {
 6 
 7         List<String> stringListA = new ArrayList<String>();
 8         List<String> stringListB = new ArrayList<String>();
 9 
10         ArrayBuilder.addToList(stringListA, "Seven", "Eight", "Nine");
11         ArrayBuilder.addToList(stringListA, "Ten", "Eleven", "Twelve");
12         List<List<String>> listOfStringLists = new ArrayList<List<String>>();
13         ArrayBuilder.addToList(listOfStringLists, stringListA, stringListB);
14 
15         ArrayBuilder.faultyMethod(Arrays.asList("Hello!"), Arrays.asList("World!"));
16     }
17 }

JDK7編譯器為該方法的定義生成以下警告ArrayBuilder.addToList:

warning: [varargs] Possible heap pollution from parameterized vararg type T

當編譯器遇到可變引數方法時,它將可變引數形式引數轉換為陣列。但是,Java程式語言不允許建立引數化型別的陣列。在ArrayBuilder.addToList方法中,編譯器將可變引數形式引數T... elements轉換為形式引數T[] elements。即陣列。但是,由於型別擦除,編譯器將可變引數形式引數轉換為Object[] 。因此,可能存在堆汙染

下面是ArrayBuilder.addToList方法的反編譯結果:

public static <T> void addToList(List<T> listArg, T... elements) {
    Object[] var2 = elements;
    int var3 = elements.length;

    for(int var4 = 0; var4 < var3; ++var4) {
        T x = var2[var4];
        listArg.add(x);
    }

}

注意:JDK5和6編譯器在ArrayBuilder.addToList呼叫時會生成警告HeapPollutionExample。這些編譯器不會在宣告上生成警告。但是,JDK7會在宣告和呼叫上生成警告(除非這些警告被註解取代)。

 

可變引數方法在傳遞非具體引數的缺點

該方法ArrayBuilder.faultyMethod顯示了編譯器為什麼會警告您這些型別的方法。該方法的第一條語句將可變引數形式引數l賦值給objectArgs的Object陣列:

Object[] objectArray = l;

這裡可能產生堆汙染。可變引數l可以賦值給objectArray陣列。但是,編譯器不會在此語句中生成未經檢查的警告。當編譯器編譯List<String>... l 為List[] l 時已經生成警告。List[]是Object[]型別的子類,所以該宣告是有效的。

因此,如果您將List任何型別的物件賦值給陣列objectArray,則編譯器不會發出警告或錯誤,如此語句所示:

objectArray[0] = Arrays.asList(new Integer(42));

這個語句將List<Integer>賦值給objectArray陣列。

如果你呼叫以下方法:

ArrayBuilder.faultyMethod(Arrays.asList("Hello!"), Arrays.asList("World!"));

呼叫以下方法,在執行時JVM會丟擲ClassCastException異常。

String s = l[0].get(0);    // 丟擲ClassCastException 

變數l儲存的是List<Integer>型別,但確期待是一個List<String>型別。

 

抑制可變引數方法在傳遞不具體形式引數的警告

如果聲明瞭一個具體引數化的可變引數方法。且方法的體不會丟擲ClassCastException或其他類似的異常。您可以使用以下幾種方式來抑制編譯器為這些可變引數方法生成的警告:

  • 將@SafeVarargs註解新增到靜態非建構函式方法宣告中:
  • 將@SuppressWarnings({"unchecked", "varargs"})註解新增到方法宣告中:
  • 使用編譯器選項-Xlint:varargs

例如,以下ArrayBuilder類的有兩個方法addToList2和addToList3:

 1 import java.util.*;
 2 
 3 public class ArrayBuilder {
 4 
 5     public static <T> void addToList (List<T> listArg, T... elements) {
 6         for (T x : elements) {
 7             listArg.add(x);
 8         }
 9     }
10 
11     @SuppressWarnings({"unchecked", "varargs"})
12     public static <T> void addToList2 (List<T> listArg, T... elements) {
13         for (T x : elements) {
14             listArg.add(x);
15         }
16     }
17 
18     @SafeVarargs
19     public static <T> void addToList3 (List<T> listArg, T... elements) {
20         for (T x : elements) {
21             listArg.add(x);
22         }
23     }
24 
25     // ...
26 
27 }
 1 import java.util.*;
 2 
 3 public class HeapPollutionExample {
 4 
 5     // ...
 6 
 7     public static void main(String[] args) {
 8 
 9         // ...
10 
11         ArrayBuilder.addToList(listOfStringLists, stringListA, stringListB);
12         ArrayBuilder.addToList2(listOfStringLists, stringListA, stringListB);
13         ArrayBuilder.addToList3(listOfStringLists, stringListA, stringListB);
14 
15         // ...
16 
17     }
18 }

Java編譯器為此示例生成以下警告:

  • addToList:  
    • 在該方法的宣告中:[unchecked] Possible heap pollution from parameterized vararg type T
    • 當該方法被呼叫時:[unchecked] unchecked generic array creation for varargs parameter of type List<String>[]
  • addToList2:呼叫方法時(在方法宣告中不生成警告): [unchecked] unchecked generic array creation for varargs parameter of type List<String>[]
  • addToList3:方法宣告或呼叫方法時不會生成警告。

其他

JDK7 還有許許多多的其他新特性,比如:Swing,IO and New IO,Networking 等等增強。這些可以去閱讀官方原文進行了解。下面是截圖:

前面只介紹了Java程式語言(Java Programming Language)部分,下面就簡單的介紹下增強swing,其餘不多過介紹了。

增強 Swing

Swing 的增強主要包含如下幾點:

  • JLayer 類: 是Swing元件的通用裝飾器,您能夠繪製元件並響應元件事件,而無需直接修改底層元件。
  • Nimbus Look&Feel : Nimbus Look&Feel(L&F)已經從com.sun.java.swing標準的API名稱空間轉移 到了javax.swing。檢視javax.swing.plaf.nimbus包有更詳細的介紹,雖然它不是預設的L&F,但您可以輕鬆使用它。
  • 重量級和輕量級元件: 歷史上,在同一個容器中混合重量級(AWT)和輕量級(Swing)元件是有問題的。但是,混合重量級和輕量級元件很容易在Java SE 7中完成。
  • 視窗的形狀和透明度: Java SE 7版本支援具有透明度和非矩形形狀的視窗。
  • JColorChooser 類HSL選擇: JColorChooser該類已新增HSL選項卡,允許使用者使用色相飽和度亮度(HSL)顏色模型選擇顏色。

示例程式碼:

自定義WallpaperLayerUI裝飾類繼承LayerUI重寫paint方法

 1 import javax.swing.*;
 2 import javax.swing.plaf.LayerUI;
 3 import java.awt.*;
 4 
 5 //繼承 LayerUI 實現自定義裝飾內容。
 6 public class WallpaperLayerUI extends LayerUI<JComponent> {
 7 
 8     @Override
 9     public void paint(Graphics g, JComponent c) {
10         super.paint(g, c);
11         Graphics2D g2 = (Graphics2D) g.create();
12         int w = c.getWidth();
13         int h = c.getHeight();
14         g2.setComposite(AlphaComposite.getInstance(
15                 AlphaComposite.SRC_OVER, .5f));
16         //透明顏色漸變
17         g2.setPaint(new GradientPaint(0, 0, Color.yellow, 0, h, Color.red));
18         g2.fillRect(0, 0, w, h);
19         g2.dispose();
20     }
21 }

測試程式碼:

 1 import javax.swing.*;
 2 import javax.swing.plaf.LayerUI;
 3 
 4 public class Test {
 5     public static void main(String[] args) {
 6         javax.swing.SwingUtilities.invokeLater(new Runnable() {
 7             public void run() {
 8                 createUI();
 9             }
10         });
11     }
12 
13     public static void createUI() {
14         //建立JFrame
15         JFrame f = new JFrame("Wallpaper");
16         JPanel panel = createPanel();
17 
18 //        這就是使用JDK7新特性:JLayer 類的裝飾功能。如果不使用裝飾 可以將 直接 f.add(panel)
19         LayerUI<JComponent> layerUI = new WallpaperLayerUI();
20         JLayer<JComponent> jlayer = new JLayer<JComponent>(panel, layerUI);
21         f.add (jlayer);
22 //        f.add(panel);
23 
24         //設定 JFrame 寬高 預設關閉 相對位置 和 可見。
25         f.setSize(300, 200);
26         f.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
27         f.setLocationRelativeTo (null);
28         f.setVisible (true);
29     }
30 
31     private static JPanel createPanel() {
32         //建立面板
33         JPanel p = new JPanel();
34 
35         //建立一個按鈕組,並新增 Beef、Chicken 和 Vegetable 三個單選按鈕
36         ButtonGroup entreeGroup = new ButtonGroup();
37         JRadioButton radioButton;
38         p.add(radioButton = new JRadioButton("Beef", true));
39         entreeGroup.add(radioButton);
40         p.add(radioButton = new JRadioButton("Chicken"));
41         entreeGroup.add(radioButton);
42         p.add(radioButton = new JRadioButton("Vegetable"));
43         entreeGroup.add(radioButton);
44 
45         //新增 Ketchup 、 Mustard 和 Pickles 複選框
46         p.add(new JCheckBox("Ketchup"));
47         p.add(new JCheckBox("Mustard"));
48         p.add(new JCheckBox("Pickles"));
49 
50         //新增 Special requests: 標籤 和 文字框
51         p.add(new JLabel("Special requests:"));
52         p.add(new JTextField(20));
53 
54         //新增 Place Order 按鈕
55         JButton orderButton = new JButton("Place Order");
56         p.add(orderButton);
57 
58         return p;
59     }
60 }

以上程式碼結果展示:

我將裝飾效果去掉只需將,19-21行註釋,並放開22註釋執行結果如下:

想更詳細的熟悉JDK7新特性可以瀏覽官方介紹

JDK7新特性的目錄導航:

  • 二進位制字面值
  • switch 語句支援 String
  • try-with-resources
  • catch 多個型別異常
  • 字面值中使用下劃線
  • 型別推斷
  • 改進泛型型別可變引數
  • 其它

二進位制字面值

在Java SE 7,整數型別(byte,short,int 和 long)也可以使用二進位制數。要指定二進位制,請新增字首0b或0B編號。以下示例顯示了二進位制:

// 一個 8-bit 'byte' 值:
byte aByte = (byte)0b00100001;

// 一個 16-bit 'short' 值:
short aShort = (short)0b1010000101000101;

// 一些 32-bit 'int' 值:
int anInt1 = 0b10100001010001011010000101000101;
int anInt2 = 0b101;
int anInt3 = 0B101; // B可以是大寫 或 小寫.

// 一個 64-bit 'long' 值。 注意 'L' 字尾:
long aLong = 0b1010000101000101101000010100010110100001010001011010000101000101L;

 二進位制可以使資料之間的關係比以十六進位制或八進位制更明顯。例如,以下陣列中的每個連續數字都移動一位:

public static final int [] phases = {
        0b00110001,
        0b01100010,
        0b11000100,
        0b10001001,
        0b00010011,
        0b00100110,
        0b01001100,
        0b10011000
}

在十六進位制中,數字之間的關係並不明顯:

public static final int[] phases = {
        0x31, 0x62, 0xC4, 0x89, 0x13, 0x26, 0x4C, 0x98
}

您可以在程式碼中使用二進位制常量來驗證一個規範文件,(例如假想的8位微處理器的模擬器)進行驗證:

public State decodeInstruction(int instruction, State state) {
        if ((instruction & 0b11100000) == 0b00000000) {
            final int register = instruction & 0b00001111;
            switch (instruction & 0b11110000) {
                case 0b00000000: return state.nop();
                case 0b00010000: return state.copyAccumTo(register);
                case 0b00100000: return state.addToAccum(register);
                case 0b00110000: return state.subFromAccum(register);
                case 0b01000000: return state.multiplyAccumBy(register);
                case 0b01010000: return state.divideAccumBy(register);
                case 0b01100000: return state.setAccumFrom(register);
                case 0b01110000: return state.returnFromCall();
                default: throw new IllegalArgumentException();
            }
        } else {
            final int address = instruction & 0b00011111;
            switch (instruction & 0b11100000) {
                case 0b00000000: return state.jumpTo(address);
                case 0b00100000: return state.jumpIfAccumZeroTo(address);
                case 0b01000000: return state.jumpIfAccumNonzeroTo(address);
                case 0b01100000: return state.setAccumFromMemory(address);
                case 0b10100000: return state.writeAccumToMemory(address);
                case 0b11000000: return state.callTo(address);
                default: throw new IllegalArgumentException();
            }
        }
    }

您可以使用二進位制來使點陣圖更具可讀性:

public static final short[] HAPPY_FACE = {
        (short)0b0000011111100000,
        (short)0b0000100000010000,
        (short)0b0001000000001000,
        (short)0b0010000000000100,
        (short)0b0100000000000010,
        (short)0b1000011001100001,
        (short)0b1000011001100001,
        (short)0b1000000000000001,
        (short)0b1000000000000001,
        (short)0b1001000000001001,
        (short)0b1000100000010001,
        (short)0b0100011111100010,
        (short)0b0010000000000100,
        (short)0b0001000000001000,
        (short)0b0000100000010000,
        (short)0b0000011111100000
}

 

switch 語句支援 String

在JDK7中,可以swicth表示式中使用String物件:

public String getTypeOfDayWithSwitchStatement(String dayOfWeekArg) {
    String typeOfDay;
    switch (dayOfWeekArg) {
        case "Monday":
            typeOfDay = "Start of work week";
            break;
        case "Tuesday":
        case "Wednesday":
        case "Thursday":
            typeOfDay = "Midweek";
            break;
        case "Friday":
            typeOfDay = "End of work week";
            break;
        case "Saturday":
        case "Sunday":
            typeOfDay = "Weekend";
            break;
        default:
            throw new IllegalArgumentException("Invalid day of the week: " + dayOfWeekArg);
    }
    return typeOfDay;
}

該switch語句將String其表示式中的物件與與每個case標籤關聯的表示式進行比較,就好像它使用該String.equals方法一樣;因此,程式碼中String物件的比較switch區分大小寫。Java編譯器通過switch使用String物件的 if-then-else 語句比鏈式語句生成通常更高效的位元組碼。

 

try-with-resources

try-with-resources可以自動關閉相關的資源(只要該資源實現了AutoCloseable介面,jdk7為絕大部分資源物件都實現了這個介面)

以下示例從檔案讀取一行。它使用一個BufferedReader從檔案中讀取資料的例項。BufferedReader是程式結束後必須關閉的資源:

static String readFirstLineFromFile(String path) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
}

在此示例中,在try-with-resources語句中宣告的資源是一個 BufferedReader。宣告語句出現在try關鍵字後面的括號內。該類BufferedReader在Java SE 7及更高版本中實現該介面java.lang.AutoCloseable。由於BufferedReader例項是在try-with-resource語句中宣告的,因此無論try語句是正常還是意外中斷(若BufferedReader.readLine丟擲一個IOException),它都將被關閉。

public interface Closeable extends AutoCloseable
public abstract class Reader implements Readable, Closeable 
public class BufferedReader extends Reader

如上是BufferedReader的繼承關係,他最終有實現AutoCloseable介面。

在JDK7之前,無論try語句是正常還是意外中斷,你都可以使用finally塊來確保資源已關閉。以下示例使用finally代替try-with-resources語句的塊:

static String readFirstLineFromFileWithFinallyBlock(String path) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        if (br != null) br.close();
    }
}

然而,在這個例子中,如果方法readLine和close兩者都丟擲異常,那麼readFirstLineFromFileWithFinallyBlock方法丟擲從finally塊丟擲的異常;從try塊中丟擲的異常被抑制。

相反,在本例中readFirstLineFromFile,如果try塊和try-with-resources語句都丟擲異常,則此方法丟擲該try塊丟擲的異常;從try-with-resources塊丟擲的異常被抑制。

 

您可以在try-with-resources語句中宣告一個或多個資源。以下示例:

public static void writeToFileZipFileContents(String zipFileName, String outputFileName)
        throws java.io.IOException {

    java.nio.charset.Charset charset = java.nio.charset.Charset.forName("US-ASCII");
    java.nio.file.Path outputFilePath = java.nio.file.Paths.get(outputFileName);

    // Open zip file and create output file with try-with-resources statement

    try (
            java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName);
            java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
    ) {

        // Enumerate each entry

        for (java.util.Enumeration entries = zf.entries(); entries.hasMoreElements();) {

            // Get the entry name and write it to the output file

            String newLine = System.getProperty("line.separator");
            String zipEntryName = ((java.util.zip.ZipEntry)entries.nextElement()).getName() + newLine;
            writer.write(zipEntryName, 0, zipEntryName.length());
        }
    }
}

在本例中,try-with-resources語句有兩個宣告ZipFile 和 BufferedWriter使用分號分隔。當跟隨它的程式碼塊正常結束或由於異常終止時,將按照BufferedWriter再ZipFile物件此順序自動呼叫close方法。請注意,close資源的方法按照其建立的相反順序進行呼叫。

 

以下示例使用try-with-resources語句自動關閉java.sql.Statement物件:

public static void viewTable(Connection con) throws SQLException {

    String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES";

    try (Statement stmt = con.createStatement()) {

        ResultSet rs = stmt.executeQuery(query);

        while (rs.next()) {
            String coffeeName = rs.getString("COF_NAME");
            int supplierID = rs.getInt("SUP_ID");
            float price = rs.getFloat("PRICE");
            int sales = rs.getInt("SALES");
            int total = rs.getInt("TOTAL");
            System.out.println(coffeeName + ", " + supplierID + ", " + price +
                    ", " + sales + ", " + total);
        }

    } catch (SQLException e) {
        JDBCTutorialUtilities.printSQLException(e);
    }
}

注意:try-with-resources語句可以像普通try語句一樣擁有catch和finally程式碼塊。在try-with-resources語句中,任何catch或finally塊在宣告的資源關閉後才執行。

 

抑制異常

可以在try-with-resources語句的程式碼塊中丟擲異常。在該示例中writeToFileZipFileContents,可以從該try塊中引發異常,並且try在嘗試關閉ZipFile和BufferedWriter物件時最多可以從 try-with-resources 語句中丟擲兩個異常。如果從try塊中丟擲異常,並且從try-with-resources語句丟擲一個或多個異常,那麼從try-with-resources語句丟擲的異常將被抑制,並且該塊引發的異常將被writeToFileZipFileContents方法丟擲。您可以通過Throwable.getSuppressed從該try塊引發的異常中呼叫方法來檢索這些抑制的異常。

實現AutoCloseable或Closeable介面的類

請參閱Javadoc AutoCloseable和Closeable介面以獲取實現這些介面之一的類的列表。該Closeable介面繼承了AutoCloseable介面。介面Closeable的close方法丟擲IOException異常,而介面AutoCloseable的close方法丟擲Exception異常。因此,AutoCloseable介面的子類可以重寫此close方法的行為來丟擲特殊的異常,例如IOException根本沒有異常。

 

catch 多個型別異常

這一章涵蓋兩個部分:Catch 多種型別異常 和 分析異常並重新丟擲精準異常

catch 多種型別異常

在JDK7,單個catch塊可以處理多種型別的異常。此功能可以減少程式碼重複並減少異常捕捉過度。

思考下面的例子,其中catch塊中的有重複程式碼:

catch (IOException ex) {
     logger.log(ex);
     throw ex;
catch (SQLException ex) {
     logger.log(ex);
     throw ex;
}

在JDK7之前的版本中,很難建立一個通用方法來消除重複的程式碼,因為該變數ex具有不同的型別。

以下示例在JDK7,可消除重複的程式碼:

catch (IOException|SQLException ex) {
    logger.log(ex);
    throw ex;
}

該catch塊指出可以處理的異常型別,每個異常型別用豎線(|)分隔。

注意:如果一個catch塊處理多個異常型別,則該catch引數是隱含的final。在這個例子中,catch引數ex是final,因此你不能在catch塊中為它分配任何值。

通過編譯處理多個異常型別的catch塊生成的位元組碼將會更小(因此優於)編譯許多隻處理一個異常型別的catch塊。處理多個異常型別的catch塊不會在編譯器生成的位元組碼中建立重複;位元組碼沒有重複的異常處理程式。

  

分析異常並重新丟擲精準異常

與早期版本的JDK相比,JDK7 編譯器對重新產生的異常執行更精確的分析。這使您可以在throws方法宣告的子句中指定更具體的異常型別。

思考下面例子:

static class FirstException extends Exception { }
static class SecondException extends Exception { }
public void rethrowException(String exceptionName) throws Exception {
    try {
        if (exceptionName.equals("First")) {
            throw new FirstException();
        } else {
            throw new SecondException();
        }
    } catch (Exception e) {
        throw e;
    }
}

這個例子的try塊可能會丟擲FirstException或者SecondException。假設你想在rethrowException方法中throws這些異常型別。在JDK 7之前的版本中,您不能這樣做。由於catch子句的異常引數e是Exception型別,並且catch塊重新丟擲異常引數e,所以只能在rethrowException方法丟擲Exception異常型別。

在JDK7中的編譯器可以通過catch塊確定FirstException和SecondException異常。即使該catch子句的異常引數e型別為Exception,編譯器也可以確定它是FirstException例項或者是SecondException例項:

  public void rethrowException(String exceptionName)
  throws FirstException, SecondException {
    try {
      // ...
    }
    catch (Exception e) {
      throw e;
    }
  }

如果catch引數分配給catch塊中的另一個值,則此分析將失效。但是,如果catch引數分配給另一個值,則必須在方法宣告Exception的throws子句中指定異常型別。

詳細地說,在JDK7及更高版本中,當您在catch子句中宣告一個或多個異常型別並重新丟擲由此catch塊處理的異常時,編譯器將驗證重新丟擲異常的型別是否滿足以下條件:

  • 該try塊可以throw它。
  • 沒有前面其他catch可以處理它。
  • 它是catch子句的異常引數之一的子型別或超型別。

JDK7編譯器允許在rethrowException方法throws丟擲指定Exception型別FirstException和SecondException。因為您可以重新丟擲一個throws宣告的任何型別的超型別。

在JDK7之前的版本中,您不能丟擲該catch子句的異常引數之一的超型別。在JDK7之前的編譯器Exception會在語句中生成錯誤未報告的異常;必須捕獲或宣告throw,編譯器檢查丟擲異常的型別是否可分配給rethrowException方法宣告的throws子句中宣告的任何型別。然而,捕捉引數的型別e是Exception,這是一種超型別,而不是一個子型別FirstException和SecondException。

  

字面值中使用下劃線

在JDK7中,任意數量的下劃線字元(_)可以出現在字面值的任意位置。這個特性使您能夠在字面值中分離數字組,這可以提高程式碼的可讀性。

例如,如果您的程式碼包含具有許多數字的數字,則可以使用下劃線字元以三個一組來分隔數字,這與使用逗號或空格等標點符號作為分隔符類似。

以下示例顯示了可以在字面值中使用下劃線的其他方法:

long creditCardNumber = 1234_5678_9012_3456L;
long socialSecurityNumber = 999_99_9999L;
float pi =     3.14_15F;
long hexBytes = 0xFF_EC_DE_5E;
long hexWords = 0xCAFE_BABE;
long maxLong = 0x7fff_ffff_ffff_ffffL;
byte nybbles = 0b0010_0101;
long bytes = 0b11010010_01101001_10010100_10010010;

您只能在數字之間放置下劃線; 你不能在下列地方放置下劃線:

  • 在數字的開頭或結尾
  • 與浮點數字中的小數點相鄰
  • 在F或L字尾之前
  • 在期望一串數字的位置(In positions where a string of digits is expected)

下面的例子演示了字面值中有效和無效的下劃線位置(突出顯示):

 

float pi1 = 3_.1415F;      // 無效; 不能在小數點附近加下劃線
float pi2 = 3._1415F;      // 無效; 不能在小數點附近加下劃線
long socialSecurityNumber1
        = 999_99_9999_L;         // 無效; 在L字尾之前不能加下劃線

int x1 = _52;              // 這是一個識別符號,而不是字面值
int x2 = 5_2;              // OK(十進位制文字)
int x3 = 52_;              // 無效; 不能在文字的末尾加上下劃線
int x4 = 5_______2;        // OK(十進位制文字)

int x5 = 0_x52;            // 無效; 不能在0x基數字首中加下劃線
int x6 = 0x_52;            // 無效; 不能在數字的開頭加下劃線
int x7 = 0x5_2;            // OK(十六進位制文字)
int x8 = 0x52_;            // 無效; 不能在數字的末尾加上下劃線

int x9 = 0_52;             // OK(八進位制文字)
int x10 = 05_2;            // OK(八進位制文字)
int x11 = 052_;            // 無效; 不能在數字的末尾加上下劃線

 

型別推斷

只要編譯器可以從上下文中推斷出型別引數,你就可以用一組空的型別引數(<>)來代替呼叫泛型類的建構函式所需的型別引數。這一對尖括號被非正式地稱為鑽石。

例如,請考慮以下變數宣告:

Map<String, List<String>> myMap = new HashMap<String, List<String>>();

在JDK7中,可以使用一組空的型別引數(<>)替換建構函式的引數化型別:

Map<String, List<String>> myMap = new HashMap<>();

請注意,為了在泛型類例項化過程中利用自動型別推斷,您必須指定菱形。在以下示例中,編譯器會生成未經檢查的轉換警告,因為HashMap()建構函式引用的是HashMap原始型別,而不是Map<String, List<String>>型別:

Map<String, List<String>> myMap = new HashMap(); //未經檢查的轉換警告

JDK7支援泛型例項建立的型別推斷; 如果建構函式的引數化型別在上下文中顯而易見,則只能使用型別推斷。例如,以下示例沒有編譯:

List<String> list = new ArrayList<>();
list.add("A");
// 以下語句應該失敗,因為addAll需要
// Collection<? extends String>
list.addAll(new ArrayList<>());

請注意,鑽石通常用於方法呼叫; 但是,建議您將菱形主要用於變數宣告。

相比之下,下面的例子會編譯:

// 以下語句會編譯:
List<? extends String> list2 = new ArrayList<>();
list.addAll(list2);

泛型和非泛型類的型別推斷和建構函式

請注意,建構函式在泛型和非泛型類中都可以是通用的(換句話說,宣告它們自己的正式型別引數)。思考下面的例子:

class MyClass<X> {
  <T> MyClass(T t) {
    // ...
  }
}

思考下面這個類的例項MyClass,它在JDK7和以前的版本中是有效的:

new MyClass<Integer>("")

該語句建立引數化型別的例項MyClass<Integer>;該語句明確指定了泛型類Integer的正式型別引數X的型別MyClass<X>請注意,此泛型類的建構函式包含一個正式的型別引數T。編譯器推斷這個泛型類的建構函式String的形式型別引數的型別T(因為這個建構函式的實際引數是一個String物件)

JDK7之前版本的編譯器能夠推斷泛型建構函式的實際型別引數,類似於泛型方法。但是,如果使用磚石(<>),JDK7中的編譯器可以推斷例項化的泛型類的實際型別引數<>。思考以下示例,該示例適用於JDK7及更高版本:

MyClass<Integer> myObject = new MyClass<>("");

在這個例子中,編譯器推斷泛型類Integer的形式型別引數X的型別MyClass<X>。它推斷這個泛型類的建構函式String的形式型別引數的型別T。

 

改進泛型型別可變引數

這一章涵蓋以下幾個部分:

  • 堆汙染
  • 可變引數方法和非具體化形式引數
  • 可變引數方法在傳遞非具體引數的缺點
  • 抑制可變引數方法在傳遞不具體形式引數的警告

堆汙染

大多數引數化型別(如ArrayList<Number>和List<String>)都是非具體化的型別。非具體化型別是一種在執行時不確定的型別。在編譯時,非具體化型別在此過程中經歷了一個稱為型別擦除的過程,擦除引數型別和型別引數相關的資訊。這確保了在泛型之前建立的Java庫和應用程式的二進位制相容性。因為型別擦除在編譯時從引數型別中刪除資訊,所以這些型別是非具體化的。

當引數化型別的變數引用不是該引數化型別的物件時,會發生堆汙染。這種情況只有在程式執行一些操作時才會發生,從而在編譯時產生不受約束的警告。一個未經檢查的警告如果產生,無論是在編譯時(在編譯時型別檢查規則的限制範圍內)或執行時。一個涉及引數化型別(例如,一次轉換或方法呼叫)的操作的正確性是無法被驗證的。

思考下面的例子:

List l = new ArrayList<Number>();
List<String> ls = l;       // 未經檢查的警告
l.add(0, new Integer(42)); // 另一個未經檢查的警告
String s = ls.get(0);      // 丟擲型別轉換異常 ClassCastException

在型別擦除時,ArrayList<Number>型別和List<String>型別分別變成ArrayList和List。

該ls變數具有引數化型別List<String>,當List引用l賦值給ls時,編譯器會生成未經檢查的警告;如果編譯器無法在編譯時確定,而且JVM也無法在執行時確定l它不是一個List<String>型別;因此,產生堆汙染。

因此,在編譯時,編譯器會在add語句處生成另一個未經檢查的警告。編譯器無法確定變數l是List<String>型別還是List<Integer>型別(以及另一種發生堆汙染的情況)。然而,編譯器不會在get語句中產生警告或錯誤。此宣告有效; 它呼叫List<String>的get方法來獲得一個String物件。相反,在執行時,get語句會丟擲ClassCastException。

詳細地說,當List<Number>物件l被賦值給另一個List<String>物件ls時,就會出現堆汙染情況,它有一個不同的靜態型別。然而,編譯器仍然允許這個賦值。它必須允許這個賦值來保證與不支援泛型的JDK版本相容性。因為型別擦除,List<Number>和List<String>變成List。因此,編譯器允許物件l的賦值個體ls物件,因為ls是一個List型別。

另外,l.add呼叫該方法時會發生堆汙染情況。該方法add第二引數應該是String,但實際引數是Integer。但是,編譯器仍然允許這種方法呼叫。因為型別擦除,add方法第二個引數(List<E>.add(int,E))變成Object。因此,編譯器允許這種方法呼叫。因為在型別擦除之後,該l.add方法可以新增任何型別,包括Integer型別物件,因為它Object的子類。

 

可變引數方法和非具體化形式引數

思考下面的ArrayBuilder.addToList方法。它將型別為T的elements可變引數,新增到List listArg引數中:

 1 import java.util.*;
 2 
 3 public class ArrayBuilder{
 4 
 5     public static <T> void addToList (List<T> listArg, T... elements) {
 6         for (T x : elements) {
 7             listArg.add(x);
 8         }
 9     }
10 
11     public static void faultyMethod(List<String>... l) {
12         Object[] objectArray = l;  // 有效
13         objectArray[0] = Arrays.asList(new Integer(42));
14         String s = l[0].get(0);    // 丟擲ClassCastException異常
15     }
16 
17 }
 1 import java.util.*;
 2 
 3 public class HeapPollutionExample {
 4 
 5     public static void main(String[] args) {
 6 
 7         List<String> stringListA = new ArrayList<String>();
 8         List<String> stringListB = new ArrayList<String>();
 9 
10         ArrayBuilder.addToList(stringListA, "Seven", "Eight", "Nine");
11         ArrayBuilder.addToList(stringListA, "Ten", "Eleven", "Twelve");
12         List<List<String>> listOfStringLists = new ArrayList<List<String>>();
13         ArrayBuilder.addToList(listOfStringLists, stringListA, stringListB);
14 
15         ArrayBuilder.faultyMethod(Arrays.asList("Hello!"), Arrays.asList("World!"));
16     }
17 }

JDK7編譯器為該方法的定義生成以下警告ArrayBuilder.addToList:

warning: [varargs] Possible heap pollution from parameterized vararg type T

當編譯器遇到可變引數方法時,它將可變引數形式引數轉換為陣列。但是,Java程式語言不允許建立引數化型別的陣列。在ArrayBuilder.addToList方法中,編譯器將可變引數形式引數T... elements轉換為形式引數T[] elements。即陣列。但是,由於型別擦除,編譯器將可變引數形式引數轉換為Object[] 。因此,可能存在堆汙染

下面是ArrayBuilder.addToList方法的反編譯結果:

public static <T> void addToList(List<T> listArg, T... elements) {
    Object[] var2 = elements;
    int var3 = elements.length;

    for(int var4 = 0; var4 < var3; ++var4) {
        T x = var2[var4];
        listArg.add(x);
    }

}

注意:JDK5和6編譯器在ArrayBuilder.addToList呼叫時會生成警告HeapPollutionExample。這些編譯器不會在宣告上生成警告。但是,JDK7會在宣告和呼叫上生成警告(除非這些警告被註解取代)。

 

可變引數方法在傳遞非具體引數的缺點

該方法ArrayBuilder.faultyMethod顯示了編譯器為什麼會警告您這些型別的方法。該方法的第一條語句將可變引數形式引數l賦值給objectArgs的Object陣列:

Object[] objectArray = l;

這裡可能產生堆汙染。可變引數l可以賦值給objectArray陣列。但是,編譯器不會在此語句中生成未經檢查的警告。當編譯器編譯List<String>... l 為List[] l 時已經生成警告。List[]是Object[]型別的子類,所以該宣告是有效的。

因此,如果您將List任何型別的物件賦值給陣列objectArray,則編譯器不會發出警告或錯誤,如此語句所示:

objectArray[0] = Arrays.asList(new Integer(42));

這個語句將List<Integer>賦值給objectArray陣列。

如果你呼叫以下方法:

ArrayBuilder.faultyMethod(Arrays.asList("Hello!"), Arrays.asList("World!"));

呼叫以下方法,在執行時JVM會丟擲ClassCastException異常。

String s = l[0].get(0);    // 丟擲ClassCastException 

變數l儲存的是List<Integer>型別,但確期待是一個List<String>型別。

 

抑制可變引數方法在傳遞不具體形式引數的警告

如果聲明瞭一個具體引數化的可變引數方法。且方法的體不會丟擲ClassCastException或其他類似的異常。您可以使用以下幾種方式來抑制編譯器為這些可變引數方法生成的警告:

  • 將@SafeVarargs註解新增到靜態非建構函式方法宣告中:
  • 將@SuppressWarnings({"unchecked", "varargs"})註解新增到方法宣告中:
  • 使用編譯器選項-Xlint:varargs

例如,以下ArrayBuilder類的有兩個方法addToList2和addToList3:

 1 import java.util.*;
 2 
 3 public class ArrayBuilder {
 4 
 5     public static <T> void addToList (List<T> listArg, T... elements) {
 6         for (T x : elements) {
 7             listArg.add(x);
 8         }
 9     }
10 
11     @SuppressWarnings({"unchecked", "varargs"})
12     public static <T> void addToList2 (List<T> listArg, T... elements) {
13         for (T x : elements) {
14             listArg.add(x);
15         }
16     }
17 
18     @SafeVarargs
19     public static <T> void addToList3 (List<T> listArg, T... elements) {
20         for (T x : elements) {
21             listArg.add(x);
22         }
23     }
24 
25     // ...
26 
27 }
 1 import java.util.*;
 2 
 3 public class HeapPollutionExample {
 4 
 5     // ...
 6 
 7     public static void main(String[] args) {
 8 
 9         // ...
10 
11         ArrayBuilder.addToList(listOfStringLists, stringListA, stringListB);
12         ArrayBuilder.addToList2(listOfStringLists, stringListA, stringListB);
13         ArrayBuilder.addToList3(listOfStringLists, stringListA, stringListB);
14 
15         // ...
16 
17     }
18 }

Java編譯器為此示例生成以下警告:

  • addToList:  
    • 在該方法的宣告中:[unchecked] Possible heap pollution from parameterized vararg type T
    • 當該方法被呼叫時:[unchecked] unchecked generic array creation for varargs parameter of type List<String>[]
  • addToList2:呼叫方法時(在方法宣告中不生成警告): [unchecked] unchecked generic array creation for varargs parameter of type List<String>[]
  • addToList3:方法宣告或呼叫方法時不會生成警告。

其他

JDK7 還有許許多多的其他新特性,比如:Swing,IO and New IO,Networking 等等增強。這些可以去閱讀官方原文進行了解。下面是截圖:

前面只介紹了Java程式語言(Java Programming Language)部分,下面就簡單的介紹下增強swing,其餘不多過介紹了。

增強 Swing

Swing 的增強主要包含如下幾點:

  • JLayer 類: 是Swing元件的通用裝飾器,您能夠繪製元件並響應元件事件,而無需直接修改底層元件。
  • Nimbus Look&Feel : Nimbus Look&Feel(L&F)已經從com.sun.java.swing標準的API名稱空間轉移 到了javax.swing。檢視javax.swing.plaf.nimbus包有更詳細的介紹,雖然它不是預設的L&F,但您可以輕鬆使用它。
  • 重量級和輕量級元件: 歷史上,在同一個容器中混合重量級(AWT)和輕量級(Swing)元件是有問題的。但是,混合重量級和輕量級元件很容易在Java SE 7中完成。
  • 視窗的形狀和透明度: Java SE 7版本支援具有透明度和非矩形形狀的視窗。
  • JColorChooser 類HSL選擇: JColorChooser該類已新增HSL選項卡,允許使用者使用色相飽和度亮度(HSL)顏色模型選擇顏色。

示例程式碼:

自定義WallpaperLayerUI裝飾類繼承LayerUI重寫paint方法

 1 import javax.swing.*;
 2 import javax.swing.plaf.LayerUI;
 3 import java.awt.*;
 4 
 5 //繼承 LayerUI 實現自定義裝飾內容。
 6 public class WallpaperLayerUI extends LayerUI<JComponent> {
 7 
 8     @Override
 9     public void paint(Graphics g, JComponent c) {
10         super.paint(g, c);
11         Graphics2D g2 = (Graphics2D) g.create();
12         int w = c.getWidth();
13         int h = c.getHeight();
14         g2.setComposite(AlphaComposite.getInstance(
15                 AlphaComposite.SRC_OVER, .5f));
16         //透明顏色漸變
17         g2.setPaint(new GradientPaint(0, 0, Color.yellow, 0, h, Color.red));
18         g2.fillRect(0, 0, w, h);
19         g2.dispose();
20     }
21 }

測試程式碼:

 1 import javax.swing.*;
 2 import javax.swing.plaf.LayerUI;
 3 
 4 public class Test {
 5     public static void main(String[] args) {
 6         javax.swing.SwingUtilities.invokeLater(new Runnable() {
 7             public void run() {
 8                 createUI();
 9             }
10         });
11     }
12 
13     public static void createUI() {
14         //建立JFrame
15         JFrame f = new JFrame("Wallpaper");
16         JPanel panel = createPanel();
17 
18 //        這就是使用JDK7新特性:JLayer 類的裝飾功能。如果不使用裝飾 可以將 直接 f.add(panel)
19         LayerUI<JComponent> layerUI = new WallpaperLayerUI();
20         JLayer<JComponent> jlayer = new JLayer<JComponent>(panel, layerUI);
21         f.add (jlayer);
22 //        f.add(panel);
23 
24         //設定 JFrame 寬高 預設關閉 相對位置 和 可見。
25         f.setSize(300, 200);
26         f.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
27         f.setLocationRelativeTo (null);
28         f.setVisible (true);
29     }
30 
31     private static JPanel createPanel() {
32         //建立面板
33         JPanel p = new JPanel();
34 
35         //建立一個按鈕組,並新增 Beef、Chicken 和 Vegetable 三個單選按鈕
36         ButtonGroup entreeGroup = new ButtonGroup();
37         JRadioButton radioButton;
38         p.add(radioButton = new JRadioButton("Beef", true));
39         entreeGroup.add(radioButton);
40         p.add(radioButton = new JRadioButton("Chicken"));
41         entreeGroup.add(radioButton);
42         p.add(radioButton = new JRadioButton("Vegetable"));
43         entreeGroup.add(radioButton);
44 
45         //新增 Ketchup 、 Mustard 和 Pickles 複選框
46         p.add(new JCheckBox("Ketchup"));
47         p.add(new JCheckBox("Mustard"));
48         p.add(new JCheckBox("Pickles"));
49 
50         //新增 Special requests: 標籤 和 文字框
51         p.add(new JLabel("Special requests:"));
52         p.add(new JTextField(20));
53 
54         //新增 Place Order 按鈕
55         JButton orderButton = new JButton("Place Order");
56         p.add(orderButton);
57 
58         return p;
59     }
60 }

以上程式碼結果展示:

我將裝飾效果去掉只需將,19-21行註釋,並放開22註釋執行結果如下: