Java8新特性学习

本文借鉴:https://blog.csdn.net/icarusliu/article/details/79495534该博文。在此博文基础上进行加入一些自己的学习与补充。

目录

1 Java8函数式编程语法入门

1.1 Lambda 表达式

语法

1.1.1 Lambda 表达式实例

使用 Lambda 表达式需要注意以下两点:

1.1.2变量作用域

1.1.3总结:

2 Java函数式接口

 

2.1 函数式接口实例

2.2 Consumer

2.3 Function

2.4 Predicate

 

3 函数式编程接口的使用

3.1 Stream

3.1.1 Stream对象的创建

3.1.2 Stream对象的使用

3.2 Optional

3.2.1 Optional对象创建

3.2.2 方法

3.2.3 使用场景

3.3 方法引用

3.3.1 方法引用实例

3.4 默认方法

语法

多个默认方法

静态默认方法

默认方法实例


函数式接口;它指的是有且只有一个未实现的方法的接口,一般通过FunctionalInterface这个注解来表明某个接口是一个函数式接口。函数式接口是Java支持函数式编程的基础。

 

1 Java8函数式编程语法入门

Java8中函数式编程语法能够精简代码。 
使用Consumer作为示例,它是一个函数式接口,包含一个抽象方法accept,这个方法只有输入而无输出。 
现在我们要定义一个Consumer对象,传统的方式是这样定义的:

Consumer c = new Consumer() {
    @Override
    public void accept(Object o) {
        System.out.println(o);
    }
};

而在Java8中,针对函数式编程接口,可以这样定义:

Consumer c = (o) -> {
    System.out.println(o);
};  

上面已说明,函数式编程接口都只有一个抽象方法,因此在采用这种写法时,编译器会将这段函数编译后当作该抽象方法的实现。 
如果接口有多个抽象方法,编译器就不知道这段函数应该是实现哪个方法的了。 
因此,=后面的函数体我们就可以看成是accept函数的实现。

  • 输入:->前面的部分,即被()包围的部分。此处只有一个输入参数,实际上输入是可以有多个的,如两个参数时写法:(a, b);当然也可以没有输入,此时直接就可以是()。
  • 函数体:->后面的部分,即被{}包围的部分;可以是一段代码。
  • 输出:函数式编程可以没有返回值,也可以有返回值。如果有返回值时,需要代码段的最后一句通过return的方式返回对应的值。

当函数体中只有一个语句时,可以去掉{}进一步简化:

Consumer c = (o) -> System.out.println(o);

然而这还不是最简的,由于此处只是进行打印,调用了System.out中的println静态方法对输入参数直接进行打印,因此可以简化成以下写法:

Consumer c = System.out::println;

它表示的意思就是针对输入的参数将其调用System.out中的静态方法println进行打印。 
到这一步就可以感受到函数式编程的强大能力。 
通过最后一段代码,我们可以简单的理解函数式编程,Consumer接口直接就可以当成一个函数了,这个函数接收一个输入参数,然后针对这个输入进行处理;当然其本质上仍旧是一个对象,但我们已经省去了诸如老方式中的对象定义过程,直接使用一段代码来给函数式接口对象赋值。 
而且最为关键的是,这个函数式对象因为本质上仍旧是一个对象,因此可以做为其它方法的参数或者返回值,可以与原有的代码实现无缝集成!

下面对Java中的几个预先定义的函数式接口及其经常使用的类进行分析学习。

1.1 Lambda 表达式

语法

lambda 表达式的语法格式如下:

(parameters) -> expression 或 (parameters) ->{ statements; }

以下是lambda表达式的重要特征:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值

1.1.1 Lambda 表达式实例

public class Java8Tester {
   public static void main(String args[]){
      Java8Tester tester = new Java8Tester();
        
      // 类型声明
      MathOperation addition = (int a, int b) -> a + b;
        
      // 不用类型声明
      MathOperation subtraction = (a, b) -> a - b;
        
      // 大括号中的返回语句
      MathOperation multiplication = (int a, int b) -> { return a * b; };
        
      // 没有大括号及返回语句
      MathOperation division = (int a, int b) -> a / b;
        
      System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
      System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
      System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
      System.out.println("10 / 5 = " + tester.operate(10, 5, division));
        
      // 不用括号
      GreetingService greetService1 = message ->
      System.out.println("Hello " + message);
        
      // 用括号
      GreetingService greetService2 = (message) ->
      System.out.println("Hello " + message);
        
      greetService1.sayMessage("Runoob");
      greetService2.sayMessage("Google");
   }
    
