Java8 函数式编程

函数式编程:这里“函数”应该理解为数学上的函数,即y=f(x)。

函数式编程的理解出发点,比如给Swing中Button添加监听器addListener(Listener接口)为例,没有lambda表达式时一般都是通过匿名内部类new XXXListener()做,由于Java一切皆对象,所以我们传递了一个监听器对象。但是这块语义层面,传递对象并不是最终目的,我们的最终目的就是想传递一个“行为”(比如用户点击Button后的行为),所以采用Lambda表达式这种简洁写法只是在语义层面能够更好一些,但本质还是一样的,lambda也是对象


一、Lambda表达式

1. 函数式接口

函数式接口是只能有一个抽象方法的接口(不论接口中有多少非抽象方法,其抽象方法只能有一个)。Java提供了 @FunctionalInterface 注解用于标记函数式接口,该注解只是检测作用,用于检查一个接口是否为函数式接口,如果不是,则无法编译。Lambda结合函数式接口可以让代码更加简洁明了。JDK8中引入了一个java.util.function包,该包下的接口全部是函数式接口。如果我们想要定义的接口与该包下定义的接口类似,那么拿来即用即可。

1.1 Consumer(消费型接口)

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

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        //只是返回一个Consumer对象,该对象的accept()方法如下
        return (T t) -> { accept(t); after.accept(t); };
    }
}

1.2 Function(函数型接口)

@FunctionalInterface
public interface Function<T, R> {
    //传入一个T类型,得到一个R类型
    R apply(T t);

//以下都是功能扩展:
    static <T> Function<T, T> identity() {
    //返回参数类型T自身
        return t -> t;
    }

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

    
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
}

1.3 Predicate(断定型接口)

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
 //以下都是功能扩展
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        //返回两个Predicate接口实例的test()方法返回值的&&
        return (t) -> test(t) && other.test(t);
    }

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

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

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

1.4 Supplier(供给型接口)

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

2.Lambda表达式语法

  1. “->”左侧:方法的参数列表。(参数类型可以省略不写,它可以按照类型自动匹配,若存在相同类型则按形参顺序做匹配。若抽象方法只有一个参数,则“()”可以省略不写,若抽象方法没有参数,则()和->都可以省略不写)
  2. “->”右侧:方法的具体实现(若方法体中只有一条语句则可以省略“{}”,若方法体中只有一条语句,且该语句为return语句,则return关键字可以省略)

Lambda表达式也是对象,它只是使我们实现抽象方法时的代码更加简洁。

public static void main(String[] args) {
	//无参数()和->都省略不写,方法体中只有一条语句则可以省略“{}”,若方法体中只有一条语句,且该语句为return语句,则return关键字可以省略
	Supplier<User> supplierObject = User::new;
	//只有一个参数,()省略不写,只有一条语句,{}省略不写,参数类型不写,自动做类型推断
    Predicate<Integer>  predicateObject = number -> number.equals(10086);
    //两个参数,参数类型不写,自动做类型推断
    BiConsumer<String, User> biConsumerObject = (userId,user) -> {
        System.out.println(userId);
        System.out.println(user);
    };
}

3. 方法引用

Lambda有一个常见的用法,比如想得到一个User的姓名,Lambda的表达式如下:

user -> user.getName()

这种用法非常普遍,因此Java 8 为其提供了一个简单的写法,叫做方法的引用,标准用法为:
ClassName::methodName,需要注意的是,虽然这是一个方法,但不需要在后面加括号。如下:

//
User::getName

二、Stream API

1.惰性求值和及早求值

  1. 返回为Stream对象的API叫做 “惰性求值”(如filter方法),惰性求值不会被立刻执行,它更像是在给及早求值描述一些规则。
  2. 返回一个新集合 或者 其他具体值 的API叫做 及早求值(如count()这种方法),及早求值会立刻被执行。

惰性求值生成一个求值链,然后由一个及早求值返回想要的结果。这个过程和建造者模式有点类似,先一系列操作设置属性和配置,然后最后build()真正返回结果。及早求值方法就是类似与build()方法。

2. 常用的流操作

调用流的API,就相当于将流中的元素交给该API方法进行处理。(Stream中的元素流出来交给lambda,作为函数接口中抽象方法的实参)

