常用的函数式接口、延迟方法与终结方法、Stream流、探究

Supplier接口

所在包:

java.util.function.Supplier

概述:

  1. 泛型接口。
  2. 用来生产数据,生产数据的类型通过泛型变量指定。

抽象方法:

 T get()

  • 生产指定类型的数据。

案例:求数组的最大值。

示例代码:

import java.util.function.Supplier;
public class DemoIntArray {
    public static void main(String[] args) {
        int[] array = { 10, 20, 100, 30, 40, 50 };
        printMax(() ‐> {
            int max = array[0];
            for (int i = 1; i < array.length; i++) {
                if (array[i] > max) {
                   max = array[i];  
                }
            }
            return max;
        });
    }
    private static void printMax(Supplier supplier) {
        int max = supplier.get();
        System.out.println(max);
    }
}

Consumer接口

所在包:

java.util.function.Consumer

概述:

  1. 泛型接口。
  2. 用来消费数据,消费的数据类型通过泛型变量指定。

抽象方法:

void accept(T t)

  • 消费指定类型的数据t。

示例代码:

import java.util.function.Consumer;
public class Demo09Consumer {
    private static void consumeString(Consumer function) {
       function.accept("Hello");  
    }
    public static void main(String[] args) {
        consumeString(s ‐> System.out.println(s));
        consumeString(System.out::println);
    }
}

默认方法:

default Consumer andThen(Consumer after)

  • 消费同一个数据的时候,首先做一个操作,然后再做一个操作,实现组合。(一步接一步)
  • 示范使用格式:  one.andthen(two).accept(" aaa" );
  • 等价于:one.accept(" aaa" );  two.accept(" aaa" );

该方法的源码:

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

示例代码:

import java.util.function.Consumer;
public class DemoConsumer {
    public static void main(String[] args) {
        String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" };
        printInfo(s ‐> System.out.print("姓名:" + s.split(",")[0]),
                  s ‐> System.out.println("。性别:" + s.split(",")[1] + "。"),
                  array);
    }
    private static void printInfo(Consumer one, Consumer two, String[] array) {
        for (String info : array) {
            one.andThen(two).accept(info); // 姓名:迪丽热巴。性别:女。
        }
    }
}

Predicate接口

所在包:

java.util.function.Predicate

概述:

  1. 对某种类型的数据进行判断,从而得到一个boolean值结果。
  2. 用来封装判断条件。

抽象方法:

boolean test(T t)

  • 执行判断,返回true或false

示例代码:

public class PredicateDemo01 {
    public static void main(String[] args){
        // 匿名内部类调用
        testPredicate(new Predicate() {
            @Override
            public boolean test(String s) {
                return s.length() > 5;
            }
        });
        // 使用lambda表达式调用
        testPredicate(str ‐> str.length() > 5);
    }
    public static void testPredicate(Predicate predicate){
        boolean b =  predicate.test("HelloWorld");
        System.out.println("b = " + b);
    }
}

默认方法:and

概述:

实现逻辑关系中的 “与”。

  • 用法: one.and( two ).test( 参数);

该方法的源码:

default Predicate and(Predicate other) {
    Objects.requireNonNull(other);
    return (t) ‐> test(t) && other.test(t);
}

默认方法: or

概述:

实现逻辑关系中的“或”。

  • 用法: one.or( two ).test( 参数);

该方法的源码:

default Predicate or(Predicate other) {
    Objects.requireNonNull(other);
    return (t) ‐> test(t) || other.test(t);
}

注意:

默认方法 and 和 or 都存在短路问题。

默认方法:negate

概述:

执行了test方法之后,对结果boolean值进行 “ ! ” 取反。

该方法的源码:

default Predicate negate() {
    return (t) ‐> !test(t);
}

该方法使用的注意事项:

一定要在 test 方法调用之前调用 negate 方法,正如 and 和 or 方法一样。

示例代码:

import java.util.function.Predicate;
public class Demo17PredicateNegate {
   private static void method(Predicate predicate) {
      boolean veryLong = predicate.negate().test("HelloWorld");
      System.out.println("字符串很长吗:" + veryLong);
   }
   public static void main(String[] args) {
      method(s ‐> s.length() < 5);
   }
}

Function接口

所在包:

ava.util.function.Function

概述:

接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件,有进有出。

抽象方法:

R apply(T t)

  • 将参数t的数据类型从T类型转换为R类型。

示例代码:

