Lambda表达式

Lambda表达式是基于函数式接口(Functional Interface - FI)而产生的,函数式接口就是具有唯一的一个抽象方法的接口。所以满足这个条件的接口都可以写成Lambda表达式。 比如Runnable接口

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

我们平时在运行一个任务时会这样写

new Thread(new Runnable(){
    @Override
    public void run() {
    System.out.println("hi");
    }
}).start();

而Runnable是能够被改编为Lambda表达式的,第一版改编如下:

new Thread(()->{
    System.out.println("hi");
    }
).start();

可以看出,没有入参时,就直接用括号代替。又因为花括号内只有一个语句,因此花括号也可以省略,第二版改编如下:

new Thread(()->System.out.println("hi")).start();

Java 8 为Lambda表达式新建了一个包,该路径下的接口均为函数式接口,且被打上了@FunctionalInterface

java.util.function包下面,有5种类型的接口:

  • Consumer:消费型T->void,流的消费者
  • Function:函数型T->R,完成参数类型T向结果类型R的转换。配合map()使用
  • Operator:函数的输入类型和输出类型相同,即可用xxxOperator
  • Predicate:判断型T->boolean,判断条件是否满足,配合filter()使用
  • Supplier:供给型void->T,流的提供者,配合generate()使用
Stream.generate(Supplier).map(Function).filter(Predicate).forEach(Consumer)

Stream

Stream 是Java 8的另一个新特性,它就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。

现实生活中的水流,中间经过层层处理(分流、过滤、限流...),最后流到每一个水龙头出口,水流完了就没有了。

这里面的操作分为:中间操作(分流、过滤、消毒...),最终操作(用水),限流操作(限流)。

Java8的流的操作类比于水流,也将流的操作分为三种:

  • Intermediate:一个流后面可以跟零个或多个 intermediate 操作。其主要目的是做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。这类操作有:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
  • Terminal:一个流只能有一个 terminal 操作,Terminal操作属于一种消费型的操作,当这个操作执行后,流就被使用“光”了,无法再被操作,所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。这类操作有:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
  • Short-circuiting:一个流后面可以跟零个或多个 Short-circuiting 操作。其主要目的是限流。这类操作有:anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

需要注意的是,对于基本数值型,目前有三种对应的包装类型 Stream:IntStream、LongStream、DoubleStream。 当然我们也可以用 Stream、Stream、Stream,但是 boxing 和 unboxing 会很耗时,所以特别为这三种基本数值型提供了对应的 Stream。

一般情况下,我们在想要打印结果时会使用forEach(System.out::println),但是这是一个 terminal 操作,流在这里会被消耗掉。 在 Intermediate 操作中有一个类似的方法 peek(x -> System.out.printf("value=", x)),但是它不会消耗流。

List<List<String>> nestedList = Lists.newArrayList(
        Lists.newArrayList("b", "a", "c"),
        Lists.newArrayList("Cc", "Bb", "Aa"),
        Lists.newArrayList("aaa", "mmm", "sss", "zzz"));
List<String> result = nestedList.stream()
        .flatMap(Collection::stream)//等同于x->x.stream(),即将每一个list也转为stream,这样就相当于将所有的list拼接为一个stream
        .filter(x -> x.length() >= 2)//将长度小于2的元素过滤掉,这样就剩下了:"Cc", "Bb", "Aa", "aaa", "mmm","sss","zzz"
        .map(x -> x.substring(0, 1))//保留每个元素的第一个字符,这时的元素为:C, B, A, a, m ,s, z
        .map(String::toUpperCase)//将所有的字母转大写
        .sorted()//按照字符顺序排序,得到了:A, A, B, C, M, S, Z
        .skip(3)//跳过前3个元素
        .collect(Collectors.toList());//转换为List,得到C, M, S, Z

//注意:在调用sorted()后使用limit/skip,会使得sorted()的排序次数并没有减少,因为在做limit/skip时,并不知道排序后的顺序是什么样的。

Arrays.asList("ae", "f", "gqet", "abcd", "ertyu", "zxc")
                .stream()
                .sorted(Comparator.comparing(String::length).thenComparing(String::compareTo))//多个比较器
                .forEach(System.out::println);

