JDK8新特性 - 高飞网
564 人阅读

JDK8新特性

2018-06-29 01:41:41

原文链接:JDK 8 New Feature

    JDK8是一个重大的升级版本!引入新的语法(Lambda表达式)支持函数式编程;改造了现有类库(特别是集合框架);增加了新的类库和特性。

    JDK8带来了三个重要的特性:

  1. Lambda表达式
  2. Stream API
  3. Date/Time API(java.time)

1. 函数式编程

1.1 面向对象编程与函数式编程

参考

    1、面向对象编程与函数式编程 https://www.codenewbie.org/blogs/object-oriented-programming-vs-functional-programming

    面向对象编程(Object-Oriented Programming,OOP)是一种编程方式,它基于对象(Object)的概念,由数据(以字段或属性的形式)和代码(以过程或方法的形式)构成。

    函数式编程(Functional programming,FP)是一种编程方式,是一种构建程序元素和结构的风格,它将计算机的运算作为数学上的函数运算,并且避免使用程序状态和易变对象。

    在程序中,有两种主要的组件,即数据和行为(或代码)。在OOP看来,将数据和与之关联的行为结合在一起到一个对象中会使程序如何运行变得更易理解。而FP则认为数据和行为是不同的,并且应该清晰的区别开来。

举例

让我们通过一个例子来说明这两种编程风格的不同吧。例如你正在运营一家公司,并准备给你的员工涨薪。

OOP的示例

使用OOP时,我们将创建一个Empoyee类,并将数据(name/salary)和行为(raiseSalary())放在类里,如下:

public class Employee {
   // Data
   private String name;
   private int salary;

   // Constructor
   public Empolyee(String name, int salary) {
      this.name = name;
      this.salary = salary;
   }

   // Behavior
   public void raiseSalary(int amount) {
      salary += amount;
   }
   public String toString() {
      return "Employee[name=" + name + ",salary=" + salary + "]";
   }
}

我们需要创建员工的列表List:

List<Empolyee> empolyees = = List.of(new Employee("Peter", 1000), new Employee("Paul", 2000));

然后在for循环中调用raiseSalary()方法

for (Employee employee : employees) {
   employee.raiseSalary(100);
   System.out.println(employee);  // debugging
}

使用面向对象编程(OOP)时:

  1. 我们首先定义类(class)
  2. 然后创建类的实例(instance)
  3. 再调用实例的方法

在创建实例的时候把数据提供给对象,我们运行对象上的方法,使与存储的数据相关作用。

函数式编程(FP)示例

使用函数式编程,数据和行为是分开的。数据经常放在简单的结构体中,比如数组中。数据是不可变的,而行为是由微小、独立和特写的函数实,如下:

// Data
Map<String, Integer> workers = Map.of("Peter", 1000, "Paul", 2000);

// Behavior
[TODO] 
raiseSalary(): one
raiseSalaries(): delegate to raiseSalary()

使用函数式编程时,数据被保存在简单的数据或者哈希结构中,而不是高级别的类对象中。数据和行为不会搀和在一起。数据是不可变的,状态共享被禁止。函数式编程特别依赖小而专业性的方法,这些方法是大的job的一部分,并且将细节委派给其他小方法。

函数式编程将计算重点放在在一个纯函数上,一个纯函数(pure function)是下面这样的函数。

  1. 返回值依赖于输入,相同的输入总会产生相同的输出
  2. 没有负作用
  3. 不会改变入参的值

在函数式编程中:

  1. 有一个完全独立的数据和行为(函数function)
  2. 数据(对象)是不可变的
  3. 状态共享被禁止(没有继承关系)

很有代表性的一点是,函数编程中会使用更少的代码,因为我们没必要对定义一个复杂的类。

比较

  1. 函数编程的一个优点是数据集是固定;并且随着你的应用程序的演进,你会添加新的函数处理已存在的数据,而当前的那些函数是相对独立的。
  2. 函数编程的另一个优点是在并发环境中。众所周知不可变的状态在并发环境中没有任何问题。
  3. 而面向对象编程的优点在于,类(数据和行为的集合)集是固定的;并且随着你应用程序的演进,你可以添加新类进去(利用组件或继承)。已存在的类是相对独立的。

然而,当应用程序持续演进过程中,有可能会走错路,遇到下面这样的问题:

  1. 添加新数据时不得不对现有的函数进行改造
  2. 添加新的方法到OO程序时不得不对现有的类进行改造