   interface MathOperation {
      int operation(int a, int b);
   }
    
   interface GreetingService {
      void sayMessage(String message);
   }
    
   private int operate(int a, int b, MathOperation mathOperation){
      return mathOperation.operation(a, b);
   }
}

使用 Lambda 表达式需要注意以下两点:

  • Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。在上面例子中,我们使用各种类型的Lambda表达式来定义MathOperation接口的方法。然后我们定义了sayMessage的执行。

  • Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。

1.1.2变量作用域

lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。

在 Java8Tester.java 文件输入以下代码:

public class Java8Tester {
 
   final static String salutation = "Hello! ";
   
   public static void main(String args[]){
      GreetingService greetService1 = message -> 
      System.out.println(salutation + message);
      greetService1.sayMessage("Runoob");
   }
    
   interface GreetingService {
      void sayMessage(String message);
   }
}

我们也可以直接在 lambda 表达式中访问外层的局部变量:

public class Java8Tester {
    public static void main(String args[]) {
        final int num = 1;
        Converter s = (param) -> System.out.println(String.valueOf(param + num));
        s.convert(2);  // 输出结果为 3
    }
 
    public interface Converter {
        void convert(int i);
    }
}

lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)

int num = 1;  
Converter s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2);
num = 5;  
//报错信息:Local variable num defined in an enclosing scope must be final or effectively 
 final

在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。

String first = "";  
Comparator comparator = (first, second) -> Integer.compare(first.length(), second.length());  //编译会出错 

1.1.3总结:

  System.out.println("10 + 5 = " + tester.operate(10, 5, addition));还有一个写法如下:即使用函数式接口

interface MathOperation { int operation(int a, int b); }
MathOperation addition = (int a, int b) -> a + b;
int sun = addition.operation(1, 2);
这种直接调用接口写法,必须要声明为函数式接口  可以使用注解(@FunctionalInterface)强制限定接口是函数式接口,即只能有一个抽象方法。
当只有一个方法时,使用lambda表达式调用,默认为函数式接口
此接口要求必须是函数式接口,如果其中有两个方法则lambda表达式会编译错误。但java8的新特性如许实现如下写法:

    interface MathOperation {
        int operation(int a, int b);
            default int addition(int a, int b){
            return a+b;
        }
    }

  如果接口函数不止一个,可以使用
  在该类中声明接口调用方法
   private int operate(int a, int b, MathOperation mathOperation){
         return mathOperation.operation(a, b);
     }
      Lambdademo lambdademo = new Lambdademo();
       System.out.println("10 + 5 = " + lambdademo.operate(10, 5, addition));
       即可

2 Java函数式接口

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。

函数式接口可以被隐式转换为lambda表达式。

函数式接口可以现有的函数友好地支持 lambda。

JDK 1.8之前已有的函数式接口:

  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.security.PrivilegedAction
  • java.util.Comparator
  • java.io.FileFilter
  • java.nio.file.PathMatcher
  • java.lang.reflect.InvocationHandler
  • java.beans.PropertyChangeListener
  • java.awt.event.ActionListener
  • javax.swing.event.ChangeListener

JDK 1.8 新增加的函数接口:

  • java.util.function

java.util.function 它包含了很多类,用来支持 Java的 函数式编程,该包中的函数式接口有:

1 BiConsumer

代表了一个接受两个输入参数的操作,并且不返回任何结果

2 BiFunction

代表了一个接受两个输入参数的方法,并且返回一个结果

3 BinaryOperator

代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果

4 BiPredicate

代表了一个两个参数的boolean值方法

5 BooleanSupplier

代表了boolean值结果的提供方

6 Consumer

代表了接受一个输入参数并且无返回的操作

7 DoubleBinaryOperator

代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。

8 DoubleConsumer

代表一个接受double值参数的操作,并且不返回结果。

9 DoubleFunction

代表接受一个double值参数的方法,并且返回结果

10 DoublePredicate

代表一个拥有double值参数的boolean值方法

11 DoubleSupplier

代表一个double值结构的提供方

12 DoubleToIntFunction

接受一个double类型输入,返回一个int类型结果。

13 DoubleToLongFunction

接受一个double类型输入,返回一个long类型结果

14 DoubleUnaryOperator

接受一个参数同为类型double,返回值类型也为double 。

15 Function

接受一个输入参数,返回一个结果。

