具有示例的Java 8特性

Java 8发布于2014年3月18日。虽然这已经是很久以前的事情了,但仍然有许多项目在使用Java 8。这是因为它是一个具有许多新功能的重大版本。让我们来看看Java 8的所有令人兴奋和重大特性,以及示例代码。

Java 8特性快速概述

一些重要的Java 8特性包括:

  1. Iterable接口中的forEach()方法
  2. 接口中的默认方法和静态方法
  3. 函数式接口和Lambda表达式
  4. 用于集合的批量数据操作的Java Stream API
  5. Java Time API
  6. 集合API改进
  7. 并发API改进
  8. Java IO改进

让我们简要地了解一下这些Java 8特性。我将提供一些代码片段,以便更好地理解这些特性。

1. Iterable接口中的forEach()方法

每当我们需要遍历一个集合时,我们需要创建一个迭代器,其整个目的是迭代,并且我们在循环中为集合中的每个元素执行业务逻辑。如果迭代器未正确使用,我们可能会遇到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) {
		
		//创建示例集合
		List<Integer> myList = new ArrayList<Integer>();
		for(int i=0; i<10; i++) myList.add(i);
		
		//使用迭代器遍历
		Iterator<Integer> it = myList.iterator();
		while(it.hasNext()){
			Integer i = it.next();
			System.out.println("Iterator Value::"+i);
		}
		
		//使用匿名类遍历Iterable的forEach方法
		myList.forEach(new Consumer<Integer>() {

			public void accept(Integer t) {
				System.out.println("forEach anonymous class Value::"+t);
			}

		});
		
		//使用Consumer接口实现遍历
		MyConsumer action = new MyConsumer();
		myList.forEach(action);
		
	}

}

//可以重用的Consumer实现
class MyConsumer implements Consumer<Integer>{

	public void accept(Integer t) {
		System.out.println("Consumer impl Value::"+t);
	}
}

行数可能会增加,但forEach方法有助于将迭代逻辑和业务逻辑分开放置,从而实现更高的关注分离和更清晰的代码。

2. 接口中的默认方法和静态方法

如果你仔细阅读 forEach 方法的细节,你会注意到它是在 Iterable 接口中定义的,但我们知道接口不能有方法体。从 Java 8 开始,接口被增强以具有带有实现的方法。我们可以使用 defaultstatic 关键字创建带有方法实现的接口。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);
	}
	
	//尝试重写 Object 方法会在编译时报错
	//"默认方法无法重写 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没有自己的log()实现将无法编译
	@Override
	public void log(String str){
		System.out.println("MyClass logging::"+str);
		Interface1.print("abc");
	}
	
}

正如您所见,Interface1具有静态方法实现,该实现在MyClass.log()方法中使用。Java 8 在集合 API中大量使用默认静态方法,并添加默认方法以保持向后兼容性。

如果层次结构中的任何类具有相同签名的方法,则默认方法将变得无关紧要。 Object 是基类,因此如果我们在接口中有 equals()、hashCode() 默认方法,它将变得无关紧要。 这就是为什么为了更清晰起见,不允许接口具有 Object 默认方法。

有关 Java 8 中接口更改的完整详细信息,请阅读《Java 8 接口更改》。

3. 功能接口和Lambda表达式

如果你注意到上面的界面代码,你会注意到@FunctionalInterface注释。功能接口是Java 8中引入的一个新概念。具有确切一个抽象方法的接口成为功能接口。我们不需要使用@FunctionalInterface注释来标记一个接口为功能接口。

@FunctionalInterface注释是为了避免在功能接口中意外添加抽象方法的便利措施。你可以把它看作是@Override注释,最好的做法是使用它。java.lang.Runnable具有单一抽象方法run()是功能接口的一个很好的例子。

功能接口的一个主要优点是可以使用lambda表达式来实例化它们。我们可以使用匿名类实例化一个接口,但是代码看起来很臃肿。

Runnable r = new Runnable(){
			@Override
			public void run() {
				System.out.println("My Runnable");
			}};

由于功能接口只有一个方法,lambda表达式可以轻松提供方法实现。我们只需要提供方法参数和业务逻辑。例如,我们可以使用lambda表达式编写上面的实现如下:如果方法实现中只有一条语句,我们也不需要大括号。例如,上述Interface1匿名类可以使用lambda进行实例化,如下所示:

Runnable r1 = () -> {
			System.out.println("My Runnable");
		};

如果在方法实现中只有一条语句,我们也可以不需要花括号。例如,上面的Interface1匿名类可以使用lambda表达式进行实例化,如下所示:

