最近抽空看了《Java8实战这本书》,收获很多,这本书着重介绍了Java8的两个新特性:Lambda表达式和stream()的使用,简化了我们的开发。下面是我在读这本书所做的笔记,也是我的一些收获。
第一段代码
对苹果按重量排序
代码语言:javascript复制//Java8之前
Collections.sort(inventory, new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
});
//Java8特性(方法引用)
inventory.sort(comparing(Apple::getWeight));
//Lambda表达式
Comparator<Apple> byWeight =
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
筛选金额较高的交易
代码语言:javascript复制//未使用流
Map<Currency, List<Transaction>> transactionsByCurrencies = new HashMap<>();
for (Transaction transaction : transactions) {
if(transaction.getPrice() > 1000){
Currency currency = transaction.getCurrency();
List<Transaction> transactionsForCurrency = transactionsByCurrencies.get(currency);
if (transactionsForCurrency == null) {
transactionsForCurrency = new ArrayList<>();
transactionsByCurrencies.put(currency,transactionsForCurrency);
}
transactionsForCurrency.add(transaction);
}
}
//使用流
import static java.util.stream.Collectors.toList;
Map<Currency, List<Transaction>> transactionsByCurrencies = transactions.stream().filter((Transaction t) -> t.getPrice() > 1000).collect(groupingBy(Transaction::getCurrency));
函数式接口
只定义了一个方法的接口,例如:
代码语言:javascript复制public interface Predicate<T>{
boolean test (T t);
}
public interface Comparator<T> {
int compare(T o1, T o2);
}
//注:此接口不能继承其他接口,不然会继承其方法
函数式接口的使用
代码语言:javascript复制//注意:此接口的方法返回boolean
@FunctionalInterface
public interface Predicate<T>{
boolean test(T t);
}
//定义一个实现功能的方法
public static <T> List<T> filter(List<T> list, Predicate<T> p) {
List<T> results = new ArrayList<>();
for(T s: list){
if(p.test(s)){
results.add(s);
}
}
return results;
}
//使用Lambda表达式
Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);
//上面两段代码相当于
List<String> nonEmpty = filter(listOfStrings, (String s) -> !s.isEmpty());
Java8中forEach方法的使用
假如有一个list
集合,循环获取里面的值,Java8
之前是这样做的。
//使用foreach循环获取
for (int i:list) {
System.out.println("Iterator Value::" i);
}
//或者使用迭代
Iterator<Integer> it = list.iterator();
while (it.hasNext()){
System.out.println("Iterator Value::" it.next());
}
Java8
后有一个forEach的方法,配合Lambda表达式。简直不要更简单。
list.forEach(a -> {
System.out.println("Iterator Value::" a);
});
Java8中的default关键字
用于在接口中扩充方法,而不影响子接口,或子类。
代码语言:javascript复制//接口中的方法用default修饰后,可以有结构体
public interface DefaultTest {
default void foo(){
System.out.println("Calling A.foo()");
}
}
//该类实现了DefaultTest接口,并不用实现foo(),因为foo()被default关键字修饰
public class DefaultTestImpl implements DefaultTest {
public static void main(String[] args){
DefaultTestImpl defaultTest = new DefaultTestImpl();
defaultTest.foo();
}
}
Lambda表达式及函数式接口的例子
Lambda表达式使用的例子
T -> R | Function<T,R>,将类型T的对象转换为类型R的对象 R apply(T t) |
---|---|
(int, int)->int | IntBinaryOperator具有唯一一个抽象方法,叫作applyAsInt int applyAsInt(int left, int right |
T->void | Consumer具有唯一一个抽象方法叫作accept void accept(T t) |
()->T | Supplier具有唯一一个抽象方法叫作get T get() |
(T, U)->R | BiFunction<T, U, R>具有唯一一个抽象方法叫作apply R apply(T t,) |
Lambda表达式类型检查过程示例
Lambda表达式类型检查
注意特殊的兼容规则
如果一个Lambda的主体是一个语句表达式, 它就和一个返回void的函数描述符兼容(当然需要参数列表也兼容)。例如,以下两行都是合法的,尽管List的add方法返回了一个 boolean,而不是Consumer上下文(T -> void)所要求的void:
代码语言:javascript复制// Predicate返回了一个boolean
Predicate<String> p = s -> list.add(s);
// Consumer返回了一个void
Consumer<String> b = s -> list.add(s);
方法引用
类似Lambda表达式,但比Lambda表达式更直观,简洁
代码语言:javascript复制//先前:
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
//之后(使用方法引用和java.util.Comparator.comparing):
inventory.sort(comparing(Apple::getWeight));
//Apple::getWeight相当于(Apple a) -> a.getWeight()
Lambda表达式及其等效方法引用
Lambda | 等效的方法引用 |
---|---|
(Apple a) -> a.getWeight() | Apple::getWeight |
() -> Thread.currentThread().dumpStack() | Thread.currentThread()::dumpStack |
(str, i) -> str.substring(i) | String::substring |
(String s) -> System.out.println(s) | System.out::println |
快速创建list集合
代码语言:javascript复制List<Integer> weights = Arrays.asList(7,3,4,10);
Java8 stream学习
代码举例
假设我现在要获取卡路里小于400的食物,并将这些食物排序
代码语言:javascript复制public static void main(String[] args){
getLowCaloricDishesNamesInJava8(Dish.menu).forEach(System.out::println);
}
public static List<String> getLowCaloricDishesNamesInJava8(List<Dish> dishes){
return dishes.stream().filter(d -> d.getCalories() < 400).sorted(Comparator.comparing(Dish::getCalories))
.map(Dish::getName).collect(Collectors.toList());
}
//Dish类是一个实体类,用于存储数据的
stream流的中间操作和终端操作
stream流的中间操作和终端操作
如上图,流是有数据连(如集合),中间操作链(形成流的一条流水线),终端操作(生成结果)。其中,中间操作的返回结果类型为:Stream<T>
。
流的总结
- 流是“从支持数据处理操作的源生成的一系列元素”。
- 流利用内部迭代:迭代通过filter、map、sorted等操作被抽象掉了。
- 流操作有两类:中间操作和终端操作。
- filter和map等中间操作会返回一个流,并可以链接在一起。可以用它们来设置一条流 水线,但并不会生成任何结果。
- forEach和count等终端操作会返回一个非流的值,并处理流水线以返回结果。
- 流中的元素是按需计算的。
将字符串列表转成字母列表
代码如下:
代码语言:javascript复制List<String> title = Arrays.asList("Java8", "In", "Action");
List<Integer> wordLengths = title.stream().map(String::length).collect(toList());
List<String> collect = title.stream().map(word -> word.split("")).flatMap(Arrays::stream).distinct().collect(toList());
System.out.println(collect);
//打印结果:[J, a, v, 8, I, n, A, c, t, i, o]
执行过程如图:
flatMap拆分数组
Lambda表达式打印数组类型的集合
代码语言:javascript复制List<Integer> numbers1 = Arrays.asList(1, 2, 3);
List<Integer> numbers2 = Arrays.asList(3, 4);
List<int[]> pairs = numbers1.stream().flatMap(i -> numbers2.stream().map(j -> new int[]{i, j})).collect(toList());
//如上面,有一个pairs的数组集合。java8的打印方式如下。
pairs.forEach(pair -> System.out.println("(" pair[0] "," pair[1] ")"));
由于本书才看了一半,后续的笔记还在记录当中。
参考
《Java8实战 》作者: 厄马(Raoul-Gabriel Urma) / 弗斯科(Mario Fusco) / 米克罗夫特(Alan Mycroft)