JDK1.7与JDK1.8的区别,你知道了吗

Java 8于2014年3月18日发布,在阅读相关文章后,在本教程中,我们将通过示例研究 Java 8功能。

一、Java 8 的新特性

  1. Iterable 接口中的 forEach()方法
  2. 接口中允许有默认方法和静态方法
  3. 函数式接口与 Lambda 表达式
  4. 用于集合上批量数据操作的 Java Stream API
  5. 新的时间 API
  6. 集合 API 的改进
  7. 并发 API 的改进
  8. Java IO 的改进
  9. 其他核心 API 的改进

以上是 Java 8 的新特性的列举,接下来展示一些代码片段,来更好理解这些新特性

二、新特性应用

2.1、Iterable 接口中的 forEach()方法

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()方法代码行数会增加,但是它将遍历逻辑与业务处理逻辑分离,有助于我们将关注点放在业务逻辑的处理上。

2.2、接口中的默认方法和静态方法

在上面的例子中,如果有点进去看 forEach()方法,你会发现它是在接口 Iterable 中定义的。但是我们知道接口不能有方法体。从 Java 8开始,接口已改进,允许有默认的实现方法。我们可以在接口中使用关 defaultstatic 关键字来创建方法实现。以下是 forEach()的定义。

JDK1.7与JDK1.8的区别,你知道了吗_第1张图片

我们知道在 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()默认方法,那这些默认方法也将不起作用。

2.3、函数式接口与 Lambda 表达式

如果您仔细观察上述接口代码,则会注意到 @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表达式和方法引用的目标类型。

2.4、用于集合上批量数据操作的 Java Stream API

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

请注意,并行流不按集合元素排列顺序处理,但在处理大量元素的集合时将非常有用。

2.5、新的时间 API

在 Java中 使用日期,时间和时区一直很困难。Java中没有用于日期和时间的标准方法或API。java.time程序包是 Java 8一个不错的附加功能,它将简化Java中使用时间的过程。

仅查看 Java Time API软件包,我就可以感觉到它非常易于使用。它具有一些子包java.time.format,这些子包提供用于打印和解析日期和时间的类,并java.time.zone提供对时区及其规则的支持。

新的Time API在整月的几个月和一周中的几天中都更喜欢枚举而不是整数常量。将DateTime对象转换为字符串的类是DateTimeFormatter

2.6、集合API的改进

我们已经看到了 forEach()方法和用于集合的 Stream API。Collection API还有一些新方法是:

  • Iterator forEachRemaining(Consumer action)在所有元素都已处理完毕或该动作引发异常之前,对每个剩余元素执行给定操作的默认方法。
  • Collection removeIf(Predicate filter)删除此集合中所有满足特定条件的元素的默认方法。
  • Collection spliterator() 该方法返回Spliterator实例,该实例可用于顺序或并行遍历元素。
  • map replaceAll()compute()merge()方法。
  • 具有键冲突的HashMap类的性能改进

2.7、并发 API 的改进

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

  • ConcurrentHashMap compute(),forEach(),forEachEntry(),forEachKey(),forEachValue(),merge(),reduce()和search()方法。
  • CompletableFuture 可以明确完成(设置其值和状态)。
  • Executors newWorkStealingPool() 使用所有可用处理器作为目标并行度级别创建窃取线程池的方法。

2.8、Java IO改进

一些IO改进包括:

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

2.9、其他核心API改进

一些其他API改进:

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

总结

这就是带有代码片段的 Java 8 全部新特性。

你可能感兴趣的:(java)