Java 8 函数式编程学习笔记

来源:互联网 发布:字体识别软件 编辑:程序博客网 时间:2024/06/05 06:06

Java 8 函数式编程学习笔记

@(JAVASE)[java8, 函数式编程, lambda]

  • Java 8 函数式编程学习笔记
    • 参考内容
      • Java 8中重要的函数接口
        • 扩展函数接口
      • 常用的流操作
      • reduce模式
        • 基本原理
        • reduce方法API
        • 案例
          • 使用reduce和Lambda表达式实现map
          • 使用reduce和Lambda表达式实现filter
      • 类库
        • 基本类型
        • 重载解析
          • 总结
          • 情况一
          • 情况二
          • 情况三
        • 默认方法
        • Optional
      • 高级集合类和收集器
        • 收集器
          • 定制收集器
            • 案例1
            • 案例2
      • 数据并行化
        • 模拟系统
        • 限制
        • 性能
        • 并行化数组操作
      • 调试

参考内容

Java 8中重要的函数接口

接口 参数 返回类型 示例 Predicate<T> T boolean 这张唱片已经发行了吗 Consumer<T> T void 输出一个值 Function<T,R> T R 获得Artist 对象的名字 Supplier<T> None T 工厂方法 UnaryOperator<T> T T 逻辑非(!) BinaryOperator<T> (T, T) T 求两个数的乘积(*)

扩展函数接口

接口 参数 返回类型 示例 ToLongFunction<T> T long LongFunction<R> long R LongUnaryOperator long long

常用的流操作

    // collect    @Test    public void collectToList() {        List<String> collected = Stream.of("a", "b", "c").collect(Collectors.toList());        assertEquals(Arrays.asList("a", "b", "c"), collected);    }    // map    @Test    public void mapToUpperCase() {        List<String> collected = Stream.of("a", "b", "hello").map(String::toUpperCase).collect(toList());        assertEquals(asList("A", "B", "HELLO"), collected);    }    // filter    @Test    public void functionalStringsWithNumbers() {        List<String> beginningWithNumbers = Stream.of("a", "1abc", "abc1")                .filter(value -> Character.isDigit(value.charAt(0))).collect(Collectors.toList());        assertEquals(Collections.singletonList("1abc"), beginningWithNumbers);    }    // flatMap    @Test    public void flatMapCharacters() {        List<Integer> together = Stream.of(Arrays.asList(1, 2), Arrays.asList(3, 4))                .flatMap(Collection::stream).collect(Collectors.toList());        assertEquals(asList(1, 2, 3, 4), together);    }    // min    @Test    public void streamsMinLength() {        List<Track> tracks = asList(                new Track("Bakai", 524),                new Track("Violets for Your Furs", 378),                new Track("Time Was", 451));        Track shortestTrack = tracks.stream().min(Comparator.comparing(Track::getLength)).get();        assertEquals(tracks.get(1), shortestTrack);    }    // reduce    @Test    public void sumUsingReduce() {        int count = Stream.of(1, 2, 3).reduce(0, (acc, element) -> acc + element);        assertEquals(6, count);    }    @Test    public void expandedReduce() {        BinaryOperator<Integer> accumulator = (acc, element) -> acc + element;        int count = accumulator.apply(accumulator.apply(accumulator.apply(0, 1), 2), 3);        assertEquals(6, count);    }    @Test    public void countUsingReduceFor() {        int acc = 0;        for (Integer element : asList(1, 2, 3)) {            acc = acc + element;        }        assertEquals(6, acc);    }

reduce模式

基本原理

    // 初始值    int acc = 0;    for (Integer element : asList(1, 2, 3)) {        // 累加处理+合并处理        acc = acc + element;    }

reduce方法API

    // accumulator 累加处理    Optional<T> reduce(BinaryOperator<T> accumulator);    // identity 初始值    // accumulator 累加处理    T reduce(T identity, BinaryOperator<T> accumulator);    // identity 初始值    // accumulator 累加处理    // combiner 合并处理    <U> U reduce(U identity,                 BiFunction<U, ? super T, U> accumulator,                 BinaryOperator<U> combiner);

案例

使用reduce和Lambda表达式实现map
    public static <I, O> List<O> map(Stream<I> stream, Function<I, O> mapper) {        return stream.reduce(new ArrayList<O>(), (acc, x) -> {           List<O> newAcc = new ArrayList<>(acc);           newAcc.add(mapper.apply(x));           return newAcc;        }, (List<O> left, List<O> right) -> {            List<O> newLeft = new ArrayList<>(left);            newLeft.addAll(right);            return newLeft;        });    }