话又说回来了,你其实不用太纠结选OOP还是FP,你完全可以在面向对象的思想框架中使用函数编程。开发者可以在页面对象环境中写出短小,独立且特定功能一的函数。

1.2 JDK8对FP的支持

为了支持函数式编程,JDK8重新设计了接口interface,引进了lamba表达式,改造了集合框架,并入了流式API。

在详细讲解函数式编程思想之前,我们先来看一下这些变化。

2. 接口的默认方法(default)和静态方法(Static Methods)

接口的主要目的是,让你“面向接口而不是面向实现编程”。

在JDK8之前,接口包含了两类实体:

  1. public abstract描述的抽象方法:没有实现或方法体的方法,子类实现必须覆盖抽象方法并且提供实现的方法体
  2. public static final字段或常量。

因此设计和维护接口非常困难,因为当我们在接口中添加一个方法时,所有的实现类不得不跟着实现这些方法。

为了解决这个问题,从JDK8开始,接口可以包含一个public static 的方法和一个public default方法(JDK9未来会把private方法和private static方法引入到接口中)。

  1. public default方法和public static 方法都是非抽象(non-abstract)的方法,它们都有实现(方法体)。
  2. 添加一个public default方法或者public static 方法到接口中不用在子类中去实现。
  3. 接口的public default方法可以被子类继承(子类或子接口),子类实现或子接口不能覆盖public default方法,当然也没有必要。
  4. 接口的public static方法不能被子类继承。只能被超类调用;不能被子类或子类的实现所调用。
  5. (JDK9)private方法和private static方法就像接口的工具方法,不能被他们的子类所继承。

总之,在JDK8或9中接口包含了:

  1. public static (class)final的字段或常量
  2. public static(instance)的没有实现的方法——必须被子类实现的
  3. public default(instance)自带实现的方法——可以被继承,也可以被覆盖只是没必要
  4. public static(class)自带实现的方法——不能被子类继承(不像超类的那种static方法)
  5. (JDK9)的private(intance)自带实现的方法——不可以被继承,不能被其他static方法调用
  6. (JDK9)的private static(class)自带实现的方法——不能被子类承继,可以被本接口的其他static方法调用

2.1 接口的默认(实例)方法

JDK8的接口可以通过default关键字来声明默认方法。默认方法是非抽象的,言下之义,必须有默认的实现,其子没有必要去覆盖这个方法,当然有需要时也可以。default的方法必须是public的。

public interface MyJ8InterfaceWithDefault {
   void foo();   // JDK8之前的抽象方法

   // 通过default关键字标记的默认方法
   default void bar() {    // JDK8之后的默认方法
      System.out.println("MyJ8InterfaceWithDefault runs default bar()");
   }
   
   //default void bar1();
   // error: missing method body, or declare abstract
}

如果忘了给默认方法写实现,会报错:error: missing method body, or declare abstract

public class MyImplClass1 implements MyJ8InterfaceWithDefault {
   // Need to override all the abstract methods,
   //   but not necessarily for the default methods.
   @Override
   public void foo() {
      System.out.println("MyImplClass1 runs foo()");
   }

   // Test Driver
   public static void main(String[] args) {
      MyImplClass1 c = new MyImplClass1();
      c.foo();  // MyImplClass1 runs foo()
      c.bar();  // MyJ8InterfaceWithDefault runs default bar()
   }
}

实现多个接口

Java中的类可以实现多个接口(但只能实现一个父类)。在上面的例子中,如果另一个接口也提供了一个叫foo的默认方法,而一个类实现了这两个接口的话,问题就来了,此时类就不知道用哪个默认方法的实现了,为了解决这个问题,JDK8要求当实现的接口中有相同的默认方法声明时,就必须覆盖这个方法。如下:

public interface MyJ8InterfaceWithDefault1 {
   // Same signature (but different implementation) as the default method in MyJ8InterfaceWithDefault
   default void bar() {   // public (instance) (post-JDK 8)
      System.out.println("MyJ8InterfaceWithDefault1 runs default bar() too!");
   }
}
public class MyImplClass2 implements MyJ8InterfaceWithDefault, MyJ8InterfaceWithDefault1 {
   @Override
   public void foo() {
      System.out.println("MyImplClass2 runs foo()");
   }

   @Override
   public void bar() {
      System.out.println("MyImplClass2 runs overridden bar()");
   }
   // bar() exists in both interfaces.
   // MUST override, or
   //    error: class MyImplClass2 inherits unrelated defaults for bar()
   //    from types MyJ8InterfaceWithDefault and MyJ8InterfaceWithDefault1

