jdk8新特性-亮瞎眼的lambda表达式

jdk8之前,尤其是在写GUI程序的事件监听的时候,各种的匿名内部类,大把大把拖沓的代码,程序毫无美感可言!既然java中一切皆为对象,那么,就类似于某些动态语言一样,函数也可以当成是对象啊!代码块也可以当成是对象啊!随着函数式编程的概念越来越深入人心,java中CODE=OBJECT的这一天终于到来了!如果你认为lambda表达式仅仅是为了从语法上简化匿名内部类,那就太小看jdk8的lambda了!

下面我们就来看下lambda表达式是如何亮瞎你的眼的!

lambda的定义 
Funda-men-tally, a lambda expression is just a shorter way of writing an implementation of a method for later execution.  
(1)lambda是方法的实现 
(2)lambda是延迟执行的 
首先看一个用匿名内部类的例子: 

public class Test1{
  public static void main(String args[]){
    Runnable r = new Runnable(){
      public void run(){
        System.out.println("hello,lambda!");
      }
    };
    r.run();
  }
}
要换成lambda是什么样的呢?
public class Test2{
        public static void main(String args[]){
                Runnable r = ()->System.out.println("hello,lambda");
                r.run();
        }
}
原先要5行代码,现在换成了仅仅1行! 
这他妈的得省多少代码啊! 
有木有很兴奋啊! 
下面还有更刺激的! 
lambda是如何做到的呢?看一下反编译之后的字节码:
public static void main(java.lang.String[]);
   descriptor: ([Ljava/lang/String;)V
   flags: ACC_PUBLIC, ACC_STATIC
   Code:
    stack=1, locals=2, args_size=1
      0: invokedynamic #2,  0				  // InvokeDynamic #0:run:()Ljava/lang/Runnable;
      5: astore_1		
      6: aload_1		 
      7: invokeinterface #3,  1				// InterfaceMethod java/lang/Runnable.run:()V
      12: return		  
    LineNumberTable:
      line 3: 0
      line 4: 6
      line 5: 12
}
注意:上面有一个叫做invokedynamic的指令。invokedynamic是从jdk7开始引入的,jdk8开始落地。 
可以看出来lambda并不是语法糖,它不是像匿名内部类那样生成那种带有$的匿名类。 
简单的说,这里只是定义了一个方法调用点,具体调用那个方法要到运行时才能决定,这就是前面所说的:延迟执行。 
具体的细节请google:invokedynamic。 
为了配合lambda,jdk8引入了一个新的定义叫做:函数式接口(Functional interfaces) 
函数式接口: 
it is an interface that requires exactly one method to be implemented in order to satisfy the requirements of the interface.  
(1)是一个接口 
(2)只有一个待实现的方法 
因为jdk8开始,接口可以有default方法,所以,函数式接口也是可以有default方法的,但是,只能有一个未实现的方法。 
与此对应,新引入了一个注解: @FunctionalInterface 
这个注解只是起文档的作用,说明这个接口是函数式接口,编译器并不会使用这个注解来决定一个接口是不是函数式接口。 
不管加不加@FunctionalInterface这个注解,下面的接口都是函数式接口: 
interface Something { 
  public String doit(Integer i); 

lambda的语法 
A lambda in Java essentially consists of three parts: a parenthesized set of parameters, an arrow, and then a body,  
which can either be a single expression or a block of Java code. 
lambda包含3个部分: 
(1)括弧包起来的参数 
(2)一个箭头 
(3)方法体,可以是单个语句,也可以是语句块 
参数可以写类型,也可以不写,jvm很智能的,它能自己推算出来
public class Test3{
        public static void main(String... args) {
            Comparator<String> c = (String lhs, String rhs) -> lhs.compareTo(rhs);
            int result = c.compare("Hello", "World");
                System.out.println(result);
        }
}
方法可以有返回,也可以无返回,如果有多个语句,还要返回值,需要加上return
public class Test4{
    public static void main(String... args) {
      Comparator<String> c =(lhs, rhs) ->{
            System.out.println("I am comparing " +lhs + " to " + rhs);
              return lhs.compareTo(rhs);
        };
        int result = c.compare("Hello", "World");
        System.out.println(result);
    } 
}
一个很有意思的事情: 
之前我们说Object是一切类的父类,然而在加入了lambda以后,这种大一统的局面将不复存在:
public class Test5{
        public static void main(String args[]){
                Object r = ()->System.out.println("hello,lambda");
        }

}
编译报错: 
Test5.java:3: error: incompatible types: Object is not a functional interface 
                Object r = ()->System.out.println("hello,lambda"); 
                           ^ 
1 error

很显然,编译器会检查变量的引用类型里面是否真的是一个函数式接口。那么如何让这段代码通过编译呢? 
只需要加一个强制类型转换就可以了:

public class Test6{
        public static void main(String args[]){
                Object r = (Runnable)()->System.out.println("hello,lambda");
        }

}
lambda的词法作用域

我们知道,在匿名内部类中:

class Hello {
  public Runnable r = new Runnable() {
      public void run() {
        System.out.println(this);
        System.out.println(toString());
      }
    };