public class FunctionDemo01 {
    public static void main(String[] args){
        // 整数字符串
        String str = "123";
        // 将字符串转换为整型数据 123
        // 使用匿名内部类创建Function接口的实现类对象
        Function f = new Function(){
            @Override
            public Integer apply(String s) {
                return Integer.parseInt(s);
            }
        };
        // 调用apply方法进行转换
        int num01 = f.apply(str);
        System.out.println(num01);
        // 使用lambda表达式简化
        Function ff = s ‐> Integer.parseInt(s);
        int num02 = ff.apply(str);
        System.out.println(num02);
        // 使用方法引用简化lambda表达式
        Function fff = Integer::parseInt;
        int num03 = fff.apply(str);
        System.out.println(num03);
}
}

默认方法:andThen

概述:

将上一个操作执行的结果作为下一个操作的执行参数。

该方法的源码:

default  Function andThen(Function after) {
    Objects.requireNonNull(after);
    return (T t) ‐> after.apply(apply(t));
}

示例代码:

public class FunctionDemo02 {
    public static void main(String[] args){
        // 字符串
        String str = "123";
        // 操作1:将字符串转换为整型  "123" ==> 123
        // String ==> Integer
        Function one = Integer::parseInt;
        // 操作2:将整型数据乘以10    R apply(T t)
        // Integer ==> Integer
        Function two = num ‐> num * 10;
        // 按顺序执行操作1和操作2
        // 将上一个操作执行的结果作为下一个操作的执行参数
        int result = one.andThen(two).apply(str);
        System.out.println("result = " + result); // 1230
    }
}

默认方法: compose

概述:

将下一个操作执行的结果作为上一个操作的执行参数。(与andThen恰好相反

该方法的源码:

  default  Function compose(Function before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

延迟方法与终结方法

函数式接口当中,方法可以分成两种:

延迟方法:

  • 只是在拼接Lambda函数模型的方法,并不立即执行得到结果。

终结方法:

  • 根据拼好的Lambda函数模型,立即执行得到结果值的方法。

规律:

通常情况下,这些常用的函数式接口中唯一的抽象方法为终结方法,而默认方法为延迟方法。

接口名称 方法名称 抽象 / 默认 延迟 / 终结
Supplier get 抽象

终结

Consumer accept 抽象 终结
  andThen 默认 延迟
Predicate test 抽象 终结
  and 默认 延迟
  or 默认 延迟
  negate 默认 延迟
Function apply 抽象 终结
  andThen 默认 延迟
  compose 默认 延迟

Stream流

概述:

对Java中集合或数组功能进行增强的技术。

使用Stream流操集合和数组:

  1. 提高编程效率和程序可读性。
  2. 支持并发方式处理。

Stream流获取方式:

1)单列集合流的获取方式:

调用集合对象的stream方法获取。

2)双列集合流的获取方式:

1)获得键对应的流对象。

  • Stream keyStream = map.keySet().stream();

2)获得值对应的流对象。

  • Stream valueStream = map.values().stream();

3)获得Entry对象对应的流对象。

  • Stream> entryStream = map.entrySet().stream();

3)数组流的获取方式

通过Stream接口中的静态方法of方法获得。

       // 创建字符串数组
        String[] strs = {"a","b"};
        // 调用Stream接口的静态方法获得流对象
        Stream arrayStream = Stream.of(strs);
        Stream arrStream = Stream.of("a","b","c","d");

Stream流常用方法

Stream流常用方法01:

filter:

Stream filter(Predicate predicate) 

  • 根据条件过滤流中的元素。将当前流中的满足条件的元素存储到另一个流中。
  • Predicate函数式接口的抽象方法:boolean test(T t);

示例代码:

public class StreamDemo01 {
    public static void main(String[] args){
        // 创建流对象
        Stream stream01 = Stream.of("abc", "ancd", "afdsf");
        // 将字符串长度大于3的元素存储到另一个流中
        Stream stream02 = stream01.filter(str ‐> str.length() > 3);
    }
}

Stream流常用方法02:

count:

long count()

  • 获得流中元素的个数。

示例代码:

public class StreamDemo01 {
    public static void main(String[] args){
        // 创建流对象
        Stream stream01 = Stream.of("abc", "ancd", "afdsf");
        System.out.println(stream01.count());// 3
    }
}

​​​​​​​Stream流常用方法03:

limit:

Stream limit(long maxSize) 

  • 将当前流中的前maxSize个元素存储到另一个流中。
  • 如果maxSize大于当前流中的元素个数,则会将所有元素存储到新流中。
  • 注意:maxSize必须大于等于0,如果等于0,则会产生一个空流。

示例代码:

