Lambda简介
Lambda
表达式是JDK8
的一个新特性,可以取代大部分的匿名内部类
,写出更优雅的Java
代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构
。- JDK 也提供了大量的
内置函数式接口
供我们使用,使得Lambda
表达式的运用更加方便、高效。 Lambda
表达式是Java SE 8
中一个重要的新特性。Lambda
表达式允许你通过表达式来代替功能接口。Lambda
表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body
,可以是一个表达式或一个代码块)。Lambda
表达式还增强了集合库。Java SE 8
添加了2个对集合数据进行批量操作的包:java.util.function
包以及java.util.stream
包。 流(stream
)就如同迭代器(iterator
),但附加了许多额外的功能。 总的来说Lambda
表达式和stream
是自Java
语言添加泛型(Generics
)和注解(annotation
)以来最大的变化。
Lambda定义
把Lambda
表达式理解为简洁地表示可传递的匿名函数的一种方式:
它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。
- 匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而想得多
- 函数——我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。但和方法一样,Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。
- 传递——Lambda表达式可以作为参数传递给方法或存储在变量中。
- 简洁——无需像匿名类那样写很多模板代码。
对接口的要求
虽然使用Lambda
表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用Lambda
表达式来实现。Lambda
规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法
jdk 8 中有另一个新特性:default, 被 default 修饰的方法会有默认实现,不是必须被实现的方法,所以不影响 Lambda 表达式的使用。
函数式接口
什么是函数式接口
(1)只包含一个抽象方法的接口,称为函数式接口。
(2)你可以通过 Lambda
表达式来创建该接口的对象。(若Lambda
表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方 法上进行声明)。
(3)我们可以在任意函数式接口上使用 @FunctionalInterface
注解, 这样做可以检查它是否是一个函数式接口,同时 javadoc
也会包 含一条声明,说明这个接口是一个函数式接口。
@FunctionalInterface
修饰函数式接口的,要求接口中的抽象方法只有一个。 这个注解往往会和 lambda
表达式一起出现。
Lambda表达式语法
基本语法
- Java8中引入了一个新的操作符,”->”,该操作符称为箭头操作符或者Lambda操作符,箭头操作符将Lambda表达式拆分成两部分;
左侧: Lambda表达式的参数列表,对应的是接口中抽象方法的参数列表;
右侧: Lambda表达式中所需要执行的功能(Lambda体),对应的是对抽象方法的实现;(函数式接口(只能有一个抽象方法))
- Java8中引入了一个新的操作符,”->”,该操作符称为箭头操作符或者Lambda操作符,箭头操作符将Lambda表达式拆分成两部分;
- Lambda表达式的实质是 对接口的实现;
(parameters) ->{ statements; }
//示例代码
Comparator<Apple> byWeight2 = (Apple o1, Apple o2)-> o1.getWeight().compareTo(o2.getWeight());
解析:
- 参数列表——这里它采用了
Comparator
中compare
方法的参数,两个Apple
- 箭头——箭头 -> 把参数列表与
Lambda
主体分隔开 Lambda
主体——比较两个Apple
的重量。表达式就是Lambda
的返回值了
Lambda语法示例
下面是Java Lambda
表达式的简单例子:
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
Lambda五大语法格式
语法格式一:接口中的抽象方法 : 无参数,无返回值;
@Test
public void test1(){
/**
*语法格式一、
* 接口中的抽象方法 : 无参数,无返回值;
*/
/*final */int num = 2; //jdk1.7之前必须定义为final的下面的匿名内部类中才能访问
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello world!" + num); //本质还是不能对num操作(只是jdk自己为我们设置成了final的)
}
};
r.run();
System.out.println("----------使用Lambda输出-----------");
Runnable r1 = () -> System.out.println("Hello world!" + num);//省去乐:大括号,分号
r1.run();
}
语法格式二:接口中的抽象方法 : 一个参数且无返回值; (若只有一个参数,那么小括号可以省略不写)
@Test
public void test2(){
/**
*语法格式二、
* 接口中的抽象方法 : 一个参数且无返回值; (若只有一个参数,那么小括号可以省略不写)
*/
Consumer<String> con = x -> System.out.println(x);
con.accept("Lambda牛逼!");
}
语法格式三:两个参数,有返回值,并且有多条语句 : 要用大括号括起来,而且要写上return
@Test
public void test3(){
/**
*语法格式三、
* 两个参数,有返回值,并且有多条语句 : 要用大括号括起来,而且要写上return
*/
Comparator<Integer> com = (x,y) -> {
System.out.println("函数式接口,");
return Integer.compare(y,x); //降序
};
Integer[] nums = {4,2,8,1,5};
Arrays.sort(nums,com);
System.out.println(Arrays.toString(nums));
}
语法格式四:两个参数,有返回值,但是只有一条语句: 大括号省略,return省略
@Test
public void test4(){
/**
*语法格式四、
* 两个参数,有返回值,但是只有一条语句: 大括号省略,return省略
*/
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);//升序
Integer[] nums = {4,2,8,1,5};
Arrays.sort(nums,com);
System.out.println(Arrays.toString(nums));
}
语法格式五:表达式的参数列表的数据类型 可以省略不写,因为JVM编译器通过上下文推断出数据类型,即”类型推断”,
(Integer x,Integer y ) -> Integer.compare(x,y)
//可以简写成
(x,y) -> Integer.compare(x,y);
Lambda 表达式常用示例
Lambda创建线程
- 创建 线程对象,然后通过匿名内部类重写
run()
方法,提到匿名内部类我们可以通过使用lambda
表达式来简化线程的创建过程。
通过Thread创建线程示例代码:
Thread t = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(2 + ":" + i);
}
});
t.start();
通过Runnable创建线程示例代码:
/*
* Lambda实现Runnable接口
* Runnable 的 lambda表达式,使用块格式,将五行代码转换成单行语句
*/
@Test
public void test6() {
// 1.1原来方式使用匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello world !使用匿名内部类,开线程");
}
}).start();
// 1.2使用 lambda expression
new Thread(() -> System.out.println("使用 lambda expression,开线程")).start();
// 2.1使用匿名内部类
Runnable race1 = new Runnable() {
@Override
public void run() {
System.out.println("使用匿名内部类,不开线程");
}
};
// 2.2使用 lambda expression
Runnable race2 = () -> System.out.println("使用 lambda expression,不开线程");
// 直接调用 run 方法(没开新线程哦!)
race1.run();
race2.run();
}
Lambda遍历集合
通过调用集合的 public void forEach(Consumer<? super E> action)
方法,通过lambda
表达式的方式遍历集合中的元素。以下是Consumer
接口的方法以及遍历集合的操作。Consumer 接口是 jdk 为我们提供的一个函数式接口。
//jdk提供的函数式接口
@FunctionalInterface//这个注解是用判断是否是函数式接口
public interface Consumer<T> {
void accept(T t);
//....
}
String[] atp = {"Rafael Nadal", "Novak Djokovic",
"Stanislas Wawrinka",
"David Ferrer","Roger Federer",
"Andy Murray","Tomas Berdych",
"Juan Martin Del Potro"};
List<String> players = Arrays.asList(atp);
// 以前的循环方式
for (String player : players) {
System.out.print(player + "; ");
}
// 使用 lambda 表达式以及函数操作(functional operation)
players.forEach((player) -> System.out.print(player + "; "));
// 在 Java 8 中使用双冒号操作符(double colon operator)
players.forEach(System.out::println);
}
删除集合中的某个元素
通过removeIf()
方法来删除集合中的某个元素,**Predicate**
也是 jdk 为我们提供的一个函数式接口,可以简化程序的编写。
List<Person> javaProgrammers = new ArrayList<Person>() {
{
add(new Person("Elsdon", "Jaycob", "Java programmer", "male", 43, 2000));
add(new Person("Tamsen", "Brittany", "Java programmer", "female", 23, 1500));
add(new Person("Floyd", "Donny", "Java programmer", "male", 33, 1800));
add(new Person("Sindy", "Jonie", "Java programmer", "female", 32, 1600));
add(new Person("Vere", "Hervey", "Java programmer", "male", 22, 1200));
add(new Person("Maude", "Jaimie", "Java programmer", "female", 27, 1900));
add(new Person("Shawn", "Randall", "Java programmer", "male", 30, 2300));
add(new Person("Jayden", "Corrina", "Java programmer", "female", 35, 1700));
add(new Person("Palmer", "Dene", "Java programmer", "male", 33, 2000));
add(new Person("Addison", "Pam", "Java programmer", "female", 34, 1300));
}
};
//removeIf()删除集合中符合条件的值
javaProgrammers.removeIf(ele -> ele.getSalary() == 2000);
//通过 foreach 遍历,查看是否已经删除
javaProgrammers.forEach(System.out::println);
集合内元素的排序
在以前我们若要为集合内的元素排序,就必须调用 sort 方法,传入比较器匿名内部类重写 compare 方法,我们现在可以使用 lambda 表达式来简化代码。
/*
*Lambdas排序集合
* 在Java中,Comparator 类被用来排序集合。
*/
String[] players = {"Rafael Nadal", "Novak Djokovic",
"Stanislas Wawrinka", "David Ferrer",
"Roger Federer", "Andy Murray",
"Tomas Berdych", "Juan Martin Del Potro",
"Richard Gasquet", "John Isner"};
// 1.1 使用匿名内部类根据 name 排序 players
Arrays.sort(players, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return (s1.compareTo(s2));
}
});
System.out.println("使用静态内部类排序结果:"+Arrays.toString(players));
System.out.println("-----------------------分割线-------------------------");
String[] players2 = {"Rafael Nadal", "Novak Djokovic",
"Stanislas Wawrinka", "David Ferrer",
"Roger Federer", "Andy Murray",
"Tomas Berdych", "Juan Martin Del Potro",
"Richard Gasquet", "John Isner"};
// 1.2 使用 lambda expression 排序 players
Comparator<String> sortByName = (String s1, String s2) -> (s1.compareTo(s2));
Arrays.sort(players2, sortByName);
System.out.println("使用lambda排序结果(方式一):"+Arrays.toString(players2));
// 1.3 也可以采用如下形式:
Arrays.sort(players2, (String s1, String s2) -> (s1.compareTo(s2)));
System.out.println("使用lambda排序结果(方式二):"+Arrays.toString(players2));
Lambda 表达式引用方法
有时候我们不是必须要自己重写某个匿名内部类的方法,我们可以可以利用 lambda表达式的接口快速指向一个已经被实现的方法。
语法:
方法归属者::方法名 静态方法的归属者为类名,普通方法归属者为对象
示例代码:
package com.zy.pagehelper.Interface;
@FunctionalInterface
public interface ReturnOneParam {
/**
*函数式接口
* 一个参数有返回值
*/
int method(int a);
}
public class LambdaTest {
public static void main(String[] args) {
//这里返回函数式接口ReturnOneParam一个参数
ReturnOneParam lambda1 = a -> doubleNum(a);
System.out.println(lambda1.method(3));
//lambda2 引用了已经实现的 doubleNum 方法
ReturnOneParam lambda2 = LambdaTest::doubleNum;
System.out.println(lambda2.method(3));
LambdaTest exe = new LambdaTest();
//lambda4 引用了已经实现的 addTwo 方法
ReturnOneParam lambda4 = exe::addTwo;
System.out.println(lambda4.method(2));
}
/**
* 要求
* 1.参数数量和类型要与接口中定义的一致
* 2.返回值类型要与接口中定义的一致
*/
public static int doubleNum(int a) {
return a * 2;
}
public int addTwo(int a) {
return a + 2;
}
}
构造方法的引用
一般我们需要声明接口,该接口作为对象的生成器,通过 类名::new 的方式来实例化对象,然后调用方法返回对象。
示例代码:
public interface PersonCreatorBlankConstruct {
/**接口作为对象的生成器
*无参构造器
*/
Person getPerson();
}
public interface PersonCreatorParamContruct {
/*
*有参构造器
*/
Person getPerson(String firstName, String lastName, String job,
String gender, int age, int salary);
}
/**
*构造方法的引用
* 一般我们需要声明接口,该接口作为对象的生成器,通过 类名::new 的方式来实例化对象,然后调用方法返回对象。
* 该接口作为对象的生成器---->创建一个无参构造器,
* 该接口作为对象的生成器---->创建一个有参构造器,
*/
@Test
public void test9() {
/**
*1.lambda表达式创建对象,返回无参函数接口,生参无参对象
*/
PersonCreatorBlankConstruct creator = () -> new Person();
Person person = creator.getPerson();
PersonCreatorBlankConstruct creator2 = Person::new;
Person person1 = creator2.getPerson();
PersonCreatorParamContruct creator3 = Person::new;
Person person2 = creator3.getPerson("名称", "修改名称","职位","男",23,2000);
}
完毕!,搞定搞定lambda表达式的基本知识点,接下来我们才可以更深入的认识JDK8的新特性