Java8新特性之Lambda表达式

什么是Lambda表达式?

在一般数学计算中lambda表达式就是一个函数,为部分或者所有输入值指定一个输出值,在java中Lambad表达式是以函数的概念引入,可以理解为一种语法更加紧凑的匿名方法,并且允许省略修饰符,返回类型并且某种情况下的参数类型。
语法:
lambda基本语法:

1
2
3
   (parameters) -> expression
   or
   (parameters) -> { statements; }

Examples:

1
2
3
4
5
6
  (int x,int y)->x+y   //取整数x,y并返回它们的和
  (x, y) -> x - y  //取整数x,y并返回它们的差                                
  () -> 42  //不取任何值,直接返回42                                         
  (String s) -> System.out.println(s) //取字符串s,在控制台打印,不返回任何值。             
  x -> 2 * x  //取一个数字,返回它的2倍                                      
  c -> { int s = c.size(); c.clear(); return s; }//取集合c,清空它,然后返回它清空前的大小

语法说明

   1.参数类型可以显式声明(ex.1,4)或者隐式推断(ex.2,5,6),显式声明和隐式推断参数不允许用在同一个lambda表达式。
   2.lambda body可以是一个block({}大括号包含代码块ex.6)或者是一个表达式(ex.1-5)。block body可以返回值(ex.6)
   或者什么都不返回()。
   3.如果lambda body是一个表达式,它也可能返回值(ex.1,2,3,5)或者什么都不返回(ex.4)。
   4.隐式推断可以省略()(ex.5,6)。
   5.ex.6 lambda能操作一个集合,同样的,根据上下文给定的参数,如果一个参数a拥有size和clear方法,
   它也能操作并返回其类型的值。
   

函数式接口
Lambda表达式如何匹配Java的类型系统?每一个lambda都能够通过一个特定的接口,与一个给定的类型进行匹配。
一个所谓的函数式接口必须要有且仅有一个抽象方法声明(java8允许接口有默认方法)。每个与之对应的lambda表达式必须要与抽象方法的声明相匹配。

1、一个可以接受Lambda表达式的接口只能有一个抽象方法,否则在编译时将抛出异常。
2、为了显示的说明某接口是可使用lambda表达式,可以使用 @FunctionalInterface 注解该接口。

1
2
3
4
5
6
7
8
9
10
11
12
@FunctionalInterface
interface Converter<F, T> {
    T convert(F from);//抽象方法只能一个

    default double sqrt(int a) {//可以多个接口默认方法
        return Math.sqrt(a);
    }
}

Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted);    // 123

Default Methods(默认方法)详情
注意,如果不写 @FunctionalInterface 标注,程序也是正确的。

官网例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
   public class Calculator {

    interface IntegerMath {
        int operation(int a, int b);
    }

    public int operateBinary(int a, int b, IntegerMath op) {
        return op.operation(a, b);
    }

    public static void main(String... args) {

        Calculator myApp = new Calculator();
        IntegerMath addition = (a, b) -> a + b;//隐式推断类型
        IntegerMath subtraction = (int a, int b) -> a - b;//显式声明类型
        System.out.println("40 + 2 = " +
            myApp.operateBinary(40, 2, addition));
        System.out.println("20 - 10 = " +
            myApp.operateBinary(20, 10, subtraction));
    }
}

为什么Java要增加lambda表达式?

Lambda表达式,也叫做闭包,是一个在很多现代程序语言中十分流行的特性。在众多不同的原因中当中,Java平台最迫切的原因之一是lambda表达式能简化多线程环境下集合的分布式处理。
1.内部循环和外部循环

常规的外部循环:

1
2
3
4
5
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

for (int number : numbers) {
    System.out.println(number);
}

这种情况简单的存在3个缺点:

1.只能顺序处理List中的元素

2.不能充分利用多核CPU

3.不利于编译器优化
使用Lamdba表达式内部循环:

1
2
3
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

numbers.forEach((Integer value) -> System.out.println(value));

1.不一定需要顺序处理List中的元素,顺序可以不确定

2.可以并行处理,充分利用多核CPU的优势

3.有利于JIT编译器对代码进行优化

2、传递行为,而不仅仅是传值
传值局限性的场景

1
2
3
4
5
6
7
8
9
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

public int sumAll(List<Integer> numbers) {
    int total = 0;
    for (int number : numbers) {
        total += number;
    }
    return total;
}

