《Java8实战》学习笔记

2022-05-05 15:45:57 浏览数 (1)

最近抽空看了《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之前是这样做的。

代码语言:javascript复制
//使用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表达式。简直不要更简单。

代码语言:javascript复制
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)

0 人点赞