Java 8 于2014年3月18日发布,并且成为主流的 Java,如今,虽然 Java 14 都已经发布了,但是 开发者和公司选择的版本依旧是经久不衰的 Java 8 版本,如果你还不了解这些新特性,是时候学习一下了。
Java 8 更新的一些重要功能一览
- Iterable 接口中的 forEach() 方法
- 接口中的默认方法和静态方法
- 功能接口和 Lambda 表达式
- 用于集合上批量数据操作的 Java Stream API
- Java 时间 API
- 集合 API 的改进
- 并发 API 改进
- Java IO改进
- 其他核心 API 改进
下面来简要了解一下这些Java 8功能。我将提供一些代码片段以更好地理解,因此,如果要在Java 8中运行程序,则必须按照以下步骤设置Java 8环境。
下载并安装JDK8。像其他Java版本一样,安装也很简单,运行下面的示例,必须要安装 JDK 环境。
下载最新的 IDEA 开发环境,这里我不推荐使用 Eclipse ,但是如果你有使用 Eclipse 习惯,那我在这里推荐你可以尝试 IDEA,因为它真的太棒啦~
Iterable 接口中的 forEach()方法
在 Java 8 以前,每当需要遍历 Collection 时,就需要创建一个 Iterator 来进行迭代 Collection 对象,然后针对 Collection 中 的每个元素将业务逻辑循环在一起。如果迭代器使用不正确,可能会抛出 ConcurrentModificationException。
Java 8 在接口中引入了forEach方法,java.lang.Iterable因此在编写代码时,我们仅关注业务逻辑。forEach方法将java.util.function.Consumer对象作为参数,因此有助于将我们的业务逻辑放在一个可以重用的单独位置。让我们通过简单的示例查看forEach用法。
package com.journaldev.java8.foreach;import java.util.ArrayList;import java.util.Iterator;import java.util.List;import java.util.function.Consumer;import java.lang.Integer;public class Java8ForEachExample { public static void main(String[] args) { //creating sample Collection List myList = new ArrayList(); for(int i=0; i<10; i++) myList.add(i); //traversing using Iterator Iterator it = myList.iterator(); while(it.hasNext()){ Integer i = it.next(); System.out.println("Iterator Value::"+i); } //traversing through forEach method of Iterable with anonymous class myList.forEach(new Consumer() { public void accept(Integer t) { System.out.println("forEach anonymous class Value::"+t); } }); //traversing with Consumer interface implementation MyConsumer action = new MyConsumer(); myList.forEach(action); }}//Consumer implementation that can be reusedclass MyConsumer implements Consumer{ public void accept(Integer t) { System.out.println("Consumer impl Value::"+t); }}
代码的行数可能会增加,但是forEach方法有助于将迭代逻辑和业务逻辑放在不同的位置,从而使关注点和代码更清晰地分离。
接口中的默认方法和静态方法
如果仔细阅读forEach方法的详细信息,会注意到它是在Iterable接口中定义的,但我们知道接口不能具有方法主体。从Java 8开始,接口已增强为具有实现的方法。我们可以使用default和static关键字来创建带有方法实现的接口。Iterable接口中的forEach方法实现为:
default void forEach(Consumer super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); }}
我们知道Java不会在Class中提供多重继承,因为它会导致Diamond问题。由于接口现在类似于抽象类,因此现在如何使用接口处理它。解决方案是在这种情况下编译器将引发异常,我们将不得不在实现接口的类中提供实现逻辑。
package com.journaldev.java8.defaultmethod;@FunctionalInterfacepublic interface Interface1 { void method1(String str); default void log(String str){ System.out.println("I1 logging::"+str); } static void print(String str){ System.out.println("Printing "+str); } //trying to override Object method gives compile-time error as //"A default method cannot override a method from java.lang.Object"// default String toString(){// return "i1";// }}
package com.journaldev.java8.defaultmethod;@FunctionalInterfacepublic interface Interface2 { void method2(); default void log(String str){ System.out.println("I2 logging::"+str); }}
注意,两个接口都有一个带有实现逻辑的通用方法log()。
package com.journaldev.java8.defaultmethod;public class MyClass implements Interface1, Interface2 { @Override public void method2() { } @Override public void method1(String str) { } //MyClass won't compile without having it's own log() implementation @Override public void log(String str){ System.out.println("MyClass logging::"+str); Interface1.print("abc"); }}
如你所见, Interface1 具有在 MyClass.log() 方法实现中使用的静态方法实现。Java 8 在 Collection API 中大量使用默认和静态方法,并且添加了默认方法,以便使 JDK 8 之前的代码保持向后兼容。
如果层次结构中的任何类都具有具有相同的方法,则默认方法将变得无关紧要。由于任何实现接口的类都已经具有 Object 作为超类,因此如果接口中具有 equals(),hashCode() 默认方法,它将变得无关紧要。这就是为什么为了更清楚起见,不允许接口具有Object默认方法。
有关Java 8接口特性的完整详细信息,请阅读 Java 8接口更改。
功能接口和Lambda表达式
如果你注意到上述接口代码,则会注意到 @FunctionalInterface 注解。该功能接口是Java 8 中引入的新概念。具有一种抽象方法的接口就变成了功能接口。我们不需要使用 @FunctionalInterface 注解将接口标记为Functional Interface。@FunctionalInterface 注解是一种避免在功能接口中意外添加抽象方法的工具。您可以将其视为 @Override 批注,并且是使用它的最佳实践。java.lang.Runnable 使用单个抽象方法 run() 是功能接口的一个很好的例子。
功能接口的主要优点之一是可以使用 lambda 表达式实例化它们。在 Java 8 之前,可以用匿名类实例化一个接口,但是代码看起来很庞大。
Runnable r = new Runnable(){ @Override public void run() { System.out.println("My Runnable"); }};
由于功能接口只有一种方法,因此 lambda 表达式可以轻松提供该方法的实现。只需要提供方法参数和业务逻辑。例如,可以使用 lambda 表达式编写上面的实现:
Runnable r1 = () -> { System.out.println("My Runnable"); };
如果方法实现中只有一条语句,那么我们也不需要花括号。例如,上面的Interface1匿名类可以使用lambda实例化,如下所示:
Interface1 i1 = (s) -> System.out.println(s);i1.method1("abc");
因此,lambda 表达式是轻松创建功能接口的匿名类的一种方法。使用 lambda 表达式对于代码运行没有任何的影响,因此要谨慎谨慎使用它,因为我们并不介意编写一些额外的代码行。
新包装 java.util.function 添加了带有功能接口束的,以提供 lambda 表达式和方法引用的目标类型。Lambda 表达式是一个非常复杂的话题,后续我会编写一篇文章专门针对 lambda 表达式。
您可以在 Java 8 Lambda Expressions Tutorial 中阅读完整的教程。
用于集合上批量数据操作的Java Stream API
java.util.stream 是Java 8中添加的一个新内容,以对该集合执行类似过滤/映射/遍历的操作。Stream API 将允许顺序执行和并行执行。这对我来说是非常好用的一个功能,因为我经常处理 Collections,而且通常使用很多的数据进行过滤数据,遍历数据,stream 就完美的解决了这个问题。
Collection 接口已使用 stream() 和 parallelStream() 默认方法进行了扩展,以获取用于顺序执行和并行执行的 Stream ,用一个简单的例子看看它们的用法。
package com.journaldev.java8.stream;import java.util.ArrayList;import java.util.List;import java.util.stream.Stream;public class StreamExample { public static void main(String[] args) { List myList = new ArrayList<>(); for(int i=0; i<100; i++) myList.add(i); //sequential stream Stream sequentialStream = myList.stream(); //parallel stream Stream parallelStream = myList.parallelStream(); //using lambda with Stream API, filter example Stream highNums = parallelStream.filter(p -> p > 90); //using lambda in forEach highNums.forEach(p -> System.out.println("High Nums parallel="+p)); Stream highNumsSeq = sequentialStream.filter(p -> p > 90); highNumsSeq.forEach(p -> System.out.println("High Nums sequential="+p)); }}
如果你运行示例代码,将获得如下输出:
High Nums parallel=91High Nums parallel=96High Nums parallel=93High Nums parallel=98High Nums parallel=94High Nums parallel=95High Nums parallel=97High Nums parallel=92High Nums parallel=99High Nums sequential=91High Nums sequential=92High Nums sequential=93High Nums sequential=94High Nums sequential=95High Nums sequential=96High Nums sequential=97High Nums sequential=98High Nums sequential=99
请注意,并行处理的时候。值不按顺序排列,因此在处理庞大的集合时,并行处理将非常有帮助。
这篇文章无法涵盖有关Stream API的所有内容,您可以在 Java 8 Stream API Example Tutorial 中阅读有关Stream API的所有内容。
Java 8 时间API
在之前的 Java 中使用日期,时间和时区一直很困难。Java 中没有用于日期和时间的标准方法或 API。Java 8 有一个不错的附加功能是 java.time 软件包,它简化了 Java 中使用时间的过程。
仅查看 Java Time API 软件包,就可以感觉到它非常易于使用。它具有一些子包 java.time.format,这些子包提供用于打印和解析日期和时间的类,还有java.time.zone 提供对时区及其规则的支持。
新的 Time API 在整月的几个月和一周中的几天中都使用了枚举而不是整数常量。常用的类之一是 DateTimeFormatter 将 DateTime 对象转换为字符串。
有关完整的教程,请转到 Java日期时间API示例教程。
集合API的改进
在上面的介绍已经看到了 forEach() 方法和用于集合的 Stream API。
Collection API 中添加的一些新方法是:
IteratorforEachRemaining(Consumer action) 在所有元素都已处理完毕或该动作引发异常之前,对其余每个元素执行给定操作的默认方法。
CollectionremoveIf(Predicate filter) 删除此集合中所有满足给定谓词的元素的默认方法。
Collection spliterator() 该方法返回Spliterator实例,该实例可用于顺序或并行遍历元素。地图replaceAll(),compute(),merge()方法。
另外还有一些具有 hash 冲突 的HashMap 类的性能改进
并发API改进
一些重要的并发API增强功能包括:
ConcurrentHashMap compute(),forEach(),forEachEntry(),forEachKey(),forEachValue(),merge(),reduce()和search()方法。CompletableFuture 可以明确完成(设置其值和状态)。Executors newWorkStealingPool() 使用所有可用处理器作为目标并行度级别创建窃取线程池的方法。
Java IO改进
我知道的一些IO改进:
Files.list(Path dir) 返回一个延迟加载的 Stream,其元素是目录中的文件夹和文件列表。
Files.lines(Path path) 返回一个读取指定文件所有行的文件流。
Files.find() 返回一个根据指定目录搜索指定文件的文件列表流。
BufferedReader.lines() 返回一个Stream,其元素是从此 BufferedReader 中读取的行。
其他核心API改进
一些杂项API改进在某些特殊情况可能会派上用场:
- ThreadLocal 静态方法可以使用 withInitial 方法创建实例。
- 比较器接口已扩展了许多默认和静态方法,用于自然排序,反向排序等。
- Integer,Long 和 Double 包装器类中增加了 min(),max() 和 sum() 方法。
- Boolean 类中的 logicalAnd() ,logicalOr() 和 logicalXor() 方法。
- ZipFile.stream() 方法获取 ZIP 文件条目上的有序Stream,并以压缩时的顺序出现在 Stream 中。
- Math 类中增加了几种实用方法。
- jjs 添加命令以调用 Nashorn Engine。
- jdeps 添加命令以分析类文件
- JDBC-ODBC 桥已被删除。
- PermGen 内存空间已被删除