   public static void main(String[] args) {
      MyImplClass2 c = new MyImplClass2();
      c.foo();   // MyImplClass2 runs foo()
      c.bar();   // MyImplClass2 runs overridden bar()
   }
}

这种情况下,如果没有正确覆盖默认方法的话,就会出现一个错误:error: class MyImplClass2 inherits unrelated defaults for bar() from types MyJ8InterfaceWithDefault and MyJ8InterfaceWithDefault1

2.2 接口静态(类)方法

static method和default method类似,只是它不能被子类继承。默认情况下是public的(JDK9支持private的静态方法)

public interface MyJ8InterfaceWithStatic {
   void foo();   // abstract public (instance) (pre-JDK 8)

   static void bar() {  // public (class) (post-JDK 8)
      System.out.println("MyJ8InterfaceWithStatic runs static bar()");
   }

   //static void bar1();
   // error: missing method body, or declare abstract   
}

就是默认方法一样,静态方法也必须是有实体体的,否则会出现编译错误:error: missing method body, or declare abstract 

public class MyImplClass3 implements MyJ8InterfaceWithStatic {
   @Override
   public void foo() {
      System.out.println("MyImplClass3 run foo()");
   }

   // Test Driver
   public static void main(String[] args) {
      MyImplClass3 c = new MyImplClass3();
      c.foo();  // MyImplClass3 run foo()
      MyJ8InterfaceWithStatic.bar();  // MyJ8InterfaceWithStatic runs static bar()

      // Interface's static methods are NOT inherited (Unlike Superclass)!!!
      // MyImplClass3.bar();
      // c.bar();
            // error: cannot find symbol bar()
      // MyJ8InterfaceWithStatic c1 = new MyImplClass3();
      // c1.bar();
            // error: illegal static interface method call
   }
}

注意接口的静态方法不能被子类继承!!!不像超类的静态方法是可以被它的子类承继的。这可能是因为我们只能扩展一个超类,但能实现多个接口。

public class MyImplClass4 implements MyJ8InterfaceWithStatic {
   @Override
   public void foo() {
      System.out.println("MyImplClass4 run foo()");
   }

   // @Override  // error: static methods cannot be annotated with @Override
   public static void bar() {
      System.out.println("MyImplClass4 run bar()");
   }

   // Test Driver
   public static void main(String[] args) {
      MyImplClass4 c = new MyImplClass4();
      c.foo();  // MyImplClass3 run foo()

      MyJ8InterfaceWithStatic.bar();  // MyJ8InterfaceWithStatic runs static bar()
      MyImplClass4.bar(); // MyImplClass4 run bar()
      c.bar();            // MyImplClass4 run bar()
   }
}

由于接口中的静态方法不能被子类继承,所以你可以在子类中定义一个自己的相同名字的静态方法。

Java中的超类的静态方法可以被子类继承吗?

是的。可以参考这里

Java接口的静态方法可以被子类继承吗?

不可以。可能是因为子类能实现多个接口,但只能扩展一个超类。

2.3 接口的实例方法

在JDK8的接口中,可以包含3种方法:abstract/default/static。所有方法都是public的。

JDK8的关于接口的文档中列出了实现方法,指的是非静态方法(即abstract和default的方法)

最后列一个所有的方法种类:如静态方法(static methods),实例方法(instance methods),抽象方法(Abstract methods)和默认方法(default methods)。这些你都可以在接口java.util.stream.Stream.中看到。

2.4 接口与抽象类

  1. 变量:接口只能包含常量(即public static final修饰的变量)
  2. 方法访问权限:所有的方法(abstract/static/default)都是public的。JDK9中支持private和private static 方法。

3. Labmda表达式、函数式接口和集合

参考:

  1. Lambda表达式教程:http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
  2. Lambda快速入门:http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html
  3. 集合教程:http://docs.oracle.com/javase/tutorial/collections/
  4. 聚合操作教程:http://docs.oracle.com/javase/tutorial/collections/streams/index.html

JDK8中最显著的更新莫过于Lambda表达式了,它用更简单符合来构建一个“Single-Abstract-Method-Interface”。在JDK8中,“Single-Abstract-Method-Interface”也叫“Functional Interface”。

JDK8也改造了集合框架,使用Lambda表达式、函数式接口和流式API,可以支持链式和聚合操作(或称filter-map-reduce)。

3.1 函数式接口