16 IntBinaryOperator

接受两个参数同为类型int,返回值类型也为int 。

17 IntConsumer

接受一个int类型的输入参数,无返回值 。

18 IntFunction

接受一个int类型输入参数,返回一个结果 。

19 IntPredicate

:接受一个int输入参数,返回一个布尔值的结果。

20 IntSupplier

无参数,返回一个int类型结果。

21 IntToDoubleFunction

接受一个int类型输入,返回一个double类型结果 。

22 IntToLongFunction

接受一个int类型输入,返回一个long类型结果。

23 IntUnaryOperator

接受一个参数同为类型int,返回值类型也为int 。

24 LongBinaryOperator

接受两个参数同为类型long,返回值类型也为long。

25 LongConsumer

接受一个long类型的输入参数,无返回值。

26 LongFunction

接受一个long类型输入参数,返回一个结果。

27 LongPredicate

R接受一个long输入参数,返回一个布尔值类型结果。

28 LongSupplier

无参数,返回一个结果long类型的值。

29 LongToDoubleFunction

接受一个long类型输入,返回一个double类型结果。

30 LongToIntFunction

接受一个long类型输入,返回一个int类型结果。

31 LongUnaryOperator

接受一个参数同为类型long,返回值类型也为long。

32 ObjDoubleConsumer

接受一个object类型和一个double类型的输入参数,无返回值。

33 ObjIntConsumer

接受一个object类型和一个int类型的输入参数,无返回值。

34 ObjLongConsumer

接受一个object类型和一个long类型的输入参数,无返回值。

35 Predicate

接受一个输入参数,返回一个布尔值结果。

36 Supplier

无参数,返回一个结果。

37 ToDoubleBiFunction

接受两个输入参数,返回一个double类型结果

38 ToDoubleFunction

接受一个输入参数,返回一个double类型结果

39 ToIntBiFunction

接受两个输入参数,返回一个int类型结果。

40 ToIntFunction

接受一个输入参数,返回一个int类型结果。

41 ToLongBiFunction

接受两个输入参数,返回一个long类型结果。

42 ToLongFunction

接受一个输入参数,返回一个long类型结果。

43 UnaryOperator

接受一个参数为类型T,返回值类型也为T。

2.1 函数式接口实例

Predicate 接口是一个函数式接口,它接受一个输入参数 T,返回一个布尔值结果。

该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非)。

该接口用于测试对象是 true 或 false。

我们可以通过以下实例(Java8Tester.java)来了解函数式接口 Predicate 的使用:

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
 
public class Java8Tester {
   public static void main(String args[]){
      List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
        
      // Predicate predicate = n -> true
      // n 是一个参数传递到 Predicate 接口的 test 方法
      // n 如果存在则 test 方法返回 true
        
      System.out.println("输出所有数据:");
        
      // 传递参数 n
      eval(list, n->true);
        
      // Predicate predicate1 = n -> n%2 == 0
      // n 是一个参数传递到 Predicate 接口的 test 方法
      // 如果 n%2 为 0 test 方法返回 true
        
      System.out.println("输出所有偶数:");
      eval(list, n-> n%2 == 0 );
        
      // Predicate predicate2 = n -> n > 3
      // n 是一个参数传递到 Predicate 接口的 test 方法
      // 如果 n 大于 3 test 方法返回 true
        
      System.out.println("输出大于 3 的所有数字:");
      eval(list, n-> n > 3 );
   }
    
   public static void eval(List list, Predicate predicate) {
      for(Integer n: list) {
        
         if(predicate.test(n)) {
            System.out.println(n + " ");
         }
      }
   }
}

2.2 Consumer

Consumer是一个函数式编程接口; 顾名思义,Consumer的意思就是消费,即针对某个东西我们来使用它,因此它包含有一个有输入而无输出的accept接口方法; 
除accept方法,它还包含有andThen这个方法; 
其定义如下:

default Consumer andThen(Consumer after) {
    Objects.requireNonNull(after);
    return (T t) -> { accept(t); after.accept(t); };
}

可见这个方法就是指定在调用当前Consumer后是否还要调用其它的Consumer; 
使用示例:

public static void consumerTest() {
    Consumer f = System.out::println;
    Consumer f2 = n -> System.out.println(n + "-F2");

    //执行完F后再执行F2的Accept方法
    f.andThen(f2).accept("test");

    //连续执行F的Accept方法
    f.andThen(f).andThen(f).andThen(f).accept("test1");
}

