第42条 Lambda优先于匿名类
函数对象:只带有单个形象办法的接口或者抽象类,其实例称之为函数对象。
- 删除所有lambda参数的类型,除非其存在可能使程序更加清晰。
- 与办法和类不同,Lambda没有名称和文档,如果一个计算自身不是不言自明的,或者该计算超出了三行的限度,那么就不要把它放入一个Lambda中。
- 尽可能不要序列化一个Lambda或者匿名外部类实例。
- 不要为函数式接口或者函数式抽象类去创立匿名外部类对象。
Java中减少了Lambda后,许多中央能够应用函数对象了,如下第34条的Operation枚举类型,能够从旧版本优化为新版本:
// Enum type with constant-specific class bodies & data (Item 34)
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 fields & constant-specific behavior
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);
}
}
第43条 办法援用优先于Lambda
- 当办法援用更简洁时,应用办法援用;反之,还是应用Lambda。
办法援用类型 | 范例 | Lambda等式 |
---|---|---|
动态援用 | Integer::parseInt |
str -> Integer.parseInt(str) |
无限实例援用 | Instant.now()::isAfter |
Instant then = Instant.now();t -> then.isAfter(t) |
有限实例援用 | String::toLowerCase |
str -> str.toLowerCase() |
类结构器 | TreeMap<K,V>::new |
() -> new TreeMap<K,V> |
数组结构器 | int[]::new |
len -> new int[len] |
- 无限办法援用和动态援用类似,都是所提供的办法援用与函数式接口的参数列表以及返回值类型统一,无限实例办法援用其实不须要实例对象。
- 有限办法援用,则须要一个特定的实例对象,该实例对象加上援用办法中的参数列表,才与函数式接口的参数列表统一。这个实例对象也就是原书上所说的
an additional parameter
。
注:书中的receiving object
其实指的是依据办法援用所创立的函数对象。
附上Java官网中的分类及其示例:
Kind | Example |
---|---|
Reference to a static method | ContainingClass::staticMethodName |
Reference to an instance method of a particular object | containingObject::instanceMethodName |
Reference to an instance method of an arbitrary object of a particular type | ContainingType::methodName |
Reference to a constructor | ClassName::new |
静态方法援用
public class Person {
public enum Sex {
MALE, FEMALE
}
String name;
LocalDate birthday;
Sex gender;
String emailAddress;
public int getAge() {
// ...
}
public LocalDate getBirthday() {
return birthday;
}
public static int compareByAge(Person a, Person b) {
return a.birthday.compareTo(b.birthday);
}
}
//Person::compareByAge就是静态方法援用
无限实例办法援用
String[] stringArray = { "Barbara", "James", "Mary", "John",
"Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);
有限实例办法援用
class ComparisonProvider {
public int compareByName(Person a, Person b) {
return a.getName().compareTo(b.getName());
}
public int compareByAge(Person a, Person b) {
return a.getBirthday().compareTo(b.getBirthday());
}
}
ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);
结构器办法援用
public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>>
DEST transferElements(
SOURCE sourceCollection,
Supplier<DEST> collectionFactory) {
DEST result = collectionFactory.get();
for (T t : sourceCollection) {
result.add(t);
}
return result;
}
//类结构器办法援用
Set<Person> rosterSet = transferElements(roster, HashSet::new);
//等价于
Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new);
第44条 保持应用规范的函数接口
- 只有规范的函数接口可能满足需要,通常应优先思考吗,而不是再专门构建一个新的函数接口。
- 千万不要用带包装类型的根底函数接口来代替根底类型的函数接口
- 必须始终用@FunctionalInterface注解对本人编写的函数接口进行规范
- 在Java9中,java.util.Function一共有43个接口,其中有6个根底接口:
- 参数类型和返回类型统一的有2个,别离是1个参数和两个参数的;
- 返回类型为boolean的有1个;
- 参数类型和返回类型不统一的有1个;
- 无返回值和有形参的各一个,共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> |
R apply(T t) |
Arrays::asList |
Supplier<T> |
T get() |
Instant::now |
Consumer<T> |
void accept(T t) |
System.out::println |
- 其中每个根底接口各自有3种变体,别离作用于将T的类型替换为根本类型int、long和double,共6*3种。
接口 | Int变种 | Long变种 | Double变种 |
---|---|---|---|
UnaryOperator<T> |
IntUnaryOperator |
LongUnaryOperator |
DoubleUnaryOperator |
BinaryOperator<T> |
IntBinaryOperator |
LongBinaryOperator |
DoubleBinaryOperator |
Predicate<T> |
IntPredicate |
LongPredicate |
DoublePredicate |
Function<T,R> |
IntFunction<R> |
LongFunction<R> |
DoubleFunction<R> |
Supplier<T> |
IntSupplier |
LongSupplier |
DoubleSupplier |
Consumer<T> |
IntConsumer |
LongConsumer |
DoubleConsumer |
- 针对于
Function<T,R>
还有9种变体,其中6种用于源类型和后果类型都是根底类型,但又互不雷同的状况,还有三种是将源类型为根底类型,后果类型是援用类型的(即返回了个对象):
Int |
Long |
Double |
Obj |
|
---|---|---|---|---|
Int |
– | IntToLongFunction |
IntToDoubleFunction |
IntFunction<R> |
Long |
LongToIntFunction |
– | LongToDoubleFunction |
LongFunction<R> |
Double |
DoubleToIntFunction |
DoubleToLongFunction |
– | DoubleFunction<R> |
Predicate<T>
Function<T,R>
Consumer<T>
还有各自有一个带有两个参数的版本,共3个;BiFunction<T,U,R>
还有返回相干的三个根底类型的变种共3个;Consumer<T>
接口还有带有1个援用类型参数和1个根底类型参数的变种,针对三种根底类型,共3个。
源类型 | 变种1 | 变种2 | 变种3 |
---|---|---|---|
Bi |
BiPredicate<T,U> |
BiFunction<T,U,R> |
BiConsumer<T,U> |
BiFunction<T,U,R> |
ToIntBiFunction<T,U> |
ToLongBiFunction<T,U> |
ToDoubleBiFunction<T,U> |
Consumer<T> |
ObjDoubleConsumer<T> |
ObjIntConsumer<T> |
ObjLongConsumer<T> |
- 最初有
Supplier<T>
的一个变种BooleanSupplier
,如下:
@FunctionalInterface
public interface BooleanSupplier {
/**
* Gets a result.
*
* @return a result
*/
boolean getAsBoolean();
}
第45条 审慎应用Stream
- 滥用Stream会使程序代码难以读懂和保护
- 在没有显式类型的状况下,认真命名Lambda参数,这对Stream pipeline的可读性至关重要
- 为了可读性,须要时在Stream Pipline中应用helper办法
- 防止利用Stream解决char值
- 重构现有代码来应用Stream,且只在必要的时候才在新代码中应用
- 不确定迭代和Stream哪种比拟好的状况下,两种都试下比拟下
stream的定义及形象原理
在Java8中新增了Stream API,用于简化串行或者并行的大批量操作。这个API提供了两个要害的形象:
- stream(流),表白了一个无限或者有限的数据元素序列。
- stream pipeline(流管道),表白了这些数据元素的多级计算。
stream的起源包含汇合、数组、文件、正则表达式模式匹配器、伪随机数生成器以及其余stream。其中的数据元素类型能够是援用类型和根底类型(int、long、double)。一个stream pipeline中蕴含了一个源stream ,接着是零至多个两头操作和一个终止操作。每个两头操作都会以某种形式对接管到的stream转换为另一个stream,终止操作会在最初一个两头操作产生的stream上执行一个最终的计算。
stream pipeline通常是lazy的,直到调用终止操作时才会开始计算,对于实现终止操作不须要的数据元素,将永远不会计算。这种计算使得有限stream变为可能。而没有终止操作的stream pipeline将是一个静默的无操作指令,千万不能遗记终止操作。
在默认状况下,stream pipeline是依照程序执行的,要使其并发执行,只需在该pipeline的任何stream上调用parallel办法即可。但通常不倡议这么做。
补充阐明
helper办法指的是对pipeline中的简单计算流程,能够独自封装进去作为一个函数。
public class Anagrams {
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(g -> System.out.println(g.size() + ": " + g));
}
}
//helper办法
private static String alphabetize(String word) {
String s = word.chars().sorted()
.collect(StringBuilder::new,
(sb, c) -> sb.append((char) c),
StringBuilder::append).toString();
return s;
}
}
两头操作flatMap能够将Stream中的每个元素都映射产生一个Stream,而后将这些新的Stream全副合并到一个Stream中。
// Iterative Cartesian product computation
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;
}
发表回复