使用reduce和Lambda表达式实现filter
    public static <I> List<I> filter(Stream<I> stream, Predicate<I> predicate) {        return stream.reduce(new ArrayList<I>(), (acc, x) -> {            List<I> newAcc = new ArrayList<>(acc);            if (predicate.test(x)) {                newAcc.add(x);            }            return newAcc;        }, (List<I> left, List<I> right) -> {            List<I> newLeft = new ArrayList<>(left);            newLeft.addAll(right);            return newLeft;        });    }

类库

基本类型

    public long countRunningTime() {        return countFeature(album -> album.getTracks()                .mapToLong(track -> track.getLength())                .sum());    }    public static void printTrackLengthStatistics(Album album) {        IntSummaryStatistics trackLengthStats = album.getTracks()                .mapToInt(Track::getLength).summaryStatistics();        System.out.printf("Max: %d, Min: %d, Ave: %f, Sum: %d",                trackLengthStats.getMax(),                trackLengthStats.getMin(),                trackLengthStats.getAverage(),                trackLengthStats.getSum());    }

重载解析

总结

总而言之,Lambda表达式作为参数时,其类型由它的目标类型推导得出,推导过程遵循如下规则:
- 如果只有一个可能的目标类型,由相应函数接口里的参数类型推导得出;
- 如果有多个可能的目标类型,由最具体的类型推导得出;
- 如果有多个可能的目标类型且最具体的类型不明确,则需人为指定类型。

PS:三种情况分别对应,无重载、有重载且重载参数为父子关系、有重载且重载参数无关联。