  public String toString() {
    return "Hello's custom toString()";
  }
}

public class InnerClassExamples {
  public static void main(String... args) {
    Hello h = new Hello();
    h.r.run();
  }
}
System.out.println(this);这里的this指的是匿名类,而非Hello类。 
想要引用Hello类需要Hello.this这样:
class Hello {
  public Runnable r = new Runnable() {
      public void run() {
        System.out.println(Hello.this);
        System.out.println(Hello.this.toString());
      }
    };
}
这种做法非常的反人类反直觉!看上去很恶心! 
下面我们就来看一下伟大的lambda是什么样子的:
class Hello{
  public Runnable r = () -> {
       System.out.println(this);
       System.out.println(toString());
  };

  public String toString() {
      return "Hello's custom toString()";
  }
}
public class Test7{
  public static void main(String args[]){
        Hello h = new Hello();
        h.r.run();
  }
}
输出: 
Hello's custom toString() 
Hello's custom toString() 
System.out.println(this);这里的this指的是Hello,而非lambda表达式! 
但是,如果我们想在lambda表达式中返回lambda本身该怎么做呢? 
变量捕获 
匿名内部类只能引用作用域外面的final的变量,在lambda中对这个限制做了削弱,只需要是“等价final”就可以,没必要用final关键字来标识。 
public class Test8{
        public static void main(String args[]){
                String message = "Howdy, world!";//不需要是final的
                Runnable r = () -> System.out.println(message);//这里也能访问
                r.run();
        }
}
“等效final”的意思是:事实上的final,所以,一旦赋值也是不可以改变的!比如:
public class Test9{
  public static void main(String args[]){
    String message = "Howdy, world!";
    Runnable r = () -> System.out.println(message);
    r.run();
    message = "change it";
  }
}
Test9.java:4: error: local variables referenced from a lambda expression must be final or effectively final 
                Runnable r = () -> System.out.println(message); 
                                                      ^ 
1 error

如果上面的内容看上去平淡无奇的话,真正的大杀器出现了:方法引用 
我们有一个这样的类:

class Person {
  public String firstName;
  public String lastName;
  public int age;
}
现在我们要把多个Person对象进行排序,有时候是按照firstName来排,有时候是按照lastName或者是age来排,使用lambda可以这样来做:
class Person{
  public String firstName;
  public String lastName;
  public int age;
  public Person(String firstName, String lastName, int age){
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }
  public String toString(){
    return firstName+","+lastName+","+age;
  }
}
public class Test10{
  public static void main(String args[]){
    Person people[] = new Person[]{
      new Person("Ted", "Neward", 41),
      new Person("Charlotte", "Neward", 41),
      new Person("Michael", "Neward", 19),
      new Person("Matthew", "Neward", 13)
    };
    //sort by firstName
    Arrays.sort(people, (lhs,rhs)->lhs.firstName.compareTo(rhs.firstName));
    for(Person p : people){
      System.out.println(p);
    }
  }
}
我们可以把Comparator抽取出来,变成是Person对象的成员变量:
class Person{
  public String firstName;
  public String lastName;
  public int age;
  public Person(String firstName, String lastName, int age){
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }
  public String toString(){
    return firstName+","+lastName+","+age;
  }
  public final static Comparator<Person> compareFirstName =
      (lhs, rhs) -> lhs.firstName.compareTo(rhs.firstName);

  public final static Comparator<Person> compareLastName =
      (lhs, rhs) -> lhs.lastName.compareTo(rhs.lastName);