2.3 Function

Function也是一个函数式编程接口;它代表的含义是“函数”,而函数经常是有输入输出的,因此它含有一个apply方法,包含一个输入与一个输出; 
除apply方法外,它还有compose与andThen及indentity三个方法,其使用见下述示例;

/**
 * Function测试
 */
public static void functionTest() {
    Function f = s -> s++;
    Function g = s -> s * 2;

    /**
     * 下面表示在执行F时,先执行G,并且执行F时使用G的输出当作输入。
     * 相当于以下代码:
     * Integer a = g.apply(1);
     * System.out.println(f.apply(a));
     */
    System.out.println(f.compose(g).apply(1));

    /**
     * 表示执行F的Apply后使用其返回的值当作输入再执行G的Apply;
     * 相当于以下代码
     * Integer a = f.apply(1);
     * System.out.println(g.apply(a));
     */
    System.out.println(f.andThen(g).apply(1));

    /**
     * identity方法会返回一个不进行任何处理的Function,即输出与输入值相等; 
     */
    System.out.println(Function.identity().apply("a"));
}

2.4 Predicate

Predicate为函数式接口,predicate的中文意思是“断定”,即判断的意思,判断某个东西是否满足某种条件; 因此它包含test方法,根据输入值来做逻辑判断,其结果为True或者False。 
它的使用方法示例如下:

/**
 * Predicate测试
 */
private static void predicateTest() {
    Predicate p = o -> o.equals("test");
    Predicate g = o -> o.startsWith("t");

    /**
     * negate: 用于对原来的Predicate做取反处理;
     * 如当调用p.test("test")为True时,调用p.negate().test("test")就会是False;
     */
    Assert.assertFalse(p.negate().test("test"));

    /**
     * and: 针对同一输入值,多个Predicate均返回True时返回True,否则返回False;
     */
    Assert.assertTrue(p.and(g).test("test"));

    /**
     * or: 针对同一输入值,多个Predicate只要有一个返回True则返回True,否则返回False
     */
    Assert.assertTrue(p.or(g).test("ta"));
}

3 函数式编程接口的使用

通过Stream以及Optional两个类,可以进一步利用函数式接口来简化代码。

3.1 Stream

Stream可以对多个元素进行一系列的操作,也可以支持对某些操作进行并发处理。

Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。

Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

Stream API提供了很多开发者可以用来从集合中查询数据的操作。Stream操作分为两类——中间操作和结束操作。

中间操作是从已有的Stream产生另一个Stream的函数,有filtermapsorted等。

结束操作是从Stream来产生一个不是Stream的结果的函数,有collect(toList())forEachcount等。

3.1.1 Stream对象的创建

Stream对象的创建途径有以下几种

a. 创建空的Stream对象

Stream stream = Stream.empty();

b. 通过集合类中的stream或者parallelStream方法创建;

List list = Arrays.asList("a", "b", "c", "d");
Stream listStream = list.stream();                   //获取串行的Stream对象
Stream parallelListStream = list.parallelStream();   //获取并行的Stream对象  

c. 通过Stream中的of方法创建:

Stream s = Stream.of("test");
Stream s1 = Stream.of("a", "b", "c", "d");

d. 通过Stream中的iterate方法创建: 
iterate方法有两个不同参数的方法:

public static Stream iterate(final T seed, final UnaryOperator f);  
public static Stream iterate(T seed, Predicate hasNext, UnaryOperator next)

其中第一个方法将会返回一个无限有序值的Stream对象:它的第一个元素是seed,第二个元素是f.apply(seed); 第N个元素是f.apply(n-1个元素的值);生成无限值的方法实际上与Stream的中间方法类似,在遇到中止方法前一般是不真正的执行的。因此无限值的这个方法一般与limit等方法一起使用,来获取前多少个元素。 
当然获取前多少个元素也可以使用第二个方法。 
第二个方法与第一个方法生成元素的方式类似,不同的是它返回的是一个有限值的Stream;中止条件是由hasNext来断定的。

第二种方法的使用示例如下:

/**
 * 本示例表示从1开始组装一个序列,第一个是1,第二个是1+1即2,第三个是2+1即3..,直接10时中止;
 * 也可简化成以下形式:
 *        Stream.iterate(1,
 *        n -> n <= 10,
 *        n -> n+1).forEach(System.out::println);
 * 写成以下方式是为简化理解
 */
