Skip to content

Files

Latest commit

2cab597 · Oct 5, 2021

History

History
284 lines (202 loc) · 8.11 KB

File metadata and controls

284 lines (202 loc) · 8.11 KB

十五、Java10 和 Java11 的编程新方法

在本章中,我们将介绍以下配方:

  • 使用局部变量类型推断
  • 对 Lambda 参数使用局部变量语法

介绍

本章简要介绍了影响编码的新功能。许多其他语言,包括 JavaScript,都具有使用var关键字声明变量的功能(在 Java 中,它实际上是保留类型名,而不是关键字)。它有许多优点,但并非没有争议。如果过度使用,尤其是使用较短的非描述性标识符,可能会降低代码的可读性,增加的值可能会被代码模糊度的增加所淹没。

这就是为什么在下面的配方中,我们解释了引入保留var类型的原因。在其他情况下尽量避免使用var

使用局部变量类型推断

在本教程中,您将了解 Java 10 中引入的局部变量类型推断,它可以在哪里使用,以及它的局限性。

准备

局部变量类型推断是编译器使用表达式的正确端识别局部变量类型的能力。在 Java 中,使用var标识符声明具有推断类型的局部变量。例如:

var i = 42;       //int
var s = "42";     //String
var list1 = new ArrayList();          //ArrayList of Objects;
var list2 = new ArrayList<String>();  //ArrayList of Strings

前面每个变量的类型都可以清楚地识别。我们在评论中捕捉到了他们的类型。

注意,var不是一个关键字,而是一个标识符,具有特殊含义,作为局部变量声明的类型。

它确实节省了输入,并使代码不那么混乱,重复代码。让我们看看这个例子:

Map<Integer, List<String>> idToNames = new HashMap<>();
//...
for(Map.Entry<Integer, List<String>> e: idToNames.entrySet()){
    List<String> names = e.getValue();
    //...
}

这是实现这种循环的唯一方法。但从 Java 10 开始,可以按如下方式编写:

var idToNames = new HashMap<Integer, List<String>>();
//...
for(var e: idToNames.entrySet()){
    var names = e.getValue();
    //...
}

正如您所看到的,代码变得更加清晰,但使用更具描述性的变量名(如idToNamesnames)是有帮助的。嗯,不管怎样,这是有帮助的。但是如果不注意变量名,很容易使代码不容易理解。例如,请查看以下代码:

var names = getNames();

查看前一行,您不知道names变量是什么类型。将其更改为idToNames更容易猜测。然而,许多程序员并不这样做。他们更喜欢短变量名,并使用 IDE 上下文支持(在变量名后添加一个点)确定每个变量的类型。但归根结底,这只是风格和个人喜好的问题。

另一个潜在的问题来自这样一个事实,即如果不采取额外的措施,新样式可能会违反接口原则的封装和编码。例如,考虑这个接口和它的实现:

interface A {
    void m();
}

static class AImpl implements A {
    public void m(){}
    public void f(){}
}

注意,AImpl类比它实现的接口有更多的公共方法。创建AImpl对象的传统风格如下:

A a = new AImpl();
a.m();
//a.f();  //does not compile

这样,我们只公开接口中存在的方法,而新样式允许访问所有方法:

var a = new AImpl();
a.m();
a.f();

要将引用仅限于接口的方法,需要添加类型转换,如下所示:

var a = (A) new AImpl();
a.m();
//a.f();  //does not compile

因此,与许多功能强大的工具一样,新样式可以使代码更易于编写,可读性更高,或者,如果不特别注意,可读性更低,调试更困难。

怎么做。。。

可以通过以下方式使用局部变量类型:

  • 使用右侧初始值设定项:
var i = 1; 
var a = new int[2];
var l = List.of(1, 2); 
var c = "x".getClass(); 
var o = new Object() {}; 
var x = (CharSequence & Comparable<String>) "x";

以下声明和分配是非法的,不会编译:

var e;                 // no initializer
var g = null;          // null type
var f = { 6 };         // array initializer
var g = (g = 7);       // self reference is not allowed
var b = 2, c = 3.0;    // multiple declarators re not allowed
var d[] = new int[4];  // extra array dimension brackets
var f = () -> "hello"; // lambda requires an explicit target-type