  public final static Comparator<Person> compareAge =
      (lhs, rhs) -> lhs.age - rhs.age;
}
public class Test11{
  public static void main(String args[]){
    Person people[] = new Person[]{
      new Person("Ted", "Neward", 41),
      new Person("Charlotte", "Neward", 41),
      new Person("Michael", "Neward", 19),
      new Person("Matthew", "Neward", 13)
    };
    Arrays.sort(people, Person.compareFirstName);//这里直接引用lambda
    for(Person p : people){
      System.out.println(p);
    }
  }
}
能起到同样的作用,但是语法看上去很奇怪,因为之前我们都是创建一个满足Comparator签名的方法,然后直接调用,而非定义一个变量, 
然后引用这个变量!所以,还有这么一种调用方法:
class Person{
  public String firstName;
  public String lastName;
  public int age;
  public Person(String firstName, String lastName, int age){
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }
  public String toString(){
    return firstName+","+lastName+","+age;
  }
  public static int compareFirstName(Person lhs, Person rhs){
    return lhs.firstName.compareTo(rhs.firstName);
  } 
  public static int compareLastName(Person lhs, Person rhs){
       return lhs.lastName.compareTo(rhs.lastName);
  }
}
public class Test12{
  public static void main(String args[]){
    Person people[] = new Person[]{
      new Person("Ted", "Neward", 41),
      new Person("Charlotte", "Neward", 41),
      new Person("Michael", "Neward", 19),
      new Person("Matthew", "Neward", 13)
    };
    Arrays.sort(people, Person::compareFirstName);
    for(Person p : people){
      System.out.println(p);
    }
  }
}
看Person::compareFirstName这种调用方式, 
如果是static方法使用:类名::方法名 
如果是instance方法:instance::方法名

但是,上面的代码还是不是很美观,因为Person只是一个数据对象,它不应该的对外提供compareFirstName或者是compareLastName这样的方法, 
我们需要的仅仅是根据某个字段排序而已!很幸运的是jdk的api帮我们做了这件事:

import java.util.*;
class Person{
  public String firstName;
  public String lastName;
  public int age;
  public Person(String firstName, String lastName, int age){
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }
  public String getFirstName(){
    return this.firstName;
  }
  public String getLastName(){
    return this.lastName;
  }
  public String toString(){
    return firstName+","+lastName+","+age;
  }
}
public class Test13{
  public static void main(String args[]){
    Person people[] = new Person[]{
      new Person("Ted", "Neward", 41),
      new Person("Charlotte", "Neward", 41),
      new Person("Michael", "Neward", 19),
      new Person("Matthew", "Neward", 13)
    };
    Arrays.sort(people, Comparator.comparing(Person::getFirstName));
    for(Person p : people){
      System.out.println(p);
    }
  }
}
Arrays.sort(people, Comparator.comparing(Person::getFirstName));这里调用了Comparator.comparing方法, 
但是注意这里的Person::getFirstName,很显然getFirstName()并不是static的,这是jdk做了封装的缘故! 
这样做就非常完美了! 
假如我们的排序算法改为:先按照lastName,然后按照age排序呢?
public class Test15{
  public static void main(String args[]){
    Person people[] = new Person[]{
      new Person("Ted", "Neward", 10),
      new Person("Charlotte", "Neward", 41),
      new Person("Michael", "Naward", 19),
      new Person("Matthew", "Nmward", 13)
    };
    Collections.sort(Arrays.asList(people), (lhs, rhs)->{
      if(lhs.getLastName().equals(rhs.getLastName())){
        return lhs.getAge()-rhs.getAge();
      }else{
        return lhs.getLastName().compareTo(rhs.getLastName());
      }
    });
    for(Person p : people){
      System.out.println(p);
    }
  }
}
很显然,应该还有更好的实现方式:
public class Test16{
  public static void main(String args[]){
    Person people[] = new Person[]{
      new Person("Ted", "Neward", 10),
      new Person("Charlotte", "Neward", 41),
      new Person("Michael", "Naward", 19),
      new Person("Matthew", "Nmward", 13)
    };
    Collections.sort(Arrays.asList(people),Comparator.comparing(Person::getLastName).thenComparing(Person::getAge));
    for(Person p : people){
      System.out.println(p);
    }
  }
}
Comparator.comparing(Person::getLastName).thenComparing(Person::getAge):简直帅呆了! 
还有更多的诸如:andThen()这样的方法,可以查api。 
虚方法扩展 
因为接口可以有default方法,所以很多类库都重写了,加入了一些default的方法,比如: 
interface Iterator<T> { 
  boolean hasNext(); 
  T next(); 
  void remove(); 
  void skip(int i) default { 
    for (; i > 0 && hasNext(); i--) next(); 
  } 
}  
skip(i)就是一个default方法,这样所有的Iterator的子类都具有了一个叫skip的方法! 
但是,大家对default方法的争议还是比较大的,比如:
interface I1 {
  public default void print(){
    System.out.println("I1");
  }
  public void hello();
}
interface I2{
  public default void print(){
    System.out.println("I2");
  }
  public void world();
}
class Impl implements I1,I2{
  public void hello(){
  }
  public void world(){
  }
}
如果在Impl上调用print会怎样呢?这不就是传说中的多继承么?想知道结果的话,自己试一下就可以了,哈哈 
Stream: 
之前的文章已经有介绍,下面只据一些使用的例子: 
过滤age>12的元素:
people
      .stream()
      .filter(it -> it.getAge() >= 21) ;
过滤age>12的元素,并输出:
people.stream()
      .filter((it) -> it.getAge() >= 21)
      .forEach((it) -> 
        System.out.println("Have a beer, " + it.getFirstName()));
jdk预定义的Predicate:
Predicate<Person> drinkingAge = (it) -> it.getAge() >= 21;
Predicate<Person> brown = (it) -> it.getLastName().equals("Brown");
people.stream()
      .filter(drinkingAge.and(brown))
      .forEach((it) ->System.out.println("Have a beer, " + it.getFirstName()));
map:
IntStream ages =
  people.stream()
    .mapToInt((it) -> it.getAge()); 
//sum:
int sum = people.stream()
        .mapToInt(Person::getAge)
        .sum();
重点说下reduce:
public class Test17{
        public static void main(String args[]){
                List<Integer> values = Arrays.asList(1,2,3,4,5);
                int sum = values.stream().reduce(0, (l,r)->l+r);
                System.out.println(sum);
        }
}
reduce(0, (l,r)->l+r)的工作原理是:第一个参数0作为后面lambda表达式的左操作数,然后从stream中取出一个元素作为右操作数, 
二者运算的结果作为下一次运算的左操作数,依次循环。 
最后看一个好玩的例子:
class Person{
  public String firstName;
  public String lastName;
  public int age;
  public Person(String firstName, String lastName, int age){
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }
  public String getFirstName(){
    return this.firstName;
  }
  public String getLastName(){
    return this.lastName;
  }
  public int getAge(){
    return this.age;
  }
  public String toString(){
    return firstName+","+lastName+","+age;
  }
  public String toJson(){
    return "{"+
        "firstName:\""+firstName+"\","+
        "lastName:\""+lastName+"\","+
        "age:"+age +
        "}";
  }
}
public class Test18{
  public static void main(String args[]){
    Person people[] = new Person[]{
      new Person("Ted", "Neward", 10),
      new Person("Charlotte", "Neward", 41),
      new Person("Michael", "Naward", 19),
      new Person("Matthew", "Nmward", 13)
    };
    String json = Arrays.asList(people).stream().map(Person::toJson).reduce("[",(l,r)->l + (l.equals("[")?"":",") + r)+"]";
    System.out.println(json);
  }
}
输出结果: 
[{firstName:"Ted",lastName:"Neward",age:10},{firstName:"Charlotte",lastName:"Neward",age:41},{firstName:"Michael",lastName:"Naward",age:19},{firstName:"Matthew",lastName:"Nmward",age:13}] 
还可以这样:
public class Test19{
  public static void main(String args[]){
    Person people[] = new Person[]{
      new Person("Ted", "Neward", 10),
      new Person("Charlotte", "Neward", 41),
      new Person("Michael", "Naward", 19),
      new Person("Matthew", "Nmward", 13)
    };
    String joined = Arrays.asList(people).stream().map(Person::toJson).collect(Collectors.joining(", "));
      	System.out.println("[" + joined + "]"); 
  }
}
更进一步:
public class Test20{
  public static void main(String args[]){
    Person people[] = new Person[]{
      new Person("Ted", "Neward", 10),
      new Person("Charlotte", "Neward", 41),
      new Person("Michael", "Naward", 19),
      new Person("Matthew", "Nmward", 13)
    };
    String json = Arrays.asList(people).stream().map(Person::toJson).collect(Collectors.joining(", ", "[", "]"));
    System.out.println(json);
  }
}
如果只能用一个字来形容,那就是perfect!
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值