Java 8于2014年3月18日发布,在阅读相关文章后,在本教程中,我们将通过示例研究 Java 8功能。
以上是 Java 8 的新特性的列举,接下来展示一些代码片段,来更好理解这些新特性
package com.taotao.springboot;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
public class IterableForEachExample {
public static void main(String[] args) {
List<Integer> ints = new ArrayList<>();
for (int i = 0; i < 10; i++) ints.add(i);
//迭代器遍历
Iterator<Integer> iterator = ints.iterator();
while (iterator.hasNext()) {
Integer i = iterator.next();
System.out.println("Iterator Value::" + i);
}
//使用forEach方法遍历,这里使用了匿名内部类
ints.forEach(new Consumer<Integer>() {
public void accept(Integer t) {
System.out.println("forEach anonymous class Value::" + t);
}
});
//通过实现Consumer接口的子类遍历,业务处理从遍历逻辑中抽取出来
MyConsumer action = new MyConsumer();
ints.forEach(action);
}
}
//实现Conumer接口
class MyConsumer implements Consumer<Integer> {
public void accept(Integer t) {
System.out.println("Consumer impl Value::" + t);
}
}
通过上面这个例子,看出使用 forEach()方法代码行数会增加,但是它将遍历逻辑与业务处理逻辑分离,有助于我们将关注点放在业务逻辑的处理上。
在上面的例子中,如果有点进去看 forEach()方法,你会发现它是在接口 Iterable 中定义的。但是我们知道接口不能有方法体。从 Java 8开始,接口已改进,允许有默认的实现方法。我们可以在接口中使用关 default
和 static
关键字来创建方法实现。以下是 forEach()的定义。
我们知道在 Java 中不能同时继承多个类,因为这样做会引发 Diamond 问题 ,接口允许有默认方法实现后,接口变得与抽象类非常类似,现在可以利用接口解决这个问题。我们定义两个接口 FunctionInterface1 和FunctionInterface2,以及一个它两的实现类 FunctionInterfaceImpl
package com.taotao.springboot;
@FunctionalInterface
public interface FunctionInterface1 {
void hello1(String str);
default void eat(String str){
System.out.println("I1 eatting::"+str);
}
static void print(String str){
System.out.println("Printing "+str);
}
//如果默认方法重写了 Object 类中方法,编译不能通过
// default String toString(){
// return "";
// }
}
在上面代码中,我们知道接口中的默认实现方法不能与Object类中的方法签名相同
package com.taotao.springboot;
@FunctionalInterface
public interface FunctionInterface2 {
void hello2(String str);
default void eat(String str){
System.out.println("I2 eatting::"+str);
}
}
注意,FunctionInterface1 与 FunctionInterface2 都有一个默认的实现方法 eat()。在这种情况下,我们看看它两的实现类 FunctionInterfaceImpl 会不会有要注意的点。
package com.taotao.springboot;
public class FunctionInterfaceImpl implements FunctionInterface1, FunctionInterface2 {
@Override
public void hello1(String str) {
System.out.println("hello1 " + str);
}
//如果不实现 eat() 方法,编译不通过
@Override
public void eat(String str) {
System.out.println("functionInterfaceImpl eatting " + str);
}
@Override
public void hello2(String str) {
System.out.println("hello2 " + str);
}
public static void main(String[] args) {
FunctionInterface1 interface1 = new FunctionInterfaceImpl();
interface1.hello1("world");
interface1.eat("apple");
FunctionInterface2 interface2 = new FunctionInterfaceImpl();
interface2.eat("banana");
interface2.hello2("world");
}
}
注意,FunctionInterfaceImpl 必须实现 eat()方法。运行 main 方法,打印输出如下:
hello1 world
functionInterfaceImpl eatting apple
functionInterfaceImpl eatting banana
hello2 world
可见,Java 8 在 Collection API 中大量使用默认和静态方法,以便我们的代码保持向后兼容。
同时从 functionInterfaceImpl 的例子可以看出,如果一个类同时实现了两个及以上接口,而这些接口中都有一个签名相同的方法,那么,这个类必须实现这个方法,否则将不能通过编译。此时,接口中的默认实现方法已经不起作用了。如果类只是实现一个接口,则没有这个问题
这也就解释了之前提到的 接口中的默认实现方法不能与Object类中的方法签名相同 。因为Object是所有类的超类(父类),因此如果接口中具有equals(),hashCode()默认方法,那这些默认方法也将不起作用。
如果您仔细观察上述接口代码,则会注意到 @FunctionalInterface 注解。函数式接口是 Java 8中引入的新概念。有且只有一个抽象方法的接口就是函数式接口。@FunctionalInterface 注解不是函数式接口定义的必要条件,它是一个避免在函数式接口中意外添加其他抽象方法的注解。java.lang.Runnable
具有单个抽象方法run(),是函数式接口一个很好的例子。
函数式接口的主要优点之一是可以使用 lambda 表达式实例化它们。我们可以用匿名内部类实例化一个接口,但是代码看起来很庞大。
//匿名内部类实现Runnable
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("My Runnable");
}
};
//lambda 表达式实现Runnable
Runnable runnable = () -> {
System.out.println("My Runnable");
};
如果方法实现中只有一条语句,我们也不需要花括号。如下所示:
Runnable runnable = () -> System.out.println("My Runnable");
因此,lambda表达式是轻松创建函数式接口的匿名类的一种方法。使用lambda表达式代码可读性感觉更差,因此要谨慎使用它,不介意编写一些额外的代码行。
java.util.function
是添加了带有函数式接口的新程序包,以提供lambda表达式和方法引用的目标类型。
java.util.stream
是 Java 8中添加的新程序包,以便对集合执行类似过滤/映射/归约的操作。Stream API 将允许顺序执行和并行执,是最好的功能之一,如果经常处理Collections,而且集合元素很多,我们可以根据某些条件过滤掉它们。
Collection接口已使用 *stream()*和 *parallelStream()*默认方法进行了扩展,以获取用于顺序执行和并行执行的 Stream。让我们用一个简单的例子看看它们的用法。
package com.taotao.springboot;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class StreamList {
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<Integer> highNums = parallelStream.filter(p -> p > 90);
//使用 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));
}
}
运行上述代码的 main 方法,你将看到如下输出:
High Nums parallel=91
High Nums parallel=93
High Nums parallel=96
High Nums parallel=94
High Nums parallel=95
High Nums parallel=92
High Nums parallel=97
High Nums parallel=98
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
请注意,并行流不按集合元素排列顺序处理,但在处理大量元素的集合时将非常有用。
在 Java中 使用日期,时间和时区一直很困难。Java中没有用于日期和时间的标准方法或API。java.time
程序包是 Java 8一个不错的附加功能,它将简化Java中使用时间的过程。
仅查看 Java Time API软件包,我就可以感觉到它非常易于使用。它具有一些子包java.time.format
,这些子包提供用于打印和解析日期和时间的类,并java.time.zone
提供对时区及其规则的支持。
新的Time API在整月的几个月和一周中的几天中都更喜欢枚举而不是整数常量。将DateTime对象转换为字符串的类是DateTimeFormatter
。
我们已经看到了 forEach()方法和用于集合的 Stream API。Collection API还有一些新方法是:
Iterator
forEachRemaining(Consumer action)
在所有元素都已处理完毕或该动作引发异常之前,对每个剩余元素执行给定操作的默认方法。Collection
removeIf(Predicate filter)
删除此集合中所有满足特定条件的元素的默认方法。Collection
spliterator()
该方法返回Spliterator实例,该实例可用于顺序或并行遍历元素。map
replaceAll()
,compute()
,merge()
方法。一些重要的并发API增强功能包括:
ConcurrentHashMap
compute(),forEach(),forEachEntry(),forEachKey(),forEachValue(),merge(),reduce()和search()方法。CompletableFuture
可以明确完成(设置其值和状态)。Executors
newWorkStealingPool()
使用所有可用处理器作为目标并行度级别创建窃取线程池的方法。一些IO改进包括:
Files.list(Path dir)
返回延迟填充的Stream,其元素是目录中的条目。Files.lines(Path path)
从文件中读取所有行作为流。Files.find()
通过在以给定起始文件为根的文件树中搜索文件,返回通过路径延迟填充的Stream。BufferedReader.lines()
返回一个Stream,其元素是从此BufferedReader中读取的行。一些其他API改进:
jjs
添加命令以调用 Nashorn Engine。jdeps
添加命令以分析类文件这就是带有代码片段的 Java 8 全部新特性。