//#################################################

IntStream.of(1, 2, 3, 4, 5, 4, 3, 2, 1).distinct().filter(x -> x % 2 == 0).forEach(System.out::println);//打印出2, 4
Map<Integer, List<Integer>> groupedMap = Stream.of(1, 2, 3, 4, 5, 4, 3, 2, 1).distinct().collect(Collectors.groupingBy(x -> x % 3));//将数字1~5按照模3的余数进行分组
IntStream.range(1, 3).forEach(System.out::println);//打印出1, 2
IntStream.rangeClosed(1, 3).parallel().map(x -> x * x).forEach(System.out::println);//打印出1, 4, 9
OptionalInt min = IntStream.rangeClosed(1, 3).min();

//#################################################

Random seed = new Random();
Supplier<Integer> random = seed::nextInt;
Stream.generate(random).limit(10).forEach(System.out::println);

Stream.generate(() -> "hello").limit(10).forEach(System.out::println);
Stream.iterate(1, x -> x * 2).limit(10).forEach(x -> System.out.printf("%d, ", x));

//#################################################

Stream.of("one", "two", "three", "four")
        .filter(e -> e.length() > 3)
        .peek(e -> System.out.println("Filtered value: " + e))
        .map(String::toUpperCase)
        .peek(e -> System.out.println("Mapped value: " + e))
        .collect(Collectors.toList());

//#################################################

Stream.of("A", "B", "C", "D").reduce("", String::concat);//字符串拼接,得到"ABCD"
Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);//求最小值,得到-3.0
Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);//求和,得到10
Stream.of(1, 2, 3, 4).reduce(Integer::sum);//求和,得到Optional<Integer>对象,因为没有起始值,所以可能没有足够的元素

常用流式操作:

# 实体集合某个字符转换为数组: 
String[] arrayLink = tTaskDetails.stream().map(item -> item.getSmsUrl() + "&uid=" + item.getUid()).toArray(String[]::new);
# 实体集合某两个字段组成Map
Map<String, String> phoneCrmIdMap = archivesByPhoneList.stream().collect(Collectors.toMap(TCrmUserArchives::getPhone, TCrmUserArchives::getCrmId, (exiting, replacement) -> exiting));
# 实体集合根据某个属性值去重
List<TPhoneOrig> origPhoneDataList = origPhoneDataList.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(t -> t.getOriPhone()))), ArrayList::new));
# 实体集合根据某个属性值分组
Map<Integer, List<TPhoneOrig>> groupMap = phoneOrigData.stream().collect(Collectors.groupingBy(TPhoneOrig::getGroupId));

# 实体集合按照某个属性值从小到大排序: 此处Priorit为实体Task的属性,值为整数型
taskList.stream().sorted(Comparator.comparing(Task::getPriority)).collect(Collectors.toList());

# 从大到小排序
taskList.stream().filter(item -> item.getCircleType().equals(circleType)).sorted(Comparator.comparing(Task::getPriority).reversed()).collect(Collectors.toList());
# 统计数组元素中的个数
String[] arr = {"a", "c", "a", "b", "d", "c"};
Stream.of(arr).collect(Collectors.toMap(k -> k, k -> 1, Integer::sum)).forEach((k, v) -> System.err.println(k + " : " + v));
在上面的代码中
Collectors.toMap(k -> k, k -> 1, Integer::sum)这一部分可能不好理解,对于这里面的三个参数,
第一个参数代表将arr中的每一个元素作为Map中的key,第二个参数代表每一个key所对应的value,
在这里每一个元素都对应个数1,第三个参数代表,如果存在相同的key,该如何进行合并,这里通过使用
Integer::sum,代表将具有相同key的元素进行合并时,其value进行相加,这样便实现了每个元素个数的统计。
源码:
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);
}
# 统计数组中前 k 个高频元素 
public int[] topKFrequent(int[] nums, int k) {
    return Arrays.stream(nums)
            .boxed()
            .collect(Collectors.toMap(e -> e, e -> 1, Integer::sum))
            .entrySet()
            .stream()
            .sorted((m1, m2) -> m2.getValue() - m1.getValue())
            .limit(k)
            .mapToInt(Map.Entry::getKey)
            .toArray();
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值