Lamdba 和 Stream

1.Lamdba优于匿名内部类

lamdba 类似于匿名类的函数,但是要比匿名类更加简洁

// 匿名类
Collections.sort(words, new Comparator<String>() {public int compare(String s1, String s2) {return Integer.compare(s1.length(), s2.length());}
});
//lamdba 更简洁
// 编译器利用一个类似于类型推倒的过程根据上下文推断出 s1,s2的数据类型。很少情况下需要指定类型。
Collections.sort(words,(s1, s2) -> Integer.compare(s1.length(), s2.length()));

lamdba

  • 使之前不能使用函数对象的地方现在也能使用了。
  • lambda中的不要超过三行,一行最理想。
  • lambda中无法访问枚举的实例成员。
  • lambda无法创建抽象类的实例,但匿名内部类可以。
  • lambda无法获取到对自身的引用。
  • 尽可能的不要序列化一个Lamdba

如果需要反序列化一个函数接口,如:Comparator,我们需要使用私有静态内部类。

2.方法引用优先于Lamdba

如果方法引用更加简洁和清晰,请使用方法引用,反之使用Lambda表达式。

map.merge(key, 1, (count, incr) -> count + incr); = map.merge(key, 1, Integer::sum);//Static
Integer::parseInt == str -> Integer.parseInt(str)
//Bound
Instant.now()::isAfter == Instant then = Instant.now();then.isAfter(t)
//Unbound
String::toLowerCase = str.toLowerCase()
//Class Constructor
TreeMap::new = () -> new TreeMap
//Array Constructor
int[]::new = len -> new int[len]

3.坚持使用标准的函数接口

(1)模板方法模式

  • 只要标准的函数接口能够满足需求,通常应该优先考虑,而不是专门在构造一个新的函数接口。
  • 不要用带包装类型的基础函数接口来代替基本函数接口
  • 用@FunctionalInterface注解

在模版方法模式中通过子类覆盖基本类型方法实现限定其超类的行为已经过时了,可以使用一个接受函数对象的静态工厂或者构造器来实现。
一般来说,我们将编写更多以函数对象作为参数的构造函数和方法。

abstract class TestA {public void print() {System.out.println("TestA");doSubThing();}abstract void doSubThing();
}class TestB extends TestA {@Overridevoid doSubThing() {System.out.println("TestB");}
}// lambda
class TestA {private Supplier<String> supplier;public testA(Supplier<String> supplier) {this.supplier = supplier;}public void print() {System.out.println("A");System.out.println(supplier.get());}
}public static void main(String[] args) {TestA a = new TestA(() -> "TestB");a.print();
}

6个基础函数接口

接口 函数签名 例子
UnaryOperator T apply(T t) String::toLowerCase
BinaryOperator T apply(T t1, T t2) BigInteger::add
Predicate boolean test(T t) Collection::isEmpty
Function R apply(T t) Arrays::asList
Supplier T get() Instant::now
Consumer void accept(T t) System.out::println

装箱基本类型进行批量处理,可能会导致致命的性能问题
@F

4.明智地使用Stream

Stream:数据元素无限或有限的序列。
Stream pipeline:对流中的元素进行多级计算。
流的来源:集合、数组、文件、正则表达式模式匹配器、伪随机数生成器或其他流。

管道操作:源Stream后跟着零个或多个中间操作和一个终止操作。
中间操作:转换流的方式,如:元素映射或元素过滤等。
终止操作:执行最终计算,如:流装入容器中或是消费掉。

流管道只包含中间操作时是惰性的:只有调用终止操作时才会开始计算,使无限Stream成为可能。

Stream API是流式的,所有包含Pipeline的调用可以链接成一个表达式。多个pipeline 也可以链接在一起,成为一个表达式。

流管道的API被设计成链式编码风格。

不要滥用流

// 普通方式
// 读取文件中的单词,检查单词的字母,相同字母的单词收集在一起
public class Anagrams {public static void main(String[] args) throws IOException {File dictionary = new File(args[0]);int minGroupSize = Integer.parseInt(args[1]);Map<String, Set<String>> groups = new HashMap<>();try (Scanner s = new Scanner(dictionary)) {while (s.hasNext()) {String word = s.next();groups.computeIfAbsent(alphabetize(word), (unused) -> new TreeSet<>()).add(word);}}for (Set<String> group : groups.values())if (group.size() >= minGroupSize)System.out.println(group.size() + ": " + group);}private static String alphabetize(String s) {char[] a = s.toCharArray();Arrays.sort(a);return new String(a);}
}// 过度使用流:虽然很简洁,但是对流不了解的开发人员可能无法理解。
// 打个比方,有些动漫是只有死宅才看的:永生之酒。
public static void main(String[] args) throws IOException {Path dictionary = Paths.get(args[0]);int minGroupSize = Integer.parseInt(args[1]);try (Stream<String> words = Files.lines(dictionary)) {words.collect(groupingBy(word -> word.chars().sorted().collect(StringBuilder::new, (sb, c) -> sb.append((char) c), StringBuilder::append).toString())).values().stream().filter(group -> group.size() >= minGroupSize).map(group -> group.size() + ": " + group).forEach(System.out::println);}
}// 合适使用流方式
// 有的动漫是大家都看的:龙珠。对动漫不需要太了解也能够接收。
public static void main(String[] args) throws IOException {Path dictionary = Paths.get(args[0]);int minGroupSize = Integer.parseInt(args[1]);try (Stream<String> words = Files.lines(dictionary)) {words.collect(groupingBy(word -> alphabetize(word))).values().stream().filter(group -> group.size() >= minGroupSize).forEach(group -> System.out.println(group.size() + ": " + group));}
}

