函数式编程

概念

函数式接口在Java中是指:有且仅有一个抽象方法的接口

函数式接口,即适用于函数式编程场景的接口,而Java中的函数式编程的体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口,只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能正确的推导

格式

只要确保接口中有且仅有一个抽象方法即可

修饰符 interface 接口名称 {
	public abstract 返回值类型 方法名称(可选参数);
	//非抽象方法内容(默认 静态 私有)
}

@FunctionalInterface注解

@Override注解的作用类似,Java8中专门为函数式接口引入了一个新的注解@FunctionalInterface标识检测一个接口为函数式接口

Lambda表达式的延迟执行


有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式是延迟执行,正好可以作为解决方案,提升性能

常用函数式接口

JDK提供了大量的函数式接口来丰富Lambda的使用场景,它们主要在java.util.function包中被提供

Supplier接口

java.util.function.Supplier<T>接口仅包含一个无参的方法:T get() 用来获取一个泛型参数指定类型的对象数据

由于这是一个函数式接口,也就意味着对应的Lambda表达式需要对外提供一个符合泛型类型对象的数据

public class Main {
    
    public static String getString(Supplier<String> supplier){
        return supplier.get();
    }

    public static void main(String[] args) {
        String message = getString(() -> "message");
    }
}

其他都相同…可自行翻阅官方给出的java.util.function包中的所有函数式接口

Stream流

说到Stream流就很容易想到IO流,而实际上,谁规定流就一定是IO流呢?在Java8中,得益于Lambda表达式所带来的函数式编程

引入了一个全新的Stream概念,用于解决已有集合类库即有的弊端

传统集合的多步遍历代码

几乎所有的集合(如Collection或者Map接口等)都支持直接的或者间接的遍历操作,当我们需要对集合中的元素进行操作时候,除了必要的添加,删除,获取外,最典型的就是集合遍历

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        list.add("ddd");

        for (String s : list) {
            System.out.println(s);
        }
    }

这是一段非常简单的集合遍历操作,对集合中的每一个字符串都进行打印输出等操作

循环遍历的弊端

Java8的Lambda让我们更加专注于什么,而不是怎么做,现在我们体会一下代码,可以发现:

  • for循环的语法就是做什么
  • for循环的循环体才是怎么做

为什么要使用循环?因为要进行遍历,但循环是遍历的唯一方式吗?遍历是指每一个元素逐一进行处理,而并不是从第一个到最后一个顺次处理的循环。前者是目的,后者是手段

试想一下,如果希望对集合中的元素进行筛选过滤

1.将集合A根据条件一过滤为子集B

2.然后再根据条件二过滤为子集C

那么怎么办?在Java8之前的做法可能为:

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        list.add("ddd");
        list.add("angel");
        
        List<String> list1 = new ArrayList<>();
	    //过滤  只要a开头的
        for (String name : list){
            if (name.startsWith("a")){
                list1.add(name);
            }
        }

        List<String> list2 = new ArrayList<>();
	    //过滤  只要长度为3的
        for (String name : list1){
            if (name.length() == 3){
                list2.add(name);
            }
        }

        for (String name : list2) {
            System.out.println(name);
        }
    }

上述代码有三个循环用来过滤和输出

每当我们需要对集合中的元素进行操作的时候,总是需要进行循环循环再循环,这是理所当然的吗?不是。循环是做事情的方式,不是目的。另一方面,循环只能遍历一次,如果希望再次遍历,就只能再起一次循环

那,Lambda的衍生物Stream能给我们带来怎样更加优雅的写法呢?

Stream的更优写法

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        list.add("ddd");
        list.add("and");

        list.stream()
                .filter((name) -> name.startsWith("a"))
                .filter((name) -> name.length() == 3)
                .forEach((name) -> System.out.println(name));
    }

流式思想概述

拼接流式模型:建立一个生产线,按照生产线来生产商品

当需要对多个元素进行操作(特别是多步操作)的时候,考虑到性能以及遍历性,我们应该首先拼接好一个模型步骤方案然后执行

Stream流是来自数据源的元素队列

  • 元素是特定类型的对象,形成一个队列。Java中的Stream并不会存储元素,而是按需计算
  • **数据源 **流的来源,可以是集合数组等

和以前的Collection操作不同,Stream操作还有两个基础特征

  • Pipelinling:中间操作都会返回流本身,这样多个操作可以串联成一个管道,如同流式风格,可对操作优化如延迟执行和短路
  • 内部迭代:以前对集合的遍历都是Iterator或者for循环,显式的在集合外部进行迭代,这叫做外部迭代。Stream提供了内部迭代方式

当使用一个流的时候,通常包括基本的三个步骤:获取一个数据源 -> 数据转换 -> 执行操作获取想要结果,每次转换原有Stream对象不改变,返回一个新的Stream对象(可有多次转换),这就允许对其操作可以像链条一样排列,形成一个管道

