JDK 1.7 新特性

switch 支持 String 做参数

switch语句支持了对字符串String的判定,这对于开发来说是非常便利的改进。

public class SwitchTest {
    public static void switchTest(String arg) {
        switch (arg) {
        case "Lisa":
            System.err.println("我是Lisa!");
            break;
        case "Mike":
            System.err.println("我是Mike!");
            break;
        default:
            System.err.println("我是你的唯一!");
            break;
        }
    }
    public static void main(String[] args) {
        switchTest("Lisa");//我是Lisa!
        switchTest("Mike");//我是Mike!
        switchTest("");//我是你的唯一!
    }
}

二进制整型字面值

在旧版的 Java 中,字面值只支持十进制、八进制、十六进制3种类型,在Java7中又多了一种二进制,它的前缀是0B,配合需要位运算的场景特别合适,尤其是跟下划线组合使用:

int i = 0B1010_1100_0010_1100_0000_1111_0001_1011;

数字常量 - 新形式写法

这个特性的引入对我们阅读数字有很好的帮助。可以使用下划线去划分,这样更加便于阅读。这个下划线不是说一定要 3 位数字划分一次,多少位都可以,可以根据个人阅读习惯,编译的时候JVM会将下划线去掉。

long amount = 123000555999666L;
//JDK7特性写法
long amount1 = 123_000_555_999_666L;

注意:只能将下划线置于数字之间。

以下地方不能放置下划线:

  • 数字的开头或结尾;浮点数中靠近小数点的位置;
  • F 或 L 后缀之前期望放置一串数字的地方。

try-with-resources

在Java编程过程中,如果打开了外部资源(文件、数据库连接、网络连接等),我们必须在这些外部资源使用完毕后,需要手动关闭它们。因为外部资源不由JVM管理,无法享用JVM的垃圾回收机制,如果我们不在编程时确保在正确的时机关闭外部资源,就会导致外部资源泄露,紧接着就会出现文件被异常占用,数据库连接过多导致连接池溢出等诸多很严重的问题。

传统做法:

