从J ava 8 继续前进的 20 个理由

“那些不愿提供生活所需的人遭受了后果。如果您不想改变,那么您将被甩在后面。如果您不想改变,那么您将被赶出技术时代。如果您不喜欢改变,那您就会过时。您将无法生活在瞬息万变的世界中。生活需要改变。”

- 周日牧师Adelaja

从1995年Java的第一个beta版本发布到2006年底,该语言的新版本大约每两年出现一次。但是,在2006年末发布Java 6之后,开发人员不得不等待近五年才能使用该语言的新版本。Java 8和9同样也表现不佳,每个版本分别等待了近三年和三年半。

Java版本7、8和9都在API、垃圾收集器、编译器等方面对Java生态系统进行了重大更改。甲骨文公司Java首席架构师Mark Reinhold承诺为Java的未来版本提供为期6个月的快速发布周期,并散布了长期支持(LTS)版本,到目前为止,他的团队已经兑现了这一承诺-版本10、11、12和13每6个月(平均13.5天)发布一次(平均)(朋友之间有两星期了?)。

最新的LTS版本是Java 11,于2018年9月发布,它将在下一个LTS版本(2021年9月)发布之前进行免费的公共更新,并且扩展支持到2026年9月。Reinhold 的新愿景是每三年发布一次LTS Java版本。(每六个版本),因此下一个LTS版本将是Java SE 17,暂定于2021年9月发布。这将允许从旧LTS版本到新LTS版本的较长过渡期。

请注意,Oracle JDK、OpenJDK和AdoptOpenJDK的发布日期和支持周期之间存在细微但重要的差异,我将在以后的文章中介绍。

尽管Java开发人员并不像Python用户那样糟糕-去年他们中的很大一部分仍在使用8岁 * 退休 * 版本的Python 进行编码,但到目前为止,我们从Java 8过渡到现在进展缓慢,即使Java 9已经存在两年多了。有很多充分的理由说明为什么您应该考虑从现在使用的任何Java版本迁移到(至少)Java 11,包括...

目录

  1. Oracle不再为Java 8提供免费支持
  2. jshell,Java REPL(Java 9)
  3. 模块和链接(Java 9)
  4. 改进的Javadoc(Java 9)
  5. Collection 不变工厂方法(Java 9)
  6. Stream 改进(Java 9)
  7. 多发行版jar(Java 9)
  8. private interface 方法(Java 9)
  9. GraalVM,新的Java虚拟机(Java 9/10)
  10. 局部变量类型推断(Java 10)
  11. 不可修改的Collection增强功能(Java 10)
  12. 容器意识(Java 10)
  13. 单个源文件启动(Java 11)
  14. switch 表达式-迈向模式匹配的一步(Java 12)
  15. teeing Collectors (Java 12)
  16. 多行文本块(Java 13(预览版))
  17. 带有Project Metropolis的Java-on-Java编译器(Java 14+)
  18. Project Amber(Java 14+)中的流类型,匿名变量,数据类和密封类型
  19. Fiber使用Project Loom(Java 14+)进行协程,尾部呼叫优化和轻量级用户模式
  20. Valhalla(Java 14+)中的值类型,泛型专业化和修饰泛型
 

1、Oracle不再为Java 8提供免费支持

尽管AdoptOpenJDK 至少在2023年9月之前将为其Java 8版本提供免费的公共更新,但Oracle已经放弃了对其JDK的免费支持。“扩展支持”将一直提供到2025年3月,可以通过购买Oracle Java订阅来获得。

在此处了解Oracle JDK,OpenJDK和AdoptOpenJDK之间的一些区别。

2、jshell,Java REPL(Java 9)

一个读-评估-打印循环(REPL)已经成为了现代编程语言的几乎强制性的特征。Python有一个,Ruby有一个,并且从JDK 9开始,Java有一个。Java REPL jshell是一种尝试一些小的代码并实时获取反馈的好方法:

$ jshell
|  Welcome to JShell -- Version 11.0.2
|  For an introduction type: /help intro
 
jshell> var x = List.of(1, 2, 3, 4, 5)
x ==> [1, 2, 3, 4, 5]
 
jshell> x.stream().map(e -> (e + " squared is " + e*e)).forEach(System.out::println)
1 squared is 1
2 squared is 4
3 squared is 9
4 squared is 16
5 squared is 25
 

[返回目录]

3、模块和链接(Java 9)

Java 9还引入了模块,这些模块是软件包之上的组织级别。如果您安装了Java 9+ ,则即使您不知道,也已经在使用modules。您可以使用以下命令检查哪些模块可用:

$ java --list-modules
java.base@11.0.2
java.compiler@11.0.2
java.datatransfer@11.0.2
...
jdk.unsupported.desktop@11.0.2
jdk.xml.dom@11.0.2
jdk.zipfs@11.0.2
 

模块具有许多好处,包括与新的Java链接器结合时减小Java应用程序的大小(通过仅包括该应用程序所需的模块和子模块),允许私有包(封装在模块中)以及快速失败(如果您将应用程序取决于模块,并且该模块在您的系统上不可用)。这样可以防止当一些代码尝试访问系统中不存在的程序包中定义的方法时发生运行时错误。