lambda中参数的命名尤为重要,好的命名能够提升可读性。
使用lambda来消灭循环,但实际是不可取的(元素少时lambda存在性能问题)。

Stream的缺点

  • 代码块能够读取或修改范围内的局部变量,lambda只能操作final变量和当前范围的局部变量。
  • 代码块中能够return、抛出异常、跳出循环或是跳过循环,lambda中都无法做到。

Stream的优势

  • map:统一转换元素类型
  • filter:过滤序列
  • min、compute:计算最小值、合并序列等
  • reduce:累计序列
  • grouping:分组

流无法做到同时在多级阶段访问相应的元素

//通过操作反转来获取上一个流元素
public static void main(String[] args) {primes().map(p -> TWO.pow(p.intValueExact()).subtract(ONE))// (1-1/50)=98%代表isProbablePrime只有当98%几率为素数才返回true。.filter(mersenne -> mersenne.isProbablePrime(50)).limit(20)// mp.bitLength等于p值,反向运算来获取上一个流的值。.forEach(mp -> System.out.println(mp.bitLength() + ": " + mp));
}static Stream<BigInteger> primes() {return Stream.iterate(TWO, BigInteger::nextProbablePrime);
}
(4)笛卡尔积
private static List<Card> newDeck() {List<Card> result = new ArrayList<>();for (Suit suit : Suit.values())for (Rank rank : Rank.values())result.add(new Card(suit, rank));return result;
}// flatMap 用于展平一个序列,如:List<String> -> String.
private static List<Card> newDeck() {return Stream.of(Suit.values()).flatMap(suit ->Stream.of(Rank.values()).map(rank -> new Card(suit, rank))).collect(toList());
}

5.优先选择Stream中无副作用的功能

为了得到stream的表现力、速度和并行度,我们必须遵守范式和使用API。
stream范式最重要的部分:计算 -> 转换 ,每个转换(中间或终止操作)都是纯函数。
纯函数应该都是无副作用的(不依赖任何可变状态,不更新任何状态)。