public class StreamDemo02 {
    public static void main(String[] args){
        // 创建流对象
        Stream stream01 = Stream.of("abc", "ancd", "afdsf");
        Stream stream02 = stream01.limit(0);
        System.out.println(stream02.count());
    }
}

​​​​​​​Stream流常用方法04:

skip:

Stream skip(long n) 

  • 将当前流中前n个后面的所有元素存储到另一个流中。
  • n必须大于等于0,如果大于当前流中的元素个数,则产生一个空流。

示例代码:

public class StreamDemo03 {
    public static void main(String[] args){
        // 创建流对象
        Stream stream01 = Stream.of("abc", "ancd", "afdsf");
        Stream stream02 = stream01.skip(0);
        System.out.println(stream02.count());
    }
}

​​​​​​​Stream流常用方法05:

map:

Stream map(Function mapper)

  • map===> 映射(一个对一个)
  • 将流中的元素从数据类型T转换为数据类型R并存储到新流中。
  • Function函数式接口抽象方法:R apply(T t)
  • 将参数t从数据类型T换行为数据类型R。

示例代码:

public class StreamDemo04 {
    public static void main(String[] args){
        // 创建Stream流对象
        Stream stream01 = Stream.of("123", "456", "789");
        // 将流中的所有元素类型从字符串转换为整型并存储到另一个流中
        // Stream stream02 = stream01.map(str ‐> Integer.parseInt(str));
        Stream stream02 = stream01.map(Integer::parseInt);
        // 遍历输出流中的元素
        stream02.forEach(System.out::println);
    }
}

​​​​​​​Stream流常用方法06:

concat:

static Stream concat(Stream a,Stream b)

  • 将流a和流b合并为一个新的流。

示例代码:

public class StreamDemo05 {
    public static void main(String[] args){
        // 创建两个流对象
        Stream streamA = Stream.of("a","b");
        Stream streamB = Stream.of("c","d");
        // 合并流
        Stream streamC = Stream.concat(streamA, streamB);
        // 遍历输出streamC中的元素
        streamC.forEach(System.out::println);
    }
}

​​​​​​​Stream流常用方法07:

forEach:

void forEach(Consumer action)

  • 逐一处理流中的元素,将每一个元素传递给消费者处理。
  • Consumer函数式接口的抽象方法:void accept(T t) 消费数据t

示例代码:

public class StreamDemo04 {
    public static void main(String[] args){
        // 创建Stream流对象
        Stream stream01 = Stream.of("123", "456", "789");
        // 遍历输出流中的元素
        stream02.forEach(System.out::println);
    }
}

对Stream流常用方法的小结:

  • 凡是返回值仍然为 Stream 接口的为非终结方法(函数拼接方法),它们支持链式调用。
  • 而返回值不再为 Stream 接口的为终结方法,不再支持链式调用。
方法名 方法作用 方法种类 是否支持链式调用
count 统计个数 终结
forEach 逐一处理 终结
filter 过滤 函数拼接
limit 取用前几个 函数拼接
skip 跳过前几个 函数拼接
map 映射 函数拼接
concat 组合 函数拼接

终结方法:

  1. count
  2. forEach
  3. collect

Stream流常用方法使用注意事项:

  1. 流一旦调用了终结方法就被关闭了,就不能继续调用方法操作流中的元素。
  2. 如果通过流方法产生了新的流,那么旧的流就不能继续操作了。

​​​​​​​否则会报异常:

java.lang.IllegalStateException:stream has already been operated upon or closed

收集流中的元素

1)收集到List集合:

流对象.collect( Collectors.toList() )

  • 获得List集合。

2)收集到Set集合:

流对象.collect( Collectors.toSet() )

  • 获得Set集合。

3)收集到数组:

(1)Stream提供 toArray 方法来将结果放到一个数组中,由于泛型擦除的原因,返回值类型是Object[]的:

Object[] toArray();

示例代码:

import java.util.stream.Stream;
public class Demo16StreamArray {
   public static void main(String[] args) {
      Stream stream = Stream.of("10", "20", "30", "40", "50");
      Object[] objArray = stream.toArray();
   }
}

(2)使用 toArray 方法的另一种重载形式传递一个 IntFunction 的函数,继而从外面指定泛型参数,从而指定数组类型:

A[ ] toArray(IntFunction generator);

  • 抽象方法: A[ ] apply(int value)

示例代码:

import java.util.stream.Stream;
public class Demo17StreamArray {
   public static void main(String[] args) {
      Stream stream = Stream.of("10", "20", "30", "40", "50");
    //String[] StrArray = stream.toArray(invalue -> new String[invalue]);
      String[] strArray = stream.toArray(String[]::new);
   }
}

