1 Lamdba优于匿名内部类

(1)DEMO1

  • 匿名内部类:过时
Collections.sort(words, new Comparator<String>() {
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
});
  • 上述使用了策略模式,Comparator接口为排序的抽象策略,匿名内部类为具体实现策略,但是匿名内部类的实现过于冗长。

  • 在java8中,如果一个接口只有一个方法,那么这个接口可以看作一个函数接口,功能接口的实现类可以通过lambda来实现,lambda与匿名内部类类似,但是更加简洁。

  • lamdba:常规

Collections.sort(words,(s1, s2) -> Integer.compare(s1.length(), s2.length()));
  • 参数为String类型,返回值为int类型,编译器是如何知道的呢?

  • 编译器使用称为类型推断的过程从上下文中推导出这些类型,但是编译器不是万能的,有时候仍然需要显式设定。

  • lamdba:方法引用

Collections.sort(words, comparingInt(String::length));
words.sort(comparingInt(String::length));

(2)DEMO2

  • 常量类:enum with function object
public enum Operation {
PLUS("+") {
public double apply(double x, double y) { return x + y; }
},
MINUS("-") {
public double apply(double x, double y) { return x - y; }
},
TIMES("*") {
public double apply(double x, double y) { return x * y; }
},
DIVIDE("/") {
public double apply(double x, double y) { return x / y; }
}; private final String symbol; Operation(String symbol) { this.symbol = symbol; } @Override public String toString() { return symbol; } public abstract double apply(double x, double y);
}
  • lambda:enum with function object
public enum Operation {
PLUS ("+", (x, y) -> x + y),
MINUS ("-", (x, y) -> x - y),
TIMES ("*", (x, y) -> x * y),
DIVIDE("/", (x, y) -> x / y);
private final String symbol;
private final DoubleBinaryOperator op; Operation(String symbol, DoubleBinaryOperator op) {
this.symbol = symbol;
this.op = op;
}
@Override public String toString() { return symbol; }
public double apply(double x, double y) {
return op.applyAsDouble(x, y);
}
}
  • lambda中的不要超过三行。
  • lambda中无法访问枚举的实例成员。
  • lambda无法创建抽象类的实例,但匿名内部类可以。
  • lambda无法获取到对自身的引用。
  • 如果需要反序列化一个函数接口,如:Comparator,我们需要使用私有静态内部类。

2 lambda中优先使用方法引用

(1)DEMO

// lambda代码块
map.merge(key, 1, (count, incr) -> count + incr); // 方法引用
map.merge(key, 1, Integer::sum);

(2)类型

方法引用类型 例子 Lambda等效方案
Static Integer::parseInt str -> Integer.parseInt(str)
Bound Instant.now()::isAfter Instant then = Instant.now();
t -> then.isAfter(t)
Unbound String::toLowerCase str -> str.toLowerCase()
Class Constructor TreeMap::new () -> new TreeMap
Array Constructor int[]::new len -> new int[len]
  • 如果方法引用更加简洁和清晰,请使用方法引用,反之使用Lambda表达式。

3 优先使用标准功能接口

(1)模板方法模式

  • 由于Lambda的存在,通过子类重写基本方法以专门化超类的行为的方式有点过时。
  • 替代方案:提供一个静态工厂或者构造器,它们接收一个函数对象来实现相同的效果。
  • 一般来说,我们将编写更多以函数对象作为参数的构造函数和方法。
// 模板方法
abstract class A {
public void print() {
System.out.println("A");
doSubThing();
} abstract void doSubThing();
} class B extends A {
@Override
void doSubThing() {
System.out.println("B");
}
} // lambda
class A {
private Supplier<String> supplier; public A(Supplier<String> supplier) {
this.supplier = supplier;
} public void print() {
System.out.println("A");
System.out.println(supplier.get());
}
} public static void main(String[] args) {
A a = new A(() -> "B");
a.print();
}

(2)标准函数接口

接口 函数签名 例子
UnaryOperator<T> T apply(T t) String::toLowerCase
BinaryOperator<T> T apply(T t1, T t2) BigInteger::add
Predicate<T> boolean test(T t) Collection::isEmpty
Function<T> R apply(T t) Arrays::asList
Supplier<T> T get() Instant::now
Consumer<T> void accept(T t) System.out::println
  • 优先使用标准函数接口,这能够缩小概念表面积,从而降低学习成本。
  • 但是如果所有标准函数接口都不能很好表示时,请
  • 上述的六种接口拥有许多变种,如:int、long和double,甚至是int->long等等。
  • 其实大多数变种都使用了基础类型,而不是包装类型,基础类型的运算更快,节省内存空间,不要使用包装类去替换它们。
  • 其实我们并不会去记忆所有变种,变种随着JDK升级可能会增加或减少,只需要在使用时去翻翻java.util.function包是否有需要的接口即可。

具体请参考:JAVA8的java.util.function包

(3)Comparator接口

  • Comparator接口与ToIntBiFunction接口的结构相同,但是仍然不要用ToIntBiFunction去替代Comparator
  • 因为Comparator的名称含义十分清晰,它在jdk中已经广泛使用了,而且Comparator提供了许多有用默认方法。