// 不遵守范式,forEach应该只用于呈现流执行的计算结果
Map<String, Long> freq = new HashMap<>();
try (Stream<String> words = new Scanner(file).tokens()) {words.forEach(word -> freq.merge(word.toLowerCase(), 1L, Long::sum));
}// 使用流初始化频率表
Map<String, Long> freq;
try (Stream<String> words = new Scanner(file).tokens()) {freq = words.collect(groupingBy(String::toLowerCase, counting()));
}// 按照频次获取前十个元素
List<String> topTen = freq.keySet().stream().sorted(comparing(freq::get).reversed()).limit(10).collect(toList());// groupingByConcurrent返回并发Map
ConcurrentHashMap<String, Long> freq;
try (Stream<String> words = new Scanner(file).tokens()) {freq = words.collect(groupingByConcurrent(String::toLowerCase, counting()));
}Collector<T, ?, Map<Boolean, List>> partitioningBy(Predicate<? super T> predicate):true和false分成两组。
Collector<T, ?, Map<K, List>> groupingBy(Function<? super T, ? extends K> classifier):按照key值分组。
List<String> words = new ArrayList<>();
words.add("1");
words.add("1");
words.add("2");
words.add("3");Map<Boolean, List<String>> map = words.stream().collect(partitioningBy(s -> s.equals("1"))
);
// {false=[2, 3], true=[1, 1]}
System.out.println(map); Map<String, List<String>> map = words.stream().collect(groupingBy(String::toLowerCase)
);
// {1=[1, 1], 2=[2], 3=[3]}
System.out.println(map); // List转Map的正确实现
Map<Integer, Data> collect = words.stream().collect(toMap(Data::getId, e -> e));// key值重复时,获取销量最大的Album
Map<Artist, Album> topHits = albums.collect(toMap(Album::artist, a->a,maxBy(comparing(Album::sales)))
);// 后访问的覆盖先访问的
Map<Artist, Album> topHits = albums.collect(toMap(Album::artist, a->a,(v1, v2) -> v2)
);// 指定返回Map的类型
HashMap<Artist, Album> topHits = albums.collect(toMap(Album::artist, a->a,(v1, v2) -> v2,HashMap::new)
);// joining
List<String> words = new ArrayList<>();
words.add("2");
words.add("1");
words.add("1");
words.add("3");
String join1 = words.stream().collect(joining());
String join2 = words.stream().collect(joining(","));
String join3 = words.stream().collect(joining(",","[","]"));
// 2113
System.out.println(join1);
// 2,1,1,3
System.out.println(join2);
//[2,1,1,3]
System.out.println(join3); // mapping和map类似List<String> words = new ArrayList<>();
words.add("2");
words.add("1");
words.add("1");
words.add("3");
List<Integer> list1 = words.stream().collect(mapping(e -> Integer.valueOf(e), toList()));
List<Integer> list2 = words.stream().map(e -> Integer.valueOf(e)).collect(toList());
// [2, 1, 1, 3]
System.out.println(list1);
// [2, 1, 1, 3]
System.out.println(list2);
(7)计算
List<String> words = new ArrayList<>();
words.add("2");
words.add("1");
words.add("3");// 求和
Integer sum1 = words.stream().collect(summingInt(value ->  Integer.valueOf(value)));
Integer sum2 = words.stream().mapToInt(value -> Integer.valueOf(value)).sum();// 平均值
Double avg = words.stream().collect(averagingInt(value -> Integer.valueOf(value)));// 最大值
String max1 = words.stream().max(comparing(Integer::valueOf)).get();
String max2 = words.stream().collect(maxBy(comparing(Integer::valueOf))).get();// 总结值
IntSummaryStatistics summary = words.stream().collect(summarizingInt(Integer::valueOf));
System.out.println(summary.getAverage());
System.out.println(summary.getSum());
System.out.println(summary.getCount());
System.out.println(summary.getMax());
System.out.println(summary.getMin());

Stream 要优先用Collection作为返回值

//stream的iterator
// need to cast
for (ProcessHandle ph : (Iterable<ProcessHandle>)ProcessHandle.allProcesses()::iterator){...
}// Adapter from Stream<E> to Iterable<E>
public static <E> Iterable<E> iterableOf(Stream<E> stream) {return stream::iterator;
}for (ProcessHandle p : iterableOf(ProcessHandle.allProcesses())) {// Process the process
}
//spliterator
// spliterator用于并行迭代
public static <E> Stream<E> streamOf(Iterable<E> iterable) {return StreamSupport.stream(iterable.spliterator(), false);
}

Collection是Iterable的子类型,具有stream方法,因此提供迭代和流访问,所以Collection或适当的子类型通常是返回方法的最佳返回类型。
如果返回的序列小到足够放到内存中,则最好返回一个标准集合实现。

前缀子集、后缀子集
public class SubLists {public static <E> Stream<List<E>> of(List<E> list) {return Stream.concat(Stream.of(Collections.emptyList()),prefixes(list).flatMap(SubLists::suffixes));}// (a,b,c) => ((a),(a,b),(a,b,c))private static <E> Stream<List<E>> prefixes(List<E> list) {return IntStream.rangeClosed(1, list.size()).mapToObj(end -> list.subList(0, end));}// (a,b,c) => ((a,b,c),(b,c),(c))private static <E> Stream<List<E>> suffixes(List<E> list) {return IntStream.range(0, list.size()).mapToObj(start -> list.subList(start, list.size()));}
}
//所有子列表
// [1,3,2] => [[1], [1, 3], [1, 3, 2], [3], [3, 2], [2]]
public static <E> Stream<List<E>> of(List<E> list) {return IntStream.range(0, list.size()).mapToObj(start -> IntStream.rangeClosed(start + 1, list.size()).mapToObj(end -> list.subList(start, end)))// subList使用闭区间.flatMap(x -> x);
}

6. 谨慎使用并行流

ArrayList、HashMap、HsahSet、CouncurrentHashMap、数组、int范围流和long范围流的并行性性能效益最佳。
它们的范围可以确定,而执行任务的抽象为spliterator。
数组存储的元素在内存中相近,数据定位更快。而上面涉及的数据结构基本都基于数组实现。

流的终止操作会影响并行执行的有效性。而流的reduce操作或预先打包(min、max、count和sum)是并行流的最佳实践。
流的中间操作(anyMatch、allMatch和noneMatch)也适合并行操作。
流的collect操作则不适合。
自己实现Stream、Iterable或Collection且希望有良好的并行性能,则需要覆盖spliterator方法。
并行流是基于fork-join池实现的。
当无法写出正确的并行流,将导致异常或者错误的数据。