Stream.iterate(1,
        new Predicate() {
            @Override
            public boolean test(Integer integer) {
                return integer <= 10;
            }
        },
    new UnaryOperator() {
        @Override
        public Integer apply(Integer integer) {
            return integer+1;
        }
}).forEach(System.out::println);

e. 通过Stream中的generate方法创建 
与iterate中创建无限元素的Stream类似,不过它的每个元素与前一元素无关,且生成的是一个无序的队列。也就是说每一个元素都可以随机生成。因此一般用来创建常量的Stream以及随机的Stream等。 
示例如下:

/**
 * 随机生成10个Double元素的Stream并将其打印
 */
Stream.generate(new Supplier() {
    @Override
    public Double get() {
        return Math.random();
    }
}).limit(10).forEach(System.out::println);

//上述写法可以简化成以下写法:
Stream.generate(() -> Math.random()).limit(10).forEach(System.out::println);

f. 通过Stream中的concat方法连接两个Stream对象生成新的Stream对象 
这个比较好理解不再赘述。

3.1.2 Stream对象的使用

Stream对象提供多个非常有用的方法,这些方法可以分成两类: 
中间操作:将原始的Stream转换成另外一个Stream;如filter返回的是过滤后的Stream。 
终端操作:产生的是一个结果或者其它的复合操作;如count或者forEach操作。

其清单如下所示,方法的具体说明及使用示例见后文。 
所有中间操作

方法 说明
sequential 返回一个相等的串行的Stream对象,如果原Stream对象已经是串行就可能会返回原对象
parallel 返回一个相等的并行的Stream对象,如果原Stream对象已经是并行的就会返回原对象
unordered 返回一个不关心顺序的Stream对象,如果原对象已经是这类型的对象就会返回原对象
onClose 返回一个相等的Steam对象,同时新的Stream对象在执行Close方法时会调用传入的Runnable对象
close 关闭Stream对象
filter 元素过滤:对Stream对象按指定的Predicate进行过滤,返回的Stream对象中仅包含未被过滤的元素
map 元素一对一转换:使用传入的Function对象对Stream中的所有元素进行处理,返回的Stream对象中的元素为原元素处理后的结果
mapToInt 元素一对一转换:将原Stream中的使用传入的IntFunction加工后返回一个IntStream对象
flatMap 元素一对多转换:对原Stream中的所有元素进行操作,每个元素会有一个或者多个结果,然后将返回的所有元素组合成一个统一的Stream并返回;
distinct 去重:返回一个去重后的Stream对象
sorted 排序:返回排序后的Stream对象
peek 使用传入的Consumer对象对所有元素进行消费后,返回一个新的包含所有原来元素的Stream对象
limit 获取有限个元素组成新的Stream对象返回
skip 抛弃前指定个元素后使用剩下的元素组成新的Stream返回
takeWhile 如果Stream是有序的(Ordered),那么返回最长命中序列(符合传入的Predicate的最长命中序列)组成的Stream;如果是无序的,那么返回的是所有符合传入的Predicate的元素序列组成的Stream。
dropWhile 与takeWhile相反,如果是有序的,返回除最长命中序列外的所有元素组成的Stream;如果是无序的,返回所有未命中的元素组成的Stream。

所有终端操作

方法 说明
iterator 返回Stream中所有对象的迭代器;
spliterator 返回对所有对象进行的spliterator对象
forEach 对所有元素进行迭代处理,无返回值
forEachOrdered 按Stream的Encounter所决定的序列进行迭代处理,无返回值
toArray 返回所有元素的数组
reduce 使用一个初始化的值,与Stream中的元素一一做传入的二合运算后返回最终的值。每与一个元素做运算后的结果,再与下一个元素做运算。它不保证会按序列执行整个过程。
collect 根据传入参数做相关汇聚计算
min 返回所有元素中最小值的Optional对象;如果Stream中无任何元素,那么返回的Optional对象为Empty
max 与Min相反
count 所有元素个数
anyMatch 只要其中有一个元素满足传入的Predicate时返回True,否则返回False
allMatch 所有元素均满足传入的Predicate时返回True,否则False
noneMatch 所有元素均不满足传入的Predicate时返回True,否则False
findFirst 返回第一个元素的Optioanl对象;如果无元素返回的是空的Optional; 如果Stream是无序的,那么任何元素都可能被返回。
findAny 返回任意一个元素的Optional对象,如果无元素返回的是空的Optioanl。
isParallel 判断是否当前Stream对象是并行的

下面就几个比较常用的方法举例说明其用法:

