Java7语法新特性

可能有点标题党了,有些特性其实是在Java5引入的,包括泛型、基本类型的自动装箱拆箱、参数长度可变、注解等等。下面将介绍一下Java中比较重要的的新特性:(大佬轻拍

1. 在 switch 语句中使用字符串

Java 字符串可以包含Unicode转义字符,所以对于case中字符串重复值的检查也包含了对Unicode转义字符的处理。例如:

//编译器报错
switch (sex) {
  case "男":
    break;
  case "\u7537"
    break;
}

实现原理

实际上,这个新特性是在编译器层次上实现的,而在 Java 虚拟机和字节代码这个层次上,还是只支持在switch语句中使用与整数类型兼容的类型,这样做的目的是减少这个特性所影响的范围。

//反编译后的伪代码
String s="Java";
switch(s.hashCode())
  case 1000:
    if(s.equals("Java"))
      break;

原来用在switch语句中的字符串被替换成了对应的哈希值,而在case子句的值也被换成了原来字符串常量的哈希值。经过这样的转换,Java虚拟机所看到的仍然是与整数类型兼容的类型。在这里值得注意的是,在case子句对应的语句块中仍然需要使用String的equals方法来进行字符串比较。这是因为可能存在哈希冲突。

2. 枚举

Java 语言中的枚举类型的最大优势在于它是一个完整的 Java 类,除了定义其中包含的枚举值之外,还可以包含任意的方法和域,以及实现任意的接口,这使得枚举类型可以很好地与其他 Java 类进行交互。

3. 数值字面量的改进

在编程语言中,字面量指的是在源代码中直接表示的一个固定的值。

二进制整数字面量

在Java7之前,所支持的进制包括十进制、八进制、十六进制。十进制是默认使用的进制。八进制是用在整数字面量之前添加“0”来表示的,而十六进制则是用在整数字面量之前添加“0x”或"0X"来表示的。Java7中增加了一种可以在字面量中使用的进制,即二进制。二进制整数字面量是通过在数字前添加“0b”或“0B”来表示。

在数值字面量中使用下划线

在Java7中,数值字面量,不管是整数还是浮点数,都允许在数字之间插入任意多个下划线。这些下划线不会对字面量的数值产生影响,其目的主要是方便阅读。典型的用法是每三个数字插入一个下划线,例:

int number = 233_44

需要注意的是,下划线只能出现在数字中间,这样的限制在于降低实现的复杂度。

4. 优化的异常处理

Java7对异常处理做了两个重要的改动:一个是支持在一个catch子句中同时捕获多个异常,另外一个是在捕获并重新抛出异常时的异常类型更加精确。

当一个异常抛出的时候,如果没有被捕获到,就会一直沿着调用栈往上传递,直到被上层方法捕获或是最终由Java虚拟机来处理。

异常处理

开发人员对异常处理的try-catch-finally语句块都比较熟悉。如果在try语句块中抛出了异常,在控制权转移到调用栈上一层代码之前,finally语句块中的语句也会执行。但是finally语句块在执行过程中,也可能会抛出异常,如果finally语句块也抛出了异常,那么这个异常会往上传递,而之前try语句块中的那个异常就丢失了。

对于这种问题的解决办法一般有两种:一种是抛出try语句块中产生的原始异常,忽略在finally语句块中产生的异常。这么做的出发点是try语句块中的异常才是问题的根源。另外一种是把产生的异常都记录下来,这么做的好处是不会丢失异常。在Java7之前,这种做法需要实现自己的异常类,而在Java7中,已经对Throwable类进行了修改以支持这种情况,这种做法的关键在于把finally语句中产生的异常通过addSuppressed方法加到try语句产生的异常中。

Java7异常处理新特性

  • 一个catch子句捕获多个异常

    在Java7中改进了catch子句的语法,允许在其中指定多种异常,每个异常类型之间用“|”来分隔。但是需要注意的是,在catch子句中声明捕获的这些异常类中,不能出现重复的类型,也不允许其中的某个异常是另外一个异常的子类,否则会出现编译错误。如果在catch子句中声明了多个异常类,那么异常参数的具体类型是所有这些异常类型的最小上界。

    关于一个catch子句中的异常类型不能出现其中一个是另外一个的子类的情况,其实涉及捕获多个异常的内部实现方式。

    //编译正常
    try{
      ...
    }catch(NumberFormatException | RuntimeException e){
      
    }
    //编译报错
    try{
      ...
    }catch(RuntimeException | NumberFormatException e){
      
    }
    //3. 编译报错其实等价于以下代码
    try{
      ...
    }
    catch(RuntimeException e){}
    catch(NumberFormatException e){}
    

    原因在于,编译器的做法其实是把捕获的多个异常的catch子句转换成了多个catch子句,在每个catch子句中捕获一个异常。所以在上一个catch子句中已经捕获了RuntimeException,在下一个catch子句中无法在捕获其子类异常。

    所以可以总纳为:当catch多个异常时,某个异常参数的具体类型是后续所有这些异常参数类型的最小上界。·

  • 更加精确的异常抛出

    在进行异常处理的时候,如果遇到当前代码无法处理的异常,应该把异常重新抛出,交由调用栈的上层代码来处理,在重新抛出异常时,需要判断异常的类型。Java7对重新抛出的异常时的异常类型做了更加精确的判断,以保证抛出的异常的确是可以被抛出的。

5. try-with-resources语句

Java7对try语句进行了增强,使它可以支持对资源进行管理,保证资源总是被正确的释放。资源的申请是在try子句中进行,而资源的释放则是自动完成的。在使用try-with-resource语句的时候,异常可能发生在try语句中,也可能发生在释放资源时。如果资源初始化时或try语句中出现异常,而释放资源的操作正常执行,try语句中的异常会被抛出;如果try语句和释放资源都出现了异常,那么最终抛出的异常是try语句中出现的异常,在释放资源时出现的异常会作为被抑制的异常添加进去,即通过Throwable.addSuppressed方法来实现。

能够被try语句所管理的资源需要满足一个条件,那就是其Java类要实现java.lang.AutoCloseable接口,否则会出现编译错误。当需要释放资源时,该接口的close方法会被自动调用。Java 类库中已经有不少接口或类继承或实现了该接口,使得它们可以用在try语句中。在这些已有的常见接口或类中,最常用的就是与I/O操作和数据库相关的接口。与I/O相关的java.io.Closeable继承了AutoCloseable,而与数据库相关的java.sql.Connection、java.sql.ResultSet和java.sql.Statement也继承了该接口。如果希望自己开发的类也能用try语句的自动化资源管理,只需要实现AutoCloseable接口即可。

6. 优化变长参数的方法调用

J2SE 5.0中引入的一个新特性就是允许在方法声明中使用可变长度的参数。一个方法的最后一个形式参数可以被指定为代表任意多个相同类型的参数。在调用的时候,这些参数是以数组的形式来传递,在方法体中也可以按照数组的方式来引用这些参数。

可变长度的参数在实际开发中可以简化方法的调用方式。但是在Java7之前,如果可变长度的参数与泛型一起使用会遇到一个麻烦,那就是编译器产生的警告过多。原因很简单,因为可变长度的方法参数实际值是通过数组来传递的,而数组中存储的是不可具体化的泛型类对象,自身存在类型安全问题,因此编译器会给出警告信息。我们可以通过添加@SuppressWarnings("unchecked")注解来抑制编译器警告,但是这种方式是极不优雅的。Java7中引入了一个新的注解@SafeVararges,如果确信使用了可变长度参数的方法,在与泛型类一起使用时不会出现类型安全问题,就可以使用这个注解进行声明。在使用这个注解之后,编译器遇到类似的情况,就不会再给出相关的警告信息了。

@SafeVarargs注解只能用在参数长度可变的方法或构造方法上,且方法必须声明为static或final,否则会出现编译错误。一个方法使用@SafeVarargs注解的前提是,开发人员必须确保这个方法的实现中对泛型类型参数的处理不会引发类型安全问题。(为什么要必须声明为static或final呢?我也不知道,有大佬释义嘛?哭泣脸 =。=)

遗留问题

  1. 目前的主流意见是,最好优先使用非受检异常。(书中言论)

    ???纳尼,不理解。

  2. 对于@SafeVarargs注解的方法为什么必须声明为static或final呢?

你可能感兴趣的:(Java7语法新特性)