JDK有许多“Single-Abstract-Method Interfaces”,在JDK8中称为函数式接口(Functional Interface)。最常见的是java.awt.event.ActionListener 、java.lang.Runnable、 java.util.Comparator。这些方接口通常会在匿名类中构造匿名实例。

注解@FunctionInterface 

这个注解可以用来标记只有一个抽象方法的接口。可以有效的阻止向一个函数式接口中添加额外的方法。

3.2 示例1:Swing中的监听Lambda

略(译者不常用)

3.3 Lambda表达式的语法和使用

在JDK8之前,想要构造一个函数式接口的实例,需要多行代码才能完成。而Lambda表达式却可以用简单的符号完成。此外,还可以把Lambda表达式当成一个方法的参数来用。

Lambda表达式定义了函数式接口的唯一方法。它有两部分组成:即参数和方法体,通过->符号隔开。参数之间用","隔开,通过()来包裹。当只有一个参数的时候,括号是可以省略的。方法体可能是一条语句,或者一段语句块。由于函数式接口只有一个方法,因此方法名是省略掉的。参数的类型和返回值值都是可选的,因为这些都可以通过方法签名推测出来。

语法是这样的:

(arguments) -> (body)

示例:

() -> statement    // 无参方法和只有一条语句的方法体

arg -> statement   // 一个参数(注意括号省略了)和方法体

(arg1, arg2, ...) -> { 
   body-block 
}   // 参数可以通过逗号隔开

(Type1 arg1, Type2 arg2, ...) -> { 
   method-body-block;
   return return-value; 
}   // 多个参数和多行语句的代码块

在其他支持函数式变量和函数式对象的语句中,Lambda被用来定义一个匿名函数。然而,在JDK8中,Lambda表达式只能定义一个单抽象方法接口的实例。

如果你像下面这样写:

int i = () -> 5;
// error: incompatible types: int is not a functional interface

是的,报错了。可以换成下面的就可以。

// 使用Lambda表达式来构建一个 Runnable 实例
// 换句话说,Lambda表达式返回的是一个函数式接口的实例
Runnable r1 = () -> System.out.println("run run()");
   // Runnable is a functional interface
   // Lambda expression is used to define the implementation of the abstract method run()

// Using Lambda expression to construct an ActionListener instance
ActionListener lis = e -> System.out.println("run actionPerformed()");

Java是一门面向对象的语言,Java中的一切都是对象(除了基本数据类型)。函数在Java中不是对象(但是对象的一部分),所以函数不能独立的存在。不像其他语言(如C++,Python或JavaScript),函数可以独立的存在,你甚至可以把一个函数像一个参数一样传递,从一个函数中返回另一个函数等等。

JDK8的函数式接口和Lambda表达式让我们可以用一行代码(或更少的代码)就可以构造一个“函数对象”。然而,只能是有一个方法的对象。

3.4 示例2:Runnable接口的Lambda

Runnable接口包含了单一的抽象方法,定义如下:

@FunctionalInterface
public interface Runnable {
   void run();  // public abstract
}

因此可以通过匿名内部类或Lamda表达式来创建一个Runnable对象

public class TestRunnableLambda {
   public static void main(String[] args) {
      // Using an anonymous inner class
      Runnable r1 = new Runnable() {
         public void run() {
            System.out.println("Runnable 1");
         }
      };
      // Using a one-liner Lambda Expression for One-Method Interface
      Runnable r2 = () -> System.out.println("Runnable 2");

      r1.run();
      r2.run();
   }
}

3.5 示例3:二元运算中的Lambda

下面定义了一个函数式接口来表示二元运算操作(如加减法):

@FunctionalInterface
public interface MyIntBinaryOperator {
   int applyAsInt(int left, int right);
}

下面的类中,方法operate()持有两个int类型的参数,和一个MyIntBinaryOperator类型的参数,返回二元运算。

public class MyMathBinaryOperation {
   // Define instances of IntBinaryOperator for add, subtract, multiply and divide
   public MyIntBinaryOperator add = (a, b) -> a + b;
   public MyIntBinaryOperator sub = (a, b) -> a - b;
   public MyIntBinaryOperator mul = (a, b) -> a * b;
   public MyIntBinaryOperator div = (a, b) -> a / b;

   // Carry out the binary operation
   public int operate(int left, int right, MyIntBinaryOperator op) {
      return op.applyAsInt(left, right);
   }