3.1.2.1 filter

用于对Stream中的元素进行过滤,返回一个过滤后的Stream 
其方法定义如下:

Stream filter(Predicate predicate);

使用示例:

Stream s = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
//查找所有包含t的元素并进行打印
s.filter(n -> n.contains("t")).forEach(System.out::println);

3.1.2.2 map

元素一对一转换。 
它接收一个Funcation参数,用其对Stream中的所有元素进行处理,返回的Stream对象中的元素为Function对原元素处理后的结果 
其方法定义如下:

 Stream map(Function mapper);

示例,假设我们要将一个String类型的Stream对象中的每个元素添加相同的后缀.txt,如a变成a.txt,其写法如下:

Stream s = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
s.map(n -> n.concat(".txt")).forEach(System.out::println);

3.1.2.3 flatMap

元素一对多转换:对原Stream中的所有元素使用传入的Function进行处理,每个元素经过处理后生成一个多个元素的Stream对象,然后将返回的所有Stream对象中的所有元素组合成一个统一的Stream并返回; 
方法定义如下:

 Stream flatMap(Function> mapper);

示例,假设要对一个String类型的Stream进行处理,将每一个元素的拆分成单个字母,并打印:

Stream s = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
s.flatMap(n -> Stream.of(n.split(""))).forEach(System.out::println);

3.1.2.4 takeWhile

方法定义如下:

default Stream takeWhile(Predicate predicate)

如果Stream是有序的(Ordered),那么返回最长命中序列(符合传入的Predicate的最长命中序列)组成的Stream;如果是无序的,那么返回的是所有符合传入的Predicate的元素序列组成的Stream。 
与Filter有点类似,不同的地方就在当Stream是有序时,返回的只是最长命中序列。 
如以下示例,通过takeWhile查找”test”, “t1”, “t2”, “teeeee”, “aaaa”, “taaa”这几个元素中包含t的最长命中序列:

Stream s = Stream.of("test", "t1", "t2", "teeeee", "aaaa", "taaa");
//以下结果将打印: "test", "t1", "t2", "teeeee",最后的那个taaa不会进行打印 
s.takeWhile(n -> n.contains("t")).forEach(System.out::println);

3.1.2.5 dropWhile

与takeWhile相反,如果是有序的,返回除最长命中序列外的所有元素组成的Stream;如果是无序的,返回所有未命中的元素组成的Stream;其定义如下:

default Stream dropWhile(Predicate predicate)

如以下示例,通过dropWhile删除”test”, “t1”, “t2”, “teeeee”, “aaaa”, “taaa”这几个元素中包含t的最长命中序列:

Stream s = Stream.of("test", "t1", "t2", "teeeee", "aaaa", "taaa");
//以下结果将打印:"aaaa", "taaa"  
s.dropWhile(n -> n.contains("t")).forEach(System.out::println);

3.1.2.6 reduce与collect

关于reduce与collect由于功能较为复杂,在后续将进行单独分析与学习,此处暂不涉及。

3.2 Optional

用于简化Java中对空值的判断处理,以防止出现各种空指针异常。 
Optional实际上是对一个变量进行封装,它包含有一个属性value,实际上就是这个变量的值。

3.2.1 Optional对象创建

它的构造函数都是private类型的,因此要初始化一个Optional的对象无法通过其构造函数进行创建。它提供了一系列的静态方法用于构建Optional对象:

Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

Optional 类的引入很好的解决空指针异常。

3.2.1.1 empty

用于创建一个空的Optional对象;其value属性为Null。 
如:

Optional o = Optional.empty();

3.2.1.2 of

根据传入的值构建一个Optional对象; 
传入的值必须是非空值,否则如果传入的值为空值,则会抛出空指针异常。 
使用:

o = Optional.of("test"); 

3.2.1.3 ofNullable

根据传入值构建一个Optional对象 
传入的值可以是空值,如果传入的值是空值,则与empty返回的结果是一样的。

3.2.2 方法

Optional包含以下方法:

