Java 8 函数式编程学习笔记
Java 8 函数式编程学习笔记@(JAVASE)[java8, 函数式编程, lambda]Java 8 函数式编程学习笔记参考内容Java 8中重要的函数接口扩展函数接口常用的流操作reduce模式基本原理reduce方法API案例使用reduce和Lambda表达式实现map使用reduce和Lambda表达式实现filter类库基本类型重载解析总结情况一情况二
·
Java 8 函数式编程学习笔记
@(JAVASE)[java8, 函数式编程, lambda]
参考内容
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;
};
}
}
限制
- 初值必须为组合函数的恒等值。例:
(acc, element) -> acc + element
,初值为0 - 组合操作必须符合结合律。例: (4+2)+1=4+(2+1)=7
- 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 函数式编程》
更多推荐
已为社区贡献5条内容
所有评论(0)