情况一
    private void overloadedMethod(Predicate<Integer> predicate) {        System.out.print("Predicate");    }    @Test    public void mostSpecificPredicate() {        // 输出:IntPredicate        overloadedMethod((x) -> true);    }
情况二
    private interface IntPredicate extends Predicate<Integer>{        @Override        boolean test(Integer value);    }    private void overloadedMethod(Predicate<Integer> predicate) {        System.out.print("Predicate");    }    private void overloadedMethod(IntPredicate predicate) {        System.out.print("IntPredicate");    }    @Test    public void mostSpecificPredicate() {        // 输出:IntPredicate        overloadedMethod((x) -> true);    }
情况三
    private interface IntPredicate {        public boolean test(int value);    }    private void overloadedMethod(Predicate<Integer> predicate) {        System.out.print("Predicate");    }    private void overloadedMethod(IntPredicate predicate) {        System.out.print("IntPredicate");    }    @Test    public void mostSpecificPredicate() {        // 输出:IntPredicate        overloadedMethod((IntPredicate) (x) -> true);    }

默认方法

三定律
1. 类胜于接口。如果在继承链中有方法体或抽象的方法声明,那么就可以忽略接口中定义的方法。
2. 子类胜于父类。如果一个接口继承了另一个接口,且两个接口都定义了一个默认方法,那么子类中定义的方法胜出。
3. 没有规则三。如果上面两条规则不适用,子类要么需要实现该方法,要么将该方法声明为抽象方法。

其中第一条规则是为了让代码向后兼容。

    public default void welcome() {        message("Parent: Hi!");    }

Optional

    @Test    public void examples() {        Optional<String> a = Optional.of("a");        assertEquals("a", a.get());        Optional emptyOptional = Optional.empty();        Optional alsoEmpty = Optional.ofNullable(null);        assertFalse(emptyOptional.isPresent());        assertTrue(a.isPresent());        assertEquals("b", emptyOptional.orElse("b"));        assertEquals("c", emptyOptional.orElseGet(() -> "c"));    }

高级集合类和收集器

收集器

    // 指定收集类型    public void toCollectionTreeset() {        Stream<Integer> stream = Stream.of(1, 2, 3);        stream.collect(Collectors.toCollection(TreeSet::new));    }    // 最大值    public Optional<Artist> biggestGroup(Stream<Artist> artists) {        Function<Artist, Long> getCount = artist -> artist.getMembers().count();        return artists.collect(Collectors.maxBy(comparing(getCount)));    }    // 平均值    public double averageNumberOfTracks(List<Album> albums) {        return albums.stream()                .collect(Collectors.averagingInt(album -> album.getTrackList().size()));    }    // 数组分块,使用Predicate函数接口判断,ture一块;false一块    public Map<Boolean, List<Artist>> bandsAndSolo(Stream<Artist> artists) {        return artists.collect(Collectors.partitioningBy(Artist::isSolo));    }    // 数据分组    public Map<Artist, List<Album>> albumsByArtist(Stream<Album> albums) {        return albums.collect(Collectors.groupingBy(Album::getMainMusician));    }    // 字符串合并    public static String formatArtists(List<Artist> artists) {        return artists.stream()                .map(Artist::getName)                .collect(Collectors.joining(", ", "[", "]"));    }    // 分组后获取每组数量的总数    public Map<Artist, Long> numberOfAlbums(Stream<Album> albums) {        return albums.collect(Collectors.groupingBy(Album::getMainMusician, Collectors.counting()));    }    // 分组后获取每组数据中的映射数据    public Map<Artist, List<String>> nameOfAlbums(Stream<Album> albums) {        return albums.collect(Collectors.groupingBy(Album::getMainMusician,                Collectors.mapping(Album::getName, Collectors.toList())));    }
定制收集器
案例1
    // reduce实现    public static String formatArtistsRefactor4(List<Artist> artists) {        return artists.stream()                .map(Artist::getName)                .reduce(new StringCombiner(", ", "[", "]"),                        StringCombiner::add,                        StringCombiner::merge)                .toString();    }    // 自定义收集器    public static String formatArtistsRefactor5(List<Artist> artists) {        return artists.stream()                .map(Artist::getName)                .collect(new StringCollector(", ", "[", "]"));    }    // reducing收集器    public static String formatArtistsReducing(List<Artist> artists) {        return artists.stream()                .map(Artist::getName)                .collect(Collectors.reducing(                        new StringCombiner(", ", "[", "]"),                        name -> new StringCombiner(",", "[", "]").add(name),                        StringCombiner::merge))                .toString();    }
import java.util.Collections;import java.util.Set;import java.util.function.BiConsumer;import java.util.function.BinaryOperator;import java.util.function.Function;import java.util.function.Supplier;import java.util.stream.Collector;public class StringCollector implements Collector<String, StringCombiner, String> {    private static final Set<Characteristics> characteristics = Collections.emptySet();    private final String delim;    private final String prefix;    private final String suffix;    public StringCollector(String delim, String prefix, String suffix) {        this.delim = delim;        this.prefix = prefix;        this.suffix = suffix;    }    @Override    public Supplier<StringCombiner> supplier() {        return () -> new StringCombiner(delim, prefix, suffix);    }    @Override    public BiConsumer<StringCombiner, String> accumulator() {        return StringCombiner::add;    }    @Override    public BinaryOperator<StringCombiner> combiner() {        return StringCombiner::merge;    }    @Override    public Function<StringCombiner, String> finisher() {        return StringCombiner::toString;    }    @Override    public Set<Characteristics> characteristics() {        return characteristics;    }}
public class StringCombiner {    private final String prefix;    private final String suffix;    private final String delim;    private final StringBuilder buIlder;    public StringCombiner(String delim, String prefix, String suffix) {        this.prefix = prefix;        this.suffix = suffix;        this.delim = delim;        this.buIlder = new StringBuilder();    }    public StringCombiner add(String word) {        if (!this.areAtStart()) {            this.buIlder.append(delim);        }        this.buIlder.append(word);        return this;    }    public StringCombiner merge(StringCombiner other) {        if (!other.equals(this)) {            if (!other.areAtStart() && !this.areAtStart()) {                other.buIlder.insert(0, this.delim);            }            this.buIlder.append(other.buIlder);        }        return this;    }    @Override    public String toString() {        return prefix + buIlder.toString() + suffix;    }    private boolean areAtStart() {        return buIlder.length() == 0;    }}
案例2
import java.util.*;import java.util.function.BiConsumer;import java.util.function.BinaryOperator;import java.util.function.Function;import java.util.function.Supplier;import java.util.stream.Collector;public class GroupingBy<T, K> implements Collector<T, Map<K, List<T>>, Map<K, List<T>>> {    private final static Set<Characteristics> characteristics = new HashSet<>();    static {        characteristics.add(Characteristics.IDENTITY_FINISH);    }    private final Function<? super T, ? extends K> classifier;    public GroupingBy(Function<? super T, ? extends K> classifier) {        this.classifier = classifier;    }    @Override    public Supplier<Map<K, List<T>>> supplier() {        return HashMap::new;    }    @Override    public BiConsumer<Map<K, List<T>>, T> accumulator() {        return (map, element) -> {            K key = classifier.apply(element);            List<T> elements = map.computeIfAbsent(key, k -> new ArrayList<>());            elements.add(element);        };    }    @Override    public BinaryOperator<Map<K, List<T>>> combiner() {        return (left, right) -> {            right.forEach((key, value) -> left.merge(key, value, (leftValue, rightValue) -> {                leftValue.addAll(rightValue);                return leftValue;            }));            return left;        };    }    @Override    public Function<Map<K, List<T>>, Map<K, List<T>>> finisher() {        return map -> map;    }    @Override    public Set<Characteristics> characteristics() {        return characteristics;    }}

数据并行化

模拟系统

import org.openjdk.jmh.annotations.*;import org.openjdk.jmh.runner.Runner;import org.openjdk.jmh.runner.RunnerException;import org.openjdk.jmh.runner.options.Options;import org.openjdk.jmh.runner.options.OptionsBuilder;import java.util.Map;import java.util.concurrent.ThreadLocalRandom;import java.util.function.IntFunction;import java.util.stream.IntStream;import static java.util.stream.Collectors.groupingBy;import static java.util.stream.Collectors.summingDouble;@State(Scope.Benchmark)@BenchmarkMode(Mode.All)public class DiceRolls {    private static final int N = 1000000;    public static void main(String[] args) throws RunnerException {        Options options = new OptionsBuilder()            .include(DiceRolls.class.getSimpleName())            .forks(1)            .build();        new Runner(options).run();    }    @Benchmark    public Map<Integer, Double> serialDiceRolls() {        double fraction = 1.0 / N;        return IntStream.range(0, N)                .mapToObj(twoDiceThrows())                .collect(groupingBy(side -> side, summingDouble(n -> fraction)));    }    @Benchmark    public Map<Integer, Double> parallelDiceRolls() {        double fraction = 1.0 / N;        return IntStream.range(0, N)                .parallel()                .mapToObj(twoDiceThrows())                .collect(groupingBy(side -> side, summingDouble(n -> fraction)));    }    private static IntFunction<Integer> twoDiceThrows() {        return i -> {            ThreadLocalRandom random = ThreadLocalRandom.current();            int firstThrow = random.nextInt(1, 7);            int secondThrow = random.nextInt(1, 7);            return firstThrow + secondThrow;        };    }}

限制

  1. 初值必须为组合函数的恒等值。例:(acc, element) -> acc + element,初值为0
  2. 组合操作必须符合结合律。例:(4+2)+1=4+(2+1)=7
  3. parallel方法、sequential方法。在要对流求值时,不能同时处于两种模式,要么是并行的,要么是串行的。如果同时调用了parallel和sequential方法,最后调用的那个方法起效。

性能

  • 数据大小
    • 只有数据足够大、每个数据处理管道花费的时间足够多时,并行化处理才有意义。
  • 源数据结构
    • 数据源分割开销影响了在管道中并行处理数据时到底能带来多少性能上的提升。
  • 装箱
    • 处理基本类型比处理装箱类型要快。
  • 核的数量
    • 极端情况下,只有一个核,因此完全没必要并行化。显然,拥有的核越多,获得潜在性能提升的幅度就越大。在实践中,核的数量不单指你的机器上有多少核,更是指运行时你的机器能使用多少核。这也就是说同时运行的其他进程,或者线程关联性(强制线程在某些核或CPU 上运行)会影响性能。
  • 单元处理开销
    • 比如数据大小,这是一场并行执行花费时间和分解合并操作开销之间的战争。花在流中每个元素身上的时间越长,并行操作带来的性能提升越明显。

并行化数组操作

方法名 操作 parallelPrefix 任意给定一个函数,计算数组的和 parallelSetAll 使用Lambda表达式更新数组元素 parallelSort 并行化对数组元素排序

调试

    public static Set<String> nationalityReportUsingPeek(Album album) {        Set<String> nationalities = album.getMusicians()                .filter(artist -> artist.getName().startsWith("The"))                .map(artist -> artist.getNationality())                .peek(nation -> System.out.println("Found nationality: " + nation))                .collect(Collectors.<String>toSet());        return nationalities;    }

———–参考《Java 8 函数式编程》

原创粉丝点击