lambda表达式是一种没有名字的函数,它拥有函数体和参数。java
lambda表达式的语法十分简单:参数->主体。经过->来分离参数和主体。程序员
lambda表达式能够有零个参数,一个参数或多个参数,参数能够指定类型,在编译器能够推导出参数类型的状况下,也能够省略参数类型。 数组
两个参数的例子:app
(String first, String second)-> Integer.compare(first.length(), second.length())
0个参数的例子:ide
() -> { for (int i = 0; i < 1000; i++) doWork(); }
关于省略参数类型,能够参考泛型省略类型来理解。从jdk7开始,泛型能够简化写成以下形式:函数
Map<String, String> myMap = new HashMap<>();
编译器会根据变量声明时的泛型类型自动推断出实例化HashMap时的泛型类型。this
一样的,若是编译器能够推导出Lambda表达式中参数的类型,也能够将其省略,例如:spa
Comparator<String> comp = (first, second) -> Integer.compare(first.length(), second.length());
上例lambda建立了一个函数式接口Comparator的对象(后文将介绍函数式接口),编译器根据声明,能够推断出first和second的类型为String。此时,参数类型可省略。在只有一个参数,且可推断出其类型的状况下,能够再将括号省略: 线程
EventHandler<ActionEvent> listener = event ->System.out.println("Thanks for clicking!");
同方法参数同样,表达式参数也能够添加annotations或者final修饰:code
(final String name) -> ... (@NonNull String name) ->
lambda表达式的主体必定要有返回值。
若是主体只有一句,则能够省略大括号:
Comparator<String> comp = (first, second) -> Integer.compare(first.length(), second.length());
多于一句的状况,须要用{}括上:
(String first, String second) -> {
if (first.length() < second.length()) return -1;
else if (first.length() > second.length()) return 1;
else return 0;
}
主体必须有返回值,只在某些分支上有返回值也是不合法的,例如:
(int x) -> { if (x >= 0) return 1; }
这个例子是不合法的。
只包含一个抽象方法的接口叫作函数式接口。
函数式接口可以使用注解@FunctionalInterface标注(不强制,可是若是标注了,编译器就会检查它是否只包含一个抽象方法)
能够经过lambda表达式建立函数式接口的对象,这是lambda表达式在java中作的最重要的事情
在jdk8之前,其实已经存在着一些接口,符合上述函数式接口的定义。
java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener
在jdk8之前,这些接口的使用方式与其余接口并没有不一样。
经过两个例子来讲明lambda表达式如何建立函数式接口实例
1.建立Runnable函数式接口实例,以启动线程——jdk8之前:
import java.util.*; public class OldStyle { public static void main(String[] args) { // 启动一个线程
Worker w = new Worker(); new Thread(w).start(); // 启动一个线程
new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }).start(); } } class Worker implements Runnable { public void run() { System.out.println(Thread.currentThread().getName()); } }
运行结果:
Thread-0
Thread-1
从代码角度来看,无论是经过内部类仍是经过匿名内部类,启动线程须要编写的代码都较为繁琐,其中,由程序员自定义的仅仅是run方法中的这一句话:
System.out.println(Thread.currentThread().getName());
lambda表达式风格的启动线程:
// 启动一个线程 Runnable runner = () -> System.out.println(Thread.currentThread().getName()); runner.run();
第一行实际上建立了一个函数式接口Runnable的实例runner,能够看出,lambda表达式的实体,刚好是run方法的方法体部分。
2.建立Comparator函数式接口实例,实现根据String的长度来排序一个String数组——jdk8之前:
import java.util.*; public class OldStyle { public static void main(String[] args) { // 排序一个数组
class LengthComparator implements Comparator<String> { public int compare(String first, String second) { return Integer.compare(first.length(), second.length()); } } String[] strings = "Mary had a little lamb".split(" "); Arrays.sort(strings, new LengthComparator()); System.out.println(Arrays.toString(strings)); } }
lambda表达式:
import java.util.*; public class LambdaStyle { public static void main(String[] args) { // 排序一个数组
String[] strings = "Mary had a little lamb".split(" "); Arrays.sort(strings, (first, second) -> Integer.compare(first.length(), second.length())); System.out.println(Arrays.toString(strings)); } }
能够看出,函数式接口经过lambda表达式建立实例,是如此的精简
jdk8的java.util.function包下,又
定义了一些函数式接口以及针对基本数据类型的子接口。
Predicate -- 传入一个参数,返回一个bool结果, 方法为boolean test(T t) Consumer -- 传入一个参数,无返回值,纯消费。 方法为void accept(T t) Function<t,r> -- 传入一个参数,返回一个结果,方法为R apply(T t) Supplier -- 无参数传入,返回一个结果,方法为T get() UnaryOperator -- 一元操做符, 继承Function<t,t>,传入参数的类型和返回类型相同。 BinaryOperator -- 二元操做符, 传入的两个参数的类型和返回类型相同, 继承BiFunction
方法引用加强了lambda表达式的可读性
方法表达式的三种主要状况:
方法引用将会执行该类(对象)的指定静态(实例)方法。
方法引用例1:根据字母顺序(不区分大小写)排序一个字符串数组:
import java.util.*; public class LambdaStyle { public static void main(String[] args) { // 排序一个数组 String[] strings = "Mary had a little lamb".split(" "); Arrays.sort(strings, (s1, s2) -> { int n1 = s1.length(); int n2 = s2.length(); int min = Math.min(n1, n2); for (int i = 0; i < min; i++) { char c1 = s1.charAt(i); char c2 = s2.charAt(i); if (c1 != c2) { c1 = Character.toUpperCase(c1); c2 = Character.toUpperCase(c2); if (c1 != c2) { c1 = Character.toLowerCase(c1); c2 = Character.toLowerCase(c2); if (c1 != c2) { // No overflow because of numeric promotion return c1 - c2; } } } } return n1 - n2; }); System.out.println(Arrays.toString(strings)); } }
上述例子,因为lambda表达式的主体代码较长,致使代码可读性降低,经过方法引用能够解决这个问题
方法引用例2:类::静态方法
import java.util.*; public class LambdaStyle { public static void main(String[] args) { // 排序一个数组 String[] strings = "Mary had a little lamb".split(" "); Arrays.sort(strings, LambdaStyle::myCompareToIgnoreCase); System.out.println(Arrays.toString(strings)); } public static int myCompareToIgnoreCase(String s1, String s2){ int n1 = s1.length(); int n2 = s2.length(); int min = Math.min(n1, n2); for (int i = 0; i < min; i++) { char c1 = s1.charAt(i); char c2 = s2.charAt(i); if (c1 != c2) { c1 = Character.toUpperCase(c1); c2 = Character.toUpperCase(c2); if (c1 != c2) { c1 = Character.toLowerCase(c1); c2 = Character.toLowerCase(c2); if (c1 != c2) { // No overflow because of numeric promotion return c1 - c2; } } } } return n1 - n2; } }
将主体代码抽出来写到一个方法中,而后引用这个方法。
方法引用例3:对象::实例方法
import java.util.*; public class LambdaStyle { public static void main(String[] args) { // 排序一个数组 String[] strings = "Mary had a little lamb".split(" "); LambdaStyle lambdaStyle = new LambdaStyle(); Arrays.sort(strings, lambdaStyle::myCompareToIgnoreCase); System.out.println(Arrays.toString(strings)); } public int myCompareToIgnoreCase(String s1, String s2){ int n1 = s1.length(); int n2 = s2.length(); int min = Math.min(n1, n2); for (int i = 0; i < min; i++) { char c1 = s1.charAt(i); char c2 = s2.charAt(i); if (c1 != c2) { c1 = Character.toUpperCase(c1); c2 = Character.toUpperCase(c2); if (c1 != c2) { c1 = Character.toLowerCase(c1); c2 = Character.toLowerCase(c2); if (c1 != c2) { // No overflow because of numeric promotion return c1 - c2; } } } } return n1 - n2; } }
对类::实例方法这种状况的方法引用来讲,第一个参数会成为执行方法的对象。
经过一个例子来讲明。在String类中实际上已经提供了不区分大小写比较字符串的方法:
public int compareToIgnoreCase(String str)
这个方法的用法为:
String s = "jdfjsjfjskd"; String ss = "dskfksdkf"; int i = s.compareToIgnoreCase(ss);
System.out.println(i);
方法引用例4:类::实例
import java.util.*; public class LambdaStyle { public static void main(String[] args) { // 排序一个数组 String[] strings = "Mary had a little lamb".split(" "); Arrays.sort(strings, String::compareToIgnoreCase); System.out.println(Arrays.toString(strings)); } }
分析例4,对于函数式接口Comparator来讲,它的抽象方法为:
int compare(T o1, T o2);
这个方法有两个参数,对于例1来讲,出如今lambda表达式参数中的s1,s2,实际上就是这两个参数。例2,例3中的方法myCompareToIgnoreCase的参数也是如此。
而对于第三个关于方法引用的例子,String的compareToIgnoreCase方法只有一个参数。这时,第一个参数将会做为执行方法的对象,(s1.compareToIgnoreCase(s2))
另外,也能够经过以下形式方法引用:
this::实例方法
super::实例方法
方法引用例5:
public class SuperTest { public static void main(String[] args) { class Greeter { public void greet() { System.out.println("Hello, world!"); } } class ConcurrentGreeter extends Greeter { public void greet() { Thread t = new Thread(super::greet); t.start(); } } new ConcurrentGreeter().greet(); } }
和方法引用类似,只不过经过以下方式引用:
类::new
构造器引用能够生成一个类的实例
例1
Stream<Button> stream = labels.stream().map(Button::new); Button[] buttons4 = stream.toArray(Button[]::new);
lambda表达式引用值,而不是变量。
lambda表达式中引用的局部变量必须是:显示声明为final的,或者虽然没有被声明为final,但实际上也算是有效的final的。
在Java中与其类似的是匿名内部类关于局部变量的引用。
例1:匿名内部类引用局部变量——jdk8之前
public class Outter { public static void main(String[] args) { final String s1 = "Hello "; new Inner() { @Override public void printName(String name) { System.out.println(s1 + name); } }.printName("Lucy"); } } interface Inner{ public void printName(String name); };
如例1所示,在jdk8之前,匿名内部类引用外部类定义的局部变量,则该变量必须是final的。
jdk8将这个条件放宽,匿名内部类也能够访问外部类有效的final局部变量——即这个变量虽然没有显示声明为final,但定义后也没有再发生变化。
例2:匿名内部类引用局部变量——jdk8
public class Outter { public static void main(String[] args) { String s1 = "Hello "; new Inner() { @Override public void printName(String name) { System.out.println(s1 + name); } }.printName("Lucy"); } } interface Inner{ public void printName(String name); };
匿名内部类引用的外部类变量s1能够不显示定义为final。可是s1必须在初始化后再也不改变。
lambda表达式对于引用局部变量的规则同jdk8中的匿名内部类同样:显示声明为final的,或者虽然没有被声明为final,但实际上也算是有效的final的
import java.io.*; import java.nio.charset.*; import java.nio.file.*; import java.util.*; import java.util.stream.*; public class VariableScope { public static void main(String[] args) { repeatMessage("Hello", 100); } public static void repeatMessage(String text, int count) { Runnable r = () -> { for (int i = 0; i < count; i++) { System.out.println(text); Thread.yield(); } }; new Thread(r).start(); } public static void repeatMessage2(String text, int count) { Runnable r = () -> { while (count > 0) { // count--; // Error: Can't mutate captured variable System.out.println(text); Thread.yield(); } }; new Thread(r).start(); } public static void countMatches(Path dir, String word) throws IOException { Path[] files = getDescendants(dir); int matches = 0; for (Path p : files) new Thread(() -> { if (contains(p, word)) { // matches++; // ERROR: Illegal to mutate matches } }).start(); } private static int matches; public static void countMatches2(Path dir, String word) { Path[] files = getDescendants(dir); for (Path p : files) new Thread(() -> { if (contains(p, word)) { matches++; // CAUTION: Legal to mutate matches, but not threadsafe } }).start(); } // Warning: Bad code ahead public static List<Path> collectMatches(Path dir, String word) { Path[] files = getDescendants(dir); List<Path> matches = new ArrayList<>(); for (Path p : files) new Thread(() -> { if (contains(p, word)) { matches.add(p); // CAUTION: Legal to mutate matches, but not threadsafe } }).start(); return matches; } public static Path[] getDescendants(Path dir) { try { try (Stream<Path> entries = Files.walk(dir)) { return entries.toArray(Path[]::new); } } catch (IOException ex) { return new Path[0]; } } public static boolean contains(Path p, String word) { try { return new String(Files.readAllBytes(p), StandardCharsets.UTF_8).contains(word); } catch (IOException ex) { return false; } } }
-----
-----
---
、