Interface1 i1 = (s) -> System.out.println(s);
		
i1.method1("abc");

因此,lambda表达式是一种创建函数式接口匿名类的简便方法。使用lambda表达式并没有运行时的好处,所以我将谨慎使用它,因为我并不介意多写几行代码。

A new package java.util.function has been added with bunch of functional interfaces to provide target types for lambda expressions and method references. Lambda expressions are a huge topic, I will write a separate article on that in the future.

你可以在Java 8 Lambda表达式教程中阅读完整的教程。

4. Java Stream API用于集合的大量数据操作

A new java.util.stream has been added in Java 8 to perform filter/map/reduce like operations with the collection. Stream API will allow sequential as well as parallel execution. This is one of the best features for me because I work a lot with Collections and usually with Big Data, we need to filter out them based on some conditions.

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);
		
		//顺序流
		Stream<Integer> sequentialStream = myList.stream();
		
		//并行流
		Stream<Integer> parallelStream = myList.parallelStream();
		
		//使用带有Stream API的lambda,过滤示例
		Stream<Integer> highNums = parallelStream.filter(p -> p > 90);
		//在forEach中使用lambda
		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的所有内容,您可以阅读有关Stream API的全部内容,请访问Java 8 Stream API示例教程

5. Java时间API

在Java中处理日期、时间和时区一直很困难。Java中没有用于日期和时间的标准方法或API。Java 8中一个很好的补充是java.time包,它将简化在Java中处理时间的过程。

仅通过查看Java时间API包,我可以感觉到它们将非常易于使用。它有一些子包,如java.time.format,提供了用于打印和解析日期和时间的类,以及java.time.zone,提供了对时区和其规则的支持。

新的时间API更倾向于使用枚举而不是整数常量来表示月份和一周的天数。一个有用的类是DateTimeFormatter,用于将DateTime对象转换为字符串。要获取完整的教程,请访问Java日期时间API示例教程

6. 集合API改进

我们已经看到了forEach()方法和Stream API用于集合。集合API中添加了一些新方法,包括:

  • Iterator默认方法forEachRemaining(Consumer action),用于对每个剩余元素执行给定操作,直到所有元素都被处理或操作抛出异常。
  • Collection默认方法removeIf(Predicate filter),用于移除集合中满足给定谓词的所有元素。
  • Collectionspliterator()方法,返回一个Spliterator实例,可用于顺序或并行遍历元素。
  • MapreplaceAll()compute()merge()方法。
  • HashMap类在键冲突时的性能改进

7. 并发API改进

一些重要的并发API增强包括:

  • ConcurrentHashMap的compute()、forEach()、forEachEntry()、forEachKey()、forEachValue()、merge()、reduce()和search()方法。
  • 一个可以被显式完成(设置其值和状态)的CompletableFuture
  • 使用ExecutorsnewWorkStealingPool()方法创建一个使用所有可用处理器作为其目标并行度级别的工作窃取线程池。

8. Java IO改进

我所知道的一些IO改进包括:

  • Files.list(Path dir)返回一个惰性填充的Stream,其元素是目录中的条目。
  • Files.lines(Path path)读取文件中的所有行作为一个Stream。
  • Files.find()返回一个Stream,该Stream通过在以给定起始文件为根的文件树中搜索文件来惰性填充Path。
  • BufferedReader.lines()返回一个Stream,其元素是从这个BufferedReader读取的行。

Java 8核心API的其他改进

一些可能派上用场的其他API改进包括:

  1. ThreadLocal静态方法withInitial(Supplier supplier)可以轻松创建实例。
  2. Comparator接口已经扩展了许多默认和静态方法,用于自然排序、逆序等。
  3. Integer、Long和Double包装类中的min()、max()和sum()方法。
  4. Boolean类中的logicalAnd()、logicalOr()和logicalXor()方法。
  5. ZipFile.stream()方法可以获取一个有序的Stream,它覆盖了ZIP文件的条目。条目在Stream中出现的顺序与它们在ZIP文件的中央目录中的顺序相同。
  6. Math类中添加了几个实用方法。
  7. jjs命令用于调用Nashorn引擎。
  8. jdeps命令用于分析类文件。
  9. JDBC-ODBC桥已被移除。
  10. PermGen内存空间已被移除。

以上就是Java 8特性的全部内容,包括示例程序。如果我错过了Java 8的一些重要特性,请通过评论告诉我。

Source:
https://www.digitalocean.com/community/tutorials/java-8-features-with-examples