时至今日,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<T> { void accept(T t); } 
如果我们想使用forEach来实现对P的x轴和y轴坐标的某种操作。使用Consumer的匿名内部类实现,我们将传递转置函数,如下所示:
pointList.forEach(new Consumer<Point>() {
        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<ActionEvent> {
    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<T> { T make(); }
Factory<ArrayList<String>> f1 = ArrayList::<String>new;

 

接下来,讲述lambda最重要的Stream API

Stream的中文意思是流,却java中的输入输出流无关,它是jdk8中处理集合的抽象概念,定义了许多对集合的操作方法,可以说,使用它,不但对集合的操作更加简洁,性能也是大大地提高。

jdk8以前,如果需要对集合元素操作,编写一下代码:

String contents = new String(Files.readAllBytes(Paths.get("文件路径")), StandardCharsets.UTF_8);
List<String> words = 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方法,

Stream<String> stream = Stream.of(contents.split("分隔符"));

Stream的其他方法可以参考JAVA8 API.

2.  filter / map / flatMap方法

流转换,从一个流中读取数据,写入另一个流当中,根据不同的需要,可以使用filter / map / flatMap方法这三个流转换方法。

List<String> wordlist = new ArrayList<>();
Stream<String> words = wordlist.stream();
Stream<String> longwords = words.filter(w->w.length()>12);

将符合某条件的元素写入新的流当中。

Stream<String> lowerwords = words.map(String::toLowerCase);

对流中的元素值修改。

Stream<String> letters = words.flatMap(w->characterMap(w));

characterMap函数能将字符转化为字符数组,从而产生包含多个字符数组的数组,flatMap方法能将其合并为一个字符数组。

 

 

10-06 13:39