   // Test Driver
   public static void main(String args[]){
      MyMathBinaryOperation op = new MyMathBinaryOperation();
      // Use pre-defined IntBinaryOperator
      System.out.println("8 + 9 = " + op.operate(8, 9, op.add));
      System.out.println("8 - 9 = " + op.operate(8, 9, op.sub));
      System.out.println("8 x 9 = " + op.operate(8, 9, op.mul));
      System.out.println("8 / 9 = " + op.operate(8, 9, op.div));
      // Use a custom IntBInaryOperator
      System.out.println("2 ^ 5 = " + op.operate(2, 5, (a, b) -> (int)Math.pow(a, b)));
   }
}

java.util.function包

JDK8的新包java.util.function提供了相似的函数式接口,如特定类型的二元操作IntBinaryOperatorLongBinaryOperatorDoubleBinaryOperator。也有通用类型的二元运算。后面会详细讨论。

3.6 示例4:Comparator比较器中的Lambda

我们可以使用Collections.sort()的静态方法,来给集合对象自定义的排序。静态方法签名如下:

public static <T> void sort(List<T> list, Comparator<? super T> c)

或者使用函数式接口Comparator的两个参数的sort方法,其中包含用以比较两个集合对象大小的抽象方法,定义如下:

package java.util;

@FunctionalInterface
public interface Comparator<T> {
   // Compares its two arguments for order.
   // Returns a negative integer, zero, or a positive integer
   //   as the first argument is less than, equal to, or greater than the second.
   int compare(T o1, T o2);  // public abstract
}

假设现在有一个以Person为元素的List对象,我们需要执行一个自定义排序,在JDK8之前使用匿名内部类的方法实现Comparator接口,JDK8之后使用Lambda表达式。

Person.java

public class Person {
   private String name;
   private int age;

   public Person(String name, int age) {  // Constructor
      this.name = name; this.age = age;
   }
   public String getName() {
      return name;
   }
   public int getAge() {
      return age;
   }
   public String toString() {
      return name + "(" + age + ")";
   }
   // To be used in testing Consumer
   public void sayHello() {
      System.out.println(name + " says hello");
   }
}
import java.util.*;
public class TestComparatorLambda {
   public static void main(String[] args) {
      List<Person> pList = new ArrayList<>();
      pList.add(new Person("Peter", 21));
      pList.add(new Person("Paul", 18));
      pList.add(new Person("Patrick", 22));
      System.out.println(pList);
         // Unsorted: [Peter(21), Paul(18), Patrick(22)]

      // In JDK 9, you can simply write:
      List<Person> pList9 = List.of(new Person("Peter", 21), new Person("Paul", 18));
      System.out.println(pList9);
      
      // Using an anonymous inner class to create a Comparator instance
      Collections.sort(pList, new Comparator<Person>() {
         @Override
         public int compare(Person p1, Person p2){
            return p1.getName().compareTo(p2.getName());  // String's compareTo()
         }
      });
      System.out.println(pList);
         // Sort by name: [Patrick(22), Paul(18), Peter(21)]

      // Using a Lambda Expression to create a Comparator instance
      Collections.sort(pList, (p1, p2) -> p1.getAge() - p2.getAge());
      System.out.println(pList);
         // Sort by age: [Paul(18), Peter(21), Patrick(22)]
   }
}

上面代码是如何工作的?

第一个sort方法使用了匿名内部类的形式构造了Comparator接口的实例对象,而第二个sort方法使用一行Lambda表达式来构造的Comparator实例对象。

3.7 示例5:集合

JDK8对集合框架做了重大的改进,集成了Lambda表达式和流式API来支持函数式编程。

用例:Filter与Reduce

假设我们有一个由Person对象组成的List,要实现下面的功能:

  1. 遍历这个list
  2. 根据几个条件来过滤(比如age>12)
  3. 在过滤出来的对象上都运行某一操作(比如调用sayHello()方法),就被称为归约操作(reduction operation)可以让我们计算一个结果。

代码应该能用来处理过滤任何几种条件,并且运行任何几种归约操作。

方法1:Roll Your Own

PersonPredicate.java:定义了一个函数式接口,用以处理过滤操作,其中包含了一个返回类型为boolean的test()方法。

@FunctionalInterface
public interface PersonPredicate {
   boolean test(Person p);  // Perform this boolean test on the given Person
}

PersonConsumer.java:定义了一个函数式接口,用来运行Person对象上的某些方法。

@FunctionalInterface
public interface PersonConsumer {
   void accept(Person p);  // Run these operations on the given Person
}

PersonsFilterReduce.java

import java.util.List;