FileInputStream inputStream = null;
try {
    inputStream = new FileInputStream(new File("test"));
    System.out.println(inputStream.read());
} catch (IOException e) {
    throw new RuntimeException(e.getMessage(), e);
} finally {
    if (inputStream != null) {
        try {
            inputStream.close();
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }
}

JDK1.7之后:

try (FileInputStream inputStream = new FileInputStream(new File("test"))) {
    System.out.println(inputStream.read());
} catch (IOException e) {
    throw new RuntimeException(e.getMessage(), e);
}

try-with-resource 并不是 JVM 虚拟机的新增功能,只是 JDK 实现了一个语法糖,实际上就是自动调用资源的close()函数,当你将上面代码反编译后会发现,其实对JVM虚拟机而言,它看到的依然是之前的写法。

那什么是try-with-resource呢?简而言之,当一个外部资源的句柄对象(比如FileInputStream对象)实现了AutoCloseable 或者 Closeable接口,将外部资源的句柄对象的创建放在try关键字后面的括号中,当这个try-catch代码块执行完毕后,Java会确保外部资源的close方法被调用。代码是不是瞬间简洁许多!

注意:资源关闭会按声明时的相反顺序被执行!

在传统做法中,调用close方法时若出现异常可以直接在 finally 块中捕获并作出相应处理,但是try-with-resource做了简化,在自动调用相应的close方法时发生异常后如何处理?

  • 如果对外部资源的处理未发生异常,但对外部资源的关闭发生了异常的时候,关闭异常将被抛出。
  • 如果对外部资源的处理和对外部资源的关闭均发生了异常,关闭异常将被抑制,处理异常将被抛出。但关闭异常没有丢失,而是存放在处理异常的被抑制的异常列表中。你可以通过被try代码块抛出的异常的Throwable.getSuppressed方法找回被压抑的异常。
public class Connection implements AutoCloseable {  
    public void sendData() throws Exception {
        throw new Exception("send data");
    }
    public void close() throws Exception {
        throw new Exception("close");
    }
}
// 测试
@Test
public void test() {
    try (Connection conn = new Connection()) {
        conn.sendData();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
// 控制台异常输出结果
java.lang.Exception: send data
    at Connection.sendData(Connection.java:4)
    at TryWithResource.test(TryWithResource.java:8)
    .......
    Suppressed: java.lang.Exception: close
        at Connection.close(Connection.java:8)
        at TryWithResource.test(TryWithResource.java:9)
        ... 23 more

上面的代码不难看出,close方法被执行了。但是由于sendData方法也抛出了异常,而close方法也抛出了异常。信息中多了一个Suppressed的提示,附带上close的异常。这个异常其实由两个异常组成, 执行close方法的异常是被Suppressed【抑制】的异常。

// 获取被抑制异常
Throwable[] th = e.getSuppressed();
System.out.println(th[0].getMessage());
th[0].printStackTrace();
// 控制台异常输出结果
close
java.lang.Exception: close
    at Connection.close(Connection.java:8)
    at TryWithResource.test(TryWithResource.java:9)
    ..........

改进的异常处理

  • Multi catch:开发者现在能够在一个catch代码块中捕获多个异常类型。
  • Final Rethrow:它可以让开发者捕获一个异常类型及其子类型,并且无需向方法声明中增加抛出子句,就能重新将其抛出。

Multi catch

Multi catch 开发者现在能够在一个 catch 代码块中捕获多个异常类型。原来我们捕获多个异常的时候常用的写法是:

} catch (FirstException ex) {  
    logger.error(ex);  
    throw ex;  
} catch (SecondException ex) {  
    logger.error(ex);  
    throw ex;  
} 

这种写法除了冗长外没有什么优点。可能我们想到的一个解决办法是找出这两个异常类型的共同父类型,只对其进行捕获。但是这种方法通常会捕获一些你并不需要的异常。Muti-catch就解决了此类问题,借助于这个新增的功能,我们可以使用以下代码:

} catch (FirstException | SecondException ex) {  
    logger.error(ex);  
    throw ex;  
}

Final Rethrow

Final Rethrow 【不常用可略过】可以让开发者捕获一个异常类型及其子类型,并且无需向方法声明中增加抛出子句,就能重新将其抛出。

假如开发过程中希望在捕获所有异常后,进行必要的几个操作后,然后再将其抛出。就代码编写而言并不是一件难事,但是我们必须在方法声明中增加一个抛出子句,来管理自己代码发出的新异常。

class SubException1 extends Exception {}
class SubException2 extends Exception {}

public void testThrow() throws Exception {
    try{
        throw new SubException1();
    }catch(Exception e){
        try{
            throw e; //1
        }catch(SubException2 e2){ //JDK6 可编译通过,JDK7 下无法通过编译
        }
    }
}

在 JDK7 下报错为:

Unreachable catch block for App.SubException2. This exception is never thrown from the try statement body

JDK7 编译器在 1 处能推断出抛出的异常类型是 SubException1, 底下的 catch(SubException2 e2) 就别白费心思啦。

public void doSomething() /*throws Exception*/{
    try {
        doSomethingElse();
    } catch (Exception e) {
        //JDK6 下报 Unhandled exception type Exception 错误,必须声明抛出 Exception
        throw e; 
    }
}

public void doSomethingElse(){
   throw new RuntimeException(); 
}

在 JDK6 下 doSomething() 方法必须声明 throws Exception 抛出 Exception 类型的异常才成。而 JDK7 编译器在 doSomethingElse() 推断出 catch(Exception e) 就是一个 RuntimeException 非检测异常类型,所以 doSomething() 方法中可以省去 throws Exception

下面的代码对 JDK7 来说可以通过,糊弄不了它的,也无须为 doSomething() 方法声明 throws Exception

public void doSomething() {
    try {
        doSomethingElse();
    } catch (Exception e) {
        throw e; 
    }
}

public void doSomethingElse(){
   doAnotherThing();
}

public void doAnotherThing(){
    throw new RuntimeException();
}

但 JDK7 看到如下的代码同样会傻眼:

public void doSomething() /*JDK7 下也必须加上 throws Exception*/{
    try {
        doSomethingElse();
    } catch (Exception e) {
        throw e;
    }
}

public void doSomethingElse() throws Exception{
    throw new RuntimeException();
}

  在 JDK7 下为 doSomething() 加上 throws Exception 声明,只看到 doSomethingElse() 方法的 throws Exception 声明就认定它抛出的是 Exception 类型,而跳过的 throw new RuntimeException() 内容的具体推断。

  因此当我们在为 catch(Exception e) { throw e; } 后要不要为所在方法加上 throws 声明时,可以查查 try 代码块中调用的方法有没有声明抛出需检测的异常。

  当然,有现代化的 IDE 根本不担心这个,按错误提示来办事,通常只需一个简单的快捷键就帮你做好了,但任何时候理解万岁。

创建泛型实例时自动类型推断

在以前的版本中使用泛型类型,需要在声明并赋值的时候,两侧都加上泛型类型。例如:

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

不过在Java SE 7中,这种方式得以改进,现在你可以使用如下语句进行声明并赋值:

Map<String, String> myMap = new HashMap<>();    //注意后面的"<>"

在这条语句中,编译器会根据变量声明时的泛型类型自动推断出实例化HashMap时的泛型类型。再次提醒一定要注意new HashMap后面的<>,只有加上这个<>才表示是自动类型推断,否则就是非泛型类型的HashMap,并且在使用编译器编译源代码时会给出一个警告提示。

注意:Java SE 7在创建泛型实例时的类型推断是有限制的,你只能在联系上下文可以明确确定参数化类型的时候使用泛型推断。

下面的例子无法正确编译:

List<String> list = new ArrayList<>();
list.add("A");
// 由于addAll期望获得Collection<? extends String>类型的参数,因此下面的语句无法通过
list.addAll(new ArrayList<>());

与上面的例子相比,下面的这个例子可以通过编译:

List<String> list = new ArrayList<>();
list.add("A");
List<? extends String> list2 = new ArrayList<>();
list.addAll(list2);

改进使用带泛型可变参数的方法时的编译器警告和错误提示

【暂时还不理解,所以上面的链接是外链接】

参考资料:

赞赏

猜你喜欢

转载自blog.csdn.net/fanxiaobin577328725/article/details/81981618