在线上有很多很好的教程,它们解释了如何构造和配置模块。继续模块化!

[返回目录]

4、改进的Javadoc(Java 9)

从Java 9开始,诸如“ java字符串类”之类的Google搜索已成为历史。新改进的Javadoc可搜索,符合HTML5并与新模块层次结构兼容。在此处查看JDK 8和JDK 9 Javadocs 之间的区别。

有了这个小小的Chrome插件,我就可以直接从多功能框中搜索API的多个版本,从而使搜索Oracle Javadoc变得比以往更加轻松。

[返回目录]

5、Collection不可变工厂方法(Java 9)

在Java 9之前,快速生成小的,不可修改Set的预定义值的最简单方法是:

Set<String> seasons = new HashSet<>();
 
seasons.add("winter");
seasons.add("spring");
seasons.add("summer");
seasons.add("fall");
 
seasons = Collections.unmofidiableSet(seasons);
 

对于这么小的任务,真是可笑的代码量。Java 9 通过引入不可变的工厂方法(见上文)和,大大简化了这种表示法:Map.of()List.of()Set.of()

Set<String> seasons = Set.of("winter", "spring", "summer", "fall")
 

需要注意的是,Collection以这种方式创建的s不能包含null值-在Map条目中包含值。

总体而言,这是一个可喜的变化,有助于(有点)抵制Java作为不必要的冗长语言的声誉,并减少了完成诸如创建较小Set的预定义值之类的次要任务所需的精力。

[返回目录]

6、Stream改进(Java 9)

在过去的十年中,StreamAPI无疑已经在Java范围内提供了最大的增长。几乎单枪匹马地Stream将函数式编程带入了Java,将许多for循环变成了map()管道。

Java的9带来了一些小的改进Stream,在iterate()takeWhile()dropWhile(),和ofNullable()方法。

Stream.iterate()允许我们将函数递归地应用到Objects 流(以some开头seed Object),并选择何时停止返回值。基本上是Java 8 Stream.iterate()加上Java 8Stream.filter()

jshell> Stream.iterate("hey", x -> x.length() < 7, x -> x + "y").forEach(System.out::println)
hey
heyy
heyyy
heyyyy
 

新方法takeWhile()dropWhile()方法本质上将过滤器Stream分别应用于接受或拒绝所有值,直到满足某些条件为止:

jshell> IntStream.range(0, 5).takeWhile(i -> i < 3).forEach(System.out::println)
0
1
2
 
jshell> IntStream.range(0, 5).dropWhile(i -> i < 3).forEach(System.out::println)
3
4
 

Java的9也带来了OptionalStream紧密联系起来,提供Stream.ofNullable()Optional.stream()方法,使工作与StreamS和Optional值的微风:

jshell> Optional.ofNullable(null).stream().forEach(System.out::println)
 
jshell> Optional.of(1).stream().forEach(System.out::println)
1
 
jshell> Stream.ofNullable(null).forEach(System.out::println)
 
jshell> Stream.ofNullable(1).forEach(System.out::println)
1
 

Java的还没有一个完全成熟的列表理解接口,所以开发商都自己曾到海里试图拿出自己的解决方案。希望Java的下一个LTS版本将包括真实的列表理解,就像Haskell中出现的那样:

List.comprehension(
  Stream<T> input,
  Predicate super T>... filters)
jshell> // NOT ACTUAL JAVA CODE
jshell> List.comprehension(
   ...>   Stream.iterate(1, i -> ++i), // 1, 2, 3, 4, ...
   ...>   i -> i%2 == 0,               // 2, 4, 6, 8, 10, ...
   ...>   i -> i > 7,                  // 8, 10, 12, 14, ...
   ...>   i -> i < 13                  // 8, 10, 12
   ...> ).take(3).forEach(System.out::println)
8
10
12
 
 

Java已经Stream通过该iterate()方法提供了infinite (只需使用不带过滤器的Java 8iterate()),但是一旦单个元素失败filterStream就会被截断。为了实现真正的列表理解,只需将不符合过滤条件的元素从输出中删除,而不必终止Stream

我们可以梦想!

[返回目录]

7、多发行版jar(Java 9)

Java 9的另一个令人兴奋的功能是能够创建多版本jar文件。简而言之,这意味着您的软件包(或模块)可以包含针对每个Java版本9及更高版本的特定实现。因此,如果在客户端计算机上安装了Java 9,则可以具有为Java 9加载的类的特定版本。

您所需要做的就是Multi-Release: true 在的META-INF/MANIFEST.MF文件中指定jar,然后在目录中包含不同的类版本META-INF/versions

jar root /
  - Foo.class
  - Bar.class
  - META-INF
     - MANIFEST.MF
     - versions
        - 9
           - Foo.class
        - 10
           - Foo.class
 

在上面的例子中,如果这个jar在安装了Java 10的机器上使用,那么Foo.class引用/META-INF/versions/10/Foo.class。否则,如果Java 9可用,那么Foo.class引用/META-INF/versions/9/Foo.class。如果目标机器上没有安装这两个Java版本,则使用默认/Foo.class。

如果您暂时不使用Java 8,但是想为将来切换到新版本做准备,则多版本jars是您的理想之选。实施它们,您将一无所获!

[返回目录]