(4)@FunctionalInterface

  • @FunctionalInterface注解能够帮助开发者检查这个接口是否只有一个抽象方法,如果不只一个将无法编译。
  • @FunctionalInterface目的:将某个接口标志为函数接口且提供编译时检查。

4 明智地使用Stream

4.1 概念

  • 流:无限或有限的数据元素序列。

  • 管道:对流中的元素进行多级计算。

  • 流的源:集合、数组、文件、正则表达式或模式匹配器、伪随机数生成器或其他流。

  • 管道操作:由源流后跟着零个或多个中间操作和一个终止操作。

  • 中间操作:某种转换流的方式,如:元素映射或元素过滤等。

  • 终止操作:执行最终计算,如:流装入容器中或是消费掉。

4.2 补充

  • 流管道只包含中间操作时是惰性的:当一个流没有最终操作时,流管道是什么都不做的。
  • 流管道的API被设计成链式编码风格。

4.3 流的使用时机

(1)不要滥用流
// 普通方式
// 读取文件中的单词,检查单词的字母,相同字母的单词收集在一起
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存在性能问题)。
(2)代码块与lambda
  • Stream的缺点

    • 代码块能够读取或修改范围内的局部变量,lambda只能操作final变量和当前范围的局部变量。
    • 代码块中能够return、抛出异常、跳出循环或是跳过循环,lambda中都无法做到。
  • Stream的优势
    • map:统一转换元素类型
    • filter:过滤序列
    • min、compute:计算最小值、合并序列等
    • reduce:累计序列
    • grouping:分组
(3)流无法做到同时在多级阶段访问相应的元素
  • 通过操作反转来获取上一个流元素
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 优先选择流中无副作用的功能

(1)概述

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

(2)Collectors的基本方法

名称 作用
toCollection toList toSet 流转换为集合
toMap 流转换为Map
partitioningBy groupingBy groupingByConcurrent 分组
minBy maxBy 最值
counting 计数
summingInt averagingInt 和 平均值
joining mapping 合并 映射

(3)示例

// 不遵守范式,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()));
}

(4)分组

  • 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"))
);
System.out.println(map); // {false=[2, 3], true=[1, 1]} Map<String, List<String>> map = words.stream().collect(
groupingBy(String::toLowerCase)
);
System.out.println(map); // {1=[1, 1], 2=[2], 3=[3]}

(5)List转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)
);

(6)List->字符串

// 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(",","[","]"));
System.out.println(join1); // 2113
System.out.println(join2); // 2,1,1,3
System.out.println(join3); //[2,1,1,3] // 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());
System.out.println(list1); // [2, 1, 1, 3]
System.out.println(list2); // [2, 1, 1, 3]

(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());

6 优先选择集合作为返回值

(1)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
}

(2)spliterator

// spliterator用于并行迭代
public static <E> Stream<E> streamOf(Iterable<E> iterable) {
return StreamSupport.stream(iterable.spliterator(), false);
} // 例子:并行计算1+2+...+10000
public static void main(String[] args) throws InterruptedException {
List<String> words = new ArrayList<>();
for (int i = 1; i <= 10000; i++) {
words.add(i + "");
}
final AtomicInteger atomicInteger = new AtomicInteger(0);
int count = 10;
CountDownLatch latch = new CountDownLatch(count); final List<Spliterator<String>> splitList = split(words, count);
for (int i = 0; i < count; i++) {
int finalI = i;
new Thread(() -> {
try {
splitList.get(finalI)
.forEachRemaining(s -> atomicInteger.getAndAdd(Integer.valueOf(s)));
} finally {
latch.countDown();
}
}, "Thread:" + i).start();
}
latch.await();
System.out.println(atomicInteger.get());
} public static <T> List<Spliterator<T>> split(List<T> list, int size) {
List<Spliterator<T>> returnList = new ArrayList<>();
returnList.add(list.spliterator());
if (size > 1) spliterator(returnList, 2, size);
return returnList;
} private static <T> void spliterator(List<Spliterator<T>> returnList, int i, int size) {
int j = i / 2 - 1;
returnList.add(returnList.get(j).trySplit());
if (size == i) return;
spliterator(returnList, i + 1, size);
}

(3)原则

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

(4)DEMO

  • 幂集
// {a,b,c}的幂集为{{},{a},{b},{c},{a,b},{a,c},{b,c},{a,b ,c}}
public class PowerSet {
public static final <E> Collection<Set<E>> of(Set<E> s) {
List<E> src = new ArrayList<>(s);
if (src.size() > 30)
throw new IllegalArgumentException("Set too big " + s);
return new AbstractList<Set<E>>() {
@Override public int size() {
// 如果size > 31将导致溢出int的范围
return 1 << src.size(); // 2^size
}
@Override public boolean contains(Object o) {
return o instanceof Set && src.containsAll((Set)o);
}
@Override public Set<E> get(int index) {
Set<E> result = new HashSet<>();
for (int i = 0; index != 0; i++, index >>= 1)
if ((index & 1) == 1) result.add(src.get(i));
return result;
}
};
}
}
  • 前缀子集、后缀子集
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);
}