注:程序的安全性、正确性比性能更重要。

(2)DEMO
// 串行,10^8需要30秒
static long pi(long n) {
return LongStream.rangeClosed(2, n)
.mapToObj(BigInteger::valueOf)
.filter(i -> i.isProbablePrime(50))
.count();
}

// 并行,10^8需要9秒
static long pi(long n) {
return LongStream.rangeClosed(2, n)
.parallel()
.mapToObj(BigInteger::valueOf)
.filter(i -> i.isProbablePrime(50))
.count();
}

Lamdba 和 Stream相关推荐

  1. java streamhandler_java中的Lamdba表达式和Stream

    基于JDK 1.8 1.循环: // 以前的循环方式 for (String player : players) { System.out.print(player + "; ") ...

  2. Java8 Lamdba表达式 002

    本篇将讲述lamdba表达式的排序,本例包括一个Player对象的集合[稍后定义],通过每一个player的分数高低对列表的player进行排序.类定义001例如以下 public class Sor ...

  3. Java中lambda表达式去重_JAVA8中Lambda和Stream

    Java8于2014年3月份发布,其主要更新的特性有:函数式接口.Lambda 表达式.集合的流式操作.注解的更新.安全性的增强.IO\NIO 的改进.完善的全球化功能等,本文将介绍Lambda表达式 ...

  4. Java之Stream的管道处理

    一.Java Stream管道数据处理操作 在开始本文之前,我们有必要介绍一下这张Java Stream 数据处理过程图,图中主要分三个部分: 将数组.集合类.文本文件转换为管道流(图中的蓝色方块的部 ...

  5. Stream流创建,常用方法

    常用词句说明 ​ 语法糖: Syntactic sugar 这种语法对语言的功能并没有影响,但是更方便使用,通常来说使用语法糖可以增加程序的可读性. stream简介 A sequence of el ...

  6. Java8——Stream流

    Java8--Stream流 Stream是数据渠道,用于操作集合.数组等生成的元素序列. Stream操作的三个步骤: 创建Stream 中间操作 终止操作 一.获取stream的四种方式 1.通过 ...

  7. java使用Stream流找出集合对象中最小值

    一.问题 有如下四个人,如何使用Lamdba找到年龄最小的并打印出来 @Data @AllArgsConstructor @NoArgsConstructor @EqualsAndHashCode p ...

  8. Java8新特性 Lambda、Stream、Optional实现原理

    Java8新特性 Lambda.Stream.Optional实现原理 一.接口中默认方法修饰为普通方法 二.Lambda表达式 2.1.什么是Lambda表达式 2.2.为什么要使用Lambda表达 ...

  9. Stream排序Map集合

    前置知识: Lamdba表达式 StreamApi map根据value倒序排序 map.entrySet().stream().sorted(Collections.reverseOrder(Map ...

最新文章

  1. 使用PermissionsDispatcher轻松解决Android权限问题
  2. 2019年智能手机AI要被深度开发,这五项技术将是重点
  3. numpy如何生成随机数
  4. 汇编语言 寄存器 2.9~2.12 总结
  5. M产品研发日志(4)---项目出差
  6. LF 和 CRLF 区别
  7. 解决:Unable to open debugger port (127.0.0.1:55017): java.net.SocketException “Socket closed“
  8. asp.net上传到服务器 步骤:
  9. 【WP8】Uri关联启动第三方App
  10. AS3.0文档类:FLASH右键菜单文档类
  11. DBV命令行工具检测坏块
  12. android 打好的补丁位置,Android 6.0上打完第一个补丁后再打新补丁Crash
  13. 2020-11-12
  14. 代码质量保证体系(上)
  15. (附源码)springboot菠萝大学课室预约系统分析与设 毕业设计 641656
  16. latex 公式换行_如何在微信公众号中!编辑出漂亮的数学公式?
  17. 基于git(分布式版本控制系统)的各种服务器权限工具对比 Gitlab服务器搭建 以及邮箱、LDAP配置 实现公司多人协同开发
  18. 画论13 朱景玄《唐朝名画录》
  19. 下载的中文文件名乱码,如何转码
  20. i512450h和i512500h对比区别大吗

热门文章

  1. iOS 加载html文本图片空白,UITextView 加载html文本(图片自适应)
  2. bio rad是哪个国家的的公司
  3. 电脑关机一直显示正在关机
  4. 扫福字,集五福——用python制作个性化词云
  5. Unity技术手册-初识编辑器-其他窗口
  6. 教外篇(6):C++ qrencode 实现二维码生成
  7. vue高德/腾讯地图只显示某一区域的地图,其他地区不显示
  8. [论文阅读笔记]Two-Stream Convolutional Networks for Action Recognition in Videos
  9. 小觅相机问题解决后开始进行标定的事
  10. cartographer算法(一)——概述及框架