1.Lambda
1.1 方法引用
-
指向
静态方法
的方法引用(例如Integer的parseInt方法,写作Integer::parseInt)。 -
指向
任意类型实例方法
的方法引用(例如String的length方法,写作 String::length)。 -
指向
现有对象的实例方法
的方法引用(假设你有一个局部变量expensiveTransaction 用于存放Transaction类型的对象,它支持实例方法getValue,那么你就可以写expensive- Transaction::getValue)。
List<String> str = Arrays.asList("a", "b", "A", "B");
str.sort((s1,s2) -> s1.compareToIgnoreCase(s2));
str.sort(String::compareToIgnoreCase);
Function<String,Integer> stringToInteger = (String s) -> Integer.parseInt(s);
Function<String,Integer> sti = Integer::parseInt;
BiPredicate<List<String>, String> contains = (list, element) -> list.contains(element);
BiPredicate<List<String>, String> c = List::contains;
构造函数引用
public class Apple {
private Integer weight;
private String color;
public Apple(Integer weight) {
this.weight = weight;
}
public Apple(String color, Integer weight) {
this.weight = weight;
this.color = color;
}
}
public static List<Apple> map(List<Integer> list,
Function<Integer, Apple> f){
List<Apple> result = new ArrayList<>();
for(Integer e: list){
result.add(f.apply(e));
}
return result;
}
public static List<Apple> mapTwo(List<Integer> list,
BiFunction<String,Integer, Apple> f){
List<Apple> result = new ArrayList<>();
for(Integer e: list){
result.add(f.apply("green",e));
}
return result;
}
public static void main(String[] args) {
List<Integer> weights = Arrays.asList(7, 3, 4, 10);
List<Apple> apples = map(weights, Apple::new);
BiFunction<String,Integer,Apple> c3 = Apple::new;
Apple green = c3.apply("green", 10);
List<Apple> two = mapTwo(weights, Apple::new);
}
排序:
//1.
public class AppleComparator implements Comparator<Apple> {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
}
inventory.sort(new AppleComparator());
//2.
inventory.sort(new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
});
//3.
inventory.sort((Apple a1, Apple a2)
-> a1.getWeight().compareTo(a2.getWeight())
);
inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight()));
Comparator<Apple> c = Comparator.comparing((Apple a) -> a.getWeight());
inventory.sort(comparing((a) -> a.getWeight()));
//4. 方法引用
inventory.sort(comparing(Apple::getWeight));
1.2 复合Lambda表达式
1.2.1 比较器复合
-
逆序
//苹果按重量递减排序 Comparator<Apple> c = Comparator.comparing(Apple::getWeight); inventory.sort(comparing(Apple::getWeight).reversed());
-
比较器链
//两个苹果一样重怎么办?哪个苹果应该排在前面呢?你可能 需要再提供一个Comparator来进一步定义这个比较 inventory.sort(comparing(Apple::getWeight) .reversed() .thenComparing(Apple::getCountry)) //一样重是按国家排序
1.2.2 谓词复合 negate、and、or
//苹果不是红的
Predicate<Apple> notRedApple = redApple.negate();
//一个苹果是红的 重量大于150
Predicate<Apple> redAndHeavyApple = redApple.and(a -> a.getWeight() > 150);
//表达要么是重(150克以上)的红苹果,要么是绿苹果:
Predicate<Apple> redAndHeavyAppleOrGreen =
redApple.and(a -> a.getWeight() > 150)
.or(a -> "green".equals(a.getColor()));
1.2.3 函数复合 andThen、compose
-
andThen : 会返回一个函数,它先对输入应用一个给定函数,再对输出应用另一个函数。
Function<Integer,Integer> f = x -> x + 1; Function<Integer,Integer> g = x -> x * 2; Function<Integer,Integer> h = f.andThen(g); int result = h.apply(1); //4
-
compose : 先把给定的函数用作compose的参数里面给的那个函数,然后再把函数本身用于结果。
Function<Integer, Integer> f = x -> x + 1; Function<Integer, Integer> g = x -> x * 2; Function<Integer, Integer> h = f.compose(g); int result = h.apply(1); //3
案例: 用String 标识的一封信做文本转换:
public class Letter{
public static String addHeader(String text){
return "From Raoul, Mario and Alan: " + text;
}
public static String addFooter(String text){
return text + " Kind regards";
}
public static String checkSpelling(String text){
return text.replaceAll("labda", "lambda");
}
}
public static void main(String[] args){
Function<String, String> addHeader = Letter::addHeader;
//先加上 抬头,然后进行拼写检查,最后加上一个落款
Function<String, String> transformationPipeline
= addHeader.andThen(Letter::checkSpelling)
.andThen(Letter::addFooter);
//只加抬头、落款,而不做拼写检查:
Function<String, String> transformationPipeline
= addHeader.andThen(Letter::addFooter);
}
1.3 Lambda 小结
- Lambda表达式可以理解为一种匿名函数:它没有名称,但有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常的列表。
- Lambda表达式让你可以简洁地传递代码。
- 函数式接口就是仅仅声明了一个抽象方法的接口。
- 只有在接受函数式接口的地方才可以使用Lambda表达式。
- Lambda表达式允许你直接内联,为函数式接口的抽象方法提供实现,并且将整个表达式作为函数式接口的一个实例。
- Java 8自带一些常用的函数式接口,放在java.util.function包里,包括Predicate、Function<T,R>、Supplier、Consumer和BinaryOperator,如表3-2所述。
- 为了避免装箱操作,对Predicate和Function<T, R>等通用函数式接口的原始类型特化:IntPredicate、IntToLongFunction等。
- 环绕执行模式(即在方法所必需的代码中间,你需要执行点儿什么操作,比如资源分配 和清理)可以配合Lambda提高灵活性和可重用性。
- Lambda表达式所需要代表的类型称为目标类型。
- 方法引用让你重复使用现有的方法实现并直接传递它们。
- Comparator、Predicate和Function等函数式接口都有几个可以用来结合Lambda表达式的默认方法
2. Stream
//单核处理
List<String> lowCaloricDishesName =
menu.stream()
.filter(d -> d.getCalories() < 400) //选出400卡路里 以下的菜肴
.sorted(comparing(Dish::getCalories)) //按照卡路;里排序
.map(Dish::getName) //提取菜肴的名称
.collect(toList());//将所有名称保存在List中
//多核处理
List<String> lowCaloricDishesName =
menu.parallelStream()
.filter(d -> d.getCalories() < 400) //选出400卡路里 以下的菜肴
.sorted(comparing(Dish::getCalories)) //按照卡路;里排序
.map(Dish::getName) //提取菜肴的名称
.collect(toList());//将所有名称保存在List中
2.1 流与集合
2.1.1 流只能遍历一次
List<String> title = Arrays.asList("Java8", "In", "Action");
Stream<String> s = title.stream();
s.forEach(System.out::println); //只能被消费一次
s.forEach(System.out::println); //java.lang.IllegalStateException:流已被操作 或关闭
2.1.2 外部迭代与内部迭代
-
集合:
外部迭代
//用for-each循环 List<String> names = new ArrayList<>(); for(Dish d: menu){ names.add(d.getName()); } //用背后的迭代器做外部迭代 List<String> names = new ArrayList<>(); Iterator<String> iterator = menu.iterator(); while(iterator.hasNext()) { Dish d = iterator.next(); names.add(d.getName()); }
-
流:
内部迭代
List<String> names = menu.stream() .map(Dish::getName) .collect(toList());
2.1.3 流操作—中间操作
诸如filter或sorted等中间操作会返回另一个流
2.1.4 流操作—中端操作
终端操作会从流的流水线生成结果。
2.1.5 流操作—使用流
- 一个数据源(如集合)来执行一个查询;
- 一个中间操作链,形成一条流的流水线;
- 一个终端操作,执行流水线,并能生成结果。
2.2 流操作
2.2.1 筛选和切片
用谓词筛选,筛选出各不相同的元素,忽略流中的头几个元素,或将流截短至指定长度。
-
用谓词筛选
List<Dish> vegetarianMenu = menu.stream() //方法引用检查菜肴是否适合素食者 .filter(Dish::isVegetarian) .collect(toList());
-
去重
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4); numbers.stream() .filter(i -> i % 2 == 0) .distinct() //去重 .forEach(System.out::println);
-
截短流
List<Dish> dishes = menu.stream() .filter(d -> d.getCalories() > 300) .limit(3) .collect(toList());
-
跳过元素
List<Dish> dishes = menu.stream() .filter(d -> d.getCalories() > 300) .skip(2) //跳过 .collect(toList());
2.2.2 映射
-
对流中每一个元素应用函数
List<String> dishNames = menu.stream() .map(Dish::getName) //提取菜肴的名字 .collect(toList());
-
流的扁平化
List<String> uniqueCharacters = words.stream() .map(w -> w.split("")) //将每个单词转换为由 其字母构成的数组 .flatMap(Arrays::stream) //将各个生成流扁平化为单个流 .distinct() .collect(Collectors.toList());
案例一
:给定一个数字列表,如何返回一个由每个数的平方构成的列表呢?例如,给定[1, 2, 3, 4,5],应该返回[1, 4, 9, 16, 25]List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); List<Integer> collect1 = list.stream() .map(i -> i * i) .collect(toList()); System.out.println(collect1);
案例二
:给定两个数字列表,如何返回所有的数对呢?例如,给定列表[1, 2, 3]和列表[3, 4],应 该返回[(1, 3), (1, 4), (2, 3), (2, 4), (3, 3), (3, 4)]。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());
案例三
:扩展前一个例子,只返回总和能被3整除的数对呢?例如(2, 4)和(3, 3)是可以的List<int[]> collect = numbers1.stream() .flatMap(i -> numbers2.stream() .filter(j -> (i + j) % 3 == 0) .map(j -> new int[]{i, j})) .collect(toList());
2.2.3 查找和匹配
-
检查谓词是否至少匹配一个元素
//anyMatch if(menu.stream().anyMatch(Dish::isVegetarian)){ System.out.println("The menu is (somewhat) vegetarian friendly!!"); }
-
检查谓词是否匹配所有元素
//allMatch boolean isHealthy = menu.stream() .allMatch(d -> d.getCalories() < 1000); //noneMatch boolean isHealthy = menu.stream() .noneMatch(d -> d.getCalories() >= 1000);
-
查找元素
Optional<Dish> dish = menu.stream() .filter(Dish::isVegetarian) .findAny(); //返回当前流中的任意元素
-
查找第一个元素
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5); Optional<Integer> firstSquareDivisibleByThree = someNumbers.stream() .map(x -> x * x) .filter(x -> x % 3 == 0) .findFirst(); // 9
2.2.4 归约
-
元素求和
int sum = numbers.stream().reduce(0, (a, b) -> a + b); int sum = numbers.stream().reduce(0, Integer::sum);
-
最大值和最小值
//最大值 Optional<Integer> max = numbers.stream().reduce(Integer::max); //最小值 Optional<Integer> min = numbers.stream().reduce(Integer::min);
案例:
public class Trader { private final String name; private final String city; } public class Transaction { private final Trader trader; private final int year; private final int value; } public class TradeTest { private static List<Transaction> transactions; static { Trader raoul = new Trader("Raoul", "Cambridge"); Trader mario = new Trader("Mario", "Milan"); Trader alan = new Trader("Alan", "Cambridge"); Trader brian = new Trader("Brian", "Cambridge"); transactions = Arrays.asList( new Transaction(brian, 2011, 300), new Transaction(raoul, 2012, 1000), new Transaction(raoul, 2011, 400), new Transaction(mario, 2012, 710), new Transaction(mario, 2012, 700), new Transaction(alan, 2012, 950) ); } public static void main(String[] args) { //1.找出2011年发生的所有交易,并按交易额排序(从低到高)。 List<Transaction> case1 = transactions.stream() .filter(t -> t.getYear() == 2011) .sorted(comparing(Transaction::getValue)) .collect(Collectors.toList()); //2.交易员都在哪些不同的城市工作过? List<String> case2 = transactions.stream() .map(Transaction::getTrader) .map(Trader::getCity) .distinct() .collect(Collectors.toList()); //3.查找所有来自于剑桥的交易员,并按姓名排序。 List<Trader> case3 = transactions.stream() .map(Transaction::getTrader) .filter(trader -> trader.getCity().equals("Cambridge")) .distinct() .sorted(comparing(Trader::getName)) .collect(Collectors.toList()); //4.返回所有交易员的姓名字符串,按字母顺序排序。 String case4 = transactions.stream() .map(transaction -> transaction.getTrader().getName()) .distinct() .sorted() .reduce("",(name1,name2) ->name1 + name2); //reduce(joining()) //5.有没有交易员是在米兰工作的? boolean case5 = transactions.stream() .anyMatch(transaction -> transaction.getTrader().getCity().equals("Milan")); //6.打印生活在剑桥的交易员的所有交易额。 transactions.stream() .filter(transaction -> transaction.getTrader().getCity().equals("Cambridge")) .map(Transaction::getValue) .forEach(System.out::println); //7.所有交易中,最高的交易额是多少? Optional<Integer> case7 = transactions.stream() .map(Transaction::getValue) .reduce(Integer::max); //8.找到交易额最小的交易。 Optional<Integer> case8 = transactions.stream() .map(Transaction::getValue) .reduce(Integer::min); }
2.2.5 数值流
//这段代码的问题是,它有一个暗含的装箱成本。每个Integer都必须拆箱成一个原始类型再进行求和
transactions.stream()
.map(Transaction::getValue)
.reduce(Integer::min);
}
-
原始类型流特化
IntStream
、DoubleStream
和LongStream
-
映射到数值流
int calories = menu.stream()//返回一个Stream<Dish> .mapToInt(Dish::getCalories) // 返回一个 IntStream .sum();
-
转换回对象流
//将 Stream 转 换为数值流 IntStream intStream = menu.stream().mapToInt(Dish::getCalories); //将数值流转 换为Stream Stream<Integer> stream = intStream.boxed();
-
默认值
OptionalInt
OptionalInt maxCalories = menu.stream() .mapToInt(Dish::getCalories) .max(); //现在,如果没有最大值的话,你就可以显式处理OptionalInt去定义一个默认值了: int max = maxCalories.orElse(1);
-
-
数值范围
IntStream evenNumbers = IntStream.rangeClosed(1, 100) //表示范围[1, 100] .filter(n -> n % 2 == 0); //一个从1到 100的偶数流 //range是不包含结束值的。[1,100)
-
数值流应用:勾股数
2.2.6 构建流
-
由值创建流
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action"); stream.map(String::toUpperCase).forEach(System.out::println); //你可以使用empty得到一个空流,如下所示: Stream<String> emptyStream = Stream.empty();
-
由数组创建流
int[] numbers = {2, 3, 5, 7, 11, 13}; int sum = Arrays.stream(numbers).sum();
-
由文件生成流
long uniqueWords = 0; try(Stream<String> lines = //流会自动 关闭 Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){ //生成单词流 uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" "))) .distinct() //删除重复项 .count();//数一数有多少各 不相同的单词 }catch(IOException e){ }
-
由函数生成流:创建无限流
-
迭代
Stream.iterate(0, n -> n + 2) .limit(10) .forEach(System.out::println);
-
生成
Stream.generate(Math::random) .limit(5) .forEach(System.out::println);
-
2.3 用流收集数据
2.3.1 归纳与汇总
-
查找流中的最大值和最小值
Comparator<Dish> dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories); Optional<Dish> mostCalorieDish = menu.stream() .collect(maxBy(dishCaloriesComparator));//maxBy 最大 minBy最小
-
汇总
//总和 int totalCalories = menu.stream().collect(summingInt(Dish::getCalories)); //平均 double avgCalories = menu.stream().collect(averagingInt(Dish::getCalories)); //IntSummaryStatistics{count=9, sum=4300, min=120,average=477.777778, max=800} //个数,并得 总和、平均值、最大值和最小值: IntSummaryStatistics menuStatistics = menu.stream().collect(summarizingInt(Dish::getCalories));
-
连接字符串
String shortMenu = menu.stream().map(Dish::getName).collect(joining()); //逗号分隔的名称列表: String shortMenu = menu.stream().map(Dish::getName).collect(joining(", "));
-
广义的归约总汇
Optional<Dish> mostCalorieDish = menu.stream().collect(reducing( (d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2));
-
收集框架的灵活性:以不同的方法执行同样的操作
int totalCalories = menu.stream().collect(reducing(0, //初始值 Dish::getCalories, //转换函数 Integer::sum)); //累积函数
-
2.3.2 分组
Map<Dish.Type, List<Dish>> dishesByType =
menu.stream().collect(groupingBy(Dish::getType));
//{FISH=[prawns, salmon], OTHER=[french fries, rice, season fruit, pizza], MEAT=[pork, beef, chicken]}
//你可能想把热量不到400卡路里的菜划分为“低热量”(diet),热量400到700 卡路里的菜划为“普通”(normal),高于700卡路里的划为“高热量”(fat)。
public enum CaloricLevel { DIET, NORMAL, FAT }
Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = menu.stream().collect(
groupingBy(dish -> {
if (dish.getCalories() <= 400)
return CaloricLevel.DIET;
else if (dish.getCalories() <= 700)
return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
} ));
-
多级分组
要实现多级分组,我们可以使用一个由双参数版本的Collectors.groupingBy工厂方法创 建的收集器,它除了普通的分类函数之外,还可以接受collector类型的第二个参数。那么要进 行二级分组的话,我们可以把一个内层groupingBy传递给外层groupingBy,并定义一个为流 中项目分类的二级标准
Map<Dish.Type, Map<CaloricLevel, List<Dish>>> dishesByTypeCaloricLevel = menu.stream().collect( groupingBy(Dish::getType, // 一级分类函数 groupingBy(dish -> {//二级分类函数 if (dish.getCalories() <= 400) return CaloricLevel.DIET; else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL; else return CaloricLevel.FAT; } )); // 这个二级分组的结果就是像下面这样的两级Map: //{MEAT={DIET=[chicken], NORMAL=[beef], FAT=[pork]}, FISH={DIET=[prawns], NORMAL=[salmon]}, //OTHER={DIET=[rice, seasonal fruit], NORMAL=[french fries, pizza]}}
-
按子组收集数据
//1.要数一数菜单中每类菜有多少个 Map<Dish.Type, Long> typesCount = menu.stream().collect( groupingBy(Dish::getType, counting())); //{MEAT=3, FISH=2, OTHER=4} //2. 查找菜单中热量最高的菜肴的收集器改一改 Map<Dish.Type, Optional<Dish>> mostCaloricByType = menu.stream() .collect(groupingBy(Dish::getType, maxBy(comparingInt(Dish::getCalories)))); //{FISH=Optional[salmon], OTHER=Optional[pizza], MEAT=Optional[pork]} //3. 去掉返回值的Optional Map<Dish.Type, Dish> mostCaloricByType = menu.stream() .collect(groupingBy(Dish::getType, maxBy(comparingInt(Dish::getCalories)), Optional::get )); //4. 对每一组的菜肴热量求总和 Map<Dish.Type, Integer> totalCaloriesByType = menu.stream().collect(groupingBy(Dish::getType, summingInt(Dish::getCalories))); //5. 对于每种类型的Dish, 菜单中都有哪些CaloricLevel Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType = menu.stream().collect( groupingBy(Dish::getType, mapping( dish -> { if (dish.getCalories() <= 400) return CaloricLevel.DIET; else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL; else return CaloricLevel.FAT; }, toSet() )));
2.3.3 分区
由一个谓词(返回一个布尔值的函数)作为分类函数,它称分区函 数
//1. 菜单按照素食和非素食分开
Map<Boolean,List<Dish>> partitionedMenu =
menu.stream().collect(partitioningBy(Dish::isVegetarian))//分区函数
//2. 将数字按质数和非质数分区 它接受参数int n,并将前n个自然数分为质数和非质数
public boolean isPrime(int candidate) {
int candidateRoot = (int) Math.sqrt((double) candidate);
return IntStream.rangeClosed(2, candidateRoot)
.noneMatch(i -> candidate % i == 0);
}
public Map<Boolean, List<Integer>> partitionPrimes(int n) {
return IntStream.rangeClosed(2, n).boxed()
.collect(
partitioningBy(candidate -> isPrime(candidate)));
}
Collectors类的静态工厂方法
-
toList
:用于把流中所有项目收集到一个ListList<Dish> dishes = menuStream.collect(toList());
-
toSet
: 把流中所有项目收集到一个 Set,删除重复项Set<Dish> dishes = menuStream.collect(toSet());
-
toCollection
:把流中所有项目收集到给定的供应源创建的集合Collection<Dish> dishes = menuStream.collect(toCollection(), ArrayList::new);
-
counting
计算流中元素的个数long howManyDishes = menuStream.collect(counting());
-
summingInt
对流中项目的一个整数属性求和int totalCalories = menuStream.collect(summingInt(Dish::getCalories));
-
averagingInt
计算流中项目 Integer 属性的平均值double avgCalories = menuStream.collect(averagingInt(Dish::getCalories));
-
summarizingInt
收集关于流中项目 Integer 属性的统计值,例如最大、最小、总和与平均值IntSummaryStatistics menuStatistics = menuStream.collect(summarizingInt(Dish::getCalories));
-
joining
连接对流中每个项目调用 toString 方法所生成的字符串String shortMenu = menuStream.map(Dish::getName).collect(joining(", "));
-
maxBy
一个包裹了流中按照给定比较器选出的最大元素的 Optional, 或如果流为空则为 Optional.empty()Optional<Dish> fattest = menuStream.collect(maxBy(comparingInt(Dish::getCalories)));
-
minBy
一个包裹了流中按照给定比较器选出的最小元素的 Optional, 或如果流为空则为 Optional.empty()Optional<Dish> lightest = menuStream.collect(minBy(comparingInt(Dish::getCalories)));
-
reducing
从一个作为累加器的初始值开始,利用 BinaryOperator 与流 中的元素逐个结合,从而将流归约为单个值int totalCalories = menuStream.collect(reducing(0, Dish::getCalories, Integer::sum));
-
collectingAndThen
包裹另一个收集器,对其结果应用转换函数int howManyDishes =menuStream.collect(collectingAndThen(toList(), List::size));
-
groupingBy
根据项目的一个属性的值对流中的项目作问组,并将属性值作 为结果 Map 的键Map<Dish.Type,List<Dish>> dishesByType = menuStream.collect(groupingBy(Dish::getType));
-
partitioningBy
根据对流中每个项目应用谓词的结果来对项目进行分区Map<Boolean,List<Dish>> vegetarianDishes =Map<Boolean,List<T>> = menuStream.collect(partitioningBy(Dish::isVegetarian));
2.3.4 收集器接口
public interface Collector<T, A, R> {
Supplier<A> supplier();
BiConsumer<A, T> accumulator();
Function<A, R> finisher();
BinaryOperator<A> combiner();
Set<Characteristics> characteristics();
}
T
是流中要收集的项目的泛型。A
是累加器的类型,累加器是在收集过程中用于累积部分结果的对象。R
是收集操作得到的对象(通常但并不一定是集合)的类型。