流的特点:只能遍历一次 我们可以把流想象成一条流水线,流水线的源头是我们的数据源,数据源中的元素依次被输送到流水线上,我们可以在流水线上对元素进行各种操作。一旦元素走到了流水线的另一头,那么这些元素就被“消费掉了”,我们无法再对这个流进行操作。当然,我们可以从数据源那里再获得一个新的流重新遍历一遍。

流的数据源可以是多种形式,比如集合、数组、以及多个值组合而成。如下:

//1.集合类型
Collection<String> names = new ArrayList<>();
Stream<String> stream = names.stream();

//2.数组,利用Arrays中提供的工具将数组转换为流
String[] names = new String[10];
Stream<String> stream = Arrays.stream(names);

//3.多个值组合
Stream<String> stream = Stream.of("张三","李四","王麻子");

2.1 收集collect

这是一个及早求值的操作,通过传递一个收集器,将流中的元素收集到我们想要的容器中。

public static void main(String[] args) {
	Stream<String> stream = Stream.of("张三","李四","王麻子");
	//将流中的元素收集到列表中
    List<String> names = stream.collect(Collectors.toList());
}

2.2 map

如果一个函数可以将一种类型的值转换为另外一种类型,map操作就可以使用该函数,将一个流中的值转换为一个新的流。这是一个惰性求值的操作。map接收一个Function<T,R>接口的对象,将一个类型的值转换为另一个类型。

Stream<Integer> nameLen = Stream.of("张三","李四","王麻子")
				.map(name -> {
          			return name.length();
          		});

2.3 forEach

它接受一个Consumer接口的对象,遍历流中的元素,是一个及早求值的操作。

Stream.of("张三","李四","王麻子").forEach(name -> {
            System.out.println("姓名:" + name);
        });

2.4 filter

它接受一个断定性接口的对象,将方法返回为false的元素过滤掉,如下,只保留了“张三”。

Stream.of("张三","李四","王麻子").filter(name -> {
            return "张三".equals(name);
        }).forEach(name -> {
            System.out.println("姓名:" + name);
        });

2.5 max和min

Stream上常用的操作之一是求最大值和最小值。而max()和min()正好可以解决这个问题。它接受一个Comparator接口的对象,通过自定义的比较规则求最大、最小值。

3.Optional介绍

Optional是Java8新加入的一个容器,这个容器 只存1个或0个元素,它用于防止出现NullPointException,它提供如下方法:

  • isPresent(): 判断容器中是否有值。
  • ifPresent(Consume lambda): 容器若不为空则执行括号中的Lambda表达式。
  • T get(): 获取容器中的元素,若容器为空则抛出NoSuchElement异常。
  • T orElse(T other): 获取容器中的元素,若容器为空则返回括号中的默认值。

of和ofNullable方法

of 和 ofNullable方法都用于创建包含值的 Optional对象。两个方法的不同之处在于如果你把 null 值作为参数传递进去,of() 方法会抛出NullPointerException。你看,我们并没有完全摆脱 NullPointerException。因此,你应该明确对象不为 null 的时候使用 of()。 如果对象即可能是 null 也可能是非 null,你就应该使用 ofNullable() 方法。

//将user对象放入容器中,但是不清楚user是否为null,如果user不是null,那么获取User的年龄,如果年龄为null,则默认是18岁。
Integer userAge = Optional.ofNullable(user).map(User::getAge).orElse(18);

4 收集器

利用流的collect()方法可以讲流中的元素收集到我们想要的数据结构中,比如前面的List,有时人们还希望将其收集到Set、Map等。JDK已经提供了许多收集器,如下:

  • toSet()
  • toList()
  • toMap()
  • toConcurrentMap()
  • toCollection()

4.1 toMap

/*
1. keyMapper:Key 的映射函数
2. valueMapper:Value 的映射函数
3. mergeFunction:当 Key 冲突时,调用的合并方法
4. mapSupplier:Map 构造器,在需要返回特定的 Map 时使用
*/
public static <T, K, U, M extends Map<K, U>>
    Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
                                Function<? super T, ? extends U> valueMapper,
                                BinaryOperator<U> mergeFunction,
                                Supplier<M> mapSupplier)

//这个方法没有提供mapSupplier参数,默认收集到HashMap中
public static <T, K, U>
    Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper,
                                    BinaryOperator<U> mergeFunction) {
        return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
    }
//没有mergeFunction和mapSupplier,默认收集到HashMap中,且当键冲突时会抛出异常
public static <T, K, U>
    Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper) {
        return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
    }

