咱们知道接口是没法直接被实例化的,你只能new他的实现类,可是假如当前接口只有一个抽象方法,又不想再建立一个类,在JDK8以前,你就只能使用匿名内部类。像下面这样:html
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello world");
}
}).start();
复制代码
如今的IDE已经很强大了,你只用写到new Runnable()就会帮你把代码补全,可是你仍是要移动光标到run方法体内,写对应的代码。在学到匿名内部类的时候,我就但愿代码变的简洁紧凑一些 (事实上官方写的指导书《The Java™ Tutorials》也是将Lambda式放在匿名类这一章下面的,介绍Lambda表达式这一章在: docs.oracle.com/javase/tuto… )。 JDK8以后上面的代码就能够化简成下面这样:java
new Thread(()-> System.out.println("hello world")).start();
复制代码
怎么样是否是变得至关简洁了呢! ()-> System.out.println("hello world") 就能够称之为Lambda表达式。Lambda是λ这个符号的名称。 维基百科是这样描述Lambda表达式的:sql
a function (or a subroutine) defined, and possibly called, without being bound to an identifier。数据库
直接翻译的话就是: 可能被调用的一个函数或者是子程序,可是没有绑定到一个标识符上。express
咱们放到java里来理解一下,众所周知java是面向对象的,函数也是放在一个类中的。调用方法的话就大体有如下两种形式:编程
因此在java中,咱们姑且就能够将类名+方法名称之为一个标识符,由于咱们经过类名(严谨的说是全类名)+方法名定位到一个方法。()-> System.out.println("hello world"),就是一个匿名类和匿名方法的组合化简以后产物,类名没有直接给出,方法名也是没有,这些统统是编译器来给。bash
百度百科是这样描述Lambda表达式的:oracle
Lambda 表达式(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。--《百度百科》app
咱们知道数学中的演算是须要运算符(演算符的),调用一个函数是须要给定参数的,是须要方法体的。编程语言
() 里面就能够放参数,-> 是运算符,-> 以后就是方法体。形如()->{}的表达式,在java中咱们就能够称之为Lambda表达式。 由于Runnable接口中的run方法没有参数,因此()里面啥也没写。
顺便提一下,Lambda表达式并不算是java率先提出。2007年11月19,.NET Framework 3.5就已经引入了,jdk8的发布时间是2014-03-18。主流的编程语言应该都有这个概念。
咱们来想一想为何能够化简到这种地步,假如咱们有这样两个方法:
咱们称之为方法重载,同一个方法名,参数类型不一样。咱们在调用的时候,编译器或者说是JVM,就能根据咱们给定的参数来调用对应的方法。那么new Thread(()-> System.out.println("hello world"))来推断这是Thread的哪个构造函数也是可以作到的事情。因此看似你写的是这个:
new Thread(()-> System.out.println("hello world")).start();
复制代码
事实上编译器会帮你补成:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello world");
}
}).start();
复制代码
这也是一些人说在某些状况下Lambda性能不佳的缘由。
若是你打开Runnable接口看的话,会发现Runnable接口上有一个注解: @FunctionalInterface。FunctionalInterface意味函数式接口,什么是函数式接口呢? 就是一个接口中只有一个抽象方法的接口,咱们称之为函数式接口。咱们知道,jdk8以前接口中只能放抽象方法,jdk8以后能够放非抽象方法。方法中要加上default关键字,咱们称之为默认实现。形式以下:
@FunctionalInterface
public interface MyLambdaInterface {
void sayHello(String str);
default void sayWorld(String str){
System.out.println(str);
}
}
复制代码
加上@FunctionalInterface的接口,只能有一个抽象方法,容许存在和Object中方法名相同的方法,但要求参数类型一致。不然会通不过编译。以下所示:
@FunctionalInterface
public interface MyLambdaInterface {
void sayHello(String str);
int hashCode();
boolean equals(Object obj);
default void sayWorld(String str){
System.out.println(str);
}
}
复制代码
Lambda表达式的适用场景即为: 假如我有一个函数式接口,可是呢,我又不想建立这个类的显示实现类(显示实现类即为新建Class文件),我又想使用这个方法,Lambda表达式大显身手的就到了。
咱们首先介绍Lambda表达式的形式,在根据形式去讲解如何使用。
方法参数的话 new Thread(()-> System.out.println("hello world")).start(); 这个就是一个例子。虽然原则上讲并不该该直接建立一个线程,应该经过线程池。 咱们知道函数的参数类型能够是基本类型和引用类型,new Thread(()-> System.out.println("hello world")) 这个例子好像就是将一个方法做为参数传递进去了同样。
MyLambdaInterface myLambdaInterface = (str)-> System.out.println(str);
复制代码
从形式上来说,myLambdaInterface好像指向一个函数同样。 (str)-> System.out.println(str);就是方法sayHello的实现。
public class Person {
LocalDate birthday;
public static int compareByAge(Person a, Person b) {
return a.birthday.compareTo(b.birthday);
}
}
复制代码
方法引用的写法: Person::compareByAge
可是这句话直接出如今代码中会报错,咱们须要找一个函数式接口来指向它。函数式接口中的方法返回类型和接收参数要和compareByAge保持一致,像下面这样。
public interface CompareTo<T> {
int compare(T t1,T t2);
}
复制代码
CompareTo<Person> p = Person::compareByAge;
复制代码
String[] stringArray = {"Barbara", "James", "Mary", "John",
"Patricia", "Robert", "Michael", "Linda"};
Arrays.sort(stringArray, String::compareToIgnoreCase);
复制代码
Lambda表达式本质上也是内部类,那么同内部类同样,要想要访问局部变量,那么这个局部变量必定要是final类型的。 如今咱们来总结一下Lambda表达式的写法: (函数式接口所需的参数)->方法体; 假如方法体只有一行,能够不用写{ 例如:
new Thread(()-> System.out.println()).start();
复制代码
假若有多行的话须要写{
咱们能够认为每个Lambda表达式都是一个函数式接口的匿名实现。
JDK也内置了一些函数式接口,这些函数式接口大多都在java.util.function下,理解这些函数式接口是学习Stream API的关键。这里咱们选取几个比较经典的讲解一下。
boolean test(T t); 这个方法接收一个参数,返回一个boolean类型的值。
咱们能够这样写:
Predicate<String> predicate = (str)-> str.contains("s");
复制代码
还能够这样写:
List<String> list = new ArrayList<>();
Predicate<String> predicate = list::add;
predicate.test("add");//等价于调用list.add("add")
predicate.test("bb");
复制代码
Consumer、 Function、 Supplier用法相似,这里再也不作过多的介绍。
首先Stream是一个接口,位于java.util.stream下。 假如咱们的集合泛型是一个学生类,像下面这样。
List<Student> student = new ArrayList<>();
复制代码
那么假如我像统计年龄在18岁以上的学生总数,那么在不用流的状况下,咱们一般是这样作的。
int count = 0;
List<Student> studentList = new ArrayList<>();
for (Student student : studentList) {
if (student.getAge() > 18){
count++;
}
}
复制代码
Student能够映射进数据库中,咱们在数据库作这样的事情是简单的,那么在代码可否像sql同样执行count,where操做呢。 有了流以后就能够,咱们能够这样写:
studentStream.filter( (student) -> student.getAge() > 18).count();
复制代码
是否是变的简单多了呢! 那我能不能group by 在count呢,也是能够的。按性别分组求出男生和女生的人数。
Map<Boolean, Long> result = studentStream.collect(Collectors.groupingBy(Student::isSex, counting()));
复制代码
细究他的实现的话,这个并非本篇的主题,本篇只是简单入门,详细讨论是另外一篇了。 顺便提一下, 流有两种类型,串行和并行流,这将是咱们下一篇文章重点阐释的内容。
参考资料: The Java™ Tutorials