《深入理解Java7:核心技术与最佳实践》读书笔记(一)

Java 7是至Java 6以来最大幅度的语法更新,“他山之石,可以攻玉”,了解Java 7的新特性和底层相关原理对于自己的编码工作和实践想必会大有裨益。此文为《深入理解Java7:核心技术与最佳实践》一书的读书笔记,此为第一篇,主要记录Java7新特性。

在switch语句中使用字符串

  • 在Java 7之前,switch语句中的条件表达式只能使用和整数兼容的类型:charbyteshortint,以及这些基本类型对应的包装类型CharacterByteShortInteger

  • 在Java 7中,在上述的支持外,还新增了对字符串String类型的支持,即可以在switch语句的表达式中使用字符串参数,在case子句中使用字符串常量,一个简单用法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /**
    * 根据性别向姓名后加上称谓
    */
    public class Titile{
    public String generate(String name, String gender){
    String title = "";
    switch(gender){
    case "男":
    title = name + "先生";
    break;
    case "女":
    title = name + "女士";
    break;
    default:
    title = name;
    }
    return title;
    }
    }
  • switch语句中的表达式和case子句的值均不可为null,如果为null前者会在运行时抛出NullPointerException,后者则直接无法通过编译;

  • switch语句要求各个其case子句的值不能相同,这点对字符串也适用。Java中字符串是可以包含Unioncode转义字符的,而Java编译器对case相同值的检查是在对相关Java源代码进行词法转换后进行的,所以如果Java源代码中看到的值是不一样的,但是经词法处理后却是一样的,这样就会造成编译错误;

  • 实现原理:这个新特性是在编译器级别上实现,在JVM和字节码上仍是原来的实现方式,即只能使用和整数兼容的类型。虽然在Java源代码中switch使用了字符串,但是编译器会根据源代码的含义来进行转换,将字符串转换成与整数类型兼容的格式;

  • 最佳实践:对于switch语句,如果在代码中有多个地方来枚举字符串,就考虑使用枚举类型进行替换。

数字字面量的增强与改进

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

  • 在之前八/十/十六进制整数字面量的基础上,Java7新增二进制整数字面量,通过在数字前面添加“0b”或“0B”来表示;举个栗子:

    1
    2
    3
    4
    5
    6
    7
    import static java.lang.System.out;
    public class BinaryIntegerLiteral{
    public void display(){
    out.println(0b001001);//输出数字 9
    out.println(0B001110);//输出数字 14
    }
    }
  • 优化长数字字面量的表示,使用任意多个下划线的方式对长数字字面量进行分割,但是下划线只能用在数字之间。举个栗子:

    1
    2
    3
    4
    5
    6
    7
    8
    import static java.lang.System.out;
    public class underscore{
    public void display(){
    out.println(1_500___000);//输出数字 1500000
    out.println(5_6.3_4);//输出数字 56.34
    out.println(89_____3_1);//输出数字 8931
    }
    }

异常处理

  • 异常分为受检异常(checked exception)和不受检异常(unchecked exception),是否受检是相对于编译器而言的;

  • 由于编译器需要检查异常的合法性,受检异常所在方法的使用者则必须进行try-catch-finally处理,这个时候受检异常就成为API的一部分,而不受检异常则不会有这样的情况;

  • 自定义异常的最佳实践

    • 程序中的自定义异常要自己的层次结构,最好与程序的抽象层次(如典型3层web应用:展示层、业务层和数据访问层)相对应;
    • 程序中出现的各种错误都需应该一个异常类与之对应,每一个抽象层次都应该定义一个基本异常类;
    • 异常在跨越程序抽象层次边界时,异常包装成上一个调用栈(或抽象层次)的基本异常类,以保证异常只出现其对应的抽象层次中;
    • 异常要包含足够的信息,可以通过添加所需的域或方法来实现;
    • 适当的区分异常和错误提示:异常是程序中的错误,而错误则是用户不当操作程序引起的,要避免让用户看到与异常相关的信息。
  • 处理异常的最佳实践

    • 如果抛出异常的代码处于程序抽象层次的边界,则要将当前异常捕获后包装成上一个调用栈的异常类;

    • 如果从异常中恢复有合理的办法存在,则也可以捕获异常并进行恢复异常的操作;

    • try-catch-finally语句块中,try语句和finally语句中都可能抛出异常,并且如果两者都抛出了异常,则finally语句的异常会先于try语句的异常向上抛出,从而try语句块抛出的异常则会消失,不会再向上传递。这个是时候可以选择保留try语句的异常或者使用Java7的新特性调用public final void addSuppressed(Throwable exception)try语句块中的异常放入异常栈中,一起向上传递;

    • Java7中一个catch子句可以捕获多个异常,多个异常之间使用|分隔且同一个catch子句多个异常参数之间不能有继承关系,举个栗子:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      public class ExceptionHandler{
      public void handle(){
      ExceptionThrower thrower = new ExceptionThrower();
      try{
      thrower.manyExceptions();
      }catch(ExceptionA | ExceptionB ab){
      //do somethings
      }catch(ExceptionC c){
      //do somethings
      }
      }
      }
    • 关于一个catch子句中的异常类型不能出现一个是另一个子类的情况,实际上涉及异常的内部实现方式,下面的栗子就可以编译通过:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      public void testSequence(){
      try{
      Integer.parseInt("hello");
      }
      //NumberFormatException是RuntimeException子类
      catch(NumberFormatException | RuntimeException e){
      //do somethings
      }
      }

      如果上述catch子句中异常声明调换下位置则不能够编译通过;

    • 在Java7中,对抛出的异常类型做了更加精确的判断,举个栗子:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      public class PreciseThrwoUse{
      public void testThrow() throws ExceptionA{
      try{
      throw new ExceptionASub1();
      }catch(ExceptionA e1){//编译器可以准确知道e1是ExceptionASub1类型的
      try{
      throw e1
      }catch(ExceptionASub2 e2){//此处试图将e1以ExceptionASub2类型捕获,编译错误
      //do somethings
      }
      }
      }
      }

      而在Java7之前上面的代码是可以编译通过的。

增加try-with-resources语句

  • 在进行资源操作时很可能出现各种异常,而资源管理要求不管操作是否成功,资源都要被正确释放,即通过try-catch-finally语句块的finally语句进行资源释放操作。为了减少代码冗余,Java7对try语句进行了增强:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class ResourcesBasicUsage{
    public String readFile(String path) thrwos IOException{
    try(BufferedReader reader = new BufferedReader(new FileReader(path))){
    StringBuilder builder = new StringBuilder();
    String line = null;
    while((line = reader.readLine()) != null){
    builder.append(line).append(String.format("%n"));
    }
    return builder.toString();
    }
    }
    }

    上面的代码并不需要使用finally来确保正确释放资源,因为这是自动完成的。当然这是有前提的,资源对应的类需要实现java.lang.AutoCloseable接口,否则会出现编译错误。

  • 如果资源在初始化时或try语句出现异常,而资源释放正常执行,则try的异常则会抛出;

  • 如果try语句和资源释放都出现了异常,则最终抛出的是try语句的异常,而在释放资源的异常则会通过Throwable.addSuppressed方法作为被抑制的异常添加进去。

优化变长参数的方法调用

  • 在Java7之前,向可变参数方法的参数类型如果是non-refiable的,比如泛型,则会产生警告信息;
  • 在Java7中对上述情况作出了改进,可以在参数可变的方法或构造方法上使用注解@SasfeVarargs,但前提是这些方法必须是staticfinal的,并且你可以确定在这些方法实现中对泛型参数的处理不会引发类型安全问。