lambda 表达式是 java 8 中最引人注目的新特性,可是它自身的概念并很差理解,为了更好的掌握它,咱们必须先了解一下函数式编程。网上关于这方面的介绍有不少,可是大多说的含糊不清。这里提供一篇我认为说的最明白的文章,是一篇译文,若是有疑问还能够直接对比原文。 傻瓜函数式编程java
一个lambda 是一段带有参数的代码块,它能够被传递,所以,它能够执行一次或屡次。先从一个简单、经典的比较器例子入手,来感觉一下 lambda 表达式git
List<String> list = Arrays.asList("aaa", "bbb", "ccc"); list.sort(new Comparator<String>() { @Override public int compare(String o1, String o2) { return Integer.compare(o1.length(), o2.length()); } });
它改写成 lambda 表达式的样子是:github
List<String> list = Arrays.asList("aaa", "bbb", "ccc"); list.sort((o1, o2) -> Integer.compare(o1.length(), o2.length()));
仔细对比一下上面的例子的两段代码,咱们思考几个问题:编程
简单抽象一下,基本语法的格式是这个样子:安全
(Type1 param1, Type2 param2, ..., TypeN paramN) -> { statment1; statment2; //............. return statmentM; }
可是上面的例子 (o1, o2) -> Integer.compare(o1.length(), o2.length())) 显然和语法格式不一致,咱们继续以这段代码为例,详细说一下 lambda 表达式的语法。 o1, o2 是什么呢?它们都是字符串,Java 是强类型语言,咱们必须为每一个变量或参数指明类型,一个正经 lambda 的写法以下: 若是一个表达式的参数类型是能够被推导的,那么能够忽略它们的类型ide
(o1, o2) -> { return Integer.compare(o1.length(), o2.length()); }
若是一个表达式的返回类型是能够被推导的,那么它将会从上下文自动推导函数式编程
(o1, o2) -> Integer.compare(o1.length(), o2.length());
也就变成了例子中的样子。 其它简化写法:若是某个表达式不含有参数,你可使用一对空的小括号,跟无参方法同样;若是只含有一个参数,且能够被推导,能够不写小括号。函数
() -> System.out.println("无参的 lambda 表达式"); event -> System.out.println("按钮点击");
这部分是理解 Java lambda 的重点 对于只包含一个抽象 方法的接口,你能够经过 lambda 表达式建立该接口的对象。这种接口被称为函数式接口线程
上面已经提到,咱们有传递一段代码的需求。一样,Java 中不少已有的接口也须要封装代码块,好比 Runnable、Comparator。使用 lambda 表达式实现本应由这些接口实例实现的功能的行为,即lambda 表达式转换成一个接口的实例叫函数式接口转换(ps:我的理解,非标准定义)。 实际上完成函数式接口的转换是 lambda 表达式在Java 中惟一能作的事。咱们以API 中的几段代码来讲明什么是函数式接口转换:code
public void sort(Comparator<? super E> c) { final int expectedModCount = modCount; Arrays.sort((E[]) elementData, 0, size, c); if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } modCount++; }
上面一段代码是 Arraylist 类中的 sort 方法,从方法签名中的参数列表看,这个方法须要一个参数:比较器实现。回顾一下咱们最开始的例子,参数传的是什么? lambda 表达式对不对?
List<String> list = Arrays.asList("aaa", "bbb", "ccc"); list.sort((o1, o2) -> Integer.compare(o1.length(), o2.length()));
即咱们以 lambda 表达式实现了接口实例的功能。是怎么实现的呢?list.sort 方法会接收一个实现了 Comparator 接口的类的实例,调用该对象的 compare 方法会执行 lambda 表达式中的代码。
Java API 在 java.util.function 包中定义了一些通用的函数式接口,好比 BiFunction<T, U, R>意思是T,U类型的参数以及R类型的返回值。
BiFunction<String, String, Integer> c = (o1, o2) -> Integer.compare(o1.length(), o2.length());
虽然你可使用 BiFunction 声明、引用一个表达式,可是并无什么卵用,由于没有任何一个方法能够接收函数式接口做为参数。
把一个已有的方法传递给其余代码。 好比说上面一直用的排序例子,咱们不按长度排序了,如今按字符串大小排序,而且忽略大小写,代码应该是这样子的:
List<String> list = new ArrayList<>(); list.sort((String x, String y) -> x.compareToIgnoreCase(y));
其实咱们传递给sort 方法的代码片断就是 String 类的 compareToIgnoreCase 方法。为了更优雅的写代码,API 简化这种写法,即
List<String> list = new ArrayList<>(); list.sort(String::compareToIgnoreCase);
::操做符分隔对象/类名 和 方法名,主要有三种状况
list.forEach(System.out::print); // 至关于 list.forEach(x -> System.out.println(x));
对于第三种状况,第一个参数会成为执行方法的对象,第2个是方法的参数。好比上面的字符串排序 demo。
构造器引用与方法引用相似,不一样的是构造器引用引用的方法名是 new。貌似用处不大,了解便可。
简单地说,lambda 表达式的变量做用域和内部类的变量做用域相似。
static int b = 10; public static void main(String[] args) { int a = 100; Runnable r = () -> { a++; // 错误,不能更改局部变量的值 b++; // 正确,但有线程安全问题 int a = 1; // 错误,不能与局部变量重名 int b = 1; // 正确 }; new Thread(r).run(); }
接口能够有实现方法,并且不须要实现类去实现其方法。只需在方法名前面加个default关键字便可。
为何要定义默认方法? Java 的接口是不可扩展的,一旦你为某个父接口增长了一个方法,你就必须为全部的实现类增长这个方法的实现。 由于新增了对 lambda 表达式的支持,如今 Java 有了传递代码块的能力,因此对集合的处理有了更简洁、高效的方式,好比,输出集合元素:
// old for(String s : list) { System.out.println(s); } // now list.forEach(System.out::println);
可是,若是给Collection 或 Iterator 增长forEach 接口,会要求全部的实现类增长 forEach 的实现,显然这是没法接受的。因此,为了解决接口的修改与现有的实现不兼容的问题,引入了默认方法。 在JVM中,默认方法的实现是很是高效的,而且经过字节码指令为方法调用提供了支持。 使用规则 若是接口接口定义了一个默认方法,而另外一个父类/接口有定义了一个同名方法,该选择哪一个? 1.选择父类中的方法。若是父类中提供了实现,忽略接口中的方法。 2.接口冲突。若是多个父接口中都提供了对同一方法的实现,接口中的默认方法都忽略,实现类必须本身定义实现。
Java 8 中,接口中容许添加静态方法。好比咱们在 章节“5.3 比较器” 中使用到的 Comparator.comparing 方法。使用静态方法,代码会更简洁。咱们再以开头的那段代码做为例子:
List<String> list = Arrays.asList("aaa", "bbb", "ccc"); list.sort(new Comparator<String>() { @Override public int compare(String o1, String o2) { return Integer.compare(o1.length(), o2.length()); } });
能够写成
List<String> list = Arrays.asList("aaa", "bbb", "ccc"); list.sort(Comparator.comparing(String::length));