如下,将UserList中的元素收集到TreeMap中:

public static void main(String[] args) {
        List<User> userList = new ArrayList<>();
        userList.add(new User("10086","张三"));
        userList.add(new User("10087","李四"));
		Map<String,User> userMap = userList.stream().collect(Collectors.toMap(user -> user.getUserId() + user.getUserName(), Function.identity(), (key1, key2) -> key1, TreeMap::new));
}

当然Map可以自己指定,集合也可以自己指定,不仅限于Set和List,可以通过如下方式指定:

userList.stream().collect(Collectors.toCollection(TreeSet::new));

4.2 数据分组(groupingBy)

/*
1.classifier:分类器(分类函数)(根据函数返回值来分类)
2.downstream:下游收集器(起名:分组收集器)。每个组的元素将流到下游收集器中。
数据分组后存放在Map中,如下默认将分组后的数据存放在HashMap中
*/
public static <T, K, A, D>
    Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
                                          Collector<? super T, A, D> downstream) {
        return groupingBy(classifier, HashMap::new, downstream);
    }
    
//自定义Map类型,比如可以将分组后的数据存放在TreeMap中等。
public static <T, K, D, A, M extends Map<K, D>>
    Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
                                  Supplier<M> mapFactory,
                                  Collector<? super T, A, D> downstream)
//如下,将UserList按照UserId进行分组,每组是一个Map<String,User>
Map<String, Map<String, User>> userIdMap =
                userList.stream().collect(Collectors.groupingBy(User::getUserId,
                        Collectors.toMap(User::getUserName, Function.identity(), (key1, key2) -> key1, HashMap::new)));
//如下,将UserList按照UserId进行分组,每组是一个List<User>
Map<String, List<User>> userIdMap =
                userList.stream().collect(Collectors.groupingBy(User::getUserId, Collectors.toList()));
//如下,将UserList按照UserId进行分组,每组是一个List<User>,将分组后的数据存放在TreeMap中
TreeMap<String, List<User>> userIdMap =
                userList.stream().collect(Collectors.groupingBy(User::getUserId,TreeMap::new, Collectors.toList()));                

4.3 数据分块(partitioningBy)

数据分块可以将流中元素分为两部分,它使用Predicate对象判断一个元素应该属于哪一部分,并根据布尔值返回一个Map到列表,对于true List中的元素,Predicate返回true,对于其他List中的元素,Predicate返回false。

public static <T>
    Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {
        return partitioningBy(predicate, toList());
}

如下将UserList中的元素分成两部分,userId为10086的存放在一个List中,剩余其他的存放在一个List中。

public static void main(String[] args) {
        List<User> userList = new ArrayList<>();
        userList.add(new User("10086","张三"));
        userList.add(new User("10087","李四"));
        userList.add(new User("10088","李四"));
        Map<Boolean,List<User>> userMap = userList.stream().collect(Collectors.partitioningBy(user ->  "10086".equals(user.getUserId())));
}

4.3 computeIfAbsent

构建Map时,为给定值计算键值是常用的操作之一,一个经典的例子就是实现一个缓存。传统的处理方式是先试着从Map中取值,如果没有取到,则创建一个新值返回。Java 8引入了一个新方法computeIfAbsent,该方法接受一个Lambda表达式,值不存在时使用该Lambda计算新值,并将新值存入Map中。如下:

private static User getUser(String userId){
	return userCache.computeIfAbsent(userId,this::readUserFromDB);
}

4.4 遍历Map

Java8之前我们遍历Map通常都是先找到Key的集合,然后迭代key集合获取value进行操作。Java 8为Map接口提供了forEach方法,简化我们遍历Map,该方法接受一个BiConsumer对象为参数(该对象接受两个参数,无返回值),通过内部迭代编写出易于阅读的代码。

default void forEach(BiConsumer<? super K, ? super V> action) {
        Objects.requireNonNull(action);
        for (Map.Entry<K, V> entry : entrySet()) {
            K k;
            V v;
            try {
                k = entry.getKey();
                v = entry.getValue();
            } catch(IllegalStateException ise) {
                // this usually means the entry is no longer in the map.
                throw new ConcurrentModificationException(ise);
            }
            action.accept(k, v);
        }
    }

如下,打印Map中的键值对:

userMap.forEach((key,value) -> System.out.println("键:" + key + "\t值:" + value));
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值