8、private interface方法(Java 9)

Java 8 在interfaces中引入了方法default,这对于DRY(不要重复自己)软件开发是有帮助的。您不再需要在单个的多个实现中重新定义相同的方法interface相反,您可以在接口中定义具有默认主体的方法,实现该接口的任何类都可以继承该方法。

interface MyInterface {
 
  default void printSquared (int n) {
    System.out.println(n + " squared is " + n*n);
  }
 
  default void printCubed (int n) {
    System.out.println(n + " cubed is " + n*n*n);
  }
}
 
public class MyImplementation implements MyInterface { }
 

我们可以像这样使用此类jshell

jshell> var x = new MyImplementation()
x ==> MyImplementation@39c0f4a
 
jshell> x.printSquared(3)
3 squared is 9
 
jshell> x.printCubed(3)
3 cubed is 27
 

Java 9 通过允许其中的private方法进一步改进了interface。这意味着我们可以进一步提高代码重用性,尤其是在这些默认方法实现之间,而用户无法访问这些“帮助”方法:

interface MyInterface {
 
  private void printHelper (String verb, int n, int pow) {
    System.out.printf("%d %s is %d%n", n, verb, (int) Math.pow(n, pow));
  }
 
  default void printSquared (int n) {
    printHelper("squared", n, 2);
  }
 
  default void printCubed (int n) {
    printHelper("cubed", n, 3);
  }
}
 
public class MyImplementation implements MyInterface { }
 

在重新实现MyInterface中,我们使用的方法与上面使用的方法完全相同,但是现在,方法体内的重复代码被提取为一个小的“辅助”方法。通过减少复制和粘贴的代码量,我们可以使interface维护更加容易。

[返回目录]

9、GraalVM,新的Java虚拟机(Java 9/10)

GraalVM(发音为“ crawl”,带有硬的“ g”而不是“ c”)是Oracle基于HotSpot和OpenJDK创建的新Java虚拟机和开发套件。

开发Graal的目的是通过尝试与本机语言(编译为机器代码)相匹配的速度来提高Java应用程序的性能。GraalVM与其他Java虚拟机的主要区别在于两个方面:

  1. 允许提前(AOT)编译
  2. 支持多语言编程

正如大多数Java开发人员所知道的那样,Java会编译为Java字节码,然后由Java虚拟机读取并转换为用户计算机专用于处理器的代码。这种分两步进行的编译是Java “编写一次,随处运行”座右铭的一部分,Java程序员无需担心特定机器体系结构的特定实现。如果可以在她的计算机上运行,​​则只要在该计算机上安装了Java Runtime Environment(JRE),它就可以在其他任何计算机上运行。

注意,该模型只是将特定于体系结构的详细信息从Java程序员推送到JVM工程师。仍然需要编写特定于机器的代码,但对于普通的Java开发人员而言,它是隐藏的。当然,这就是为什么Windows、Mac和Linux有不同的JDK版本的原因。

GraalVM结合了这两个步骤来生成机器本地映像——二进制代码,该代码是针对运行VM的特定体系结构创建的。这种从字节码到机器语言的提前编译意味着GraalVM可以生成二进制可执行文件,这些可执行文件可以立即运行而无需通过JVM。

这不是一个新概念,因为像C这样的语言总是会编译为特定于机器的二进制代码,但是对于Java生态系统来说却是新的。(或新建- ISH,由于Android运行时使用了AOT编译自2013年左右),与GraalVM AOT编译带来减少启动时间,并提高性能比JIT编译的代码。

但是真正使GraalVM与其他Java VM脱颖而出的是Graal是一个多语言 VM:

const express = require('express');
const app = express();
app.listen(3000);
 
app.get('/', function(req, res) {
  var text = 'Hello World!';
  const BigInteger = Java.type('java.math.BigInteger');
  text += BigInteger.valueOf(2).pow(100).toString(16);
  text += Polyglot.eval('R', 'runif(100)')[0];
  res.send(text);
})
 

借助Truffle语言实施框架,Graal 在Java、JavaScript、R、Python、Ruby和C之间提供了零开销的互操作性。用任何一种语言编写的代码都可以在用任何一种语言编写的程序中运行。您可以编译调用Python代码的Ruby程序,或使用C库的Java程序。要使所有这些语言相互正确地沟通确实是一项艰巨的工作,其结果几乎令人难以置信。

[返回目录]

10、局部变量类型推断(Java 10)

Java 10继续通过var关键字与样板间的战争:

jshell> var x = new ArrayList<Integer>();
x ==> []
 
jshell> x.add(42)
$2 ==> true
 
jshell> x
x ==> [42]
 

var 类型允许在Java版本10及更高版本中进行本地类型推断。这小小的新语法,就像之前的Diamond运算符(<>在JDK 7中定义)一样,使变量的定义稍微有些冗长。

局部类型推断意味着var 只能在方法主体或其他类似的代码块内部使用。它不能用于声明实例变量或用作方法的返回类型,等等。

请注意,x上面的变量仍然具有类型 ,只是从上下文中推断出来的。当然,这意味着我们不能将非arraylist 值赋给x:

jshell> x = "String"
|  Error:
|  incompatible types: java.lang.String cannot be converted to java.util.ArrayList<java.lang.Integer>
|  x = "String"
|      ^------^
 

