乐趣区

关于java8:Java8新特性函数式编程StreamFunctionOptionalConsumer

Java8 新引入函数式编程形式,大大的进步了编码效率。本文将对波及的对象等进行对立的学习及记录。

首先须要分明一个概念:函数式接口;它指的是有且只有一个未实现的办法的接口,个别通过 FunctionalInterface 这个注解来表明某个接口是一个函数式接口。函数式接口是 Java 反对函数式编程的根底。

本文目录:

  • 1 Java8 函数式编程语法入门
  • 2 Java 函数式接口

    • 2.1 Consumer
    • 2.2 Function
    • 2.3 Predicate
  • 3 函数式编程接口的应用

    • 3.1 Stream

      • 3.1.1 Stream 对象的创立
      • 3.1.2 Stream 对象的应用

        • 3.1.2.1 filter
        • 3.1.2.2 map
        • 3.1.2.3 flatMap
        • 3.1.2.4 takeWhile
        • 3.1.2.5 dropWhile
        • 3.1.2.6 reduce 与 collect
    • 3.2 Optional

      • 3.2.1 Optional 对象创立

        • 3.2.1.1 empty
        • 3.2.1.2 of
        • 3.2.1.3 ofNullable
      • 3.2.2 办法
      • 3.2.3 应用场景

        • 3.2.3.1 判断后果不为空后应用
        • 3.2.3.2 变量为空时提供默认值
        • 3.2.3.3 变量为空时抛出异样,否则应用

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 中的几个事后定义的函数式接口及其常常应用的类进行剖析学习。

2 Java 函数式接口

2.1 Consumer

Consumer 是一个函数式编程接口;顾名思义,Consumer 的意思就是生产,即针对某个货色咱们来应用它,因而它蕴含有一个有输出而无输入的 accept 接口办法;
除 accept 办法,它还蕴含有 andThen 这个办法;
其定义如下:

default Consumer<T> andThen(Consumer<? super T> 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.2 Function

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

/**
 * Function 测试
 */
public static void functionTest() {
    Function<Integer, Integer> f = s -> s++;
    Function<Integer, Integer> 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.3 Predicate

Predicate 为函数式接口,predicate 的中文意思是“判定”,即判断的意思,判断某个货色是否满足某种条件;因而它蕴含 test 办法,依据输出值来做逻辑判断,其后果为 True 或者 False。
它的应用办法示例如下:

/**
 * Predicate 测试
 */
private static void predicateTest() {Predicate<String> p = o -> o.equals("test");
    Predicate<String> 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 能够对多个元素进行一系列的操作,也能够反对对某些操作进行并发解决。

3.1.1 Stream 对象的创立

Stream 对象的创立路径有以下几种

a. 创立空的 Stream 对象

Stream stream = Stream.empty();

b. 通过汇合类中的 stream 或者 parallelStream 办法创立;

List<String> 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<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f);  
public static<T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> 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<Integer>() {
            @Override
            public boolean test(Integer integer) {return integer <= 10;}
        },
    new UnaryOperator<Integer>() {
        @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<Double>() {
    @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<T> filter(Predicate<? super T> predicate);

应用示例:

Stream<String> 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 对原元素解决后的后果
其办法定义如下:

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

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

Stream<String> 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 并返回;
办法定义如下:

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

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

Stream<String> 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<T> takeWhile(Predicate<? super T> predicate)

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

Stream<String> 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<T> dropWhile(Predicate<? super T> predicate)

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

Stream<String> 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 对象:

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<String> 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<String> 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<String> o = Optional.ofNullable(s);
System.out.println(o.orElseThrow(()->new Exception("test")));
退出移动版