JDK7 新特性

2018/06/14 14:54
阅读数 12

想更详细的熟悉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注释执行结果如下:

 

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部