什么是lambda表达式?
Lambda 表达式是Java 8 的新特性,是一种新的编程语法。lambda语义简洁明了,性能良好,是Java 8 的一大亮点。废话不多说,我们来看个例子。
从内部类到lambda
lambda简化了内部类的使用,说起内部类,我第一个想到的就是创建一个线程:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello anonymity class.");
}
});
想必大家对这段代码都不陌生,在jdk1.8之前,我们可以使用内部类便捷的创建一个线程,并指定执行内容,这里打印了"hello anonymity class."
。接着我们看下lambda的写法:
Thread thread = new Thread(() -> System.out.println("hello anonymity class."));
是不是简化了好多,从之前的“怎么做”到现在的“做什么”,我们不用再手动重写run方法,只需要写这个Thread要做什么事情就可以了。在Thread方法参数中,空括号 () 代表没有参数,打印语句就是要执行的方法体,而 -> 则是分割参数与方法体的符号。
我们再来看个例子,选取文件夹内的隐藏文件:
File[] hiddenFiles = new File(".").listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isHidden();
}
});
lambda:
File[] hiddenFiles = new File(".").listFiles(File::isHidden);
这里lambda表达式用到了类、方法和两个冒号“::”。当我们已经有了isHidden方法时,就可以用Java8 的方法引用:: 语法把方法当做参数传给listFiles()
。这就叫做 函数式编程 ,即把方法(函数)当做参数来传递。
这时你可能会有疑问,这lambda表达式究竟是怎么实现的呢?我们来跟一下代码。
函数式接口
Thread类有很多个构造方法,如何确定new Thread(() -> System.out.println("hello anonymity class."))
这个用的是哪个构造方法?
我们可以开发工具可以轻松定位到上面所调用的Thread的构造方法,是
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
一个入参为Runnable
的方法。为什么偏偏是这个,而不是Thread(String name)
呢。这里要引入一个概念,函数式接口。
函数式接口与普通接口的唯一不同点是多了@FunctionalInterface
注解。标注这个接口可以用作函数式接口。Runnable
就被标注为函数式接口,只有一个抽象方法:public abstract void run();
,入参为空,且无返回。同样的,listFiles(FileFilter filter)
的参数FileFilter
也是一个函数式接口,它也有一个方法:boolean accept(File pathname);
入参为File
对象,返回一个boolean
类型。
所以lambda表达式是以入参+返回值类型来匹配函数式接口的, "->"号左边为入参(多个参数用括号包裹),右边是与接口方法的返回类型相同的方法体(多行用大括号包裹)。
JDK已经为我们提供了现成的函数式接口(当然我们也可以自己写),在java.util.function
包下,有了这些扩展,lambda才能展现它真正的魅力。
下面我们看一些实例
- 对列表迭代(Consumer)
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 9, 8, 7, 6);
list.forEach(e -> System.out.print(e + " "));
//1 2 3 4 5 9 8 7 6
- 对列表排序(Comparator)
//打乱顺序
Collections.shuffle(list);
System.out.println(list);
//[2, 7, 5, 6, 9, 1, 8, 4, 3]
list.sort(Integer::compare);
System.out.println(list);
//[1, 2, 3, 4, 5, 6, 7, 8, 9]
- 过滤偶数(Predicate)
Predicate有一个抽象方法boolean test(T t);
入参为T,返回boolean。我们可以用它来做过滤操作
Predicate<Integer> p = i -> i % 2 == 0;
List<Integer> result = predicateFilter(list, p);
System.out.println(result);
//[2, 4, 6, 8]
public static <T> List<T> predicateFilter(List<T> list, Predicate<T> p) {
List<T> result = new ArrayList<>();
for (T t : list) {
if (p.test(t)) {
result.add(t);
}
}
return result;
}
- 循环处理(Consumer)
Consumer有一个抽象方法void accept(T t);
入参为T,无返回。
Consumer<Integer> c = System.out::println;
consumerForEach(list, c);
public static <T> void consumerForEach(List<T> list, Consumer<T> c) {
for (T t : list) {
c.accept(t);
}
}
/*1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
*/
- 获取商品的id(Function)
Function有一个抽象方法R apply(T t);
入参为T,返回R类型,用处更加广泛。
List<Product> productList = Arrays.asList(
new Product(1L, "西红柿"),
new Product(2L, "绵白糖"),
new Product(3L, "黄瓜"),
new Product(4L, "大蒜")
);
List<Long> productIds = productFunction(productList, Product::getId);
public static <T, R> List<R> productFunction(List<T> list, Function<T, R> f) {
List<R> result = new ArrayList<>();
for (T t : list) {
result.add(f.apply(t));
}
return result;
}
/*
* 4-大蒜
* 2-绵白糖
* 1-西红柿
* 3-黄瓜
*/
- 根据商品的权重排序(Comparator)
即便Comparator有多个抽象方法,lambda表达式一样可以匹配的到。
匹配了int compare(T o1, T o2);
方法
productList.sort((p1, p2) -> p1.getSortWeight().compareTo(p2.getSortWeight()));
Comparator还重载了compare方法,入参是一个Function<? super T, ? extends U>
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
所以我们还可以调用这个方法来实现排序
productList.sort(Comparator.comparing(Product::getSortWeight));
Java8中常用的函数式接口
函数式接口 | 函数描述符 |
---|---|
Predicate<T> | T -> boolean |
Consumer<T> | T -> void |
Function<T,R> | T -> R |
Supplier<T> | () -> T |
UnaryOperator<T> | T -> T |
BinaryOperator<T> | (T, T) -> T |
BiPredicate<L, R> | (L, R) -> boolean |
BiConsumer<T, U> | (T, U) -> void |
BiFunction<T, U, R> | (T, U) -> R |
lambda及函数式接口的例子
使用案例 | lambda的例子 | 对应的接口函数 |
---|---|---|
布尔表达式 | (List<String> list) -> list.empty() | Predicate<List<String>> |
创建对象 | () -> new Product(1L, “西红柿”) | Supplier<Product> |
消费对象 | (Product p) -> System.out.println(a.getId()) | Consumer<Product> |
从一个对象中提取/选择 | (String s) -> s.length() | Function<String, Integer> |
合并两个值 | (int a, int b) -> a * b | IntBinaryOperator |
比较两个对象 | (Product p1, Product p2) -> p1.getSortWeight().compareTo(p2.getSortWeight()) | Comparator<Product>或 BiFunction<Product, Product, Integer> |
函数式数据处理——Stream API
流(stream)是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现)。就现在来说,你可以把它们看成遍历数据集的高级迭代器。此外,流还可以透明地并行处理,你无需写任何多线程代码了!
我们来看个例子体会一下。
同样是提取商品id,现在我们不再需要调用productFunction()
方法了。
List<Long> productIdList = productList.stream().map(Product::getId).collect(Collectors.toList());
stream()
获取productIdList
的流map()
把流中的Product
映射成Long
collect(Collectors.toList())
从流中收集内容生成新的List
中间操作和终端操作
- 在使用流时,会有两步操作,
map()
对流进行操作,称之为中间操作;collect()
对流进行整合,称之为终端操作。
stream提供的操作方法:
中间操作
操作 | 类型 | 返回类型 | 操作参数 | 函数描述符 | 作用 |
---|---|---|---|---|---|
filter | 中间 | Stream<T> | Predicate<T> | T -> boolean | 筛选 |
map | 中间 | Stream<T> | function<T,R> | T -> R | 映射 |
limit | 中间 | Stream<T> | 数量限制 | ||
sorted | 中间 | Stream<T> | Comparator<T> | (T,T) -> int | 排序 |
distinct | 中间 | Stream<T> | 去重 | ||
flatMap | 中间 | Stream<R> | Function<T, R> | T -> Stream<R> | 平铺(展开)映射 |
peek | 中间 | Stream<T> | Consumer | T -> void | 迭代 |
skip | 中间 | Stream<T> | 跳过 | ||
builder | 中间 | Builder<T> | Builder<T> | ||
empty | 中间 | Stream<T> | () -> Stream<T> | 创建一个空流 | |
of | 中间 | Stream<T> | T -> Stream<T> | 创建包含T的流 | |
iterate | 中间 | Stream<T> | UnaryOperator<T> | (T, UnaryOperator<T>) -> Stream<T> | 以一个初始值和迭代规则创建一个T流 |
generate | 中间 | Stream<T> | Supplier<T> | () -> Stream<T> | 创建一个流 |
concat | 中间 | Stream<T> | (Stream<T>, Stream<T>) -> Stream<T> | 合并两个流 |
终端操作
操作 | 类型 | 返回类型 | 作用 |
---|---|---|---|
forEach | 终端 | void | 消费流中的每个元素并对其应用Lambda。这一操作返回void |
count | 终端 | long | 返回流中元素的个数。这一操作返回long |
collect | 终端 | R | 把流归约成一个集合,比如List、Map甚至是Integer。 |
toArray | 终端 | T[] | 把流归约成一个数组 |
reduce | 终端 | T | 规约函数,以一个初始值开始,按照规则计算 |
min | 终端 | Optional<T> | 获取最小值 |
max | 终端 | Optional<T> | 获取最大值 |
anyMatch | 终端 | boolean | 流中的任意一个对象满足条件 |
allMatch | 终端 | boolean | 流中的所有对象满足条件 |
noneMatch | 终端 | boolean | 流中的所有对象都不满足条件 |
findFirst | 终端 | Optional<T> | 返回第一个对象 |
findAny | 终端 | Optional<T> | 返回任意一个对象 |
来一些具体的使用实例:
村头的老王最近包地赚了些钱,开了一个小超市主要经营了下面这几类商品:
/**
* 商品种类
*/
enum GType {
//水果
FRUITS(1),
//啤酒
BEER(2),
//香烟
SMOKE(3),
//肉类
MEAT(4),
//零食
SNACKS(5),
;
GType(int goodsType) { this.goodsType = goodsType; }
private int goodsType;
public int getGoodsType() { return goodsType; }
public void setGoodsType(int goodsType) { this.goodsType = goodsType; }
}
然后老王进了一批货
//商品
class Goods {
//名称
private String name;
//种类
private GType goodsType;
//价格
private Float price;
//数量
private Integer count;
public Goods(String name, GType goodsType, Float price, Integer count) {
this.name = name;
this.goodsType = goodsType;
this.price = price;
this.count = count;
}
//setter and getter
}
//进货
private static List<Goods> stock() {
return Arrays.asList(new Goods("apple", GType.FRUITS, 5.0F, 30),
new Goods("banana", GType.FRUITS, 3.5F, 20),
new Goods("orange", GType.FRUITS, 8.0F, 40),
new Goods("bread", GType.FOODS, 3.9F, 15),
new Goods("milk", GType.DRINK, 6.0F, 100),
new Goods("grape", GType.FRUITS, null, 10),
new Goods("beer", GType.DRINK, 8.0F, 100),
new Goods("cookie", GType.FOODS, 2.6F, 300),
new Goods("sugar", GType.FOODS, 1.0F, 150),
new Goods("moutai", GType.DRINK, 2980.0F, 1));
}
接着要面对各种购物需求
public static void main(String[] args) {
List<Goods> goods = stock();
System.out.println("有哪些商品?------------------");
//都买哪些商品?
String goodsNames = goods.stream()
.map(Goods::getName)
.collect(Collectors.joining(", "));
System.out.println(goodsNames);
//apple, banana, orange, bread, milk, grape, beer, cookie, sugar, moutai
System.out.println("有几类商品?------------------");
//有几类商品?
long types = goods.stream()
.map(Goods::getGoodsType)
.count();
System.out.println(types);
//10
System.out.println("有哪些水果?------------------");
System.out.println("名称-种类-价格-数量");
//有哪些水果?
goods.stream()
.filter(e -> e.getGoodsType().equals(GType.FRUITS))
.forEach(System.out::println);
//名称-种类-价格-数量
//apple - 1 - 5.0 - 30
//banana - 1 - 3.5 - 20
//orange - 1 - 8.0 - 40
//grape - 1 - 18.3 - 10
System.out.println("最贵的商品------------------");
//最贵的商品
Goods dearly = goods.stream()
.max(Comparator.comparingDouble(Goods::getPrice))
.get();
System.out.println(dearly.getName());
//moutai
System.out.println("最便宜的价格------------------");
//最便宜的价格
Float cheap = goods.stream()
.map(Goods::getPrice)
.sorted(Float::compare)
.limit(1)
.findAny()
.get();
System.out.println(cheap);
//1.0
System.out.println("饮品按名字排序------------------");
//饮品按名字排序
List<Goods> sortedDrinks = goods.stream()
.filter(e -> e.getGoodsType().equals(GType.DRINK))
.sorted(Comparator.comparing(Goods::getName))
.collect(Collectors.toList());
sortedDrinks.forEach(System.out::println);
//beer - 3 - 8.0 - 100
//milk - 3 - 6.0 - 100
//moutai - 3 - 2980.0 - 1
//食品库存量
System.out.println("所有商品库存量------------------");
int goodsCount = goods.stream()
.mapToInt(Goods::getCount)
.sum();
System.out.println(goodsCount);
//766
//是否有免费的商品
System.out.println("是否有免费的商品------------------");
boolean freeGoods = goods.stream()
.anyMatch(e -> e.getPrice() == 0);
System.out.println(freeGoods);
//false
//以商品种类分类
System.out.println("以商品种类分类------------------");
Map<GType, List<Goods>> goodsMap = goods.stream()
.collect(Collectors.groupingBy(Goods::getGoodsType));
System.out.println(goodsMap);
//{FOODS=[bread - 2 - 3.9 - 15, cookie - 2 - 2.6 - 300, sugar - 2 - 1.0 - 150], FRUITS=[apple - 1 - 5.0 - 30, banana - 1 - 3.5 - 20, orange - 1 - 8.0 - 40, grape - 1 - 18.3 - 10], DRINK=[milk - 3 - 6.0 - 100, beer - 3 - 8.0 - 100, moutai - 3 - 2980.0 - 1]}
//每个商品的余量
System.out.println("每个商品的余量------------------");
Map<String, Integer> goodsCountMap = goods.stream()
.collect(Collectors.toMap(Goods::getName, Goods::getCount));
System.out.println(goodsMap);
//{orange=40, banana=20, apple=30, bread=15, cookie=300, milk=100, moutai=1, grape=10, sugar=150, beer=100}
}
使用流时要注意一点,如果没有终端操作的话,流是不会执行的哦。
goods.stream().peek(e -> System.out.print(e.getName() + " "));
//
goods.stream().peek(e -> System.out.print(e.getName() + "-")).count();
//apple-banana-orange-bread-milk-grape-beer-cookie-sugar-moutai-