Java中的函数式编程(三)lambda表达式
写在前面
lambda表达式与匿名内部类
无参的函数式接口
public static void createThreadWithAnonymousClass() { // Runnable 是接口名。我们通过匿名内部类的方式,构造了一个 Runnable 的实例。 Thread t = new Thread(new Runnable() { @Override public void run() { System.out.println("Thread is running"); } }); t.start(); }
使用匿名内部类的一个重要目的,就是为了减轻程序员的代码负担,不需要额外再定义一个类,而且这个类是一个一次性的类,没有太多的重用价值。但是,我们会发现,这个对象看起来也是多余的,因为我们实际上并不是要传入一个对象,而只是想传入一个方法。
public static void createThreadWithLambda() { // 在Java 8中,Runnable 是一个函数式接口,因此我们可以使用 lambda 表达式来实现它。 Thread t = new Thread(() -> { System.out.println("Thread is running"); }); t.start(); }
带参的函数式接口
@FunctionalInterface public interface Comparator<T> { int compare(T o1, T o2); .... }
假设一个场景:给定一个省份的拼音列表,需要对该列表中的省份进行排序,排序规则是字母长度最小的省份排在前面,如果两个省份字母长度一样,则按字母顺序排序。
public static void sortProvincesWithAnonymousClass() { List<String> list = Arrays.asList("Guangdong", "Zhejiang", "Jiangsu", "Xizang", "Fujian", "Hunan", "Guangxi"); list.sort(new Comparator<String>() { @Override public int compare(String first, String second) { int lenDiff = first.length() - second.length(); return lenDiff == 0 ? first.compareTo(second) : lenDiff; } }); list.forEach(s -> System.out.println(s)); }
public static void sortProvincesWithLambda() { List<String> list = Arrays.asList("Guangdong", "Zhejiang", "Jiangsu", "Xizang", "Fujian", "Hunan", "Guangxi"); // 下面的参数列表 first 和 second ,即方法 Comparator.compare 的参数列表 list.sort((first, second) -> { int lenDiff = first.length() - second.length(); return lenDiff == 0 ? first.compareTo(second) : lenDiff; }); list.forEach(s -> System.out.println(s)); }
注意到,带参数的lambda表达式,甚至不需要声明类型,因为编译器可以通过上下文来推断出参数的类型。当然,我们也可以显式指定参数类型,尤其是在参数类型推断失败的时候:
(String first, String second) -> { int lenDiff = first.length() - second.length(); return lenDiff == 0 ? first.compareTo(second) : lenDiff; }
this关键字的作用域
public class ThisScopeExample { public static void main(String[] args) { ThisScopeExample example = new ThisScopeExample(); // 输出 "I am Anonymous Class." example.runWithAnonymousClass(); // 输出 "I am ThisScopeExample Class." example.runWithLambda(); } public void runWithAnonymousClass() { // 以匿名类的方式运行 run(new Runnable() { @Override public void run() { // this 是实现了接口 Runnable 的匿名内部类的实例 System.out.println(this); } @Override public String toString() { return "I am Anonymous Class."; } }); } public void runWithLambda() { // 以lambda表达式的方式运行 run(() -> { // this 是类 ThisScopeExample 的实例 System.out.println(this); }); } public void run(Runnable runnable) { runnable.run(); } @Override public String toString() { return "I am ThisScopeExample Class."; } }
lambda表达式的语法
(String first, String second) -> { int lenDiff = first.length() - second.length(); return lenDiff == 0 ? first.compareTo(second) : lenDiff; }
上述是一个典型的而且完整的lambda表达式。
Supplier<Integer> supplier = () -> { return new Random().nextInt(100); }
对于上面的lambda表达式,可以发现它的方法体只有一个表达式,所以,它可以省略大括号,甚至return关键字也省略了,因为编译器可以根据上下文推断是否需要返回值:如果需要,那么就返回该唯一表达式的返回值,如果不需要,则在该唯一表达式后直接return。例如:
// Supplier 是需要返回值的,所以下面的lambda表达式等同于: // () -> { return new Random().nextInt(100); } Supplier<Integer> supplier = () -> new Random().nextInt(100); // Runnable 是不需要返回值的,所以下面的lambda表达式等同于: // () -> { new Random().nextInt(100); return; } Runnable runnable = () -> new Random().nextInt(100);
如果编译器可以推断出lambda表达式的参数类型,则可以忽略其类型:
// 在这里,编译器可以推断出 first 和 second 的类型是 String。 Comparator<String> comp = (first, second) -> { int lenDiff = first.length() - second.length(); return lenDiff == 0 ? first.compareTo(second) : lenDiff; };
如果lambda表达式只有一个参数,那么参数列表中的小括号也可以省略掉:
// 这里的 value ,等同于 (value) Consumer<String> consumer = value -> System.out.println(value);
与普通的函数不一样,lambda表达式不需要指定返回类型,它总是由编译器自行推断出返回类型。如果推断失败,则默认为Object类型。
lambda表达式与闭包
public class ClosureExample { public static void main(String[] args) { // 平方 IntUnaryOperator square = getPowOperator(2); // 立方 IntUnaryOperator cube = getPowOperator(3); // 四次方 IntUnaryOperator fourthPower = getPowOperator(4); // 5的平方 System.out.println(square.applyAsInt(5)); // 5的立方 System.out.println(cube.applyAsInt(5)); // 5的四次方 System.out.println(fourthPower.applyAsInt(5)); } public static IntUnaryOperator getPowOperator(int exp) { return base -> { // 变量 exp 是 getPowOperator 的参数,属于lambda 表达式定义时的自由变量, // 它的生命周期会延长到和返回的 lambda 表达式一样长。 return (int) Math.pow(base, exp); }; } }
上述代码的输出是:
public static IntUnaryOperator getPowOperator(int exp) { // 尝试修改 exp 的值,但编译器会在lambda表达式中报错 exp++; return base -> { // 如果尝试修改 exp 的值,会在此处报错: // Error: 从lambda 表达式引用的本地变量必须是final变量或实际上的final变量 return (int) Math.pow(base, exp); }; }
但这种限制也是有限的,因为我们可以通过将变量声明为一个数组或一个类就可以修改其中的值。例如:
public static IntUnaryOperator getPowOperator(int[] exp) { // exp 是一个int数组:exp = new int[1]; exp[0]++; return base -> { // 此时不会报错,可以正常运行 return (int) Math.pow(base, exp[0]); }; }
结语
为方便大家在移动端浏览,已注册微信公众号【员说】,欢迎关注。第一时间更新技术文章,也会不定时分享圈内热门动态和一线大厂内幕。
感谢您阅读本篇文章,如果觉得本文对您有帮助,欢迎点击推荐和关注,您的支持是我最大的写作动力。
文章欢迎转载,但需在文章页面明显位置,给出作者和原文链接,否则保留追究法律责任的权利!
注意!应各位朋友的邀请,创建了一个技术交流群,(聊技术/看内幕/找内推/读书分享等,拒绝水群,保证品质),可添加微信号【yuanshuo824】,备注:交流,即可入群。