public class PersonsFilterReduce {
}

ProcessPersons.java:我们会定义一个静态处理方法process()来执行filter-reduce操作。通过循环List<Person>,我们可以在这个测试驱动类中测试几个过滤器和规约操作。

import java.util.*;

public class ProcessPersons {
   // Given a List<Person>, filter with predicate, and consume.
   public static void process(List<Person> pList, PersonPredicate predicate, PersonConsumer consumer) {
      for (Person p : pList) {
         if (predicate.test(p)) {  // Filter
            consumer.accept(p);    // Reduce
         }
      }
   }

   public static void main(String[] args) {
      // Create a List of Person objects
      List<Person> pList = new ArrayList<>();
      pList.add(new Person("Peter", 21));
      pList.add(new Person("Paul", 60));
      pList.add(new Person("Patrick", 15));
      System.out.println(pList);  // [Peter(21), Paul(60), Patrick(15)]

      // Pre-JDK 8: Using anonymous inner classes
      ProcessPersons.process(
         pList,
         new PersonPredicate() {
            @Override
            public boolean test(Person p) {
               return p.getAge() >= 21;  // Filtering criteria
            }
         },
         new PersonConsumer() {
            @Override
            public void accept(Person p) {
               p.sayHello();   // Apply this operation
            }
         }
      );

      // JDK 8: Using Lambda Expressions
      ProcessPersons.process(pList, p -> p.getAge() >= 21, p -> p.sayHello());
   }
}

可以看到,在JDK8中只需要一行代码,就完成了JDK8之前必须15行代码完成的事情。

方法2:使用JDK8预定义函数式接口

JDK8中有一个新包java.util.function,包含了很多标准的函数式接口,包括Predicate和Consumer:

java.util.function.Predicate:

package java.util.function;

@FunctionalInterface
public interface Predicate<T> {
   boolean test(T t);  // Evaluates this predicate on the given object.
   ......
}

java.util.function.Consumer:

package java.util.function;

@FunctionalInterface
public interface Consumer<T> {
   void accept(T t);  // Run this operation on the given object.
   ......
}

为了代替上面自己撸的函数式接口,这里我们用的是通用的。

下面添加了一个process1()方法,这里用改用通用的函数式接口Predicate<Person>和Consumer<Person>方法:

......
import java.util.function.*;

public class ProcessPersons {
   ......
   public static void process1(List<Person> pList, Predicate<Person> predicate, Consumer<Person> consumer) {
      for (Person p : pList) {
         if (predicate.test(p)) {
            consumer.accept(p);
         }
      }
   }

   public static void main(String[] args) {
      ......
      // Using JDK 8 standard functional interfaces Predicate<T> and Consumer<T>
      ProcessPersons.process1(pList, p -> p.getAge() >= 21, p -> p.sayHello());
   }
}

方法3:Filter-Map-Reduce

假设不再去直接操作过滤出的Person对象,而是执行某个操作,对Person做一些映射或转换之后。下面使用通用的函数式接口java.util.function.Function作为映射器,定义如下:

java.util.function.Function:

package java.util.function;

@FunctionalInterface
public Function<T, R> {
   R apply(T t);  // Apply this mapping to the given object.
   ......
}

ProcessPersons.java:添加一个方法process2(),用来测试新方法:

import java.util.*;
import java.util.function.*;

public class ProcessPersons {
   ......
   // Given a List, filter with predicate, apply mapper, and reduce (filter-map-reduce)
   public static void process2(List<Person> pList, 
                               Predicate<Person> predicate, 
                               Function<Person, String> mapper, 
                               Consumer<String> consumer) {
      for (Person p:pList) {
         if (predicate.test(p)) {
            String s = mapper.apply(p);  // Apply mapper to transfom Person to String
            consumer.accept(s);
         }
      }
   }

   public static void main(String[] args) {
      ......

      // Using Lambda Expression
      ProcessPersons.process2(
         pList, 
         p -> p.getAge() >= 21, 
         p -> p.getName(), 
         name -> System.out.println(name)
      );

      // Using method references
      ProcessPersons.process2(pList, p -> p.getAge() >= 21, Person::getName, System.out::println);
   }
}

方法引用

在Java中引入了一个新的操作符"::",用来引用方法,而不是调用它,称之为方法引用。例如

// Method References
System.out::println
Person::getName
Person::sayHello
"xyz"::length

// Constructor References
Integer::new
int[]::new

上面的示例中,可以使用ClassName::method代替p->p.method()表达式。