sumAll算法很简单,完成的是将List中所有元素相加。某一天如果需要增加一个对List中所有偶数求和的方法sumAllEven,如下:

1
2
3
4
5
6
7
8
9
public int sumAllEven(List<Integer> numbers) {
    int total = 0;
    for (int number : numbers) {
        if (number % 2 == 0) {
            total += number;
        }
    }
    return total;
}

又有一天,需要增加第三个方法:对List中所有大于3的元素求和,那是不是继续加下面的方法呢?

1
2
3
4
5
6
7
8
9
public int sumAllEven(List<Integer> numbers) {
    int total = 0;
    for (int number : numbers) {
        if (number > 3) {
            total += number;
        }
    }
    return total;
}

比较这三个方法,发现了一个很明显的“代码臭味”—— 代码重复。 如果使用Lambda表达式,那么需要Lambda表达式中的谓词(Predicate)发挥作用:

1
2
3
4
5
6
7
8
9
10
11
12
13
public int sumAll(List<Integer> numbers, Predicate<Integer> p) {
    int total = 0;
    for (int number : numbers) {
        if (p.test(number)) {
            total += number;
        }
    }
    return total;
}

sumAll(numbers, n -> true);
sumAll(numbers, n -> n % 2 == 0);
sumAll(numbers, n -> n > 3);


3、Consumer与Loan Pattern
比如有一个资源类Resource:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Resource {
  public Resource() {
      System.out.println("Opening resource");
  }

  public void operate() {
      System.out.println("Operating on resource");
  }

  public void dispose() {
      System.out.println("Disposing resource");
  }
}
//调用
Resource resource = new Resource();
try {
    resource.operate();
} finally {
    resource.dispose();
}

但是有一个问题,如果很多地方都要用到这个资源,那么就存在很多段类似这样的代码,这很明显违反了DRY(Don’t Repeat It Yourself)原则。而且如果某位程序员由于某些原因忘了用try/finally处理资源,那么很可能导致内存泄漏。那咋办呢?Java 8提供了一个Consumer接口,代码改写为如下:
因为对资源对象resource执行operate方法时可能抛出RuntimeException,所以需要在finally语句块中释放资源,防止可能的内存泄漏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Resource {
  public Resource() {
      System.out.println("Opening resource");
  }

  public void operate() {
      System.out.println("Operating on resource");
  }

  public void dispose() {
      System.out.println("Disposing resource");
  }

  public static void withResource(Consumer<Resource> consumer) {
      Resource resource = new Resource();
      try {
          consumer.accept(resource);
      } finally {
          resource.dispose();
      }
  }

  public static void main(String[] args) {
      Resource.withResource(resource -> resource.operate());
  }
}


Console:
Opening resource
Operating on resource
Disposing resource

外部要访问Resource,只能通过withResource方法了,而且也完全杜绝了因人为疏忽而导致的潜在内存泄漏。

Lambda表达式范围

对于lambdab表达式外部的变量,其访问权限的粒度与匿名对象的方式非常类似。

1
2
3
4
5
@FunctionalInterface
interface Converter<F, T> {
    T convert(F from);
}

1.访问局部变量

1
2
3
4
5
final int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);

stringConverter.convert(2);     // 3

与匿名对象不同的是,变量num不一定是final。

1
2
3
4
5
 int num = 1;//去掉final也是正确的
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);

stringConverter.convert(2);     // 3

但是num在编译的时候隐式的当作final变量来处理。

1
2
3
4
int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);
num = 3;//这样是错误的不能对final变量重新复制,在lambda表达式内部改变num的值也是不允许的

2.访问成员变量和静态变量 与局部变量不同,我们在lambda表达式的内部能获取到对成员变量或静态变量的读写权。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class LambdaTest {
    static int outerStaticNum;
    int outerNum;

    void testScopes() {
        Converter<Integer, String> stringConverter1 = (from) -> {
            outerNum = 23;
            return String.valueOf(from);
        };

        Converter<Integer, String> stringConverter2 = (from) -> {
            outerStaticNum = 72;
            return String.valueOf(from);
        };
    }
}

默认方法无法在lambda表达式内部被访问。因此下面的代码是无法通过编译的:

1
2
3
4
5
6
7
8
interface Formula {
    double calculate(int a);

    default double sqrt(int a) {
        return Math.sqrt(a);
    }
}
Formula formula = (a) -> sqrt( a * 100);

内置函数式接口

