最近抽空看了《Java8实战这本书》,收获很多,这本书着重介绍了Java8的两个新特性:Lambda表达式和stream()的使用,简化了我们的开发。下面是我在读这本书是所做的笔记,也是我的一些收获。

第一段代码

对苹果按重量排序
1
2
3
4
5
6
7
8
9
10
11
//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());
筛选金额较高的交易
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//未使用流
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));

函数式接口

只定义了一个方法的接口,例如:

1
2
3
4
5
6
7
8
9
public interface Predicate<T>{
boolean test (T t);
}
public interface Comparator<T> {
int compare(T o1, T o2);
}
//注:此接口不能继承其他接口,不然会继承其方法
函数时接口的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//注意:此接口的方法返回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之前是这样做的。

1
2
3
4
5
6
7
8
9
10
//使用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表达式。简直不要更简单。

1
2
3
list.forEach(a -> {
System.out.println("Iterator Value::"+ a);
});
Java8中的default关键字

用于在接口中扩充方法,而不影响子接口,或子类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//接口中的方法用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表达式使用的例子
Lambda表达式使用的例子
T -> R Function,将类型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具有唯一一个抽象方法叫作apply R apply(T t,)
Lambda表达式类型检查过程示例
Lambda表达式类型检查
Lambda表达式类型检查
注意特殊的兼容规则

如果一个Lambda的主体是一个语句表达式, 它就和一个返回void的函数描述符兼容(当
然需要参数列表也兼容)。例如,以下两行都是合法的,尽管List的add方法返回了一个
boolean,而不是Consumer上下文(T -> void)所要求的void:

1
2
3
4
// Predicate返回了一个boolean
Predicate<String> p = s -> list.add(s);
// Consumer返回了一个void
Consumer<String> b = s -> list.add(s);
方法引用

类似Lambda表达式,但比Lambda表达式更直观,简洁

1
2
3
4
5
//先前:
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集合
1
List<Integer> weights = Arrays.asList(7,3,4,10);

Java8 stream学习

代码举例

假设我现在要获取卡路里小于400的食物,并将这些食物排序

1
2
3
4
5
6
7
8
9
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流的中间操作和终端操作

如上图,流是有数据连(如集合),中间操作链(形成流的一条流水线),终端操作(生成结果)。其中,中间操作的返回结果类型为:Stream<T>

流的总结
  • 流是“从支持数据处理操作的源生成的一系列元素”。
  • 流利用内部迭代:迭代通过filter、map、sorted等操作被抽象掉了。
  • 流操作有两类:中间操作和终端操作。
  • filter和map等中间操作会返回一个流,并可以链接在一起。可以用它们来设置一条流
    水线,但并不会生成任何结果。
  • forEach和count等终端操作会返回一个非流的值,并处理流水线以返回结果。
  • 流中的元素是按需计算的。
将字符串列表转成字母列表

代码如下:

1
2
3
4
5
6
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拆分数组
flatMap拆分数组
Lambda表达式打印数组类型的集合
1
2
3
4
5
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]+")"));

由于本书才看了一半,后续的笔记还在记录当中。