即使有类型推断,Java仍然是静态类型的语言。一旦变量被声明为特定类型,它就始终是该类型。例如,这与JavaScript不同,在JavaScript中,变量的类型是动态的,并且可以逐行更改。

[返回目录]

11、不可修改的Collection增强功能(Java 10)

用Java处理不可变数据非常困难。用final... 声明时,原始值是不可变的

jshell> public class Test { public static final int x = 3; }
|  created class Test
 
jshell> Test.x
$3 ==> 3
 
jshell> Test.x = 4
|  Error:
|  cannot assign a value to final variable x
|  Test.x = 4
|  ^----^
 

...但是即使像final原始数组这样简单的东西也不是真正不变的:

jshell> public class Test { public static final int[] x = new int[]{1, 2, 3}; }
|  replaced class Test
 
jshell> Test.x[1]
$5 ==> 2
 
jshell> Test.x[1] = 6
$6 ==> 6
 
jshell> Test.x[1]
$7 ==> 6
 

final上面的关键字表示对象 x是不可变的,但不一定是其内容。在这种情况下,不变性意味着x只能引用特定的内存位置,因此我们不能做类似的事情:

jshell> Test.x = new int[]{8, 9, 0}
|  Error:
|  cannot assign a value to final variable x
|  Test.x = new int[]{8, 9, 0}
|  ^----^
 

如果x不是final,则上面的代码可以正常运行(自己尝试!)。当然,当程序员拥有一个想要成为不可变对象的对象,将其声明为final,然后按照自己的喜好进行时,这会引起各种问题。final Objects根本不是final

在Java 7中引入Collections类时,它带来了一些不可修改的……()方法,提供了特定集合的“不可修改视图”。这意味着,如果您只能访问不可修改的视图,那么您就不能访问add()、remove()、set()、put()等方法。任何修改对象或其内容的方法都有效地隐藏在视图中。眼不见,心不烦。

jshell> List<Integer> lint = new ArrayList<>();
lint ==> []
 
jshell> lint.addAll(List.of(1, 9, 0, 1))
$22 ==> true
 
jshell> lint
lint ==> [1, 9, 0, 1]
 
jshell> List<Integer> view = Collections.unmodifiableList(lint);
view ==> [1, 9, 0, 1]
 