通过扩展,在循环中使用初始值设定项:

for(var i = 0; i < 10; i++){
    //...
}

我们已经讨论过这个例子:

var idToNames = new HashMap<Integer, List<String>>();
//...
for(var e: idToNames.entrySet()){
    var names = e.getValue();
    //...
}
  • 作为匿名类引用:
interface A {
 void m();
}

var aImpl = new A(){
 @Override
 public void m(){
 //...
 }
};
  • 作为标识符:
var var = 1;
  • 作为方法名称:
public void var(int i){
    //...
}

var不能用作类或接口名。

  • 作为包名:
package com.packt.cookbook.var;

对 Lambda 参数使用局部变量语法

在本配方中,您将学习如何对 Lambda 参数使用局部变量语法(在上一配方中讨论),以及引入此功能的动机。它是在 Java11 中引入的。

准备

在 Java11 发布之前,有两种方法可以显式和隐式声明参数类型。以下是一个明确的版本:

BiFunction<Double, Integer, Double> f = (Double x, Integer y) -> x / y;
System.out.println(f.apply(3., 2));    //prints: 1.5

以下是隐式参数类型定义:

BiFunction<Double, Integer, Double> f = (x, y) -> x / y;
System.out.println(f.apply(3., 2));     //prints: 1.5

在前面的代码中,编译器根据接口定义计算参数的类型。

在 Java11 中,使用var标识符引入了另一种参数类型声明方式。

怎么做。。。

  1. 以下参数声明与 Java 11 之前的隐式声明完全相同:
BiFunction<Double, Integer, Double> f = (var x, var y) -> x / y;
System.out.println(f.apply(3., 2));       //prints: 1.5
  1. 新的局部变量样式语法允许我们添加注释,而无需明确定义参数类型:
import org.jetbrains.annotations.NotNull;
...
BiFunction<Double, Integer, Double> f = 
 (@NotNull var x, @NotNull var y) -> x / y;
System.out.println(f.apply(3., 2));        //prints: 1.5

注释告诉处理代码的工具(例如 IDE)程序员的意图,因此它们可以在编译或执行过程中警告程序员,以防违反声明的意图。例如,我们尝试在 IntelliJ IDEA 中运行以下代码:

BiFunction<Double, Integer, Double> f = (x, y) -> x / y;
System.out.println(f.apply(null, 2));    

它在运行时以NullPointerException失败。然后,我们运行了以下代码(带注释):

BiFunction<Double, Integer, Double> f4 = 
           (@NotNull var x, @NotNull var y) -> x / y;
Double j = 3.;
Integer i = 2;
System.out.println(f4.apply(j, i)); 

结果如下:

Exception in thread "main" java.lang.IllegalArgumentException: 
Argument for @NotNull parameter 'x' of com/packt/cookbook/ch17_new_way/b_lambdas/Chapter15Var.lambda$main$4 must not be null

Lambda 表达式甚至没有执行。

  1. 如果当参数是具有很长名称的类的对象时,我们需要使用注释,那么在 Lambda 参数的情况下使用局部变量语法的优势就显而易见了。在 Java 11 之前,代码可能如下所示:
BiFunction<SomeReallyLongClassName, 
  AnotherReallyLongClassName, Double> f4 = 
    (@NotNull SomeReallyLongClassName x, 
     @NotNull AnotherReallyLongClassName y) -> x.doSomething(y);

我们必须显式声明变量的类型,因为我们想添加注释,而以下隐式版本甚至无法编译:

BiFunction<SomeReallyLongClassName, 
   AnotherReallyLongClassName, Double> f4 = 
      (@NotNull x, @NotNull y) -> x.doSomething(y);

对于 Java 11,新语法允许我们使用var标识符使用隐式参数类型推断:

BiFunction<SomeReallyLongClassName, 
   AnotherReallyLongClassName, Double> f4 = 
      (@NotNull var x, @NotNull var y) -> x.doSomething(y);

这就是为 Lambda 参数的声明引入局部变量语法的优点和动机。