方法4:使用JDK8的流式API和管道

JDK8为集合框架增添了新的流式API,用以支持聚合操作(如函数式编程)。这可以简化filter-map-reduce操作为一行代码。另外再也不需要明确的写for循环了。

import java.util.*;
import java.util.function.*;

public class ProcessPersons {
   public static void main(String[] args) {
      ......
      
      // Using JDK 8 Stream for filter-reduce
      pList.stream().filter(p -> p.getAge() >= 21).forEach(p -> p.sayHello());
      pList.stream().filter(p -> p.getAge() >= 21).forEach(Person::sayHello);  // Using method reference

      // Using map() to extract a specific property from the object
      Predicate<Person> adult = p -> p.getAge() >= 21;
      pList.stream().filter(adult).map(p -> p.getName()).forEach(name -> System.out.println(name));
      pList.stream().filter(adult).map(Person::getName).forEach(System.out::println);

      // Apply aggregate operation average(), sum() to an int property extracted via mapToInt()
      System.out.println(pList.stream().filter(adult).mapToInt(p -> p.getAge()).average().getAsDouble());
      System.out.println(pList.stream().filter(adult).mapToInt(Person::getAge).average().getAsDouble());
      System.out.println(pList.stream().filter(adult).mapToInt(Person::getAge).sum());
   }
}

管道(Pipeline)

一个管道就是基于集合(或数组)的操作序列,这些序列包括:

  1. 一个源:例如一个集合或数组,就像上面的List<Person>
  2. stream():用于处理一个流,这个流是从源到管道的元素集合序列
  3. 一些中间操作:例如filter(Predicate)。这个方法会输出符合条件Predicate的元素集合
  4. 一个结束操作(规约操作):如forEach()方法,将处理求得的结果。

java.util.stream.Stream接口

一个流就是元素的序列,在流的管道中,可以直接串行和并行两种操作。可以通过下面的两个方法创建流:

interface Collection<E> {
   default Stream<E> stream()          // Returns a sequential Stream with this Collection as its source
   default Stream<E> parallelStream()  // Returns a possibly parallel Stream with this Collection as its source
   ......
}

有三个原始类型的特定的流:IntStreamLongStream 和 DoubleStream

管理链中的方法

aCollection.stream(): 返回以这个集合为源的元素序列.

aStream.filter(aPredicate): 用指定的Predicate条件过滤流

Stream<T> filter(Predicate<? super T> predicate)

aStream.forEach(aConsumer): 执行一个Consumer指定的行为

Stream<T> filter(Predicate<? super T> predicate)

aStream.map(aFunction): 用给定的Function应用映射(或者叫转换),从一个对象转换为<T,R>的map

<R> Stream<R> map(Function<? super T, ? extends R> mapper)

aStream.mapToInt(anToIntFunction): map()方法的一个特殊形式,返回一个intStream

IntStream mapToInt(ToIntFunction<? super T> mapper)

anIntStream.average().getAsDouble(): 计算给定流的平均值

OptionalDouble average()  // Returns an OptionalDouble for the average of this stream,

                          // or an empty OptionalDouble if this stream is empty.

返回值中的java.util.OptionDouble对象, 包含了一个或多个double值。如果值存在的话, isPresent() 返回true,getAsDouble() 将返回相应的值

anIntStream.sum(): 返回int元素的总和

int sum()  // Returns the sum of elements in this stream as int


3.8 JDK8的java.util.function包


3.9 JDK8的java.util.stream包


3.10 更多关于Lambda表达式和函数式编程的内容


4. JDK8 日期/时间(Date/Time)API

4.1 java.time示例


4.2 java.time.DayOfWeek和java.time.Month枚举


5. Collection API实现

5.1 迭代器的forEach()方法

JDK8在java.lang.Iterable迭代器接口中添加了一个forEach()方法。由于java.util.Collection本身就是Iterable的子接口,因此forEach()也是可用的。forEach()方法是Iterable接口的默认方法,有一个Consumer类型的参数:

default void forEach(java.util.function.Consumer<? super T> action) {
   Objects.requireNonNull(action);  // Ensure that action is NOT null
   for (T t : this) {
      action.accept(t);
   }
}

示例

TestIterableForEach.java:

import java.util.*;
import java.util.function.*;

