Java 8 功能及示例
Java 8 于 2014 年 3 月 18 日发布。这已经是很久以前的事了,但仍有许多项目在 Java 8 上运行。这是因为它是一个包含许多新功能的主要版本。让我们通过示例代码来看看 Java 8 所有令人兴奋的主要功能。
Java 8 功能概览
Java 8 的一些重要功能包括:
- Iterable 接口中的 forEach() 方法
- 接口中的默认方法和静态方法
- 函数式接口和 Lambda 表达式
- 用于集合批量数据操作的 Java Stream API
- Java 时间 API
- 集合 API 改进
- 并发 API 改进
- Java IO 改进
让我们简单了解一下这些 Java 8 功能。我将提供一些代码片段,以便以简单的方式更好地理解这些功能。
1. Iterable 接口中的 forEach() 方法
每当我们需要遍历一个 Collection 时,我们都需要创建一个迭代器,其全部目的就是进行迭代,然后我们在 Collection 中的每个元素中循环执行业务逻辑。如果迭代器使用不当,我们可能会得到ConcurrentModificationException 。
Java 8 在java.lang.Iterable接口中引入了 forEach 方法,这样我们在编写代码时就可以专注于业务逻辑。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<Integer> myList = new ArrayList<Integer>();
for(int i=0; i<10; i++) myList.add(i);
//traversing using Iterator
Iterator<Integer> 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<Integer>() {
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 reused
class MyConsumer implements Consumer<Integer>{
public void accept(Integer t) {
System.out.println("Consumer impl Value::"+t);
}
}
行数可能会增加,但是 forEach 方法有助于将迭代逻辑和业务逻辑放在单独的位置,从而实现更高程度的关注点分离和更清晰的代码。
2. 接口中的默认方法和静态方法
如果你仔细阅读 forEach 方法的细节,你会注意到它是在 Iterable 接口中定义的,但我们知道接口不能有方法体。从 Java 8 开始,接口得到增强,可以拥有一个带有实现的方法。我们可以使用 default
and static
关键字来创建带有方法实现的接口。Iterable 接口中的 forEach 方法实现是:
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
我们知道 Java 不提供 类中的多重继承, 因为这会导致 钻石问题。那么既然接口现在类似于抽象类,那么现在该如何处理这个问题呢?
解决方案是,在这种情况下编译器会抛出异常,我们必须在实现接口的类中提供实现逻辑。
package com.journaldev.java8.defaultmethod;
@FunctionalInterface
public 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;
@FunctionalInterface
public 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中大量使用默认方法和静态方法,并添加了默认方法,以便我们的代码保持向后兼容。
如果层次结构中的任何类都具有相同签名的方法,则默认方法将变得无关紧要。 Object 是基类,因此如果我们在接口中有 equals()、hashCode() 默认方法,它将变得无关紧要。这就是为什么为了更清楚起见,接口不允许有 Object 默认方法。
有关 Java 8 中接口变化的完整详细信息,请阅读 Java 8 接口变化。
3. 函数式接口和 Lambda 表达式
如果您注意到上述接口代码,您会注意到@FunctionalInterface 注释。函数式接口是 Java 8 中引入的新概念。只有一个抽象方法的接口将成为函数式接口。我们不需要使用@FunctionalInterface注释将接口标记为函数式接口。
@FunctionalInterface注释是一种避免在函数式接口中意外添加抽象方法的工具。您可以将其视为 @Override注释,并且最好使用它。具有单个抽象方法 run() 的 java.lang.Runnable 是函数式接口的一个很好的例子。
函数式接口的一个主要优点是可以使用 lambda 表达式来实例化它们。我们可以使用匿名类来实例化接口 ,但代码看起来很笨重。
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 表达式是一个很大的话题,我将来会就此写一篇单独的文章。
您可以在 Java 8 Lambda 表达式教程中阅读完整的教程。
4. 用于集合批量数据操作的 Java Stream API
Java 8 中新增了一项功能java.util.stream
,用于对集合执行类似 filter/map/reduce 的操作。Stream API 将允许顺序和并行执行。这对我来说是最好的功能之一,因为我经常使用集合,而且通常使用大数据时,我们需要根据某些条件过滤掉它们。
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<Integer> myList = new ArrayList<>();
for(int i=0; i<100; i++) myList.add(i);
//sequential stream
Stream<Integer> sequentialStream = myList.stream();
//parallel stream
Stream<Integer> parallelStream = myList.parallelStream();
//using lambda with Stream API, filter example
Stream<Integer> highNums = parallelStream.filter(p -> p > 90);
//using lambda in forEach
highNums.forEach(p -> System.out.println("High Nums parallel="+p));
Stream<Integer> highNumsSeq = sequentialStream.filter(p -> p > 90);
highNumsSeq.forEach(p -> System.out.println("High Nums sequential="+p));
}
}
如果您运行上述示例代码,您将获得如下输出:
High Nums parallel=91
High Nums parallel=96
High Nums parallel=93
High Nums parallel=98
High Nums parallel=94
High Nums parallel=95
High Nums parallel=97
High Nums parallel=92
High Nums parallel=99
High Nums sequential=91
High Nums sequential=92
High Nums sequential=93
High Nums sequential=94
High Nums sequential=95
High Nums sequential=96
High Nums sequential=97
High Nums sequential=98
High Nums sequential=99
请注意,并行处理值是无序的,因此在处理大量集合时并行处理将非常有用。
本文不可能涵盖有关 Stream API 的所有内容,您可以在 Java 8 Stream API 示例教程中阅读有关 Stream API 的所有内容。
5. Java 时间 API
在 Java 中使用日期、时间和时区一直很困难。Java 中没有针对日期和时间的标准方法或 API。Java 8 中一个不错的新增功能是 java.time
简化 Java 中时间处理过程的包。
只需查看 Java Time API 包,我就能感觉到它们非常易于使用。它有一些子包 java.time.format,提供用于打印和解析日期和时间的类,而 java.time.zone 提供对时区及其规则的支持。
对于月份和星期几,新 Time API 更倾向于使用枚举而不是整数常量。其中一个有用的类是 DateTimeFormatter,用于将 DateTime 对象转换为字符串。有关完整教程,请转到 Java Date Time API 示例教程。
6. Collection API 改进
我们已经了解了集合的 forEach() 方法和 Stream API。Collection API 中添加的一些新方法包括:
Iterator
默认方法forEachRemaining(Consumer action)
对每个剩余元素执行给定的操作,直到所有元素都已处理或操作引发异常。Collection
默认方法removeIf(Predicate filter)
删除此集合中满足给定谓词的所有元素。Collection
spliterator()
方法返回 Spliterator 实例,可用于顺序或并行遍历元素。- 地图
replaceAll()
、、compute()
方法merge()
。 - 针对具有键冲突的 HashMap 类的性能改进
7. 并发 API 改进
一些重要的并发 API 增强功能包括:
ConcurrentHashMap
compute()、forEach()、forEachEntry()、forEachKey()、forEachValue()、merge()、reduce() 和 search() 方法。CompletableFuture
可以明确完成(设置其值和状态)。Executors
newWorkStealingPool()
方法创建一个工作窃取线程池,使用所有可用处理器作为其目标并行级别。
8. Java IO 改进
我所知道的一些 IO 改进包括:
Files.list(Path dir)
返回一个延迟填充的 Stream,其元素是目录中的条目。Files.lines(Path path)
将文件中的所有行作为流读取。Files.find()
它返回一个流,该流通过在以给定起始文件为根的文件树中搜索文件来惰性填充路径。BufferedReader.lines()
返回一个 Stream,其元素是从此 BufferedReader 读取的行。
其他 Java 8 核心 API 改进
一些可能有用的其他 API 改进包括:
- ThreadLocal 静态方法 withInitial(Supplier supplier) 轻松创建实例。
- Comparator 接口已经扩展了许多默认和静态方法,用于自然排序、反向排序等。
- Integer、Long 和 Double 包装类中的 min()、max() 和 sum() 方法。
- Boolean 类中的 logicalAnd()、logicalOr() 和 logicalXor() 方法。
- ZipFile .stream() 方法获取 ZIP 文件条目的有序流。条目按照它们在 ZIP 文件的中心目录中出现的顺序出现在流中。
- Math 类中的几种实用方法。
jjs
添加命令来调用 Nashorn Engine。jdeps
添加了分析类文件的命令- JDBC-ODBC Bridge 已被删除。
- PermGen 内存空间已被移除
这就是 Java 8 功能和示例程序的全部内容。如果我遗漏了 Java 8 的一些重要功能,请通过评论告诉我。