获取流

java.util.stream.Stream<T>是Java8新加入的最常用的流接口(非函数式接口)

获取一个流非常简单,有以下几种常用的方式:

  • 所有Collection集合都可以通过stream默认方法获取流
  • Stream接口的静态方法of可以获取数组对应的流
    public static void main(String[] args) {

        List<String> list = new ArrayList<>();

        Set<String> set = new HashSet<>();

        Map<String,String> map = new HashMap();

        Stream<String> stream1 = list.stream();

        Stream<String> stream2 = set.stream();

        Set<String> keySet = map.keySet();
        Stream<String> stream3 = keySet.stream();

        Collection<String> values = map.values();
        Stream<String> stream4 = values.stream();

        Set<Map.Entry<String, String>> entries = map.entrySet();
        Stream<Map.Entry<String, String>> stream5 = entries.stream();


        //数组转换为Stream流
        Stream<Integer> stream6 = Stream.of(1, 2, 3, 4, 5);

        String[] arr = {"aaa","bbb","ccc"};
        Stream<String> stream7 = Stream.of(arr);

    }

常用方法

流模型的操作很丰富,这里介绍一些常用的API,这些方法可以被分为两种

  • 延迟方法:返回值类型仍然是Stream接口自身类型的方法,因此支持链式调用(除了终结方法外的所有其余方法)
  • 终结方法:返回值类型不再是Stream接口自身类型的方法,包括count方法和foreach方法
逐一处理:foreach

虽然方法名字叫foreach但是与for循环的for-each昵称不同

void forEach(Consumer<? super T> action);

该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理

java.util.function.Consumer<T>接口是一个消费型接口

其中抽象方法accept消费一个指定泛型的数据

@FunctionalInterface
public interface Consumer<T> {
        void accept(T t);
}

使用:

public static void main(String[] args) {

    Stream<String> stream = Stream.of("aaa", "bbb", "ccc");

    stream.forEach((key) -> System.out.println(key));

}
过滤:filter

可以通过filter方法将一个流过滤为子集流

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

该方法接收一个Predicate函数式接口参数作为筛选引用

java.util.function.Predicate<T>接口是判断

其中抽象方法accept消费一个指定泛型的数据,方法返回一个boolean,为true则Stream流的filter方法会留用元素

使用:

public static void main(String[] args) {

Stream<String> stream = Stream.of("aaa", "bbbb", "cccc");

stream.filter((key) -> key.length() == 3);

}
映射:map

如果需要将流中的数据映射到另一个流中,可以使用map方法

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

该方法需要一个Function函数式接口参数,可以将当前流中T类型的数据转换成另一种R类型的流

java.util.function.Function中唯一的抽象方法

R apply(T t);

这可以将T类型转换成R类型,而这种转换的动作就称为映射

使用:

public static void main(String[] args) {

    Stream<String> stream = Stream.of("1", "2", "3");

    Stream<Integer> stream1 = stream.map((key) -> Integer.valueOf(key));

}
统计个数:count

正如旧集合Collection中的size方法一样,流提供count方法来数一数元素个数

long count();

基本使用:

public static void main(String[] args) {

    Stream<String> stream = Stream.of("1", "2", "3");

    System.out.println(stream.count());

}
取用前几个:limit

limit方法可以对流进行截取,只取用前n个

Stream<T> limit(long maxSize);

参数是一个long类型,如果集合当前长度大于参数则进行截取,否则不进行操作

public static void main(String[] args) {

    Stream<String> stream = Stream.of("1", "2", "3");

    Stream<String> stream1 = stream.limit(2);

    System.out.println(stream1.count());

}
跳过前几个:skip

如果希望跳过前几个元素,可以使用skip方法截取一个新流

Stream<T> skip(long n);

如果流的当前长度大于n,则跳过前n个,否则会得到一个长度为0的空流

public static void main(String[] args) {

    Stream<String> stream = Stream.of("1", "2", "3");

    Stream<String> stream1 = stream.skip(2);

    System.out.println(stream1.count());

}
组合:concat

如果有两个流,希望合并为一个流,那么可以使用Stream接口的静态方法concat

 public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {}

使用:

public static void main(String[] args) {

    Stream<String> stream1 = Stream.of("1", "2", "3");

    Stream<String> stream2 = Stream.of("4", "5", "6");

    Stream<String> stream = Stream.concat(stream1, stream2);

}

Stream流的特点

Stream属于管道流,只能被消费(使用)一次

第一个Stream流调用完毕方法,数据就会流转到下一个Stream流上

而这时第一个Stream流已经使用完毕,就会关闭了

所以第一个Stream流就不能再调用方法了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大恐龙的小弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值