方法名 说明
get 获取Value的值,如果Value值是空值,则会抛出NoSuchElementException异常;因此返回的Value值无需再做空值判断,只要没有抛出异常,都会是非空值。
isPresent Value是否为空值的判断;
ifPresent 当Value不为空时,执行传入的Consumer;
ifPresentOrElse Value不为空时,执行传入的Consumer;否则执行传入的Runnable对象;
filter 当Value为空或者传入的Predicate对象调用test(value)返回False时,返回Empty对象;否则返回当前的Optional对象
map 一对一转换:当Value为空时返回Empty对象,否则返回传入的Function执行apply(value)后的结果组装的Optional对象;
flatMap 一对多转换:当Value为空时返回Empty对象,否则传入的Function执行apply(value)后返回的结果(其返回结果直接是Optional对象)
or 如果Value不为空,则返回当前的Optional对象;否则,返回传入的Supplier生成的Optional对象;
stream 如果Value为空,返回Stream对象的Empty值;否则返回Stream.of(value)的Stream对象;
orElse Value不为空则返回Value,否则返回传入的值;
orElseGet Value不为空则返回Value,否则返回传入的Supplier生成的值;
orElseThrow Value不为空则返回Value,否则抛出Supplier中生成的异常对象;

3.2.3 使用场景

常用的使用场景如下:

3.2.3.1 判断结果不为空后使用

如某个函数可能会返回空值,以往的做法:

String s = test();
if (null != s) {
    System.out.println(s);
}

现在的写法就可以是:

Optional s = Optional.ofNullable(test());
s.ifPresent(System.out::println);

乍一看代码复杂度上差不多甚至是略有提升;那为什么要这么做呢? 
一般情况下,我们在使用某一个函数返回值时,要做的第一步就是去分析这个函数是否会返回空值;如果没有进行分析或者分析的结果出现偏差,导致函数会抛出空值而没有做检测,那么就会相应的抛出空指针异常! 
而有了Optional后,在我们不确定时就可以不用去做这个检测了,所有的检测Optional对象都帮忙我们完成,我们要做的就是按上述方式去处理。

3.2.3.2 变量为空时提供默认值

如要判断某个变量为空时使用提供的值,然后再针对这个变量做某种运算; 
以往做法:

if (null == s) {
    s = "test";
}
System.out.println(s);

现在的做法:

Optional o = Optional.ofNullable(s);
System.out.println(o.orElse("test"));

3.2.3.3 变量为空时抛出异常,否则使用

以往写法:

if (null == s) {
    throw new Exception("test");
}
System.out.println(s);

现在写法:

Optional o = Optional.ofNullable(s);
System.out.println(o.orElseThrow(()->new Exception("test")));

3.2.4实例

import java.util.Optional;
 
public class Java8Tester {
   public static void main(String args[]){
   
      Java8Tester java8Tester = new Java8Tester();
      Integer value1 = null;
      Integer value2 = new Integer(10);
        
      // Optional.ofNullable - 允许传递为 null 参数
      Optional a = Optional.ofNullable(value1);
        
      // Optional.of - 如果传递的参数是 null,抛出异常 NullPointerException
      Optional b = Optional.of(value2);
      System.out.println(java8Tester.sum(a,b));
   }
    
   public Integer sum(Optional a, Optional b){
    
      // Optional.isPresent - 判断值是否存在
        
      System.out.println("第一个参数值存在: " + a.isPresent());
      System.out.println("第二个参数值存在: " + b.isPresent());
        
      // Optional.orElse - 如果值存在,返回它,否则返回默认值
      Integer value1 = a.orElse(new Integer(0));
        
      //Optional.get - 获取值,值需要存在
      Integer value2 = b.get();
      return value1 + value2;
   }

3.3 方法引用

方法引用通过方法的名字来指向一个方法。

方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

方法引用使用一对冒号 :: 。

下面,我们在 Car 类中定义了 4 个方法作为例子来区分 Java 中 4 种不同方法的引用。

package com.runoob.main;
 
@FunctionalInterface
public interface Supplier {
    T get();
}
 
class Car {
    //Supplier是jdk1.8的接口,这里和lamda一起使用了
    public static Car create(final Supplier supplier) {
        return supplier.get();
    }
 
    public static void collide(final Car car) {
        System.out.println("Collided " + car.toString());
    }
 
    public void follow(final Car another) {
        System.out.println("Following the " + another.toString());
    }
 
    public void repair() {
        System.out.println("Repaired " + this.toString());
    }
}
  • 构造器引用:它的语法是Class::new,或者更一般的Class< T >::new实例如下:

    final Car car = Car.create( Car::new ); final List< Car > cars = Arrays.asList( car );

  • 静态方法引用:它的语法是Class::static_method,实例如下:

    cars.forEach( Car::collide );

  • 特定类的任意对象的方法引用:它的语法是Class::method实例如下:

    cars.forEach( Car::repair );

