1. What Is Project Amber
1.什么是 “琥珀计划”?
Project Amber is a current initiative from the developers of Java and OpenJDK, aiming to deliver some small but essential changes to the JDK to make the development process nicer. This has been ongoing since 2017 and has already delivered some changes into Java 10 and 11, with others scheduled for inclusion in Java 12 and yet more coming in future releases.
Project Amber是 Java 和 OpenJDK 开发人员目前提出的一项倡议,旨在对 JDK 进行一些小而重要的更改,以使开发过程更加完美。这项工作自2017年以来一直在进行,并且已经为Java 10和11提供了一些变化,还有一些变化计划包含在Java 12中,还有更多变化将在未来的版本中出现。
These updates are all packaged up in the form of JEPs – the JDK Enhancement Proposal scheme.
这些更新都是以JEPs的形式打包的,即JDK增强建议计划。
2. Delivered Updates
2.送达的更新信息
To date, Project Amber has successfully delivered some changes into currently released versions of the JDK – JEP-286 and JEP-323.
到目前为止,Project Amber已经成功地将一些变化交付给目前发布的JDK版本–JEP-286和JEP-323。
2.1. Local Variable Type Inference
2.1.本地变量类型推断
Java 7 introduced the Diamond Operator as a way to make generics easier to work with. This feature means that we no longer need to write generic information multiple times in the same statement when we’re defining variables:
Java 7引入了Diamond Operator,作为一种使泛型更容易操作的方法。这一特性意味着我们在定义变量时,不再需要在同一语句中多次写入泛型信息。
List<String> strings = new ArrayList<String>(); // Java 6
List<String> strings = new ArrayList<>(); // Java 7
Java 10 included the completed work on JEP-286, allowing for our Java code do define local variables without needing to duplicate the type information wherever the compiler has it already available. This is referred to in the wider community as the var keyword and brings similar functionality to Java as is available in many other languages.
Java 10包含了JEP-286的已完成工作,允许我们的Java代码定义局部变量,而不需要在编译器已经提供的地方重复类型信息。这在更广泛的社区中被称为var关键字,它为Java带来了类似于许多其他语言中的功能。
With this work, whenever we’re defining a local variable, we can use the var keyword instead of the full type definition, and the compiler will automatically work out the correct type information to use:
有了这项工作,无论何时我们在定义局部变量时,都可以使用var关键字而不是完整的类型定义,编译器会自动计算出要使用的正确类型信息。
var strings = new ArrayList<String>();
In the above, the variable strings is determined to be of type ArrayList<String>(), but without needing to duplicate the information on the same line.
在上面,变量strings被确定为ArrayList<String>()类型,但不需要在同一行重复信息。
We can use this anywhere we use local variables, regardless of how the value is determined. This includes return types and expressions, as well as simple assignments like the above.
我们可以在任何使用局部变量的地方使用它,不管值是如何确定的。这包括返回类型和表达式,以及像上面这样的简单赋值。
The word var is a special case, in that it’s not a reserved word. Instead, it’s a special type name. This means that it is possible to use the word for other parts of the code – including variable names. It is strongly recommended not to do this to avoid confusion.
var这个词是一个特殊的情况,因为它不是一个保留词。相反,它是一个特殊的类型名称。这意味着有可能将该词用于代码的其他部分–包括变量名。我们强烈建议不要这样做,以避免混淆。
We can use local type inference only when we provide an actual type as part of the declaration. It is deliberately designed not to work when the value is explicitly null, when no value is provided at all, or when the provided value can not determine an exact type – for example, a Lambda definition:
只有当我们提供一个实际的类型作为声明的一部分时,我们才能使用局部类型推理。当值是明确的null,根本没有提供值,或者提供的值不能确定确切的类型时–例如Lambda定义,它被特意设计为不工作。
var unknownType; // No value provided to infer type from
var nullType = null; // Explicit value provided but it's null
var lambdaType = () -> System.out.println("Lambda"); // Lambda without defining the interface
However, the value can be null if it’s a return value from some other call since the call itself provides type information:
然而,如果是其他调用的返回值,该值可以是null,因为该调用本身提供了类型信息。
Optional<String> name = Optional.empty();
var nullName = name.orElse(null);
In this case, nullName will infer the type String because that’s what the return type of name.orElse() is.
在这种情况下,nullName将推断出String的类型,因为这就是name.orElse()的返回类型。
Variables defined this way can have any other modifiers in the same way as any other variable – for example, transitive, synchronized, and final.
以这种方式定义的变量可以像其他变量一样拥有任何其他修饰语–例如,过渡性、同步性和最终性。
2.2. Local Variable Type Inference for Lambdas
2.2.Lambdas的局部变量类型推理
The above work allows us to declare local variables without needing to duplicate type information. However, this does not work on parameter lists, and in particular, not on parameters for lambda functions, which may seem surprising.
上述工作允许我们在不需要重复类型信息的情况下声明局部变量。然而,这对参数列表不起作用,特别是对lambda函数的参数不起作用,这似乎令人惊讶。
In Java 10, we can define Lambda functions in one of two ways – either by explicitly declaring the types or by completely omitting them:
在Java 10中,我们可以用两种方式之一来定义Lambda函数–要么明确地声明类型,要么完全省略它们。
names.stream()
.filter(String name -> name.length() > 5)
.map(name -> name.toUpperCase());
Here, the second line has an explicit type declaration — String — whereas the third line omits it completely, and the compiler works out the correct type. What we can’t do is to use the var type here.
这里,第二行有一个明确的类型声明–String–而第三行则完全省略,编译器会计算出正确的类型。我们不能做的是在这里使用var类型。
Java 11 allows this to happen, so we can instead write:
Java 11允许这种情况发生,所以我们可以改写。
names.stream()
.filter(var name -> name.length() > 5)
.map(var name -> name.toUpperCase());
This is then consistent with the use of the var type elsewhere in our code.
这就与我们代码中其他地方使用的var类型相一致。
Lambdas have always restricted us to using full type names either for every parameter, or for none of them. This has not changed, and the use of var must be for either every parameter or none of them:
Lambdas总是限制我们对每个参数使用完整的类型名,或者不对任何参数使用。这一点没有改变,使用var必须是为每个参数或没有参数。
numbers.stream()
.reduce(0, (var a, var b) -> a + b); // Valid
numbers.stream()
.reduce(0, (var a, b) -> a + b); // Invalid
numbers.stream()
.reduce(0, (var a, int b) -> a + b); // Invalid
Here, the first example is perfectly valid – because the two lambda parameters are both using var. The second and third ones are illegal, though, because only one parameter uses var, even though in the third case we have an explicit type name as well.
这里,第一个例子是完全有效的–因为两个lambda参数都在使用var。但是第二个和第三个是非法的,因为只有一个参数使用了var,尽管在第三个例子中我们也有一个明确的类型名。
3. Imminent Updates
3.即将到来的更新
In addition to the updates that are already available in released JDKs, the upcoming JDK 12 release includes one update – JEP-325.
除了已发布的 JDK 中已有的更新外,即将发布的 JDK 12 还包括一个更新 – JEP-325.。
3.1. Switch Expressions
3.1.切换表达式
JEP-325 brings support for simplifying the way that switch statements work, and for allowing them to be used as expressions to even further simplify the code that makes use of them.
JEP-325带来了对简化switch语句工作方式的支持,并允许它们作为表达式使用,以进一步简化使用它们的代码。
At present, the switch statement works in a very similar manner to those in languages such as C or C++. These changes make it much more similar to the when statement in Kotlin or the match statement in Scala.
目前,switch语句的工作方式与C或C++等语言中的语句非常相似。这些变化使其与Kotlin中的when语句或Scala中的match语句更为相似。
With these changes, the syntax for defining a switch statement looks similar to that of lambdas, with the use of the -> symbol. This sits between the case match and the code to be executed:
有了这些变化,定义switch语句的语法与lambdas的语法相似,使用了->符号。它位于case匹配和要执行的代码之间。
switch (month) {
case FEBRUARY -> System.out.println(28);
case APRIL -> System.out.println(30);
case JUNE -> System.out.println(30);
case SEPTEMBER -> System.out.println(30);
case NOVEMBER -> System.out.println(30);
default -> System.out.println(31);
}
Note that the break keyword is not needed, and what’s more, we can’t use it here. It’s automatically implied that every match is distinct and fallthrough is not an option. Instead, we can continue to use the older style when we need it.
注意,break关键字是不需要的,而且更重要的是,我们不能在这里使用它。它自动暗示每一个匹配都是不同的,而且突破也不是一个选项。相反,当我们需要时,我们可以继续使用旧的风格。
The right-hand side of the arrow must be either an expression, a block, or a throws statement. Anything else is an error. This also solves the problem of defining variables inside of switch statements – that can only happen inside of a block, which means they are automatically scoped to that block:
箭头的右侧必须是一个表达式、一个块或一个抛出语句。其他的都是错误。这也解决了在switch语句中定义变量的问题–这只能发生在一个块中,这意味着它们会被自动归入该块。
switch (month) {
case FEBRUARY -> {
int days = 28;
}
case APRIL -> {
int days = 30;
}
....
}
In the older style switch statement, this would be an error because of the duplicate variable days. The requirement to use a block avoids this.
在老式的切换语句中,这将是一个错误,因为有重复的变量days。使用块的要求避免了这种情况。
The left-hand side of the arrow can be any number of comma-separated values. This is to allow some of the same functionality as fallthrough, but only for the entirety of a match and never by accident:
箭头的左侧可以是任何数量的逗号分隔的值.这是为了允许一些与fallthrough相同的功能,但只适用于整个匹配过程,决不是意外。
switch (month) {
case FEBRUARY -> System.out.println(28);
case APRIL, JUNE, SEPTEMBER, NOVEMBER -> System.out.println(30);
default -> System.out.println(31);
}
So far, all of this is possible with the current way that switch statements work and makes it tidier. However, this update also brings the ability to use a switch statement as an expression. This is a significant change for Java, but it’s consistent with how many other languages — including other JVM languages — are starting to work.
到目前为止,所有这些都可以通过当前switch语句的工作方式实现,并使其更加整洁。然而,本次更新还带来了将switch语句作为表达式的能力。这对Java来说是一个重大的变化,但它与许多其他语言–包括其他JVM语言–开始工作的方式一致。
This allows for the switch expression to resolve to a value, and then to use that value in other statements – for example, an assignment:
这允许switch表达式解析为一个值,然后在其他语句中使用该值 – 例如,赋值。
final var days = switch (month) {
case FEBRUARY -> 28;
case APRIL, JUNE, SEPTEMBER, NOVEMBER -> 30;
default -> 31;
}
Here, we’re using a switch expression to generate a number, and then we’re assigning that number directly to a variable.
在这里,我们使用一个switch表达式来生成一个数字,然后我们把这个数字直接分配给一个变量。
Before, this was only possible by defining the variable days as null and then assigning it a value inside the switch cases. That meant that days couldn’t be final, and could potentially be unassigned if we missed a case.
之前,这只能通过将变量days定义为null,然后在switch cases中给它赋值来实现。这意味着days不可能是最终的,而且如果我们错过了一个案例,有可能会被取消赋值。
4. Upcoming Changes
4.即将发生的变化
So far, all of these changes are either already available or will be in the upcoming release. There are some proposed changes as part of Project Amber that are not yet scheduled for release.
到目前为止,所有这些变化要么已经可用,要么将在即将发布的版本中出现。作为 “琥珀项目 “的一部分,还有一些拟议的变更尚未安排发布。
4.1. Raw String Literals
4.1.原始字符串字数
At present, Java has exactly one way to define a String literal – by surrounding the content in double quotes. This is easy to use, but it suffers from problems in more complicated cases.
目前,Java只有一种方法来定义一个字符串字面,即用双引号包围内容。这很容易使用,但在更复杂的情况下会出现问题。
Specifically, it is difficult to write strings that contain certain characters – including but not limited to: new lines, double quotes, and backslash characters. This can be especially problematic in file paths and regular expressions where these characters can be more common than is typical.
具体而言,很难编写包含某些字符的字符串–包括但不限于:新行、双引号和反斜杠字符。这在文件路径和正则表达式中可能特别有问题,因为这些字符可能比一般情况下更常见。
JEP-326 introduces a new String literal type called Raw String Literals. These are enclosed in backtick marks instead of double quotes and can contain any characters at all inside of them.
JEP-326引入了一种新的字符串字面类型,称为原始字符串字面。这些字符串被括在反问号中,而不是双引号中,其内部可以包含任何字符。
This means that it becomes possible to write strings that span multiple lines, as well as strings that contain quotes or backslashes without needing to escape them. Thus, they become easier to read.
这意味着可以编写跨越多行的字符串,以及包含引号或反斜线的字符串,而无需转义。因此,它们变得更容易阅读。
For example:
比如说。
// File system path
"C:\\Dev\\file.txt"
`C:\Dev\file.txt`
// Regex
"\\d+\\.\\d\\d"
`\d+\.\d\d`
// Multi-Line
"Hello\nWorld"
`Hello
World`
In all three cases, it’s easier to see what’s going on in the version with the backticks, which is also much less error-prone to type out.
在所有这三种情况下,在有反斜线的版本中,更容易看到发生了什么,而且打出来的错误也更少。
The new Raw String Literals also allow us to include the backticks themselves without complication. The number of backticks used to start and end the string can be as long as desired – it needn’t only be one backtick. The string ends only when we reach an equal length of backticks. So, for example:
新的Raw String Literals还允许我们包括回车符本身而不复杂。用来开始和结束字符串的背号的数量可以根据需要而定–它不需要只有一个背号。只有当我们达到相同长度的回车符时,字符串才会结束。因此,举例来说。
``This string allows a single "`" because it's wrapped in two backticks``
These allow us to type in strings exactly as they are, rather than ever needing special sequences to make certain characters work.
这些允许我们准确地键入字符串,而不是永远需要特殊序列来使某些字符发挥作用。
4.2. Lambda Leftovers
4.2.兰姆达的遗留问题
JEP-302 introduces some small improvements to the way lambdas work.
JEP-302对lambdas的工作方式进行了一些小小的改进。
The major changes are to the way that parameters are handled. Firstly, this change introduces the ability to use an underscore for an unused parameter so that we aren’t generating names that are not needed. This was possible previously, but only for a single parameter, since an underscore was a valid name.
主要的变化是处理参数的方式。首先,这一变化引入了对未使用的参数使用下划线的能力,这样我们就不会产生不需要的名字了。这在以前是可行的,但只适用于单个参数,因为下划线是一个有效的名称。
Java 8 introduced a change so that using an underscore as a name is a warning. Java 9 then progressed this to become an error instead, stopping us from using them at all. This upcoming change allows them for lambda parameters without causing any conflicts. This would allow, for example, the following code:
Java 8引入了一个变化,即使用下划线作为名称是一个警告。随后,Java 9将其发展为错误,使我们根本无法使用它们。这个即将到来的变化允许它们用于lambda参数,而不会引起任何冲突。例如,这将允许下面的代码。
jdbcTemplate.queryForObject("SELECT * FROM users WHERE user_id = 1", (rs, _) -> parseUser(rs))
Under this enhancement, we defined the lambda with two parameters, but only the first is bound to a name. The second is not accessible, but equally, we have written it this way because we don’t have any need to use it.
在这种改进下,我们定义了带有两个参数的lambda,但只有第一个参数被绑定到一个名字上。第二个参数是不可访问的,但同样地,我们这样写是因为我们没有任何必要使用它。
The other major change in this enhancement is to allow lambda parameters to shadow names from the current context. This is currently not allowed, which can cause us to write some less than ideal code. For example:
本次改进的另一个主要变化是允许lambda参数对当前上下文的名称进行影射。目前这是不允许的,这可能导致我们写出一些不太理想的代码。比如说。
String key = computeSomeKey();
map.computeIfAbsent(key, key2 -> key2.length());
There is no real need, apart from the compiler, why key and key2 can’t share a name. The lambda never needs to reference the variable key, and forcing us to do this makes the code uglier.
除了编译器之外,并没有真正的需要,为什么key和key2不能共享一个名字。lambda从来不需要引用变量key,强迫我们这样做会使代码更难看。
Instead, this enhancement allows us to write it in a more obvious and simple way:
相反,这一改进使我们能够以更明显和更简单的方式来写它。
String key = computeSomeKey();
map.computeIfAbsent(key, key -> key.length());
Additionally, there is a proposed change in this enhancement that could affect overload resolution when an overloaded method has a lambda argument. At present, there are cases where this can lead to ambiguity due to the rules under which overload resolution works, and this JEP may adjust these rules slightly to avoid some of this ambiguity.
此外,在这个增强中,有一个拟议的变化,当一个重载方法有一个lambda参数时,可能会影响重载解析。目前,在一些情况下,由于重载解析的工作规则,这可能会导致歧义,而这个JEP可能会稍微调整这些规则以避免一些歧义。
For example, at present, the compiler considers the following methods to be ambiguous:
例如,目前,编译器认为以下方法是模糊的。
m(Predicate<String> ps) { ... }
m(Function<String, String> fss) { ... }
Both of these methods take a lambda that has a single String parameter and has a non-void return type. It is obvious to the developer that they are different – one returns a String, and the other, a boolean, but the compiler will treat these as ambiguous.
这两个方法都接受一个有单个String参数的lambda,并且有一个非void的返回类型。对于开发者来说,它们是明显不同的–一个返回String,另一个返回boolean,但是编译器会把这些当作模糊的。
This JEP may address this shortcoming and allow this overload to be treated explicitly.
这个JEP可能会解决这个缺点,并允许明确地处理这种过载。
4.3. Pattern Matching
4.3.模式匹配
JEP-305 introduces improvements on the way that we can work with the instanceof operator and automatic type coercion.
JEP-305介绍了对我们使用instanceof操作符和自动类型强制的工作方式的改进。。
At present, when comparing types in Java, we have to use the instanceof operator to see if the value is of the correct type, and then afterwards, we need to cast the value to the correct type:
目前,在Java中比较类型时,我们必须使用 instanceof操作符来查看值是否属于正确的类型,然后,我们需要将值转换为正确类型。
if (obj instanceof String) {
String s = (String) obj;
// use s
}
This works and is instantly understood, but it’s more complicated than is necessary. We have some very obvious repetition in our code, and therefore, a risk of allowing errors to creep in.
这样做是可行的,并能立即被理解,但它比必要的要复杂得多。我们的代码中有一些非常明显的重复,因此,有可能会让错误悄悄出现。
This enhancement makes a similar adjustment to the instanceof operator as was previously made under try-with-resources in Java 7. With this change, the comparison, cast, and variable declaration become a single statement instead:
该增强功能对instanceof操作符进行了类似于之前在Java 7中的try-with-resources的调整。有了这个变化,比较、转换和变量声明就变成了一个单一的语句。
if (obj instanceof String s) {
// use s
}
This gives us a single statement, with no duplication and no risk of errors creeping in, and yet performs the same as the above.
这给了我们一个单一的声明,没有重复,也没有错误爬行的风险,但执行情况与上述相同。
This will also work correctly across branches, allowing the following to work:
这也将在各分支间正确工作,使以下工作得以进行。
if (obj instanceof String s) {
// can use s here
} else {
// can't use s here
}
The enhancement will also work correctly across different scope boundaries as appropriate. The variable declared by the instanceof clause will correctly shadow variables defined outside of it, as expected. This will only happen in the appropriate block, though:
该增强功能也将酌情在不同的范围边界内正确工作。由instanceof子句声明的变量将正确地影射在它之外定义的变量,正如预期的那样。不过,这只发生在适当的块中。
String s = "Hello";
if (obj instanceof String s) {
// s refers to obj
} else {
// s refers to the variable defined before the if statement
}
This also works within the same if clause, in the same way as we rely on for null checks:
这也是在同一个if子句中起作用的,与我们对null检查所依赖的方式相同。
if (obj instanceof String s && s.length() > 5) {
// s is a String of greater than 5 characters
}
At present, this is planned only for if statements, but future work will likely expand it to work with switch expressions as well.
目前,这只计划用于if语句,但未来的工作可能会将其扩展到也适用于switch表达式。
4.4. Concise Method Bodies
4.4.简明方法体
JEP Draft 8209434 is a proposal to support simplified method definitions, in a way that is similar to how lambda definitions work.
JEP Draft 8209434是一项支持简化方法定义的提案,其方式与lambda定义的工作方式类似。
Right now, we can define a Lambda in three different ways: with a body, as a single expression, or as a method reference:
现在,我们可以用三种不同的方式定义Lambda:带主体、作为一个单一的表达式或作为一个方法引用。
ToIntFunction<String> lenFn = (String s) -> { return s.length(); };
ToIntFunction<String> lenFn = (String s) -> s.length();
ToIntFunction<String> lenFn = String::length;
However, when it comes to writing actual class method bodies, we currently must write them out in full.
然而,当涉及到编写实际的类方法体时,我们目前必须把它们完整地写出来。
This proposal is to support the expression and method reference forms for these methods as well, in the cases where they are applicable. This will help to keep certain methods much simpler than they currently are.
本提案是为了支持这些方法的表达式和方法引用形式,在它们适用的情况下。这将有助于使某些方法比目前更简单。
For example, a getter method does not need a full method body, but can be replaced with a single expression:
例如,一个getter方法不需要一个完整的方法体,但可以用一个表达式来代替。
String getName() -> name;
Equally, we can replace methods that are simply wrappers around other methods with a method reference call, including passing parameters across:
同样地,我们可以用方法引用调用来取代那些简单地包装其他方法的方法,包括在对面传递参数。
int length(String s) = String::length
These will allow for simpler methods in the cases where they make sense, which means that they will be less likely to obscure the real business logic in the rest of the class.
这些将允许在有意义的情况下使用更简单的方法,这意味着它们不太可能掩盖类中其他部分的真正业务逻辑。
Note that this is still in draft status and, as such, is subject to significant change before delivery.
请注意,这仍处于草案状态,因此,在交付前可能会有重大变化。
5. Enhanced Enums
5.增强型枚举
JEP-301 was previously scheduled to be a part of Project Amber. This would’ve brought some improvements to enums, explicitly allowing for individual enum elements to have distinct generic type information.
JEP-301之前被安排为Project Amber的一部分。这将为枚举带来一些改进,明确地允许单个枚举元素拥有不同的通用类型信息。
For example, it would allow:
例如,它将允许。
enum Primitive<X> {
INT<Integer>(Integer.class, 0) {
int mod(int x, int y) { return x % y; }
int add(int x, int y) { return x + y; }
},
FLOAT<Float>(Float.class, 0f) {
long add(long x, long y) { return x + y; }
}, ... ;
final Class<X> boxClass;
final X defaultValue;
Primitive(Class<X> boxClass, X defaultValue) {
this.boxClass = boxClass;
this.defaultValue = defaultValue;
}
}
Unfortunately, experiments of this enhancement inside the Java compiler application have proven that it is less viable than was previously thought. Adding generic type information to enum elements made it impossible to then use those enums as generic types on other classes – for example, EnumSet. This drastically reduces the usefulness of the enhancement.
不幸的是,在Java编译器应用中对这一增强功能的实验证明,它的可行性比以前想象的要低。在枚举元素上添加通用类型信息,使得后来无法将这些枚举作为其他类上的通用类型–例如,EnumSet。这极大地降低了该增强功能的实用性。
As such, this enhancement is currently on hold until these details can be worked out.
因此,在这些细节得到解决之前,这项改进目前被搁置。
6. Summary
6.总结
We’ve covered many different features here. Some of them are already available, others will be available soon, and yet more are planned for future releases. How can these improve your current and future projects?
我们在这里已经涵盖了许多不同的功能。其中一些已经可用,另一些即将可用,还有更多计划在未来的版本中使用。这些功能如何能改善你当前和未来的项目?