Predicates
Predicate是一个布尔类型的函数,该函数只有一个输入参数。Predicate接口包含了多种默认方法,用于处理复杂的逻辑动词(and, or,negate)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    Predicate<String> predicate = (s) -> s.length() > 0;
  predicate.test("Java");

  Predicate<String> predicate1 = String::isEmpty;

  System.out.println(predicate1.test("Java"));// false
  System.out.println(predicate1.negate().test("Java"));// true

  Predicate<String> predicate2 = String::isEmpty;

  System.out.println(predicate2.or(predicate).test("JAVA"));// true
  System.out.println(predicate2.and(predicate).test("JAVA"));// false
    System.out.println(Predicate.isEqual("Java").test("Java"));// true
  System.out.println(Predicate.isEqual("Java").test("Java8"));// false

Predicate源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@FunctionalInterface
public interface Predicate<T> {


    boolean test(T t);
    //and
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }
    //取反
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }
    //or
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }
    //isEqual
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

Functions
Function接口接收一个参数,并返回单一的结果。默认方法可以将多个函数串在一起。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 Function<String, Integer> toInteger = Integer::valueOf;
  Function<String, String> backToString = toInteger
          .andThen(String::valueOf);
  Integer integer = toInteger.apply("123");
  String string = backToString.apply("123");
  System.out.println(integer);// 123
  System.out.println(string);// "123"


    Function<String, Integer> toInteger = Integer::valueOf;
  Function<String, String> toString = (from) -> from + "123";
    //toString Function的结果作为toInteger Fuction的输入
  Integer result = toInteger.compose(toString).apply("123");//此处的"123"是toString Function的输入
  System.out.println(result);//123123     

Function源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@FunctionalInterface
public interface Function<T, R> {

  R apply(T t);
  default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
   default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
 static <T> Function<T, T> identity() {
        return t -> t;
    }
}

Suppliers
Supplier接口产生一个给定类型的结果。与Function不同的是,Supplier没有输入参数。

1
2
 Supplier<Person> personSupplier = Person::new;
  Person person=personSupplier.get();

Suppliers源码:

1
2
3
4
5
6
7
8
9
10
@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

Consumers

Consumer代表了在一个输入参数上需要进行的操作。 为什么Java增加Lambda表达式章节的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void withResource(Consumer<Resource> consumer) {
  Resource resource = new Resource();
  try {
       //输入一个resource对象
      consumer.accept(resource);
  } finally {
      resource.dispose();
  }
}

public static void main(String[] args) {
     //在输入的resource对象上执行operate()操作
  Resource.withResource(resource -> resource.operate());
}

Consumer也可以将多个操作穿串一起

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 public class Person {
   private String firstName;
   private String lastName;

   Person() {

   }

   public Person(String firstName, String lastName) {
  super();
  this.firstName = firstName;
  this.lastName = lastName;
   }
   }

   //先执行打印firstName操作   
   Consumer<Person> greeter = (p) -> System.out.println("Hello, "
          + p.getFirstName());
   //后执行打印lastName操作       
  greeter.andThen(p -> System.out.println("Hello, " + p.getLastName()))
          .accept(new Person("Luke", "Skywalker"));

   Console:
   Hello, Luke
   Hello, Skywalker

Consumer源码:

1
2
3
4
5
6
7
8
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

Comparators

Java 8 为这个接口添加了不同的默认方法。

1
2
3
4
5
6
7
8
 Comparator<Person> comparator = (p1, p2) -> p1.getFirstName()
          .compareTo(p2.getFirstName());

  Person p1 = new Person("John", "Doe");
  Person p2 = new Person("Alice", "Wonderland");

  comparator.compare(p1, p2); //> 0
  comparator.reversed().compare(p1, p2); // < 0

Optionals
Optional不是一个函数式接口,而是一个精巧的工具接口,用来防止NullPointerEception产生。
Optional类的Javadoc描述如下:
这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

of:调用工厂方法创建Optional实例
1
2
3
Optional<String> name = Optional.of("Sanaulla");
//传入参数为null,抛出NullPointerException.
Optional<String> someNull = Optional.of(null);

ofNullable:为指定的值创建一个Optional,如果指定的值为null,则返回一个空的Optional。

ofNullable与of方法相似,唯一的区别是可以接受参数为null的情况。

1
Optional empty = Optional.ofNullable(null);

