如果一个接口中只有一个抽象方法,且抽象方法的参数和返回类型与lambda表达式的参数和返回结果一致,那么就可以将接口类型作为lambda表达式的函数对象类型
interface Lambda {
int calculate(int a, int b);
}
Lambda add = (a, b) -> a + b; // 它已经变成了一个 lambda 对象
add.calculate(2,3); //调用
函数对象化的好处
interface Lambda {
boolean test(Student student);
}
static List filter(List students, Lambda lambda) {
List result = new ArrayList<>();
for (Student student : students) {
if (lambda.test(student)) {
result.add(student);
}
}
return result;
}
挑选出性别为男的人
filter(students, student -> student.sex.equals("男"));
挑选出性别为男且年龄小于18岁的人
filter(students, student -> student.sex.equals("男") && student.age < 18);
lambda 对象有两种形式:lambda 表达式与方法引用,lambda表达式和方法引用都代表一个函数对象,lambda表达式功能更全面,方法引用写法更为简洁
lambda 对象的类型是由它的行为决定的,如果有一些 lambda 对象,它们的入参类型、返回值类型都一致,那么它们可以看作是同一类的 lambda 对象,它们的类型,用函数式接口来表示
1、明确指出参数类型
(int a,int b) -> a + b
2、代码多于一行不能省略 {} 以及 return
(int a,int b) -> { int c = a + b; return c;}
3、可以根据上下文推断出参数类型时,可以省略参数类型
interface Lambda1 { interface Lambda2 {
int op(int a, int b); double op(double a, double b);
} }
Lambda1 lambda = (a, b) -> a + b;
4、只有一个参数时可以省略参数的()
a -> a
lambda表达式的参数个数类型和返回类型相同,就可以吧函数对象归为一类用函数式接口表示,该接口只能有一个抽象方法,用@FunctionalInterface
来检查
jdk中常见的函数式接口
Runnable
()-> void
Callable
()-> T
Comparator
(T,T) -> int
Consumer,BiConsumer,LongConsumer,DoubleConsumer
(T) -> void Bi指两参,Int指参数为int
Function,BiFunction,Int Long Double …
(T) -> R 有参数和返回值
Predicate,BiPredicate,Int Long Double …
(T) -> boolean 返回值为布尔类型
Supplier,Int Long Double …
() -> T 无参有返回值
UnaryOperator,BinaryOperator,Int Long Double …
(T) -> T 参数和返回值结果一致
如何理解:
public class Type2Test {
public static void main(String[] args) {
/*
需求:挑选出所有男性学生
*/
Stream.of(
new Student("张无忌", "男"),
new Student("周芷若", "女"),
new Student("宋青书", "男")
)
.filter(Type2Test::isMale)
.forEach(student -> System.out.println(student));
}
static boolean isMale(Student student) {
return student.sex.equals("男");
}
record Student(String name, String sex) {
}
}
输出
Student[name=张无忌, sex=男]
Student[name=宋青书, sex=男]
如何理解:
例1:
public class Type3Test {
public static void main(String[] args) {
highOrder(Student::hello);
}
static void highOrder(Type3 lambda) {
System.out.println(lambda.transfer(new Student("张三"), "你好"));
}
interface Type3 {
String transfer(Student stu, String message);
}
static class Student {
String name;
public Student(String name) {
this.name = name;
}
public String hello(String message) {
return this.name + " say: " + message;
}
}
}
上例中函数类型的
输出
张三 say: 你好
例2:改写之前根据性别过滤的需求
public class Type2Test {
public static void main(String[] args) {
/*
需求:挑选出所有男性学生
*/
Stream.of(
new Student("张无忌", "男"),
new Student("周芷若", "女"),
new Student("宋青书", "男")
)
.filter(Student::isMale)
.forEach(student -> System.out.println(student));
}
record Student(String name, String sex) {
boolean isMale() {
return this.sex.equals("男");
}
}
}
输出
Student[name=张无忌, sex=男]
Student[name=宋青书, sex=男]
例3:将学生对象仅保留学生的姓名
public class Type2Test {
public static void main(String[] args) {
Stream.of(
new Student("张无忌", "男"),
new Student("周芷若", "女"),
new Student("宋青书", "男")
)
.map(Student::name)
.forEach(student -> System.out.println(student));
}
record Student(String name, String sex) {
boolean isMale() {
return this.sex.equals("男");
}
}
}
输出
张无忌
周芷若
宋青书
如何理解:
public class Type4Test {
public static void main(String[] args) {
Util util = new Util(); // 对象
Stream.of(
new Student("张无忌", "男"),
new Student("周芷若", "女"),
new Student("宋青书", "男")
)
.filter(util::isMale)
.map(util::getName)
.forEach(student -> System.out.println(student));
}
record Student(String name, String sex) {
boolean isMale() {
return this.sex.equals("男");
}
}
static class Util {
boolean isMale(Student student) {
return student.sex.equals("男");
}
String getName(Student student) {
return student.name();
}
}
}
其实较为典型的一个应用就是 System.out
对象中的非静态方法,最后的输出可以修改为
.forEach(System.out::println);
这是因为
对于构造方法,也有专门的语法把它们转换为 lambda 对象
函数类型应满足
例如:
public class Type5Test {
static class Student {
private final String name;
private final int age;
public Student() {
this.name = "某人";
this.age = 18;
}
public Student(String name) {
this.name = name;
this.age = 18;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
interface Type51 {
Student create();
}
interface Type52 {
Student create(String name);
}
interface Type53 {
Student create(String name, int age);
}
public static void main(String[] args) {
hiOrder((Type51) Student::new);
hiOrder((Type52) Student::new);
hiOrder((Type53) Student::new);
}
static void hiOrder(Type51 creator) {
System.out.println(creator.create());
}
static void hiOrder(Type52 creator) {
System.out.println(creator.create("张三"));
}
static void hiOrder(Type53 creator) {
System.out.println(creator.create("李四", 20));
}
}
算是形式2的特例,只能用在类内部
public class Type6Test {
public static void main(String[] args) {
Util util = new UtilExt();
util.hiOrder(Stream.of(
new Student("张无忌", "男"),
new Student("周芷若", "女"),
new Student("宋青书", "男")
));
}
record Student(String name, String sex) {
}
static class Util {
boolean isMale(Student student) {
return student.sex.equals("男");
}
boolean isFemale(Student student) {
return student.sex.equals("女");
}
void hiOrder(Stream<Student> stream) {
stream
.filter(this::isMale)
.forEach(System.out::println);
}
}
}
算是形式2的特例,只能用在类内部(用在要用 super 区分重载方法时)
public class Type6Test {
//...
static class UtilExt extends Util {
void hiOrder(Stream<Student> stream) {
stream
.filter(super::isFemale)
.forEach(System.out::println);
}
}
}
函数接口和方法引用之间,可以差一个返回值,例如
public class ExceptionTest {
public static void main(String[] args) {
Runnable task1 = ExceptionTest::print1;
Runnable task2 = ExceptionTest::print2;
}
static void print1() {
System.out.println("task1 running...");
}
static int print2() {
System.out.println("task2 running...");
return 1;
}
}
Arrays.stream(array)
List.of("a","b","c").stream()
(List,Set 等)Stream.of("x", "y")
IntStream.range(0, 10)
或者
IntStream.rangeClosed(0, 9)
IntStream.iterate(1, x -> x + 2)
IntStream.iterate(1, x -> x < 10, x -> x + 2)
iterate 的特点是根据上一个元素计算当前元素,如果不需要依赖上一个元素,可以改用 generate 方法
例如下面是生成 5 个随机 int
Stream.generate(()-> ThreadLocalRandom.current().nextInt()).limit(5)
不过如果只是生成随机数的话,有更简单的办法
ThreadLocalRandom.current().ints(5)
如果要指定上下限,例如下面是生成从 0~9 的100个随机数
ThreadLocalRandom.current().ints(100, 0, 10)
过滤-filter
参数类型:Predicate
record Fruit(String cname, String name, String category, String color) { }
Stream.of(
new Fruit("草莓", "Strawberry", "浆果", "红色"),
new Fruit("桑葚", "Mulberry", "浆果", "紫色"),
new Fruit("杨梅", "Waxberry", "浆果", "红色"),
new Fruit("核桃", "Walnut", "坚果", "棕色"),
new Fruit("草莓", "Peanut", "坚果", "棕色"),
new Fruit("蓝莓", "Blueberry", "浆果", "蓝色")
)
找到所有蓝色的浆果
方法1:
.filter(f -> f.category().equals("浆果") && f.color().equals("蓝色"))
方法2:让每个 lambda 只做一件事,两次 filter 相对于并且关系
.filter(f -> f.category.equals("浆果"))
.filter(f -> f.color().equals("蓝色"))
方法3:让每个 lambda 只做一件事,不过比方法2强的地方可以 or,and,nagate 运算
.filter(((Predicate) f -> f.category.equals("浆果")).and(f -> f.color().equals("蓝色")))
参数类型:Function
.map(f -> f.cname() + "酱")
参数类型:返回值为流的Function
例1:
Stream.of(
List.of(
new Fruit("草莓", "Strawberry", "浆果", "红色"),
new Fruit("桑葚", "Mulberry", "浆果", "紫色"),
new Fruit("杨梅", "Waxberry", "浆果", "红色"),
new Fruit("蓝莓", "Blueberry", "浆果", "蓝色")
),
List.of(
new Fruit("核桃", "Walnut", "坚果", "棕色"),
new Fruit("草莓", "Peanut", "坚果", "棕色")
)
)
.flatMap(Collection::stream)
例2:
Stream.of(
new Order(1, List.of(
new Item(6499, 1, "HUAWEI MateBook 14s"),
new Item(6999, 1, "HUAWEI Mate 60 Pro"),
new Item(1488, 1, "HUAWEI WATCH GT 4")
)),
new Order(1, List.of(
new Item(8999, 1, "Apple MacBook Air 13"),
new Item(7999, 1, "Apple iPhone 15 Pro"),
new Item(2999, 1, "Apple Watch Series 9")
))
)
想逐一处理每个订单的商品
.flatMap(order -> order.items().stream())
Stream.concat(Stream.of("a","b","c"), Stream.of("d"))
Stream.concat(Stream.of("a", "b", "c"), Stream.of("d"))
.skip(1)
.limit(2)
skip 是跳过几个元素
limit 是限制处理的元素个数
dropWhile 是 drop 流中元素,直到条件不成立,留下剩余元素
takeWhile 是 take 流中元素,直到条件不成立,舍弃剩余元素
下面的代码找到流中任意(Any)一个偶数
int[] array = {1, 3, 5, 4, 7, 6, 9};
Arrays.stream(array)
.filter(x -> (x & 1) == 0)
.findAny()
.ifPresent(System.out::println);
与 findAny 比较类似的是 firstFirst,它俩的区别
判断流中是否存在任意一个偶数
Arrays.stream(array).anyMatch(x -> (x & 1) == 0)
判断流是否全部是偶数
Arrays.stream(array).allMatch(x -> (x & 1) == 0)
判断流是否全部不是偶数
Arrays.stream(array).noneMatch(x -> (x & 1) == 0)
已知有数据
record Hero(String name, int strength) { }
Stream.of(
new Hero("独孤求败", 100),
new Hero("令狐冲", 90),
new Hero("风清扬", 98),
new Hero("东方不败", 98),
new Hero("方证", 92),
new Hero("任我行", 92),
new Hero("冲虚", 90),
new Hero("向问天", 88),
new Hero("不戒", 88)
)
要求,首先按 strength 武力排序(逆序),武力相同的,按姓名长度排序(正序)
仅用 lambda 来解
.sorted((a,b)-> {
int res = Integer.compare(b.strength(), a.strength());
return (res == 0) ? Integer.compare(a.nameLength(), b.nameLength()) : res;
})
方法引用改写
.sorted(
Comparator.comparingInt(Hero::strength)
.reversed()
.thenComparingInt(Hero::nameLength)
)
其中:
增加一个辅助方法
record Hero(String name, int strength) {
int nameLength() {
return this.name.length();
}
}
原理:
.sorted((e, f) -> {
int res =
((Comparator<Hero>) (c, d) ->
((Comparator<Hero>) (a, b) -> Integer.compare(a.strength(), b.strength()))
.compare(d, c))
.compare(e, f);
return (res == 0) ? Integer.compare(e.nameLength(), f.nameLength()) : res;
})
如果不好看,改成下面的代码
.sorted(step3(step2(step1())))
static Comparator<Hero> step1() {
return (a, b) -> Integer.compare(a.strength(), b.strength());
}
static Comparator<Hero> step2(Comparator<Hero> step1) {
return (c, d) -> step1.compare(d, c);
}
static Comparator<Hero> step3(Comparator<Hero> step2) {
return (e, f) -> {
int res = step2.compare(e, f);
return (res == 0) ? Integer.compare(e.nameLength(), f.nameLength()) : res;
};
}
reduce(init, (p,x) -> r)
(p,x) -> r
是一个 BinaryOperator,作用是根据上次化简结果 p 和当前元素 x,得到本次化简结果 r这样两两化简,可以将流中的所有元素合并成一个结果
collect( supplier, accumulator, combiner)
()-> c
(c, x) -> void
(c1, c2) -> void
Stream<String> stream = Stream.of("令狐冲", "风清扬", "独孤求败", "方证",
"东方不败", "冲虚", "向问天", "任我行", "不戒", "不戒", "不戒", "不戒");
1) 收集到 List
List<String> result = stream.collect(() -> new ArrayList<>(), (list, x) -> list.add(x), (a, b) -> { });
ArrayList::new ()->new ArrayList()
ArrayList::add (list,x)->list.add(x)
List<String> result = stream.collect(ArrayList::new, ArrayList::add, (a, b) -> { });
2) 收集到 Set
Set<String> result = stream.collect(LinkedHashSet::new, Set::add, (a, b) -> { });
3)收集到 Map
Map<String, Integer> result = stream.collect(HashMap::new, (map,x)->map.put(x, 1), (a, b) -> { });
4)收集到 StringBuilder
StringBuilder sb = stream.collect(StringBuilder::new, StringBuilder::append, (a,b)->{});
5)收集到 StringJoiner
StringJoiner sb = stream.collect(()->new StringJoiner(","), StringJoiner::add, (a,b)->{});
收集器
collect收集操作的三个参数比较固定,所以jdk的Collectors
类中提供了很多现成的收集器
1) 收集到 List
List<String> result = stream.collect(Collectors.toList());
2) 收集到 Set
Set<String> result = stream.collect(Collectors.toSet());
3)收集到 StringBuilder
String result = stream.collect(Collectors.joining());
4)收集到 StringJoiner
String result = stream.collect(Collectors.joining(","));
5)收集到 Map
Map<String, Integer> map = stream.collect(Collectors.toMap(x -> x, x -> 1));
//toMap一般被groupingBy所代替
steam.collect(Collectors.groupingBy(x->x.length(),Collectors.toList()))
groupingBy 分组收集时,组内可能需要进一步的数据收集,如上例的Collectors.toList()
称为下游收集器
1. mapping(x->y, dc) 需求:根据名字长度分组,分组后组内只保留他们的武力值
new Hero("令狐冲", 90)->90
dc 下游收集器 down collector
stream.collect(groupingBy(h -> h.name().length(), mapping(h -> h.strength(), toList())));
2. filtering(x->boolean, dc) 需求:根据名字长度分组,分组后组内过滤掉武力小于 90 的
在分组收集的过程中,执行过滤
stream.collect(groupingBy(h -> h.name().length(), filtering(h -> h.strength() >= 90, toList())));
先过滤,再来分组收集
stream.filter(h -> h.strength() >= 90).collect(groupingBy(h -> h.name().length(), toList()));
3. flatMapping(x->substream, dc) 需求:根据名字长度分组,分组后组内保留人名,并且人名切分成单个字符
"令狐冲".chars().mapToObj(Character::toString).forEach(System.out::println);
stream.collect(groupingBy(h -> h.name().length(),
flatMapping(h -> h.name().chars().mapToObj(Character::toString), toList())));
4. counting() 需求:根据名字长度分组,分组后求每组个数
stream.collect(groupingBy(h -> h.name().length(), counting()));
5. minBy((a,b)->int) 需求:根据名字长度分组,分组后求每组武功最低的人
6. maxBy((a,b)->int) 需求:根据名字长度分组,分组后求每组武功最高的人
stream.collect(groupingBy(h -> h.name().length(), maxBy(Comparator.comparingInt(Hero::strength))));
7. summingInt(x->int) 需求:根据名字长度分组,分组后求每组武力和
8. averagingDouble(x->double) 需求:根据名字长度分组,分组后求每组武力平均值
stream.collect(groupingBy(h -> h.name().length(), averagingDouble(h -> h.strength())));
9. reducing(init,(p,x)->r)
求和
stream.collect(groupingBy(h -> h.name().length(), mapping(h -> h.strength(), reducing(0, (p, x) -> p + x))));
求个数
stream.collect(groupingBy(h -> h.name().length(), mapping(h -> 1, reducing(0, (p, x) -> p + x))));
// 求平均,缺少 finisher
Map<Integer, double[]> collect = stream.collect(groupingBy(h -> h.name().length(),
mapping(h -> new double[]{h.strength(), 1},
reducing(new double[]{0, 0}, (p, x) -> new double[]{p[0] + x[0], p[1] + x[1]}))));
细节:使用import static
静态导入引入Collectors类相当于说明啦静态方法从属于哪个类,就可以省略Collectors.