能够把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数、函数主体和返回类型,可能还有一个能够抛出的异常列表。java
何为更简介,作一个例子,使用匿名类和Lambda定义一个Comparator对象:数组
package cn.net.bysoft.chapter3; import java.util.Comparator; public class Example1 { public static void main(String[] args) { // 使用匿名类定义Comparator对象 Comparator<Apple> byWeight = new Comparator<Apple>() { @Override public int compare(Apple a1, Apple a2) { return a1.getWidth().compareTo(a2.getWidth()); } }; // 使用Lambda定义Comparator对象 // 以 -> 为界限,-> 左侧部分是方法参数, -> 右侧部分是方法主体 Comparator<Apple> byWeight_lambda = (Apple a1, Apple a2) -> a1.getWidth().compareTo(a2.getWidth()); } }
在进一步,下面给出了Java8中五个有效的Lambda表达式:app
// 具备一个String类型的参数并返回一个int,没有return语句,由于已经隐含了return (String s) -> s.length() // 具备一个Apple类型的参数并返回一个boolean (Apple a) -> a.getWeight() > 150 // 具备两个int类型的参数而没有返回值 (int x, int y) -> { System.out.println(x + y); } // 没有参数并返回一个int () -> 42 // 有两个Apple类型的参数并返回一个int (Apple a1, Apple a2) -> a1.getWeight().comparaTo(a2.getWeight())
函数式接口就是只定义一个抽象方法的接口,例如Comparator和Runnable。ide
Lambda表达式容许你之内联的形式为函数式接口的抽象方法提供实现,并把整个表达式做为函数式接口的实例。用匿名类也能够完成一样的事情,但比较笨拙。函数
让咱们经过一个例子来看看在哪里以及如何使用Lambda表达式。测试
打开一个资源,作一些处理,关闭资源:spa
package cn.net.bysoft.chapter3; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class Example2 { public static void main(String[] args) { try { String s = processFile(); System.out.println(s); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // 打开一个资源,读取一行,关闭资源 // 使用了Java7中的带资源的try语句,不须要显示关闭资源 public static String processFile() throws IOException { try (BufferedReader br = new BufferedReader(new FileReader("c://data.txt"))) { return br.readLine(); } } }
上面这段代码是有局限性的,只能读文件的第一行。若是想要返回前两行,甚至更多该怎么办?.net
通常来讲,经过4个步骤就能够将行为抽象出来:设计
具体来看看这4步,首先,建立一个函数式接口:code
package cn.net.bysoft.chapter3; import java.io.BufferedReader; import java.io.IOException; @FunctionalInterface public interface BufferedReaderProcessor { String process(BufferedReader br) throws IOException; }
接下来,行为参数化:
public static String processFile(BufferedReaderProcessor p) throws IOException { ... }
在来,执行行为:
public static String processFile(BufferedReaderProcessor p) throws IOException { try(BufferedReader br = new BufferedReader(new FileReader("c://data.txt"))) { return p.process(br); } }
最后,传递lambda:
String oneLine = processFile((BufferedReader br) -> br.readLine()); String twoLine = processFile((BufferedReader br) -> br.readLine() + br.readLine());
测试:
package cn.net.bysoft.chapter3; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class Example2 { public static void main(String[] args) { // 1.行为参数化 try { // 4.传递Lambda String oneLine = processFile((BufferedReader br) -> br.readLine()); System.out.println(oneLine); String twoLine = processFile((BufferedReader br) -> br.readLine() + br.readLine()); System.out.println(twoLine); /** * output: * aaa * aaabbb * */ } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // 2.传递行为 public static String processFile(BufferedReaderProcessor p) throws IOException { // 3.调用行为 try(BufferedReader br = new BufferedReader(new FileReader("c://data.txt"))) { return p.process(br); } } }
Java API中已经有了一些函数式接口,好比Comparable、Runnable和Callable等。
Java8的设计师帮你在java.util.function包中引入了几个新的函数式接口:
函数式接口 | 函数描述符 |
Predicate<T> | T -> boolean |
Consumer<T> | T -> void |
Function<T,R> | T -> R |
Supplier<T> | () -> T |
UnaryOperator<T> | (T) -> T |
BinaryOperator<T> | (T,T) -> T |
BiPredicate<L,R> | (L,R) -> boolean |
BiConsumer<T,U> | (T,U) -> void |
BiFunction<T,U,R> | (T,U) -> R |
写个例子使用几个经常使用的函数式接口:
package cn.net.bysoft.chapter3; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; public class Example3 { public static void main(String[] args) { // 使用Predicate<T> Apples apples = new Apples(); List<Apple> green_apples = filter(apples.getApples(), (Apple apple) -> "green".equals(apple.getColor())); // 使用Consumer<T> System.out.println("绿苹果有:"); forEach(green_apples, (Apple apple) -> System.out.println(apple.getId())); // 使用Function<T> // 将字符串的长度放到一个List<Integer>中 System.out.println("数组中的字符长度:"); List<Integer> list = map(Arrays.asList("lambdas", "in", "action"), (String s) -> s.length()); forEach(list, (Integer i) -> System.out.println(i)); } private static <T> List<T> filter(List<T> list, Predicate<T> p) { List<T> result = new ArrayList<>(); for (T t : list) if (p.test(t)) result.add(t); return result; } private static <T> void forEach(List<T> list, Consumer<T> c) { for (T t : list) c.accept(t); } private static <T, R> List<R> map(List<T> list, Function<T, R> f) { List<R> result = new ArrayList<>(); for (T t : list) { result.add(f.apply(t)); } return result; } }
Lambda的类型检查是从使用Lambda的类型上下文推断出来的。上下文中须要的类型称为目标类型。例如:
filter(apples.getApples(), (Apple apple) -> "green".equals(apple.getColor()));
Java编译器会从上下文推断出用什么函数式接口来配合Lambda表达式,这意味着它也能够推断出适用Lambda的签名,由于函数描述符能够经过目标类型来获得。这样作的好处在于,编译器能够了解Lambda表达式的参数类型,这样就能够在Lambda语法中省去标注参数类型,例如:
Comparator<Apple> byWeight_lambda = (a1, a2) -> a1.getWidth().compareTo(a2.getWidth());
有时候显示写出类型更容易读,有时候去掉他们更容易读。
Lambda容许使用自由变量,它们被称做捕获Lambda,例如:
int portNumber = 1337; Runnable r = () -> System.out.println(portNumber); r.run();
Lambda能够没有限制地捕获实例变量和敬爱变量。但局部变量必须显示声明为final,或事实上是final。例如,下面这段代码就没法编译:
int portNumber = 1337; Runnable r = () -> System.out.println(portNumber); // error portNumber = 1327; r.run();
方法引用让你能够重复使用现有的方法,并像Lambda同样传递它们,例如:
inventory.sort(comparing(Apple::getWidth));
List<String> str = Arrays.asList("a", "b", "A", "B"); str.sort(String::compareToIgnoreCase); for(String s : str) System.out.println(s);
能够把方法引用看做针对仅仅涉及单一方法的语法糖,由于表达一样的事情时要写的代码更少了,例如:
(Apple a) -> a.getWeight() | Apple::getWeight |
() -> Thread.currentThread().dumpStack() | Thread.currentThread()::dumpStack |
(str, i) -> str.substring(i) | String::substring |
(String s) -> System.out.println(s) | System.out::println |
方法引用主要有三类:
能够利用类的名称和关键字new来建立一个类,写做ClassName::new。
加入一个构造函数没有参数,它适合Supplier的签名,() -> Apple,能够这样作:
Supplier<Apple> c1 = Apple::new; Apple a1 = c1.get();
若是有带参数的构造函数,则可使用BiFunction:
BiFunction<Integer, String, Apple> c2 = Apple::new; Apple a2 = c2.apply(1, "greed");