jshell> view.add(8);
|  Exception java.lang.UnsupportedOperationException
|        at Collections$UnmodifiableCollection.add (Collections.java:1058)
|        at (#25:1)
 

但是,如果保留对基础对象的访问权限,则仍然可以对其进行修改。有权访问无法修改的视图的任何人都可以看到您的更改:

jshell> lint.addAll(List.of(1, 8, 5, 5))
$26 ==> true
 
jshell> lint
lint ==> [1, 9, 0, 1, 1, 8, 5, 5]
 
jshell> view
view ==> [1, 9, 0, 1, 1, 8, 5, 5]
 

因此,即使“无法修改的视图”仍然可以进行修改。

遵循Java 9的“不可变工厂方法”的脚步,Java 10引入了更多的API改进,使使用不可变数据变得更加容易。首先是新的copyOf()方法添加到ListSetMap。这些将创建它们各自类型的真正不变的(浅)副本:

jshell> List<Integer> nope = List.copyOf(lint)
nope ==> [1, 9, 0, 1, 1, 8, 5, 5]
 
jshell> nope.add(4)
|  Exception java.lang.UnsupportedOperationException
|        at ImmutableCollections.uoe (ImmutableCollections.java:71)
|        at ImmutableCollections$AbstractImmutableCollection.add (ImmutableCollections.java:75)
|        at (#32:1)
 
jshell> lint.set(3, 9)
$33 ==> 1
 
jshell> lint
lint ==> [1, 9, 0, 9, 1, 8, 5, 5]
 
jshell> nope
nope ==> [1, 9, 0, 1, 1, 8, 5, 5]
 

在collector类中有一些新的tounmodi…()方法,它们也可以创建真正不可变的对象:

jshell> var lmod = IntStream.range(1, 6).boxed().collect(Collectors.toList())
lmod ==> [1, 2, 3, 4, 5]
 
jshell> lmod.add(6)
$38 ==> true
 
jshell> lmod
lmod ==> [1, 2, 3, 4, 5, 6]
 
jshell> var lunmod = IntStream.range(1, 6).boxed().collect(Collectors.toUnmodifiableList())
lunmod ==> [1, 2, 3, 4, 5]
 
jshell> lunmod.add(6)
|  Exception java.lang.UnsupportedOperationException
|        at ImmutableCollections.uoe (ImmutableCollections.java:71)
|        at ImmutableCollections$AbstractImmutableCollection.add (ImmutableCollections.java:75)
|        at (#41:1)
 

Java一次迈出了一步,正朝着针对不可变数据的更全面的模型迈进。

[返回目录]

12、容器意识(Java 10)

在2006年至2008年之间,Google工程师向Linux内核添加了一个很酷的新功能,称为cgroup或“控制组”。此新功能“限制,说明和隔离进程集合的资源使用(CPU、内存、磁盘I / O、网络等)”。

如果您使用Hadoop、Kubernetes或Docker这样广泛使用控制组的工具,那么这个概念可能听起来很熟悉。如果不能限制特定进程组的可用资源,Docker就不可能存在。

不幸的是,Java是在实现cgroup之前就创建的,因此Java最初完全忽略了此功能。

但是,从Java 10开始,JVM 知道何时在容器中运行 JVM ,并且默认情况下会遵守该容器对其施加的资源限制。此功能也已反向移植到JDK8。因此,如果选择最新的Java 8版本,则JVM也将支持容器。

换句话说,随着Java 10的发布,Docker和Java 终于成为了朋友。

[返回目录]

13、单个源文件启动(Java 11)

从Java 11开始,您不再需要在运行之前编译单个源文件。在主类中java看到该main方法,并且在命令行上调用该代码时将自动编译并运行代码:

// Example.java
public class Example {
  public static void main (String[] args) {
 
    if (args.length < 1)
      System.out.println("Hello!");
 
    else
      System.out.println("Hello, " + args[0] + "!");
 
  }
}
$ java Example.java 
Hello!
 
$ java Example.java Biff
Hello, Biff!
 
 

这是对java启动程序的一个很小但有用的更改,它可以(尤其是)使Java的新手培训变得更加容易,而无需引入显式编译此类小型入门程序的“仪式”。

[返回目录]

14、switch表达式——迈向模式匹配的一步(Java 12)

我们不是已经有switchJava表达式了吗?这是什么?

jshell> int x = 2;
x ==> 2
 
jshell> switch(x) {
   ...>   case 1: System.out.println("one"); break;
   ...>   case 2: System.out.println("two"); break;
   ...>   case 3: System.out.println("three"); break;
   ...> }
two
 

…这是switch语句,不是switch表达式。语句指导程序的流程,但不计算值本身。(例如,您不能执行类似于y = switch(x){…})。另一方面,表达式求值为结果。因此表达式可以赋值给变量,从函数返回,等等。

switch 表达式看起来与switch语句略有不同。与上述语句类似的表达式可能类似于:

String name = switch(x) {
    case 1 -> "one";
    case 2 -> "two";
    case 3 -> "three";
    default -> throw new IllegalArgumentException("I can only count to 3.");
};
 
System.out.println(name);
 

您会看到此片段与早期片段之间的一些区别。首先,我们使用箭头->代替冒号:。这是,语法,如何switch语句从区分一个switch表达式编译器。

其次,break表达式代码中没有。switch表达式不会像with switch语句那样“掉队” ,因此不需要breakcase分支之后。

第三,除非列出的s 详尽无遗,否则我们必须有一个。也就是说,编译器可以判断-对于-的任何可能值,是否有一条语句可以捕获该值。如果没有,我们必须有一个案例。截至目前,用尽a 或一个值来穷尽所有可能的情况而没有a的唯一真实方法是。defaultcasexcasedefaultdefaultswitchbooleanenum

最后,我们可以将switch表达式分配给变量!switch表达式和语句之间的差异类似于Java中三元运算符? :if else语句之间的差异:

jshell> int what = 0;
what ==> 0
 
jshell> boolean flag = false;
flag ==> false
 
jshell> if (flag) what = 2; else what = 3;
 
jshell> what
what ==> 3
 
jshell> what = flag ? 4 : 5
what ==> 5
 

if else引导代码流,但不能赋值给变量(在Java中不能执行y = if (flag) 3 else 4这样的操作),而使用三元运算符?:定义一个表达式,该表达式可以赋值给一个变量。

switch表达式是Java 全面支持模式匹配的一步。这是通过Project Amber开发的,我将在下面更详细地介绍。请注意,开关表达式是Java 12和13的“预览”功能,但计划将其升级为Java 14的最终版本。

[返回目录]

15、teeing Collectors(Java 12)

注意,DoubleStream确实有一个average()方法。下面的示例仅用于说明目的。

如果您曾经尝试对StreamJava中的a 值执行复杂的操作,那么您就会知道Streams只能在一次时间内进行迭代是多么令人讨厌:

jshell> var ints = DoubleStream.of(1, 2, 3, 4, 5)
ints ==> java.util.stream.DoublePipeline$Head@12cdcf4
 
jshell> var avg = ints.sum()
avg ==> 15.0
 
jshell> avg /= ints.count()
|  Exception java.lang.IllegalStateException: stream has already been operated upon or closed
|        at AbstractPipeline.evaluate (AbstractPipeline.java:229)
|        at DoublePipeline.count (DoublePipeline.java:486)
|        at (#19:1)
 

Java 12通过引入Collectors.teeing()(受UNIX实用程序tee启发)减轻了您的痛苦,该功能“复制”了一个流,允许您Stream在合并结果之前同时执行两个操作。

上面的Java 12版本可能看起来像 ...

jshell> import static java.util.stream.Collectors.*
 
jshell> var ints = DoubleStream.of(1, 2, 3, 4, 5)
ints ==> java.util.stream.DoublePipeline$Head@574caa3f
 
jshell> ints.boxed().collect(teeing(
   ...>   summingDouble(e -> e),
   ...>   counting(),
   ...>   (a,b) -> a/b
   ...> ))
$20 ==> 3.0
 

这是完美尚远(语法有点笨重,你可以看到,我们需要框中的原始double使用S boxed()为了以后操作它们),但它的转向更灵活的进度StreamS(也许,最终,列表内涵?)。

[返回目录]

16、多行文本块(Java 13(预览版))

Java 13 现在提供的另一个很酷的功能是多行文本块。这是一个预览功能(在即将发布的JDK 14中提供了修订的预览),因此您需要--enable-preview在运行java或时传递标志jshell

请注意,这是预览功能,可能会受到将来的更改和影响。在尝试认真使用此功能之前,请阅读手册。

$ jshell --enable-preview
|  Welcome to JShell -- Version 13.0.1
|  For an introduction type: /help intro
 
jshell> String greetings = """
   ...> Hello. My name is Inigo Montoya.
   ...> You killed my father.
   ...> Prepare to die.
   ...> """
greetings ==> "Hello. My name is Inigo Montoya.\nYou killed my father.\nPrepare to die.\n"
 

多行文本块必须以三个连续的双引号字符开头""",后跟一个换行符,并且必须类似地以一个换行字符结尾,并以三个连续的双引号字符结尾"""

在上述情况下,此功能可能不是很有用,但在执行诸如生成String带有大量散布变量的s或尝试String通过多行串联模拟缩进代码之类的操作时,此功能可大大提高可读性。我们将在Java的早期版本中编写的内容为

jshell> String html1 = "\n\t\n\t\t

\"I love Java and Java loves me!\"

\n\t\n\n"
; html1 ==> "\n\t\n\t\t

\"I love Java and Java ... h1>\n\t\n\n"

 

要么是 

jshell> String html2 =
   ...>   "\n" +
   ...>     "\t\n" +
   ...>       "\t\t

\"I love Java and Java loves me!\"

\n"
+ ...> "\t\n" + ...> "\n"; html2 ==> "\n\t\n\t\t

\"I love Java and Java ... h1>\n\t\n\n"

 

...我们现在可以编写出更具可读性的文字:

jshell> String html3 = """
   ...> 
   ...>     
   ...>         

"I love Java and Java loves me!"

...> ...> ...> """
html3 ==> "\n\t\n\t\t

\"I love Java and Java ... h1>\n\t\n\n" jshell> html1.equals(html2); html2.equals(html1); $16 ==> true $17 ==> true

 

无需转义单引号或双引号,也不需要"..." +一遍又一遍地重复,只需格式化良好的空白保留文本即可。多行字符串“ fences” """也遵守代码的缩进级别。因此,如果您位于方法或类中,则无需将多行String对齐到页面的左侧:

jshell> String bleh = """
   ...>        hello
   ...>        is it me you're looking for
   ...>        """
bleh ==> "hello\nis it me you're looking for\n"
 
jshell> String bleh = """
   ...>        hello
   ...>        is it me you're looking for
   ...> """
bleh ==> "       hello\n       is it me you're looking for\n"
 

[返回目录]

17、带有Project Metropolis的Java-on-Java编译器(Java 14+)

2020年编写Java的最令人兴奋的事情不是它在过去六年中走了多远,而是在接下来的六年中可能会走到哪里。因此,在这里的最后几点,我想讨论一些正在进行的Java 项目,这些项目有望在不久的将来提供新的令人兴奋的功能。

其中第一项是工程大都市,到旨在重写JVM在Java本身的部分(或全部),在那里它目前书面(部分),如C和C等语言++(取决于哪个 JVM您正在使用)。该项目的负责人将此方法称为“ Java on Java”,它具有以下优点:

  1. 将Java与其他语言的依赖关系分离开来,新版本,错误修复,安全补丁等使这些语言变得复杂;
  2. 允许VM使用当前应用于其他已编译Java代码的“热点”优化方案进行自我优化;
  3. 可维护性/简化性 -如果可以完全用Java重写JVM,那么JVM架构师将只需要了解Java本身,而不需要了解多种语言;这将使JVM易于维护。

由于C和C ++具有“接近金属”的特性,因此它们比Java具有更多的优势。为了将完全基于Java的JVM提升到与这些当前混合语言JVM相同的水平,可能必须向Java语言添加新功能(例如值类型)。这是一个庞大的项目,具有很多来龙去脉(以及您拥有什么),所以不要指望它很快出现。仍然...在JVM世界中发生了许多令人兴奋的事情!

[返回目录]

18、Project Amber(Java 14+)中的流类型、匿名变量、数据类和密封类型

Project Amber是Java API大量改进的代号,所有这些改进都旨在简化语法。这些改进包括...

使用instanceof进行流类型输入

Java的instanceof关键字检查对象是否是特定类或接口的实例,并返回a boolean来实现:

jshell> ArrayList<Integer> alist = new ArrayList<>();
alist ==> []
 
jshell> alist instanceof List
$5 ==> true
 
jshell> alist instanceof ArrayList
$6 ==> true
 
jshell> alist instanceof Object
$7 ==> true
 

在实践中,当使用instanceof并返回true时,相关对象将被显式地转换为所需的类型,并作为该类型的对象使用:

jshell> void alertNChars (Object o) {
   ...>   if (o instanceof String)
   ...>     System.out.println("String contains " + ((String)o).length() + " characters");
   ...>   else System.out.println("not a String");
   ...> }
|  created method alertNChars(Object)
 
jshell> String s = "I am a banana";
s ==> "I am a banana"
 
jshell> Integer i = 1;
i ==> 1
 
jshell> alertNChars(s)
String contains 13 characters
 
jshell> alertNChars(i)
not a String
 

Project Amber旨在通过流敏感类型(或“流类型”)稍微简化此语法,使编译器可以在其中推理instanceof块。基本上,如果if (x instanceof C)执行块,则该对象x必须是类的实例C(或的子类C),因此C可以使用实例方法。在Project Amber之后,以上方法应类似于:

void alertNChars (Object o) {
  if (o instanceof String s)
    System.out.println("String contains " + s.length() + " characters");
  else System.out.println("not a String");
}
 

这是一个很小的更改,但是它减少了一些视觉混乱,并奠定了Java 模式匹配的一些基础工作。

匿名lambda变量

某些语言允许用户通过使用单个下划线字符而不是参数标识符来忽略lambda(和其他位置)中的参数。从Java 9开始,将下划线字符本身用作标识符将在编译时引发错误,因此它已“修复”,现在也可以在Java中以这种“匿名”方式使用。

如果您关心的是所提供的某些信息,而不是全部信息,则使用“未命名”或“匿名”变量和参数。与上面的链接给出的示例类似的示例是a BiFunction,该示例将an Integer和a Double作为其两个参数,但仅将Integeras作为返回String。实现Double不需要第二个参数()BiFunction

BiFunction<Integer, Double, String> bids = (i, d) -> String.valueOf(i);
 

那么,为什么我们完全需要命名第二个参数(d)?匿名参数将使我们能够简单地用a替换不需要的变量或不需要的变量,_并使用它们来完成:

BiFunction<Integer, Double, String> bids = (i, _) -> String.valueOf(i);
 

目前在Java中与此最接近的东西可能是?通配符泛型类型。当我们需要指定一些泛型类型参数时,可以使用它。但是当我们根本不在乎该类型实际上是什么时,这意味着我们不能在代码的其他地方使用类型。您可以将其称为“未命名”或“匿名”类型,其用法与上述类似。

数据类

项目Amber中提议的“数据类”类似于Kotlin中的数据类,Scala中的case类,或者c#中的(尚未实现的)记录。从本质上说,他们的目标是减轻大量困扰Java代码的冗长问题。

一旦实现,数据类应该把所有这些可怕的样板代码……

package test;
 
public class Boilerplate {
 
  public final int    myInt;
  public final double myDouble;
  public final String myString;
 
  public Boilerplate (int myInt, double myDouble, String myString) {
    super();
    this.myInt = myInt;
    this.myDouble = myDouble;
    this.myString = myString;
  }
 
  @Override
  public int hashCode() {
    final int prime = 31;
    int result = prime + myInt;
    long temp = Double.doubleToLongBits(myDouble);
 
    result = prime * result + (int) (temp ^ (temp >>> 32));
    result = prime * result + ((myString == null) ? 0 : myString.hashCode());
 
    return result;
  }
 
  @Override
  public boolean equals (Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (getClass() != obj.getClass()) return false;
 
    Boilerplate other = (Boilerplate) obj;
    if (myInt != other.myInt) return false;
 
    if (Double.doubleToLongBits(myDouble) !=
        Double.doubleToLongBits(other.myDouble))
      return false;
 
    if (myString == null) {
      if (other.myString != null) return false;
    } else if (!myString.equals(other.myString))
      return false;
 
    return true;
  }
 
  @Override
  public String toString() {
    return "Boilerplate [myInt=" + myInt + ", myDouble=" + myDouble + ", myString=" + myString + "]";
  }
 
}
 

...变成

record Boilerplate (int myInt, double myDouble, String myString) { }
 

新的record关键字会告诉编译器Boilerplate是一个标准的数据类,我们要简单地访问public实例变量,而且我们希望所有的标准方法:hashCode()equals()toString()

的确,大多数现代IDE都会为您生成所有这些代码,但有一种说法认为它根本不应该存在。阅读和理解record上面的非代码有很多概念上的开销,并且有很多地方可以隐藏错误,因此最好摆脱它。数据类是Java的未来。

密封类型

在Java中,如果声明了一个类final,则不能以任何方式对其进行扩展。无论是由用户还是由API开发人员编写,都不能存在任何类型的子类。当然,与此相反的是声明的类final。用户和API开发人员均可扩展此类,并具有所需的任意多个子类。

但是如果我们想上半决赛呢?假设我们有一个想要子类化的类,但是只有指定的次数(为了简单起见,我将使用下面的记录):

record FossilFuelCar (Make make, Model model) { }
record ElectricCar   (Make make, Model model) { }
record HybridCar     (Make make, Model model) { }
record FuelCellCar   (Make make, Model model) { }
 

假设我们确定这是在可预见的将来我们仅需要的四种汽车,并且我们要防止人们制造虚假的汽车品种(SteamPoweredCar?)。sealed类型提供了一种实现方法:

sealed interface Car (Make make, Model model) { }
 
record FossilFuelCar (Make make, Model model) implements Car { }
record ElectricCar   (Make make, Model model) implements Car { }
record HybridCar     (Make make, Model model) implements Car { }
record FuelCellCar   (Make make, Model model) implements Car { }
 

在该文件的源代码中,大概可以定义Car所需的尽可能多的实现。但是在此文件之外,根本不允许任何新的实现。可以将其视为类和enums之间的混搭-我们有指定数量的实现,仅Car此而已。

sealed类和接口也可以很好地与Java中成熟的模式匹配模式所需的穷举性一起工作。迈向现代化的更多步骤!

[返回目录]

19、Fiber使用Project Loom(Java 14+)进行协程,尾部呼叫优化和轻量级用户模式

Project Loom的主要重点是:轻量级多线程。

目前,如果用户想用Java实现并发/多线程应用程序,则需要在某种程度上使用Threads,“ Java并发的核心抽象”。该java.util.concurrentAPI还提供了许多其他抽象,如LocksFutureExecutor,可以使这些应用程序的构建更容易。

但是Java Thread是在操作系统级别实现的。创建它们并在它们之间切换可能非常昂贵。操作系统会极大地影响允许的并发线程的最大数量,从而限制了该方法的实用性。

Loom计划要做的是创建一个Thread类似于应用程序级别的抽象,称为a Fiber。VM级别的多线程(而不是OS级别)的想法实际上是Java的一个古老的想法-Java过去在Java 1.1中具有这些“绿色线程”,但是为了支持本机线程而逐渐淘汰了它们。有了Loom,绿色线程便复仇了。

Fibers相对于Threads,能够在相似的时间内创建更多数量级,这意味着JVM将能够把多线程放在首位和居中。Loom将允许尾部调用优化和延续(类似于Kotlin的协程),从而为Java并发编程开辟了新的途径。

[返回目录]

20、Valhalla(Java 14+)中的值类型、泛型专业化和修饰泛型

我要讨论的最后一个项目是Valhalla项目,我认为它可以给Java生态系统带来最大的变化,这是迄今为止讨论的所有即将发布的功能和提议的功能。Valhalla项目建议对Java进行三项重大更改:

  1. 值类型
  2. 通用专业化
  3. 泛型泛型

与引用类型相比,值类型更容易理解,Java的集合就是很好的例子。例如,在列表中,元素作为一个连续的引用块存储在内存中。引用本身指向它们的值,这些值可能保存在内存中完全不同的位置。然后,遍历一个列表需要做一些工作,因为要获取值,需要导航到每个引用的地址。

另一方面,值类型为aray,其所有值都存储在连续的内存块中。不需要引用,因为下一个值只是在内存中的下一个位置。一个很好的例子是C风格的数组,其中必须声明存储在数组中的数据的类型以及数组的长度:

double balance[10];
 

该长度是必需的,以便在运行代码时可以分配具有所需大小的连续内存块。Java当然已经具有此构造(数组)。但是值类型将允许用户创建一个原始状阵列像上面,即使对于化合物,struct样组数据。通过绕过现有的引用/解引用Collections,访问速度和数据存储效率将大大提高。

Java中的值类型的行为类似于类,具有方法和字段,但是访问速度与原始类型一样快。值类型也可以用作泛型类型,而没有基元和包装类的装箱/拆箱开销。这使我们进入了Valhalla项目的下一个重要功能...

通用专业化

泛型专业化听起来像是一个矛盾词,但总的来说,它的意思是,值类型(以及基本类型)也可以用作泛型方法和类中的类型参数。所以除了

List<Integer>
 

我们也可以

List<int>
 

大多数Java开发人员都知道泛型类型参数TEKV,等必须的类。它们不能是原始类型。但为什么?

好吧,在编译时,Java使用类型擦除将所有特定类型转换为一个超类。在Java的情况下,该超类为Object。由于基本类型不继承自Object,因此不能在泛型类,方法等中将它们用作类型参数。

DZone上的这篇文章很好地解释了均质翻译(Java Object在编译时将所有类转换为此类)和异质翻译(又称泛型专业化),这将允许泛型类型中不相交的类型层次结构,例如Objectvs.原始Java中的类型。对于向后兼容的原因,你将不能够只通过一个intT现有的Java API中的类型参数的任何地方。取而代之的是,Object需要使用any关键字以及泛型类型定义接受基本类型和的方法

 

泛型泛型

值类型的通用专门化意味着JVM将在运行时知道在您的应用程序中传递的至少某些类型。这与Object迄今为止用于参考类型的常规类型擦除过程相反。那么,在Valhalla项目之后,类型是否会在Java中得到统一化?也许,至少是部分。

尽管在运行时为引用类型提供类型信息的可能性很小(以保持向后兼容性),但对于值类型也有可能(可能吗?)。那么Java的类型系统的未来是什么?只有时间会给出答案。

[返回目录]

 

上面的列表只是Java 9-13中可用功能和Java 14+中即将推出的功能的一小部分。自从五年前发布Java 8以来,Java作为一种语言和一种生态系统已经发生了巨大的发展。如果从那时起您还没有升级,那么您真的会错过!

原文链接:https://dev.to//awwsmm/20-reasons-to-move-on-from-java-8-1dio

你可能感兴趣的:(从J ava 8 继续前进的 20 个理由)