isPresent:如果值存在返回true,否则返回false。
1
2
 Optional<String> name = Optional.of("Java");
 System.out.println(name.isPresent());//true

get:如果Optional有值则将其返回,否则抛出NoSuchElementException。
1
2
3
4
5
6
7
8
Optional empty = Optional.ofNullable(null);
//执行下面的代码会输出:No value present 
try {
  //在空的Optional实例上调用get(),抛出NoSuchElementException
  System.out.println(empty.get());
} catch (NoSuchElementException ex) {
  System.out.println(ex.getMessage());
}

ifPresent:如果Optional实例有值则为其调用Consumer,否则不做处理
1
2
3
4
5
 Optional<String> name = Optional.of("Java");
  name.ifPresent((value) -> {
      System.out.println("The length of the value is: " + value.length());
    //The length of the value is: 4
  });

orElse:如果有值则将其返回,否则返回指定的其它值。
1
2
3
4
5
6
7
8
    Optional<Object> empty = Optional.ofNullable(null);
  Optional<String> name = Optional.of("not null");
  System.out.println(empty.orElse("There is no value present!"));
  System.out.println(name.orElse("There is some value!"));

    Console:
    There is no value present!
    not null

orElseGet:orElseGet与orElse方法类似,区别在于得到的默认值。orElse方法将传入的字符串作为默认值,orElseGet方法可以接受Supplier接口的实现用来生成默认值。
1
2
3
4
5
6
7
8
 //orElseGet与orElse方法类似,区别在于orElse传入的是默认值,
  //orElseGet可以接受一个lambda表达式生成默认值。
  System.out.println(empty.orElseGet(() -> "Default Value"));
  System.out.println(name.orElseGet(() -> "Default Value"));

   Console:
   Default Value
   not null

orElseThrow:如果有值则将其返回,否则抛出Supplier接口创建的异常。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
try {
  //orElseThrow与orElse方法类似。与返回默认值不同,
  //orElseThrow会抛出lambda表达式或方法生成的异常 

  empty.orElseThrow(ValueAbsentException::new);
} catch (Throwable ex) {
  //输出: No value present in the Optional instance
  System.out.println(ex.getMessage());
}

class ValueAbsentException extends Throwable {

  public ValueAbsentException() {
    super();
  }

  public ValueAbsentException(String msg) {
    super(msg);
  }

  @Override
  public String getMessage() {
    return "No value present in the Optional instance";
  }
}

map:如果有值,则对其执行调用mapping函数得到返回值。如果返回值不为null,则创建包含mapping返回值的Optional作为map方法返回值,否则返回空Optional。
1
2
3
4
 Optional<String> name = Optional.of("not null");
  Optional<String> upperName = name.map((value) -> value.toUpperCase());
    //输出:NOT NULL
  System.out.println(upperName.orElse("No value found"));

flatMap:如果有值,为其执行mapping函数返回Optional类型返回值,否则返回空Optional。flatMap与map(Funtion)方法类似,区别在于flatMap中的mapper返回值必须是Optional。调用结束时,flatMap不会对结果用Optional封装。
1
2
3
4
 Optional<String> name = Optional.of("not null");
  name = name.flatMap((value) -> Optional.of(value.toUpperCase()));
    //输出:NOT NULL
  System.out.println(name.orElse("No value found"));

filter:filter个方法通过传入限定条件对Optional实例的值进行过滤。对于filter函数应该传入实现了Predicate接口的lambda表达式。
1
2
3
4
5
6
7
8
9
10
11
 Optional<String> name = Optional.of("Sanaulla");
  //filter方法检查给定的Option值是否满足某些条件。
  //如果满足则返回同一个Option实例,否则返回空Optional。
  Optional<String> longName = name.filter((value) -> value.length() > 6);
  System.out.println(longName.orElse("The name is less than 6 characters"));//输出Sanaulla
  
  //另一个例子是Optional值不满足filter指定的条件。
  Optional<String> anotherName = Optional.of("Sana");
  Optional<String> shortName = anotherName.filter((value) -> value.length() > 6);
  //输出:The name is less than 6 characters
  System.out.println(shortName.orElse("The name is less than 6 characters"));


参考资料:
http://www.lambdafaq.org/
http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#use-case
http://java.dzone.com/articles/why-we-need-lambda-expressions
http://java.dzone.com/articles/why-we-need-lambda-expressions-0
http://www.javacodegeeks.com/2013/09/deep-dive-into-optional-class-api-in-java-8.html