前几天在写算法题的时候用到了优先队列这个数据结构, 当时还不太会用比较器和Lambda表达式, 正好趁此机会搞明白这两个东西的设计的来龙去脉和用法.
Lambda表达式正是比较器的一个很好的应用, 因为比较器是一个函数式接口, Lambda表达式就专门是为了简化函数式接口的使用而出现的.
一. Lambda表达式
问题背景
函数式接口:
就是一个类里面只有一个抽象方法, 这种类就叫函数式接口. 因为我们把这个接口的实现类对象当函数来用所以叫做函数式接口.
函数式接口的一般的使用流程:
- 创建一个实现类重写实现这个接口的方法
- new一个实现类的对象
- 将这个对象赋值给这个接口的引用变量, 变成一个引用
- 用这个接口引用去调用其唯一的方法---当作函数用
1. 匿名内部类
我们在上面的使用流程中发现有一步可以简化, 这一步就是第一步, 这一步我们要创建实现类, 但是正如我们问题背景所说, 我们单单是想把这个接口当函数用, 所以我们只关心这个函数式接口中的函数方法有没有实现, 是怎么实现的.
像是想实现类的名字这种东西我们完全可以简化掉, 由此产生了匿名内部类这个语法, 这就是我们对于函数式接口使用的第一次简化.
匿名内部类的使用方法:
- 我们现在有一个函数式接口, 假设叫做Runnable, Runnable里面有一个抽象方法叫run
- 现在我们要创建实现类重写这个抽象方法, 并把实现类对象赋给这个接口的引用变量
- 看看匿名内部类的语法是怎么做到的
Java
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("任务执行");
}
};
- 这个语法是新加的, 和以前的语法都不一样, 以前的语法是一个类我们可以在等式右边直接new出来, 并且输入初始化参数, 在创建对象的同时执行初始化方法. 匿名内部类是在右边加了{ }, 重写了一个抽象方法, 然后再new出来这个对象赋给左边接口引用, 这时因为没有给这个实现类命名所以叫做匿名内部类
2. Lambda表达式
Lambda表达式也是用来简化函数式接口的使用的, 函数式接口的使用流程中甚至1和2都可以简化, 因为我们只关心函数式接口的函数方法是怎么实现的. 所以我们借助了编译器的帮助进一步简化了函数式接口的使用.
函数式接口的使用方法:
- 同样现在有一个函数式接口, 假设叫做Runnable, Runnable里面有一个抽象方法叫run
- 现在我们要创建实现类重写这个抽象方法, 并把实现类对象赋给这个接口的引用变量
- 看看Lambda表达式的语法是怎么做到的
Java
Runnable task = () -> System.out.println("任务执行");
- 首先编译器会判断当前位置引用变量是否为函数式接口的引用变量, 如果是便会用这个Lambda表达式重写他的函数方法, 函数方法原本有什么形参Lambda表达式中的形参类型和数量也要在( )中对应, 名字可以不一样. 然后把->后的作为函数体赋给函数方法, 最后创建这个实现类的对象赋给这个函数式接口的引用变量, 就可以通过这个引用来调用函数方法了.
注意Lambda表达式有一个语法细节:
1.单行表达式(无花括号 {}
)
自动返回表达式结果, 无需写 return
, 返回的就是s1.length() - s2.length()
的值
Java
Comparator<String> byLength = (s1, s2) -> s1.length() - s2.length();
2.多行代码块(有花括号 {}
)
必须显式写 return
语句(如果有返回值)
Java
Function<int[], Integer> sumOfSquares = arr -> {
int sum = 0;
for (int num : arr) {
sum += num * num;
}
return sum; // 必须显式返回
};
二. 比较器
比较器实际上就是一个函数式接口, 我们要实现比较器里面的函数方法, 所以前面的Lambda表达式是一个很常用的手段.比较器的比较函数进行了如下的约定, 用来判断你放入的两个元素谁排在前面, 谁排在后面.
函数方法输入(o1, o2)
两个元素, 比较器的唯一核心规则:
- 返回负数 → 约定排序算法中
o1
排在o2
前面 - 返回正数 → 约定排序算法中
o2
排在o1
前面 - 返回0 → 顺序无关紧要
比较器的 compare(o1, o2)
方法本质是一个"裁判 ",它只返回一个整数(负 / 正 / 0),根据规则排序算法再操作:"o1
和 o2
谁应该排在前面"
所以我们现在不管排序算法中的内容, 现在会用比较器的规则就行
- 升序 :小的在前 → 应返回
o1的属性 - o2的属性
, 此时正负都是小的在前 - 降序 :大的在前 → 应返回
o2的属性 - o1的属性
, 此时正负都是大的在前 - 多条件:依次比较每个条件,直到返回非零结果。
我们先记住这个框架,遇到任何排序需求先能快速推导, 以后看排序算法的源码会有更深的理解.