public class TestIterableForEach {
   public static void main(String[] args) {
      List<Person> pList = new ArrayList<>();
      pList.add(new Person("Peter", 21));
      pList.add(new Person("Paul", 60));
      pList.add(new Person("Patrick", 15));
      System.out.println(pList);

      // Pre-JDK 8: Using for-each loop
      for (Person p: pList) {
         System.out.println(p);
         System.out.println(p.getName());
      }

      // Pre-JDK 8: Using Iterator
      Iterator<Person> iter = pList.iterator();
	     while (iter.hasNext()) {
			Person p = iter.next();
			System.out.println(p.getName());
		 }

      // JDK 8 Iterable's forEach(): with Consumer anonymous inner class
      pList.forEach(new Consumer<Person>() {
         @Override
         public void accept(Person p) {
            p.sayHello();
         }
      });
      
      // JDK 8 Iterable's forEach(): with Lambda Expression
      pList.forEach(p -> System.out.println(p));
      pList.forEach(p -> System.out.println(p.getName()));
      pList.forEach(p -> p.sayHello());

      // JDK Iterable's forEach(): with Method Reference
      pList.forEach(System.out::println);
      pList.forEach(Person::sayHello);
   }
}

注意:Iterable.forEach()不同于Stream.forEach()

5.2 其他

~ java.lang.Iterabler接口的forEach()方法,前面提到的

~ java.util.Iterator的默认方法forEachRemaining(Consumer<? super E> action) 用来让剩余元素来执行某种操作

~ 接口 java.util.Collection的默认方法removeIf(Predicate<? super E> filter) 移除这个集合中满足指定条件的所有元素,这个实现了已存在的remove(Object o)和removeAll(Collection<?> c)抽象方法

~ 接口java.util.Collection的默认方法stream()和 parallelStream() 创建一个串行或并行的流,spliterator() 方法用来创建一个分流器,可以支持并行和串行操作

~ 接口 java.util.Map的默认方法compute(), merge(), remove(), replace(),等等

~ 接口 java.uitl.Comparator的默认方法

more.

6. IO实现

TODO

7. 并发API实现

TODO

8. 其他API的更新

8.1 包装类Integer、Long和Double中的min(), max(), sum()方法

TODO

8.2 支持无符号int和long

JDK8中并没有引入像C语言那样的无符号int,但是在包装类Integer和Long中可以把int和long当作无符号值来对待。例如:

public class TestUnsignedInteger {
   public static void main(String[] args) {
      // Pr-JDK 8
      // 32-bit signed int ranges from −2,147,483,648 −(2^31) to 2,147,483,647 (2^31 – 1)
      System.out.println(Integer.parseInt("2147483647"));  // max 32-bit unsigned integer
      System.out.println(Integer.parseInt("-2147483648")); // min 32-bit unsigned integer
      //System.out.println(Integer.parseInt("2147483648"));  // error: NumberFormatException

      // JDK 8
      // 32-bit unsigned int ranges from 0 to 4,294,967,295 (2^32 – 1)
      int i1 = Integer.parseUnsignedInt("4294967295");  // max 32-bit unsigned integer
      System.out.println(i1);   // -1 (treated as signed int)
      System.out.println(Integer.toUnsignedString(i1));  // 4294967295
      System.out.println(Integer.toUnsignedString(-1));  // 4294967295

      long l1 = Long.parseUnsignedLong("18446744073709551615");  // max 64-bit unsigned integer
      System.out.println(l1);  // -1 (treated as signed long)
      System.out.println(Long.toUnsignedString(l1));  // 18446744073709551615
      System.out.println(Long.toUnsignedString(-1));  // 18446744073709551615
   }
}

JDK 8在java.lang.Integer类中添加了静态方法用以处理无符号int值:

static int compareUnsigned(int x, int y): 

static int divideUnsigned(int dividend, int divisor):

static int remainderUnsigned(int dividend, int divisor):

static int parseUnsignedInt(...):

static String toUnsignedString(int i):

相似的静态方法在java.lang.Long中也存在。


8.3 Boolean包装类中的logicalAnd(), logicalOr(), logicalXor()方法

TODO

8.4 java.lang.Math包中其他实用功能

int addExact​(int x, int y):如果结果超出int取值范围会抛出异常

long addExact​(long x, long y):如果结果超出long取值范围会抛出异常

floorDiv​()floorMod​()

int toIntExact​(long value):

nextDown​(double), nextDown​(float):

8.5 其他

~ 添加了jjs命令调用Nashorm的JavaScript引擎

~ 添加了jdeps命令用来分析类文件

~ JDBC-ODBC被移除了


原文链接:JDK 8 New Feature

参考链接:What's New in JDK 8

还没有评论!