1.通过行为参数化传递代码
行为参数化,就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不同行为的能力。
例如
package com.anson.demo.entity;
public class Apple {
private String color;
private Integer weight;
public Apple() {
}
public Apple(String color, Integer weight) {
this.color = color;
this.weight = weight;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Integer getWeight() {
return weight;
}
public void setWeight(Integer weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Apple{" +
"color='" + color + '\'' +
", weight=" + weight +
'}';
}
}
没使用Lambda表达式
package com.anson.demo.test;
import com.anson.demo.entity.Apple;
public interface AppleFormatter {
String accept(Apple a);
}
package com.anson.demo.test;
import com.anson.demo.entity.Apple;
public class AppleFancyFormatter implements AppleFormatter {
@Override
public String accept(Apple a) {
String characteristic = a.getWeight() >150 ? "heavy" : "light";
return "A" + characteristic + "" +a.getColor() + "Apple";
}
}
package com.anson.demo.test;
import com.anson.demo.entity.Apple;
public class AppleSimpleFormatter implements AppleFormatter {
@Override
public String accept(Apple a) {
return "An apple of" + a.getWeight() + "g";
}
}
package com.anson.demo.test;
import com.anson.demo.entity.Apple;
import java.util.ArrayList;
import java.util.List;
public class AppleTest {
public static void main(String[] args) {
List<Apple> apples = new ArrayList<>();
apples.add(new Apple("红色",120));
apples.add(new Apple("红色",160));
apples.add(new Apple("红色",100));
apples.add(new Apple("绿色",120));
apples.add(new Apple("绿色",180));
prettyPrintApples(apples,new AppleFancyFormatter());
prettyPrintApples(apples,new AppleSimpleFormatter());
//prettyPrintApples(apples,(Apple a) -> "An apple of" + a.getWeight() + "g");
}
public static void prettyPrintApples(List<Apple> apples , AppleFormatter appleFormatter){
for (Apple apple : apples) {
String outPrint = appleFormatter.accept(apple);
System.out.println(outPrint);
}
}
}
使用Lambda表达式
package com.anson.demo.test;
public interface Predicate<T> {
boolean test(T t);
}
package com.anson.demo.test;
import com.anson.demo.entity.Apple;
import java.util.ArrayList;
import java.util.List;
public class AppleTest {
public static void main(String[] args) {
List<Apple> apples = new ArrayList<>();
apples.add(new Apple("红色",120));
apples.add(new Apple("红色",160));
apples.add(new Apple("红色",100));
apples.add(new Apple("绿色",120));
apples.add(new Apple("绿色",180));
List<Apple> appleFiter = fiter(apples, (Apple a) -> "红色".equals(a.getColor()));
for (Apple apple : appleFiter) {
System.out.println(apple);
}
}
public static <T> List<T> fiter(List<T> list, Predicate<T> p){
List<T> result = new ArrayList<>();
for (T e: list) {
if(p.test(e)){
result.add(e);
}
}
return result;
}
}
2.Lambda 表达式
2.1
语法:
(parameters) -> expression 或 (parameters) -> { statements; }
参数列表——这里它采用了Comparator中compare方法的参数,两个Apple。
箭头——箭头->把参数列表与Lambda主体分隔开。
Lambda主体——比较两个Apple的重量。表达式就是Lambda的返回值了。
例如
2.2
1)函数式接口就是只定义一个抽象方法的接口。
例如
注意 接口现在还可以拥有默认方法(即在类没有对方法进行实现时,其主体为方法提供默认实现的方法)。哪怕有很多默认方法,只要接口只定义了一个抽象方法,它就仍然是一个函数式接口。
2)用函数式接口可以干什么呢?
Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例例(具体说来,是函数式接口一个具体实现 的实例)。
2.3
函数描述符
函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们将这种抽象方法叫作函数描述符。
2.4
描述常见函数描述符的函数式接口
1)
Predicate
2)Consumer
3)
Function
4)
2.5
使用局部变量
闭包
你可能已经听说过闭包(closure,不要和Clojure编程语言混淆)这个词,你可能会想Lambda是否满足闭包的定义。用科学的说法来说,闭包就是一个函数的实例,且它可以无限制地访问那个函数的非本地变量。例如,闭包可以作为参数传递给另一个函数。它也可以访问和修改其作用域之外的变量。现在,Java 8的Lambda和匿名类可以做类似于闭包的事情:它们可以作为参数传递给方法,并且可以访问其作用域之外的变量。但有一个限制:它们不能修改定义Lambda的方法的局部变量的内容。这些变量必须是隐式最终的。 可以认为Lambda是对值封闭,而不是对变量封闭。如前所述,这种限制存在的原因在于局部变量保存在栈上,并且隐式表示它们仅限于其所在线程。如果允许捕获可改变的局部变量,就会引发造成线程不安全的新的可能性,而这是我们不想看到的(实例变量可以,因为它们保存在堆中,而堆是在线程之间共享的)。
2.6
方法引用
方法引用可以被看作仅仅调用特定方法的Lambda的一种快捷写法。它的基本思想是,如果一个Lambda代表的只是“直接调用这个方法”,那最好还是用名称来调用它,而不是去描述如何调用它。事实上,方法引用就是让你根据已有的方法实现来创建Lambda表达式。但是,显式地指明方法的名称,你的代码的可读性会更好。
通俗点讲,方法引用就是,在使用函数式接口的时候,我们可以通过方法引用的方式来指明我们真正调用的是哪个类的哪个方法
目标引用放在分隔符::前,方法的名称放在后面。
例子:
2.7小结
3.引入流
3.1什么是流
流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现)。就现在来说,你可以把它们看成遍历数据集的高级迭代器。此外,流还可以透明地并行处理,你无需写任何多线程代码了!
简短的定义就是“从支持数据处理操作的源生成的元素序列”。
解析
例子
public class AppleTest {
public static void main(String[] args) {
List<Apple> apples = new ArrayList<>();
apples.add(new Apple("红色", 120));
apples.add(new Apple("红色", 160));
apples.add(new Apple("红色", 100));
apples.add(new Apple("绿色", 120));
apples.add(new Apple("绿色", 180));
//选出重量小于150的苹果,按重量排序,提取苹果颜色;
System.out.println("----------Java7-----------");
List<Apple> lightApple = new ArrayList<>();
for (Apple apple : apples) {
if (apple.getWeight() < 150) {
lightApple.add(apple);
}
}
Collections.sort(lightApple, new Comparator<Apple>() {
@Override
public int compare(Apple o1, Apple o2) {
return Integer.compare(o1.getWeight(), o2.getWeight());
}
});
List<String> SelectApples = new ArrayList<>();
for (Apple apple : lightApple) {
SelectApples.add(apple.getColor());
}
for (String selectApple : SelectApples) {
System.out.println(selectApple);
}
System.out.println("----------Java8-----------");
List<String> SelectApple = apples.stream()
.filter(a -> a.getWeight() < 150)
.sorted(Comparator.comparing(Apple::getWeight))
.map(Apple::getColor)
.collect(Collectors.toList());
for (String s : SelectApple) {
System.out.println(s);
}
}
}
运行结果
3.2流与集合
粗略地说,集合与流之间的差异就在于什么时候进行计算。
1)集合是一个内存中的数据结构,它包含数据结构中目前所有的值——集合中的每个元素都得先算出来才能添加到集合中。(你可以往集合里加东西或者删东西,但是不管什么时候,集合中的每个元素都是放在内存里的,元素都得先算出来才能成为集合的一部分。)
急切创建【供应商驱动:先把仓库装满,再开始卖,就像那些昙花一现的圣诞新玩意儿一样】
2)流则是在概念上固定的数据结构(你不能添加或删除元素),其元素则是按需计算的。
延迟创建【需求驱动,甚至是实时制造】
例子
3.3只能遍历一次
和迭代器类似,流只能遍历一次。(流只能消费一次)
3.4流操作
3.4.1中间操作
诸如filter或sorted等中间操作会返回另一个流。这让多个操作可以连接起来形成一个查询。重要的是,除非流水线上触发一个终端操作,否则中间操作不会执行任何处理——它们很懒。这是因为中间操作一般都可以合并起来,在终端操作时一次性全部处理。
优化 1)短路2)循环合并
3.4.2终端操作
终端操作会从流的流水线生成结果。其结果是任何不是流的值,比如List、Integer,甚至void。
3.4.3使用流
- 一个数据源(如集合)来执行一个查询;
- 一个中间操作链,形成一条流的流水线;
- 一个终端操作,执行流水线,并能生成结果。
3.5小结
4使用流
4.1筛选和切片
4.1.1用谓词筛选
Streams接口支持filter方法。该操作会接受一个谓词(一个返回boolean的函数)作为参数,并返回一个包括所有符合谓词的元素的流。
//筛选出所有素菜,创建一张素食菜单
List<Dish> vegetarianMenu = menu.stream()
.filter(Dish::isVegetarian)
.collect(toList());
4.1.2 筛选各异的元素
流还支持一个叫作distinct的方法,它会返回一个元素各异(根据流所生成元素的hashCode和equals方法实现)的流。
//筛选出列表中所有的偶数,并确保没有重复
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);
解析
4.1.3 截短流
流支持limit(n)方法,该方法会返回一个不超过给定长度的流。所需的长度作为参数传递给limit。如果流是有序的,则最多会返回前n个元素。
//选出热量超过300卡路里的头三道菜
List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.limit(3)
.collect(toList());
4.1.4 跳过元素
流还支持skip(n)方法,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一个空流。请注意,limit(n)和skip(n)是互补的!
//跳过超过300卡路里的头两道菜,并返回剩下的。
List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.skip(2)
.collect(toList());
解析
4.2映射
4.2.1 对流中每一个元素应用函数
流支持map方法,它会接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是“创建一个新版本”而不是去“修改”)。
//例如 把方法引用Dish::getName传给了map方法,来提取流中菜肴的名称
List<String> dishNames = menu.stream()
.map(Dish::getName)
.collect(toList());
//给定一个单词列表,你想要返回另一个列表,显示每个单词中有几个字母
List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action");
List<Integer> wordLengths = words.stream()
.map(String::length)
.collect(toList());
4.2.2 流的扁平化
使用flatMap方法,各个数组并不是分别映射成一个流,而是映射成流的内容
//对于一张单词表,如何返回一张列表,列出里面 各不相同的字符
List<String> words = Arrays.asList("Java8", "Lambdas", "Action");
List<String> uniqueCharacters =
words.stream()
.map(w -> w.split("")) //将每个单词转换为由其字母构成的数组
.flatMap(Arrays::stream) //将各个生成流扁平化为单个流(使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流)
.distinct()
.collect(Collectors.toList());
解析
4.3查找与匹配
常见的数据处理套路是看看数据集中的某些元素是否匹配一个给定的属性,
Stream API通过allMatch、anyMatch、noneMatch、findFirst和findAny方法提供了这样的工具。
4.3.1 检查谓词是否至少匹配一个元素
anyMatch方法,“流中是否有一个元素能匹配给定的谓词”,anyMatch方法返回一个boolean(终端操作)
//例如:查看菜单里面是否有素食
if(menu.stream().anyMatch(Dish::isVegetarian)){
System.out.println("The menu is (somewhat) vegetarian friendly!!");
}
4.3.2 检查谓词是否匹配所有元素
allMatch方法,“流中的元素是否都能匹配给定的谓词”
//例如:查看所有菜的热量是否都低于1000卡路里
boolean isHealthy = menu.stream()
.allMatch(d -> d.getCalories() < 1000);
noneMatch方法与allMatch相对,“流中没有任何元素与给定的谓词匹配”
//例如:查看所有菜的热量是否都低于1000卡路里
boolean isHealthy = menu.stream()
.noneMatch(d -> d.getCalories() >= 1000);
短路求值
有些操作不需要处理整个流就能得到结果。例如,假设你需要对一个用and连起来的大布尔表达式求值。不管表达式有多长,你只需找到一个表达式为false,就可以推断整个表达式将返回false,所以用不着计算整个表达式。这就是短路。
对于流而言,某些操作(例如allMatch、anyMatch、noneMatch、findFirst和findAny)不用处理整个流就能得到结果。只要找到一个元素,就可以有结果了。同样,limit也是一个短路操作:它只需要创建一个给定大小的流,而用不着处理流中所有的元素。在碰到无限大小的流的时候,这种操作就有用了:它们可以把无限流变成有限流。
4.3.3 查找元素
findAny方法将返回当前流中的任意元素。
//例如:找到一道素食菜肴
Optional<Dish> dish =
menu.stream()
.filter(Dish::isVegetarian)
.findAny();
4.3.4 查找第一个元素
findFirst方法,“有些流有一个出现顺序(encounter order)来指定流中项目出现的逻辑顺序(比如由List或排序好的数据列生成的流)。对于这种流,findFirst方法找到第一个元素。”
//例如,给定一个数字列表,下面的代码能找出第一个平方能被3整除的数:
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
何时使用findFirst和findAny
你可能会想,为什么会同时有findFirst和findAny呢?答案是并行。找到第一个元素在并行上限制更多。如果你不关心返回的元素是哪个,请使用findAny,因为它在使用并行流时限制较少。
4.4 归约
reduce 归约:将流归约成一个值。用函数式编程语言的术语来说,这称为折叠(fold),因为你可以将这个操作看成把一张长长的纸(你的流)反复折叠成一个小方块,而这就是折叠操作的结果。
reduce接受两个参数:
一个初始值
一个Lambda来把两个流元素结合起来并产生一个新值
4.4.1 元素求和
List<Integer> numbers3 = Arrays.asList(1, 2, 3, 4, 5);
//归约reduce---元素求和
Integer sum = numbers3.stream()
.reduce(0, (a, b) -> a + b);
//优化[Integer类有一个静态的sum方法来对两个数求和]
Integer sum2 = numbers3.stream()
.reduce(0, Integer::sum);
System.out.println(sum+"-------"+sum2);
解析:
4.4.2 最大值和最小值
//最大值和最小值
List<Integer> numbers3 = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> max = numbers3.stream()
.reduce(Integer::max);
Optional<Integer> min = numbers3.stream()
.reduce(Integer::min);
Optional<Integer> min1 = numbers3.stream()
.reduce((a, b) -> a < b ? a : b);
System.out.println("最大值"+max+"最小值"+min);