Java 8于2014年3月18日发布,当Java 8也是广泛使用的版本,因此了解和学习Java 8功能是十分有必要的。在本教程中,我们将通过示例来学习Java 8的功能。
Java 8功能
一些重要的Java 8功能特性如下所示 -
Iterable
接口中的foreach()
方法;- 接口中的默认和静态方法;
- 功能接口和Lambda表达式;
- 用于集合上的批量数据操作的Java Stream API;
- Java Time API;
- 集合API改进;
- 并发API改进;
- Java IO的改进;
- 其他核心API改进;
下面将简要介绍一下这些Java 8的功能,并提供一些代码片段以便更好地理解,因此如果您想在Java 8中运行程序,则必须按照以下步骤设置Java 8环境。
- 下载JDK8并安装它。安装很简单,就像其他java版本一样。需要JDK安装来编写,编译和运行Java程序。
- 下载最新的Eclipse IDE或其它IDE工具,它现在提供对java 8的支持。确保项目构建路径使用Java 8库。
1. Iterable接口中的forEach()方法
每当需要遍历Collection时,创建一个Iterator
,其目的是迭代,然后在循环中为Collection中的每个元素提供业务逻辑。如果没有正确使用迭代器,可能抛出ConcurrentModificationException
异常。
Java 8在java.lang.Iterable
接口中引入了forEach()
方法,这样在编写代码时只关注业务逻辑。forEach
方法将java.util.function.Consumer
对象作为参数,因此它有助于将我们的业务逻辑放在可以重用的单独位置。下面通过简单的例子看看它的用法。
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
和static
关键字来创建方法实现的接口。Iterable
接口中的forEach
方法实现是:
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
我们知道Java不会在类中提供多重继承,因为它会导致钻石问题。那么现在如何处理接口,因为接口现在类似于抽象类。解决方案是编译器将在此场景中抛出异常,必须在实现接口的类中提供实现逻辑。
接口1:Interface1.java
@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";
// }
}
接口2:Interface2.java
@FunctionalInterface
public interface Interface2 {
void method2();
default void log(String str){
System.out.println("I2 logging::"+str);
}
}
请注意,两个接口都有一个带有实现逻辑的公共方法log()
。
public class MyClass implements Interface1, Interface2 {
@Override
public void method2() {
}
@Override
public void method1(String str) {
}
// 如果没有自己的log()实现,MyClass将无法编译
@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
类默认方法。
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
表达式将上面的实现编写为:
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
表达式和方法引用提供目标类型。
4. 集合的批量数据操作的Java Stream API
Java 8中添加了一个新的java.util.stream
,用于对集合执行filter/map/reduce
操作。Stream API将允许按顺序执行和并行执行。使用Collections工作很多,而且通常使用Big Data
,可使用它来根据某些条件过滤掉一些数据。
集合接口已使用stream()
和parallelStream()
默认方法进行扩展,以获取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
请注意,并行处理值不是有序的,因此在处理大型集合时,并行处理将非常有用。
5. Java Time API
在java中使用日期,时间和时区一直很困难。Java中没有日期和时间的标准方法或API。Java 8中的一个很好的补充是java.time
包,它将简化在java中使用时间的过程。
通过查看Java Time API包,就应该感觉到它将非常容易使用。它有一些子包java.time.format
,它提供了打印和解析日期和时间的类,java.time.zone
提供了对时区及其规则的支持。
新的Time API优先使用整数常量的枚举,包括一周中的几个月和几天。其中一个有用的类是DateTimeFormatter
,用于将datetime
对象转换为字符串。
6. 集合API改进
前面已经看到如何将forEach()
方法和Stream API用于集合。Collection API中添加的一些新方法是:
Iterator
默认方法forEachRemaining(Consumer action)
为每个剩余元素执行给定操作,直到处理完所有元素或操作引发异常。- 集合默认方法
removeIf(Predicate filter)
删除满足给定谓词的此集合的所有元素。 - 集合
spliterator()
方法返回Spliterator
实例,可用于顺序或并行遍历元素。 - 映射
replaceAll()
,compute()
,merge()
方法。 - 具有键冲突的
HashMap
类的性能改进。
7. 并发API改进
一些重要的并发API增强功能包括:
- ConcurrentHashMap compute(),forEach(),forEachEntry(),forEachKey(),forEachValue(),merge(),reduce()和search()方法。
- 可以明确完成的
CompletableFuture
(设置其值和状态)。 - 执行
newWorkStealingPool()
方法以使用所有可用处理器作为其目标并行级别来创建工作窃取线程池。
8. Java IO的改进
以下是Java 8中的一些IO改进:
Files.list(Path dir)
- 返回一个延迟填充的Stream,其元素是目录中的条目。Files.lines(Path path)
- 它将文件中的所有行读作Stream。Files.find()
通过搜索以给定起始文件为根的文件树中的文件,返回使用Path延迟填充的Stream。BufferedReader.lines()
返回一个Stream,其元素是从这个BufferedReader
读取的行。
9. 其他核心API改进
一些可用的其他核心API改进是:
ThreadLocal
静态方法withInitial(Supplier supplier)
可以轻松创建实例。- 比较器接口已经扩展了许多默认和静态方法,用于自然排序,逆序等。
Integer
,Long
和Double
包装类中的min()
,max()
和sum()
方法。Boolean
类中的logicalAnd()
,logicalOr()
和logicalXor()
方法。ZipFile.stream()
方法获取ZIP文件条目的有序流。数据项按照它们出现在ZIP文件中心目录中的顺序显示在Stream中。Math
类中的几种实用方法。- 添加
jjs
命令以调用Nashorn
引擎。 - 添加
jdeps
命令以分析类文件 - JDBC-ODBC Bridge已被删除。
- PermGen内存空间已被删除
这就是Java 8功能和示例程序的全部功能。