7 谨慎使用并行流

(1)原则

  • 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();
}

Effective Java 读书笔记(五):Lambda和Stream的更多相关文章

  1. Effective java读书笔记

    2015年进步很小,看的书也不是很多,感觉自己都要废了,2016是沉淀的一年,在这一年中要不断学习.看书,努力提升自己 计在16年要看12本书,主要涉及java基础.Spring研究.java并发.J ...

  2. Effective Java读书笔记完结啦

    Effective Java是一本经典的书, 很实用的Java进阶读物, 提供了各个方面的best practices. 最近终于做完了Effective Java的读书笔记, 发布出来与大家共享. ...

  3. Effective Java 读书笔记(一):使用静态工厂方法代替构造器

    这是Effective Java第2章提出的第一条建议: 考虑用静态工厂方法代替构造器 此处的静态工厂方法并不是设计模式,主要指static修饰的静态方法,关于static的说明可以参考之前的博文&l ...

  4. Effective Java 读书笔记之九 并发

    一.访问共享的可变数据时要同步 1.synchronized关键字既然保证访问的可见性也能保证原子性.而volatile修饰符只能保证变量的线程可见性. 2.增量操作符等不是原子性,多线程操作时可能导 ...

  5. Effective Java 读书笔记之七 通用程序设计

    一.将局部变量的作用域最小化 1.在第一次使用变量的地方声明 2.几乎每个变量的声明都应该包含一个初始化表达式:try-catch语句是一个例外 3.使方法小而集中是一个好的策略 二.for-each ...

  6. Effective Java 读书笔记之一 创建和销毁对象

    一.考虑用静态工厂方法代替构造器 这里的静态工厂方法是指类中使用public static 修饰的方法,和设计模式的工厂方法模式没有任何关系.相对于使用共有的构造器来创建对象,静态工厂方法有几大优势: ...

  7. Effective Java 读书笔记(三):类与接口

    1 最小化类和成员的可访问性 (1)封装 封装对组成系统的组件进行解耦,从而允许这些组件独立开发,测试,优化,使用,理解和修改. 封装提高了软件的复用性,因为组件间的耦合度低使得它们不仅在开发环境,而 ...

  8. Effective Java 读书笔记之十 序列化

    一.谨慎地实现Serializable接口 1.一旦一个类被发布,就大大地降低了“改变这个类的实现”的灵活性. 2.仔细设计类的序列化形式而不是接受类的默认虚拟化形式. 3.反序列化机制是一个“隐藏的 ...

  9. Effective Java 读书笔记之八 异常

    一.只针对异常的情况才使用异常 1.类具有状态相关的方法时,可采用状态测试方法和可识别的返回值两个策略. 二.对可恢复的情况使用受检异常,对编程错误使用运行时异常 1.期望调用者能够适当恢复的情况,应 ...

随机推荐

  1. pdf 中内容的坐标系

    PDF Page Coordinates (page size, field placement, etc.) AcroForm, Basics, Automation Page coordinate ...

  2. kafka笔记博客

    大数据数据流组件选择: https://www.cnblogs.com/yinzhengjie/articles/11155051.html 初识Apache Kafka 核心概念: https:// ...

  3. 如何贡献补丁到uboot社区?

    答: 首次贡献分为两步: 1. 首先需要订阅一下,地址在此https://lists.denx.de/listinfo/u-boot,使邮箱地址对应有一个成员名称,才能向uboot社区发送补丁,否则会 ...

  4. Flutter 中的路由

    Flutter 中的路由通俗的讲就是页面跳转.在 Flutter 中通过 Navigator 组件管理路由导航. 并提供了管理堆栈的方法.如:Navigator.push 和 Navigator.po ...

  5. Django 引用{% url "name"%} 避免链接硬编码

    前提条件:为每个url指定name且name值要唯一.比如: 项目中的url.py文件: urlpatterns = patterns('', url(r'^$',TemplateView.as_vi ...

  6. Python - Django - ORM 外键操作

    models.py: from django.db import models # 出版社 class Publisher(models.Model): id = models.AutoField(p ...

  7. 【mysql】reset Password

    https://www.cnblogs.com/josn1984/p/8550419.html https://blog.csdn.net/l1028386804/article/details/92 ...

  8. 网络编程之Reactor 模式

    基本的架构是 epoll+线程池. 这篇博文主要从以下几个方面进行阐述: (1)reactor模式的一个介绍:(只要是我的理解) (2)关于线程池的说明. (3)如何将epoll + 池结合起来实现一 ...

  9. web端自动化——Python的smtplib发送电子邮件

    SMTP (Simple Mail Transfer Protocol)是简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式. Python的smtplib模块提 ...

  10. mycat搭建环境

    macos完全卸载mysql: https://blog.csdn.net/u012721519/article/details/55002626 踩过的坑: mycat1.6不支持单库分表; 最少要 ...