并行流

Stream流的分类:

​​​​​​​1)串行流

  • 在当前线程中按顺序执行操作(不开线程)。

​​​​​​​2)并行流

  • 开启多个线程和当前线程一起执行操作。
  • 不需要手动开启线程。获得并行流后,并行流会自动开启线程。

集合返回Stream流:

java.util.Collection 新添加了两个默认方法。

​​​​​​​default Stream stream() 

  • 返回串行流。

​​​​​​​default Stream parallelStream()

  • 返回并行流。

并行流的获取方式:

1)将串行流转换为并行流

  • 通过调用Stream流对象的parallel方法。

​​​​​​​2)通过Collection集合获得并行流

  • 调用集合对象的parallelStream方法获得。

并行流基本使用的实例代码:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
   并行流基本使用
 */
public class StreamDemo02 {
    public static void main(String[] args){
       // 创建集合对象
        List list = new ArrayList<>();
        // 添加数字
        for (int i = 0; i < 10000; i++) {
            list.add(i);
        }
        // 将集合中的元素转换为字符串并存储到另一个集合中:串行流
        /*Stream stream = list.stream().map(num -> {
            System.out.println(Thread.currentThread().getName());
            return Integer.toString(num);
        });*/

        // 将集合中的元素转换为字符串并存储到另一个集合中:并行流
        Stream stream = list.stream().parallel().map(num -> {
            System.out.println(Thread.currentThread().getName());
            return Integer.toString(num);
        });
        // 收集流的结果到List中
        List newList = stream.collect(Collectors.toList());
        System.out.println(newList);
    }
}

如何从stream和parallelStream方法中进行选择:

我们要考虑三个问题:

  1. 是否需要并行? 
  2. 任务之间是否是独立的?
  3. 结果是否取决于任务的调用顺序? 

对于问题1:

  • 当数据量不大时,顺序执行往往比并行执行更快。毕竟,准备线程池和其它相关资源也是需要时间的。但是,当任务涉及到I/O操作并且任务之间不互相依赖时,那么并行化就是一个不错的选择。通常而言,将这类程序并行化之后,执行速度会提升好几个等级。

对于问题2:

  • 如果任务之间是独立的,那么就表明代码是可以被并行化的。

对于问题3:

  • 由于在并行环境中任务的执行顺序是不确定的,因此对于依赖于顺序的任务而言,并行化也许不能给出正确的结果。

探究

在写代码的时候,我需求是把Integer类型的数组转化成String类型的Stream流。用of方法获得Stream后,然后再用map方法转换Stream流的类型。 但是在map参数用方法块简写Function却出现了报错。

示例代码如下:

Integer[] arr = {10,20,30,40};
        Stream stream = Stream.of(arr).map(a -> Integer.toString(a));
        //用Lambda可以编译通过 Integer[] arr = {10,20,30,40};
        Stream stream = Stream.of(arr).map(Integer::toString);
        //用方法块就不能通过了 提示报的是:
        NO compile-time declaration for the method reference is found 
        :没有找到方法引用的编译时声明

一开始我的猜测是:

  • Integer类型的元素传进Function接口的抽象方法apply为: String apply(Integer i)
  • toString方法为:String toString(int i)
  • 两个方法之间没有发生自动装拆箱,所以参数类型不同,所以不能用方法块。

然后我看源码,看到了有两个方法:

1)Integer的toString方法:

 @HotSpotIntrinsicCandidate
    public static String toString(int i) {
        int size = stringSize(i);
        if (COMPACT_STRINGS) {
            byte[] buf = new byte[size];
            getChars(i, size, buf);
            return new String(buf, LATIN1);
        } else {
            byte[] buf = new byte[size * 2];
            StringUTF16.getChars(i, size, buf);
            return new String(buf, UTF16);
        }
    }

2)Object的toString方法

public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
  • 因为Integer也属于Object类,所以Object的toString方法也有被Integer继承。故Integer有两个toString方法,一个toString方法有一个参数,一个toString方法没有参数。

所以当我们用方法块时:

Stream stream = Stream.of(arr).map(Integer::toString);

编译器会以为有两种情况:

  1. 调用Integer的自身的toString方法。(静态代码引用
  2. 调用Integer继承Object的toString方法。(特定类型的示例方法引用
  • ​​​​​​​而编译器无法识别是哪一种情况,故会报错。所以不能写代码块。

你可能感兴趣的:(知识点的整理)