时至今日,jdk11发布,相信有不少在传统公司从事IT的小伙伴,还停留在jdk7的阶段,为了避免被时代一再淘汰,乘此机会想大家科普下一直被挂在嘴上的java8 lambda表达式。
为何使用lambda表达式?
很多人都觉得lambda不过是个语法糖,除了代码看起来简练之外,没其他好处,反而还要花费时间精力去学习,还不如使用以前的老办法。这样想就大错特错了!lambda表达式的底层的代码实现,完全是考虑到了当今多核处理器的发展趋势,将对集合元素的操作,分发到多个处理器上并行计算,大大提高了代码的执行速度。
在jdk8以前的版本,操作list和set集合时,通常都是由我们自己从collection获取 iterator迭代器,然后自行编写代码实现对元素的操作。即这个并行处理,完全是靠我们自己编写客户端代码实现,而无法从collection从获得相关的实现或支持。
但是,jdk8为collection提供了forEach接口,我们现在可以直接调用此接口,就可以实现对集合元素的并行计算,无需我们在客户端编写任何额外的代码。
但是,我们在编写客户端代码时,需要collection集合为我们提供简单实现方法。目前,执行此操作的标准方法是通过接口的匿名类来实现。但是,用于定义匿名内部类的语法太笨拙,无法实现此目的。
例如,collection的forEach,参数类型为Consumer,并为每个元素调用其accept方法,这个forEach方法,就称为函数式接口,并且,这个函数式的接口的参数类型Consumer,只会有一个接口方法:
interface Consumer { void accept(T t); }
如果我们想使用forEach来实现对P的x轴和y轴坐标的某种操作。使用Consumer的匿名内部类实现,我们将传递转置函数,如下所示:
pointList.forEach(new Consumer() {
public void accept(Point p) {
p.move(p.y, p.x);
}
});
如果使用lambda,只需简短的一行代码即可:
pointList.forEach(p -> p.move(p.y, p.x));
接下来继续给出学习例子:
1. Arrays.sort(strings,(v1,v2)->Integer.compare(v1.length(),v2.length()));
正常的Arrays.sort调用应该是:
Arrays.sort(strings, Comparator super T> c),c必须是实现了Comparator接口的实例。
但是lambda表示式,Comparator接口的实例c已替换为(v1,v2)->Integer.compare(v1.length(),v2.length()),其中v1,v2是字符串数组的任意元素,默认类型字符串,无需显式说明,->代表参数传递给Comparator接口的唯一方法compare。因为sort方法的第二个参数类型是Comparator,唯一方法是compare,所以很多jdk8可以将其进行转换。
2. button.setOnAction(event -> System.out.println("Thanks for clicking!"));
正常的button.setOnAction调用:
button.setOnAction( new EventHandler{ public void handle(){ System.out.println("Thanks for clicking!")); } })
button.setOnAction方法参数类型EventHandler,EventHandler有且仅有唯一的handle方法,通过函数转换接口,可以转换为简洁的lambda表达式。
3. 方法引用
lambda表达式主要有3中方法引用,分别是静态方法引用,实例方法引用,构造器引用。
在jdk8中,找到java.util.function包,里面定义了各类型的函数接口。如同匿名内部类一样,Lambda表达式可以被理解为各类型函数式接口的匿名形式。除了匿名形式外,jdk8还为我们提供另一种更加简洁的方式来,
1) 例如:
String::valueOf
Integer::compare
引用类的静态方法,看不到任何其他的局部变量。语法: 引用类型::静态方法,这就是静态方法引用。
2) 此外,假设pointList集合包含多个TransPoint类型的元素,而TransPoint类型的实例拥有方法transpose,
void transpose () { int t = x; x = y; y = t; };
我们想要写成一下的形式:
pointList.forEach(/*transpose x and y of this element*/);
实例方法的引用又支持这种类型:
TransPoint::transpose
因此,原来的lambda: (TransPoint pt) -> { pt.transpose(); },瞬间可以简化为:pointList.forEach(TransPoint::transpose);
语法:对象::实例方法,这是实例方法引用。
3) 同理,构造器的引用,语法其实和方法引用一样,只不过它以new关键字替代方法名,例如:
ArrayList::new
File::new
如果构造器是泛型,可以在new前显示地声明:
interface Factory
Factory
接下来,讲述lambda最重要的Stream API
Stream的中文意思是流,却java中的输入输出流无关,它是jdk8中处理集合的抽象概念,定义了许多对集合的操作方法,可以说,使用它,不但对集合的操作更加简洁,性能也是大大地提高。
jdk8以前,如果需要对集合元素操作,编写一下代码:
String contents = new String(Files.readAllBytes(Paths.get("文件路径")), StandardCharsets.UTF_8); Listwords = Arrays.asList(contents.split("分隔符")); int count = 0; for(String item:words){ if (item.length() > 12) count++; }
这段代码,不但冗长,而且很难并行计算。jdk8:
long count = words.stream().filter(w->w.length()>12).count();
与之前的集合操作相比,Stream的不同点在于:
1)Stream自己不会储存元素。
2)Stream操作符不会改变源对象,它会返回一个新的Stream对象。
3)Stream可以延迟操作,实际需要结果时才会执行代码。
1. 创建Stream
Collection接口中新增stream方法,可以将集合直接转化为Stream对象,对于数组,可以使用静态的Stream.of方法,
Streamstream = Stream.of(contents.split("分隔符"));
Stream的其他方法可以参考JAVA8 API.
2. filter / map / flatMap方法
流转换,从一个流中读取数据,写入另一个流当中,根据不同的需要,可以使用filter / map / flatMap方法这三个流转换方法。
Listwordlist = new ArrayList<>(); Stream words = wordlist.stream(); Stream longwords = words.filter(w->w.length()>12);
将符合某条件的元素写入新的流当中。
Streamlowerwords = words.map(String::toLowerCase);
对流中的元素值修改。
Streamletters = words.flatMap(w->characterMap(w));
characterMap函数能将字符转化为字符数组,从而产生包含多个字符数组的数组,flatMap方法能将其合并为一个字符数组。