From 9b0464fb4d16f3199980d4f48db65d93183f71f1 Mon Sep 17 00:00:00 2001 From: panguanglei Date: Sat, 2 Mar 2019 09:37:17 +0800 Subject: [PATCH] add files --- ...75\346\225\260\347\274\226\347\250\213.md" | 711 ++++++++++++++++++ 1 file changed, 711 insertions(+) create mode 100644 "Java 8\345\207\275\346\225\260\347\274\226\347\250\213.md" diff --git "a/Java 8\345\207\275\346\225\260\347\274\226\347\250\213.md" "b/Java 8\345\207\275\346\225\260\347\274\226\347\250\213.md" new file mode 100644 index 0000000..fd11a2b --- /dev/null +++ "b/Java 8\345\207\275\346\225\260\347\274\226\347\250\213.md" @@ -0,0 +1,711 @@ +### 1. 为什么引入函数编程 +为了编写这类处理批量数据的并行类库,需要在语言层面上修改现有的Java:**增加Lambda表达式**。 + +### 2. 什么是函数编程 +在思考问题时,使用不可变值和函数,函数对一个值进行处理,映射成另一个值。 + +#### 2.1 高阶函数 +高阶函数是指接受另外一个函数作为参数或返回一个函数的函数。 + +高阶函数不难辨认:看函数签名就够了。如果函数的参数列表里包含函数接口或该函数返回一个函数接口,那么该函数就是高阶函数。 + +#### 2.2 副作用 +所谓"副作用"(side effect),指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。 + +函数式编程强调没有"副作用",意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值. + +### 3. Lambda表达式 +Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。 + +Lambda表达式通过**invokedynamic**指令实现,书写Lambda表达式不会产生新的类。 + +lambda 表达式的语法格式如下: +``` +(parameters) -> expression +``` +``` +(parameters) -> { statements; } +``` + +#### 3.1 Lambda表达式的不同形式 +``` +# 无参数 +Runnable noArguments = () -> System.out.println("Hello World"); +``` + +``` +# 只包含一个参数,可省略参数的括号 +ActionListener oneArgument = event -> System.out.println("button clicked"); +``` + +``` +# 主体不仅可以是一个表达式,而且也可以是一段代码块 +Runnable multiStatement = () -> { + System.out.print("Hello"); + System.out.println(" World"); +}; +``` + +``` +# 两个参数 +BinaryOperator add = (x, y) -> x + y; +``` + +``` +# 显式声明参数类型 +BinaryOperator addExplicit = (Long x, Long y) -> x + y; +``` + +Lambda 表达式的类型依赖于上下文环境, 是由编译器推断出来的。 目标类型也不是一个全新的概念, Java 中初始化数组时, 数组的类型就是根据上下文推断出来的。 + + +#### 3.2 方法引用 + +通常与Lambda表达式联合使用,可以直接引用已有Java类或对象的方法。 + +方法引用的标准形式是:类名::方法名。(注意:只需要写方法名,不需要写括号) + +有以下四种形式的方法引用: + + +类型 | 示例 +--- |---- +引用静态方法 | ContainingClass::staticMethodName +引用某个对象的实例方法 | ContainingObject::instanceMethodName +引用某个类型的任意对象的实例方法 | ContainingType::methodName +引用构造方法 | ClassName::new + +凡是使用Lambda 表达式的地方,就可以使用方法引用。 + +#### 3.3 函数接口 + +函数接口是只有一个抽象方法的接口,用作Lambda 表达式的类型。 + +##### @FunctionalInterface注解 +该注释会强制javac 检查一个接口是否符合函数接口的标准。如果该注释添加给一个枚举类型、类或另一个注释,或者接口包含不止一个抽象方法,javac就会报错。重构代码时,使用它能很容易发现问题。 + +### 4. 流 + +Stream 是用函数式编程方式在集合类上进行复杂操作的工具。 + +#### 4.1 惰性求值和及早求值 + +如果返回值是Stream, +那么是惰性求值;如果返回值是另一个值或为空,那么就是及早求值。 + +通用形式为: + +> Stream.惰性求值.惰性求值. ... .惰性求值.及早求值 + +#### 4.2 内部迭代和外部迭代 +- 外部迭代,顾名思义,就是迭代是发生在Collection外层,由外部程序控制。 +``` +List alphabets = Arrays.asList(new String[]{"a","b","b","d"}); + +for(String letter: alphabets){ + System.out.println(letter.toUpperCase()); +} +``` +``` +List alphabets = Arrays.asList(new String[]{"a","b","b","d"}); + +Iterator iterator = alphabets.listIterator(); +while(iterator.hasNext()){ + System.out.println(iterator.next().toUpperCase()); +} +``` +- 对于内部迭代,表示迭代完全有Collection内部控制,进行迭代。 +``` +List alphabets = Arrays.asList(new String []{"a","b","b","d"}); + +alphabets.forEach(l -> System.out.println(l.toUpperCase())); + +``` + +#### 4.3 创建stream + +- 使用Stream.of(val1,val2,val3...) +``` +Stream stream = Stream.of(1,2,3,4,5,6,7,8,9); +stream.forEach(p -> System.out.println(p)); + +Stream stream = Stream.of(1,2,3,4,5,6,7,8,9); +stream.forEach(System.out::println); +``` +- 使用 Stream.of(arrayOfElements) +``` +Stream stream = Stream.of(new Integer[]{1,2,3,4,5,6,7,8,9} ); +stream.forEach(p -> System.out.println(p)); + +List list = new ArrayList<>(Arrays.asList(1, 2, 3)); +Stream> listStream = Stream.of(list); +listStream.forEach(System.out::println); +# output:[1, 2, 3] +``` + +- 使用 List.stream() +``` +Stream stream = list.stream(); +stream.forEach(p -> System.out.println(p)); + +int[] ints = new int[]{1,3,4,2,5}; +IntStream intStream= Arrays.stream(ints); +intStream.forEach(System.out::print); +``` + +- 使用Stream.generator() + +``` +Stream stream = Stream.generate(() -> { return new Date();}); +stream.forEach(p -> System.out.println(p)); + +``` + +- 使用String.chars() 或者 tokens +``` +IntStream stream = "12345_abcdefg".chars(); +stream.forEach(p -> System.out.println(p)); + +//OR + +Stream stream = Stream.of("A$B$C".split("\\$")); +stream.forEach(p -> System.out.println(p)); +``` + + +#### 4.4 处理顺序 +``` +Stream.of("d2", "a2", "b1", "b3", "c") + .filter(s -> { + System.out.println("filter: " + s); + return true; + }) + .forEach(s -> System.out.println("forEach: " + s)); +``` +处理结果 +``` +filter: d2 +forEach: d2 +filter: a2 +forEach: a2 +filter: b1 +forEach: b1 +filter: b3 +forEach: b3 +filter: c +forEach: c +``` + + + +操作类 | 接口方法 +--- | --- +中间操作 | concat distinct filter flatMap limit map peek skip sorted parallel sequential unordered +结束操作 | allMatch anyMatch collect count findAny findFirst forEach forEachOrdered max min noneMatch reduce toArray + +#### 4.5 中间操作 +中间操作返回的是Stream本身,所以多个中间操作可以作为处理链处理Stream + + + +#### 1. map +将一个流中的值转换成一个新的流 +``` +List collected = Stream.of("a", "b", "hello") + .map(string -> string.toUpperCase()) + .collect(toList()); +``` + +mapToInt将Stream中的元素转换成int类型的 +``` +Stream.of(1, 2, 3).mapToInt(data -> data * 10).forEach(System.out::println); +``` +mapToLong和mapToDouble与此类似。 + + +#### 2. filter +对stream中所有元素进行过滤 +``` +List beginningWithNumbers = + Stream.of("a", "1abc", "abc1") + .filter(value -> isDigit(value.charAt(0))) + .collect(toList()); +``` +#### 3. flatMap +可用 Stream 替换值,然后将多个 Stream 连接成一个 Stream +``` +List together = Stream.of(asList(1, 2), asList(3, 4)) + .flatMap(numbers -> numbers.stream()) + .collect(toList()); +``` +- flatMapToInt +- flatMapToLong +- flatMapToDouble + +flatMap和map的区别 +``` +List list2 = Arrays.asList("hello", "hi", "你好"); +List list3 = Arrays.asList("zhangsan", "lisi", "wangwu", "zhaoliu"); + +list2.stream() + .map(item -> list3.stream().map(item2 -> item + " " + item2)) + .collect(Collectors.toList()) + .forEach(stream -> stream.forEach(System.out::println)); +System.out.println("----------"); +list2.stream() + .flatMap(item -> list3.stream().map(item2 -> item + " " + item2)) + .collect(Collectors.toList()) + .forEach(System.out::println); +``` +#### 4. sorted +返回一个排序的Stream 视图,sorted() 默认自然排序,同时也可以定制Comparator +``` +memberNames.stream().sorted() + .map(String::toUpperCase) + .forEach(System.out::println); +``` +自然排序 +``` +list.stream().sorted() +``` + +自然序逆序元素,使用Comparator 提供的reverseOrder() 方法 + +``` +list.stream().sorted(Comparator.reverseOrder()) +``` +使用Comparator 来排序一个list + +``` +list.stream().sorted(Comparator.comparing(Student::getAge)) + +``` +把上面的元素逆序 +``` +list.stream().sorted(Comparator.comparing(Student::getAge).reversed()) + +``` +list直接排序 +``` +list.sort(Comparator.comparing(Integer::intValue)); + +list.sort(Comparator.comparing(Integer::intValue).reversed()); + +list.sort(Comparator.comparing(Student::getAge)); + +list.sort(Comparator.comparing(Student::getAge).reversed()); +``` +#### 5. limit +对一个Stream进行截断操作,获取其前N个元素,如果原Stream中包含的元素个数小于N,那就获取其所有的元素 + +#### 6. skip +返回一个丢弃原Stream的前N个元素后剩下元素组成的新Stream,如果原Stream中包含的元素个数小于N,那么返回空Stream. + +#### 7. peek +peek方法也是接收一个Consumer功能型接口,它与forEach的区别就是它会返回Stream接口,也就是说forEach是一个Terminal操作,而peek是一个Intermediate操作,forEach完了以后Stream就消费完了,不能继续再使用,而peek还可以继续使用。 +``` +Stream stream = Stream.of("I", "love", "you"); + stream.peek(System.out::println).forEach(System.out::println); +``` +输出结果: +``` +I +I +love +love +you +you +``` + +#### 8. distinct +对于Stream中包含的元素进行去重操作(去重逻辑依赖元素的equals方法),新生成的Stream中没有重复的元素 + +``` +List numList = Arrays.asList(1, 1, null, 2, 3, 4, null, 5, 6, 7, 8, 9, 10); +System.out.println("sum is " + numList.stream().filter(num -> num != null). + distinct().mapToInt(num -> num * 2). + peek(System.out::println).skip(2).limit(4).sum()); +``` + +#### 9. concat +将两个Stream合并成一个,这个方法一次只能用来合并两个Stream,不能一次多个Stream合并。 +``` +Streamstream1=Arrays.asList(1,2,3).stream(); +Streamstream2=Arrays.asList("a","b","c").stream(); +Stream.concat(stream1,stream2).forEach(System.out::print); +``` +#### 流复用 +Java 8 streams不能被复用,当你执行完任何一个最终操作(terminal operation)的时候流就被关闭了。 +``` +Stream stream = + Stream.of("d2", "a2", "b1", "b3", "c") + .filter(s -> s.startsWith("a")); + +stream.anyMatch(s -> true); // ok +stream.noneMatch(s -> true); // exception +``` +可以通过为每个最终操作(terminal operation)创建一个新的stream链的方式来解决上面的重用问题,Stream api中已经提供了一个stream supplier类来在已经存在的中间操作(intermediate operations )的stream基础上构建一个新的stream。 + +``` +Supplier> streamSupplier = + () -> Stream.of("d2", "a2", "b1", "b3", "c") + .filter(s -> s.startsWith("a")); + +streamSupplier.get().anyMatch(s -> true); // ok +streamSupplier.get().noneMatch(s -> true); // ok +``` +streamSupplier的每个get()方法会构造一个新的stream,我们可以在这个stream上执行期望的最终操作(terminal operation)。 + +#### 4.6 终止操作 +终止操作会返回一个特定的类型,而不是返回Stream本身。 + +#### collect +collect(toList()) 方法由 Stream 里的值生成一个列表,是一个及早求值操作。可以理解为 Stream 向 Collection 的转换。 + +注意这边的 toList() 其实是 Collectors.toList(),因为采用了静态倒入,看起来显得简洁。 + +``` +List collected = Stream.of("a", "b", "c") + .collect(Collectors.toList()); +``` + +##### toList +toList收集器通过使用List的add方法将元素添加到一个结果List列表中,toList收集器使用ArrayList作为List的实现。 + +##### toSet +toSet 方法采用HashSet作为Set的实现来储存结果集。 + +##### toMap +``` +List list = new ArrayList<>(); +list.add(new Person(1, "haha")); +list.add(new Person(2, "rere")); +list.add(new Person(3, "fefe")); +Map map = list.stream() + .collect(Collectors.toMap(Person::getId, Function.identity())); +System.out.println(map); +Map newmap = list.stream() + .collect(Collectors.toMap(Person::getId, Person::getName)); +System.out.println(newmap); +``` +Function.identity()-->返回stream中的元素 + +使用Collectors.toMap方法时的两个问题: + +1、当key重复时,会抛出异常:java.lang.IllegalStateException: Duplicate key ** + +2、当value为null时,会抛出异常:java.lang.NullPointerException +``` +List userList = new ArrayList<>(); +userList.add(new User(1L, "aaa")); +userList.add(new User(2L, "bbb")); +userList.add(new User(3L, "ccc")); +userList.add(new User(2L, "ddd")); +userList.add(new User(3L, "eee")); +``` +``` +Map map = userList.stream() + .collect(Collectors.toMap(User::getId, User::getUserName, (v1, v2) -> v1)); +System.out.println(map); +``` +``` +{1=aaa, 2=bbb, 3=ccc} + +``` +``` +Map map = userList.stream() + .collect(Collectors.toMap(User::getId, User::getUserName, (v1, v2) -> v2)); +System.out.println(map); +``` +``` +{1=aaa, 2=ddd, 3=eee} +``` +#### foreach +遍历stream所有的元素 +``` +memberNames.forEach(System.out::println); +``` +``` +// 使用forEach()结合Lambda表达式迭代Map +HashMap map = new HashMap<>(); +map.put(1, "one"); +map.put(2, "two"); +map.put(3, "three"); +map.forEach((k, v) -> System.out.println(k + " = " + v)); +``` +``` +# getOrDefault() +该方法跟Lambda表达式没关系,但是很有用。方法签名为V getOrDefault(Object key, V defaultValue),作用是按照给定的key查询Map中对应的value,如果没有找到则返回defaultValue。使用该方法程序员可以省去查询指定键值是否存在的麻烦. +// 查询Map中指定的值,不存在时使用默认值 +HashMap map = new HashMap<>(); +map.put(1, "one"); +map.put(2, "two"); +map.put(3, "three"); +// Java7以及之前做法 +if(map.containsKey(4)){ // 1 + System.out.println(map.get(4)); +} else { + System.out.println("NoValue"); +} +// Java8使用Map.getOrDefault() +System.out.println(map.getOrDefault(4, "NoValue")); // 2 + +``` + +forEachOrdered +``` +List list = Arrays.asList("x", "y", "z"); + +list.parallelStream().forEach(System.out::print); +System.out.println(); +list.parallelStream().forEachOrdered(System.out::print); +``` +#### match +用来判断Stream中的某个元素是否符合某项断言 + +Stream 有三个 match 方法,从语义上说: + +- allMatch:Stream 中全部元素符合传入的 predicate,返回 true +- anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true +- noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true + +``` +boolean matchedResult = memberNames.stream() + .anyMatch((s) -> s.startsWith("A")); + +System.out.println(matchedResult); + +matchedResult = memberNames.stream() + .allMatch((s) -> s.startsWith("A")); + +System.out.println(matchedResult); + +matchedResult = memberNames.stream() + .noneMatch((s) -> s.startsWith("A")); +``` +#### count +统计stream的元素个数,返回long +``` +long totalMatched = memberNames.stream() + .filter((s) -> s.startsWith("A")) + .count(); +``` + + +#### max和min + +求最大值和最小值 + +``` +List list = Lists.newArrayList(3, 5, 2, 9, 1); +int maxInt = list.stream() + .max(Integer::compareTo) + .get(); +int minInt = list.stream() + .min(Integer::compareTo) + .get(); +``` +#### sum +求和 +``` +int sum = Stream.of(1, 2, 4).mapToInt(Integer::intValue).sum(); + +or + +int sum = Stream.of(1, 2, 4).mapToInt(x -> x.intValue()).sum(); +``` +#### reduce +reduce 操作可以实现从一组值中生成一个值。 +``` +int result = Stream.of(1, 2, 3, 4) + .reduce(0, (acc, element) -> acc + element); +``` +``` +Optional reduced = memberNames.stream() + .reduce((s1,s2) -> s1 + "#" + s2); +``` +``` +// 字符串连接,concat = "ABCD" +String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat); +// 求最小值,minValue = -3.0 +double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min); +// 求和,sumValue = 10, 有起始值 +int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum); +// 求和,sumValue = 10, 无起始值 +sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get(); +// 过滤,字符串连接,concat = "ace" +concat = Stream.of("a", "B", "c", "D", "e", "F"). + filter(x -> x.compareTo("Z") > 0). + reduce("", String::concat); +``` +#### summaryStatistics +``` +List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); +IntSummaryStatistics stats = numbers + .stream() + .mapToInt((x) -> x) + .summaryStatistics(); +System.out.println(stats.toString()); +System.out.println("List中最大的数字 : " + stats.getMax()); +System.out.println("List中最小的数字 : " + stats.getMin()); +System.out.println("所有数字的总和 : " + stats.getSum()); +System.out.println("所有数字的平均值 : " + stats.getAverage()); +``` +#### 例子 + +``` +List biu = Stream.of("a", "b", "hello").map(String::toUpperCase).collect(toList()); +System.out.println(biu.toString()); + +List groceryTransactions = new Arraylist<>(); +for(Transaction t: transactions){ + if(t.getType() == Transaction.GROCERY){ + groceryTransactions.add(t); + } +} +Collections.sort(groceryTransactions, new Comparator(){ + public int compare(Transaction t1, Transaction t2){ + return t2.getValue().compareTo(t1.getValue()); + } +}); +List transactionIds = new ArrayList<>(); +for(Transaction t: groceryTransactions){ + transactionsIds.add(t.getId()); +} +``` +#### Java 8 的排序、取值实现 +``` +List transactionsIds = transactions.parallelStream(). + filter(t -> t.getType() == Transaction.GROCERY). + sorted(comparing(Transaction::getValue).reversed()). + map(Transaction::getId). + collect(toList()); +``` +### 5. 收集器 +收集器可用来计算流的最终值,是 reduce 方法的模拟。 +``` +// 指定收集类型 +public void toCollectionTreeset() { + Stream stream = Stream.of(1, 2, 3); + stream.collect(Collectors.toCollection(TreeSet::new)); +} + +// 最大值 +public Optional biggestGroup(Stream artists) { + Function getCount = artist -> artist.getMembers().count(); + return artists.collect(Collectors.maxBy(comparing(getCount))); +} + +// 平均值 +public double averageNumberOfTracks(List albums) { + return albums.stream() + .collect(Collectors.averagingInt(album -> album.getTrackList().size())); +} + +// 数组分块,使用Predicate函数接口判断,ture一块;false一块 +public Map> bandsAndSolo(Stream artists) { + return artists.collect(Collectors.partitioningBy(Artist::isSolo)); +} + +// 数据分组 +public Map> albumsByArtist(Stream albums) { + return albums.collect(Collectors.groupingBy(Album::getMainMusician)); +} + +// 字符串合并 +public static String formatArtists(List artists) { + return artists.stream() + .map(Artist::getName) + .collect(Collectors.joining(", ", "[", "]")); +} + +// 分组后获取每组数量的总数 +public Map numberOfAlbums(Stream albums) { + return albums.collect(Collectors.groupingBy(Album::getMainMusician, Collectors.counting())); +} + +// 分组后获取每组数据中的映射数据 +public Map> nameOfAlbums(Stream albums) { + return albums.collect(Collectors.groupingBy(Album::getMainMusician, + Collectors.mapping(Album::getName, Collectors.toList()))); +} +``` +``` +Map> personGroups = Stream.generate(new PersonSupplier()). + limit(100). + collect(Collectors.groupingBy(Person::getAge)); +Iterator it = personGroups.entrySet().iterator(); +while (it.hasNext()) { + Map.Entry> persons = (Map.Entry) it.next(); + System.out.println("Age " + persons.getKey() + " = " + persons.getValue().size()); +} +``` +``` +public static void main(String[] args) { + List languages = Arrays.asList("Java", "Scala", "C++", "Haskell", "Lisp"); + + System.out.println("Languages which starts with J :"); + filter(languages, (str) -> str.startsWith("J")); + + System.out.println("Languages which ends with a "); + filter(languages, (str) -> str.endsWith("a")); + + System.out.println("Print all languages :"); + filter(languages, (str) -> true); + + System.out.println("Print no language : "); + filter(languages, (str) -> false); + + System.out.println("Print language whose length greater than 4:"); + filter(languages, (str) -> str.length() > 4); +} + +public static void filter(List names, Predicate condition) { + names.stream().filter((name) -> (condition.test(name))) + .forEach((name) -> System.out.println(name + " ")); +} +``` +Output +``` +Languages which starts with J : +Java +Languages which ends with a +Java +Scala +Print all languages : +Java +Scala +C++ +Haskell +Lisp +Print no language : +Print language whose length greater than 4: +Scala +Haskell +``` + +#### 总结 +总之,Stream 的特性可以归纳为: + +- 不是数据结构 +- 它没有内部存储,它只是用操作管道从 source(数据结构、数组、generator function、IO channel)抓取数据。 +- 它也绝不修改自己所封装的底层数据结构的数据。例如 Stream 的 filter 操作会产生一个不包含被过滤元素的新 Stream,而不是从 source 删除那些元素。 +- 所有 Stream 的操作必须以 lambda 表达式为参数 +- 不支持索引访问 +- 你可以请求第一个元素,但无法请求第二个,第三个,或最后一个。不过请参阅下一项。 +- 很容易生成数组或者 List +- 惰性化 +- 很多 Stream 操作是向后延迟的,一直到它弄清楚了最后需要多少数据才会开始。 +Intermediate 操作永远是惰性化的。 +- 并行能力 +当一个 Stream 是并行化的,就不需要再写多线程代码,所有对它的操作会自动并行进行的。 +- 可以是无限的 + - 集合有固定大小,Stream 则不必。limit(n) 和 findFirst() 这类的 short-circuiting 操作可以对无限的 Stream 进行运算并很快完成。 + + +### 6. 测试或调试 +peek 方法能记录中间值,在调试时非常有用。 + +对 Stream API 的调试,IDEA 官方开发了一个 Plugin──Java Stream Debugger 来扩展 IDEA 中的 debug 工具。在 debug 的工具栏上增加了 Trace Current Stream Chain 按钮。 + +这个工具超级厉害! \ No newline at end of file