  • 特定对象的方法引用:它的语法是instance::method实例如下:

    final Car police = Car.create( Car::new ); cars.forEach( police::follow );

3.3.1 方法引用实例

   /*构造器引用:它的语法是Class::new,或者更一般的Class< T >::new实例如下:*/
        final Car car = Car.create(Car::new);
        final List cars = Arrays.asList(car);
        /*   静态方法引用:它的语法是Class::static_method,实例如下:*/
        cars.forEach(Car::collide);
        /*  特定类的任意对象的方法引用:它的语法是Class::method实例如下:*/
        cars.forEach(Car::repair);
        /*  特定对象的方法引用:它的语法是instance::method实例如下:*/
        final Car police = Car.create(Car::new);
        cars.forEach(police::follow);

        //下面来进行方法引用实例测试
        List names = new ArrayList();
        names.add("Google");
        names.add("Runoob");
        names.add("Taobao");
        names.add("Baidu");
        names.add("Sina");
        //实例中我们将 System.out::println 方法作为静态方法来引用。
        names.forEach(System.out::println);
        User user = new User();
        user.setId(1);
        user.setName("李四");
        User user1 = new User();
        user1.setId(2);
        user1.setName("张三");
        User user2 = new User();
        user2.setId(3);
        user2.setName("王二");
        List list = new ArrayList<>();
        list.add(user);
        list.add(user1);
        list.add(user2);
        list.forEach(User::test);
package syz.study.java.MethodReference;

@FunctionalInterface
public interface Supplier {
    T get();
}

class Car {
    private User user;
    //Supplier是jdk1.8的接口,这里和lamda一起使用了
    public static Car create(final Supplier supplier) {
        return supplier.get();
    }

    public static void collide(final Car car) {
        System.out.println("Collided " + car.toString());
    }

    public void follow(final Car another) {
        System.out.println("Following the " + another.toString());
    }

    public void repair() {
        System.out.println("Repaired " + this.toString());
    }

    public void get(User user){
        this.user = user;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}

3.4 默认方法

简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。

我们只需在方法名前面加个default关键字即可实现默认方法。

为什么要有这个特性?

首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的java 8之前的集合框架没有foreach方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。

语法

默认方法语法格式如下:


public interface Vehicle {
   default void print(){
      System.out.println("我是一辆车!");
   }
}

多个默认方法

一个接口有默认方法,考虑这样的情况,一个类实现了多个接口,且这些接口有相同的默认方法,以下实例说明了这种情况的解决方法:

public interface Vehicle {
   default void print(){
      System.out.println("我是一辆车!");
   }
}
 
public interface FourWheeler {
   default void print(){
      System.out.println("我是一辆四轮车!");
   }
}

第一个解决方案是创建自己的默认方法,来覆盖重写接口的默认方法:

public class Car implements Vehicle, FourWheeler {
   default void print(){
      System.out.println("我是一辆四轮汽车!");
   }
}

第二种解决方案可以使用 super 来调用指定接口的默认方法:

public class Car implements Vehicle, FourWheeler {
   public void print(){
      Vehicle.super.print();
   }
}

静态默认方法

Java 8 的另一个特性是接口可以声明(并且可以提供实现)静态方法。例如:

public interface Vehicle {
   default void print(){
      System.out.println("我是一辆车!");
   }
    // 静态方法
   static void blowHorn(){
      System.out.println("按喇叭!!!");
   }
}

默认方法实例

我们可以通过以下代码来了解关于默认方法的使用,可以将代码放入 Java8Tester.java 文件中:

public class Java8Tester {
   public static void main(String args[]){
      Vehicle vehicle = new Car();
      vehicle.print();
   }
}
 
interface Vehicle {
   default void print(){
      System.out.println("我是一辆车!");
   }
    
   static void blowHorn(){
      System.out.println("按喇叭!!!");
   }
}
 
interface FourWheeler {
   default void print(){
      System.out.println("我是一辆四轮车!");
   }
}
 
class Car implements Vehicle, FourWheeler {
   public void print(){
      Vehicle.super.print();
      FourWheeler.super.print();
      Vehicle.blowHorn();
      System.out.println("我是一辆汽车!");
   }
}

这里是学习测试代码:https://download.csdn.net/download/gududedabai/10594458 后面会继续完善,代码主要是学习测试等作用。

Stream学习资料:Java8新特性之Stream API

其它场景待补充。

你可能感兴趣的:(java8新特性)