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

Supplier接口

所在包:

java.util.function.Supplier<T>

概述:

  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<Integer> supplier) {
        int max = supplier.get();
        System.out.println(max);
    }
}

Consumer接口

所在包:

java.util.function.Consumer<T>

概述:

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

抽象方法:

void accept(T t)

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

示例代码:

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

默认方法:

default Consumer<T> andThen(Consumer<? super T> after)

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

该方法的源码:

default Consumer<T> andThen(Consumer<? super T> 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<String> one, Consumer<String> two, String[] array) {
        for (String info : array) {
            one.andThen(two).accept(info); // 姓名:迪丽热巴。性别:女。
        }
    }
}

Predicate接口

所在包:

java.util.function.Predicate<T>

概述:

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

抽象方法:

boolean test(T t)

  • 执行判断,返回true或false

示例代码:

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

默认方法:and

概述:

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

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

该方法的源码:

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

默认方法: or

概述:

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

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

该方法的源码:

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

注意:

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

默认方法:negate

概述:

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

该方法的源码:

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

该方法使用的注意事项:

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

示例代码:

import java.util.function.Predicate;
public class Demo17PredicateNegate {
   private static void method(Predicate<String> 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<T , R>

概述:

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

抽象方法:

R apply(T t)

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

示例代码:

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

默认方法:andThen

概述:

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

该方法的源码:

default <V> Function<T, V> andThen(Function<? super R, ? extends V> 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<String,Integer> one = Integer::parseInt;
        // 操作2:将整型数据乘以10    R apply(T t)
        // Integer ==> Integer
        Function<Integer,Integer> two = num ‐> num * 10;
        // 按顺序执行操作1和操作2
        // 将上一个操作执行的结果作为下一个操作的执行参数
        int result = one.andThen(two).apply(str);
        System.out.println("result = " + result); // 1230
    }
}

默认方法: compose

概述:

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

该方法的源码:

  default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

延迟方法与终结方法

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

延迟方法:

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

终结方法:

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

规律:

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

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

终结

Consumeraccept抽象终结
 andThen默认延迟
Predicatetest抽象终结
 and默认延迟
 or默认延迟
 negate默认延迟
Functionapply抽象终结
 andThen默认延迟
 compose默认延迟

Stream流

概述:

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

使用Stream流操集合和数组:

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

Stream流获取方式:

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

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

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

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

  • Stream<String> keyStream = map.keySet().stream();

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

  • Stream<String> valueStream = map.values().stream();

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

  • Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();

3)数组流的获取方式

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

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

Stream流常用方法

Stream流常用方法01:

filter:

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

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

示例代码:

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

Stream流常用方法02:

count:

long count()

  • 获得流中元素的个数。

示例代码:

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

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

limit:

Stream<T> limit(long maxSize) 

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

示例代码:

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

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

skip:

Stream<T> skip(long n) 

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

示例代码:

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

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

map:

Stream<R> map(Function<T,R> mapper)

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

示例代码:

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

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

concat:

static <T> Stream<T> concat(Stream<T> a,Stream<T> b)

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

示例代码:

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

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

forEach:

void forEach(Consumer<T> action)

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

示例代码:

public class StreamDemo04 {
    public static void main(String[] args){
        // 创建Stream流对象
        Stream<String> 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<String> stream = Stream.of("10", "20", "30", "40", "50");
      Object[] objArray = stream.toArray();
   }
}

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

<A> A[ ] toArray(IntFunction<A[ ]> generator);

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

示例代码:

import java.util.stream.Stream;
public class Demo17StreamArray {
   public static void main(String[] args) {
      Stream<String> 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<E> 新添加了两个默认方法。

​​​​​​​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<Integer> list = new ArrayList<>();
        // 添加数字
        for (int i = 0; i < 10000; i++) {
            list.add(i);
        }
        // 将集合中的元素转换为字符串并存储到另一个集合中:串行流
        /*Stream<String> stream = list.stream().map(num -> {
            System.out.println(Thread.currentThread().getName());
            return Integer.toString(num);
        });*/

        // 将集合中的元素转换为字符串并存储到另一个集合中:并行流
        Stream<String> stream = list.stream().parallel().map(num -> {
            System.out.println(Thread.currentThread().getName());
            return Integer.toString(num);
        });
        // 收集流的结果到List中
        List<String> 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<T,R>却出现了报错。

示例代码如下:

Integer[] arr = {10,20,30,40};
        Stream<String> stream = Stream.of(arr).map(a -> Integer.toString(a));
        //用Lambda可以编译通过 Integer[] arr = {10,20,30,40};
        Stream<String> 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<String> stream = Stream.of(arr).map(Integer::toString);

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

  1. 调用Integer的自身的toString方法。(静态代码引用
  2. 调用Integer继承Object的toString方法。(特定类型的示例方法引用
  • ​​​​​​​而编译器无法识别是哪一种情况,故会报错。所以不能写代码块。
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值