一、Lambda表达式简介
1.1、什么是Lambda表达式?
- Lambda是Java 8 添加的一个新的特性,Lambda表达式就是一个匿名函数,也可以理解为一个匿名方法。
- Lambda表达式是匿名内部类的简写,它实质上是个类,也是一种语法糖。
- 作用是实现了函数式接口(只含有一个抽象方法的接口),即Lambda表达式本身是对接口里的抽象方法进行重写,表达式中的参数和返回值根据抽象方法来决定,最终整个Lambda表达式就是该接口的实现 类/对象。
1.2、为什么使用Lambda?
- 使用Lambda表达式可以对一个接口进行简洁的实现,相对于其他接口实现方式显得更加简洁。
1.3、Lambda对接口的要求?
- 虽然可以使用Lambda表达式对某些接口进行简单的实现,但是并不是所有的接口都可以用Lambda表达式来实现,要求接口中定义的必须要实现的抽象方法只能是一个.。
- 注意:在Java 8 对接口加了一个新的特性:default,提供了一个默认的抽象方法,但是Lambda对此没有特殊的影响,因为其不是必须要实现的方法,所以依然可以用Lambda表达式进行实现。
1.4、注意事项
- @FunctionalInterface
这个注解用于修饰函数式接口,即意味着接口中的抽象方法只能有一个,否则编译器会报错。 - 我们总是需要对象来实现接口,Lambda表达式就是帮助我们简化这个过程,而对象中的单独的方法在接口的实现过程中并不会执行。
1.5、接口实现的3种方式
实现接口的对象创建方式有三种:
- 使用接口实现类(接口对象指向已实现了接口的类对象)
- 使用匿名内部类实现接口
- 使用Lambda表达式来实现接口
public class Program {
public static void main(String[] args) {
/***
* 1.使用接口实现类来实现接口
*/
Comparator comparator = new MyComparator();
/**
* 2.使用匿名内部类实现接口
*/
Comparator comparator1 = new Comparator() {
@Override
public int compare(int a, int b) {
return a - b;
}
};
/**
* 3.使用lambda表达式来实现接口
*/
//整个Lambda表达式就是该接口的实现类
Comparator comparator2 = (a, b) -> a - b; //多态
/**
* 调用接口的实现对象中的compare方法
*/
System.out.println(comparator.compare(1, 2));
System.out.println(comparator1.compare(1, 2));
System.out.println(comparator2.compare(1, 2));
}
}
//接口实现类
class MyComparator implements Comparator {
@Override
public int compare(int a, int b) {
return a - b;
}
}
@FunctionalInterface //该注解修饰函数式接口,限制接口中的抽象方法只能有一个
interface Comparator {
int compare(int a, int b);
}
二、Lambda表达式的基础语法
2.1、Lambda表达式的基本语法
Lambda是一个匿名函数
关注重点:参数列表 方法体
() :用来描述参数列表
{} :用来描述方法体
-> :Lambda运算符,读作goes to
2.2、Lambda表达式的基本使用
- 1、定义一个无参无返回值的接口
@FunctionalInterface //该注解修饰函数式接口,限制接口中的抽象方法只能有一个
public interface Lambda1{
void test();
}
使用Lambda表达式来实现接口:
public class Test1{
public static void main(String[] args){
Lambda1 da1 = () -> {
System.out.println("hello World !");
};
da1.test(); // Hello World !
}
}
- 2、定义单个参数,无返回值的接口
@FunctionalInterface //该注解修饰函数式接口,限制接口中的抽象方法只能有一个
public interface Lambda2{
void test(int a);
}
使用Lambda表达式来实现接口:
public class Test2{
public static void main(String[] args){
Lambda2 da2 = (int a) -> {
System.out.println("b");
};
da2.test(2); // b
}
}
- 3、定义多个参数,无返回值的接口
@FunctionalInterface //该注解修饰函数式接口,限制接口中的抽象方法只能有一个
public interface Lambda3{
void test(int a,int b,int c);
}
使用Lambda表达式来实现接口:
public class Test3{
public static void main(String[] args){
Lambda3 da3 = (int a,int b,int c) -> {
System.out.println(a+b+c);
};
da3 .test(1,2,3); // 6
}
}
- 4、定义无参数,有返回值的接口
@FunctionalInterface //该注解修饰函数式接口,限制接口中的抽象方法只能有一个
public interface Lambda4{
int test();
}
使用Lambda表达式来实现接口:
public class Test4{
public static void main(String[] args){
Lambda4 da4 = () -> {
System.out.print("demo four");
return 4;
};
int ret = da4.test();
System.out.println(" "+ret); // demo four 4
}
}
- 5、定义一个参数,有返回值的接口
@FunctionalInterface //该注解修饰函数式接口,限制接口中的抽象方法只能有一个
public interface Lambda5{
int test(int a);
}
使用Lambda表达式来实现接口:
public class Test5{
public static void main(String[] args){
Lambda5 da5 = (int a) -> {
return a+5;
};
int ret = da5.test(5);
System.out.println(ret); // 10
}
}
- 6、定义多个参数,有返回值的接口
@FunctionalInterface //该注解修饰函数式接口,限制接口中的抽象方法只能有一个
public interface Lambda6{
int test(int a,int b,int c);
}
使用Lambda表达式来实现接口:
public class Test6{
public static void main(String[] args){
Lambda6 da6 = (int a,int b,int c) -> {
return a+b+c;
};
int ret = da6.test(2,2,2);
System.out.println(ret); // 6
}
}
三、Lambda表达式语法精简
- 1、参数类型的精简:
由于在接口中已经定义了参数,所以在Lambda表达式中参数的类型可以省略;
备注:如果需要进行省略类型,那么所有参数的类型都必须都得省略,只省略部分会报错;
匿名内部类中省略参数类型是不可取的,这是Lambda表达式的优势; - 2、小括号的精简:
如果参数列表中,参数的个数有且只有一个(多了少了都不行),那么小括号可以省略,且仍然可以省略参数的类型 - 3、方法大括号的精简:
类似于 if,while 语句,如果语句块只有一条语句,那么此时大括号可以省略 - 4、return的省略:
如果出现接口只有唯一方法且方法中只有唯一语句,且是返回语句,那么如果要省略,只能一起省略掉大括号以及 return,不能省略其中之一,否则会报错。
/**
* 此类用于语法精简的Lambda表达式演示
*/
public class Syntax2 {
/**
* 参数精简
* 1.参数的精简
* 由于在接口中已经定义了参数,所以在Lambda表达式中参数的类型可以省略
* 备注:如果需要进行省略类型,但是所有参数的类型必须都得省略,省略部分会报错
* 匿名内部类中省略参数类型是不可取的
*/
LambdaNoneReturnMultipleParameter lambda1 = (a, b) -> {
System.out.println(a + b);
};
/**
* 2.精简参数小括号
* 如果参数列表中,参数的个数有且只有一个(多了少了都不行),那么小括号可以省略
* 且仍然可以省略参数的类型
*/
LambdaNoneReturnSingleParameter lambda2 = a -> {
System.out.println(a);
};
/**
* 3.方法大括号的省略
* 类似于if,while语句,如果语句块只有一条语句,那么此时大括号可以省略、
* 前面的省略方式仍然成立
*/
LambdaNoneReturnSingleParameter lambda3 = a ->
System.out.println(a);
/**
* 4.如果接口的唯一方法只有唯一返回语句,那么可以省略大括号,但是在省略大号的同时必须省略return
*/
LambdaSingleReturnNoneParameter lambda4 = () -> 10;
}
四、Lambda语法进阶
方法引用的提出: 由于如果存在一种情况,我们新建了多个接口的实现对象,其方法都是相同的,但是如果方法需要修改,那么修改的复杂度就随着对象数量的上升而上升。
方法引用的定义: 快速将一个Lambda表达式的实现指向一个已经写好的方法。
方法引用可以看作是lambda表达式的特殊形式,或者称之为语法糖。一般方法已经存在才可以使用方法引用,而方法若未存在,则只能使用Lambda表达式。
我们可以采用两种方式来在Lambda表达式中调用其他方法,第一种是一般的方法调用,第二种是方法引用。
通俗来说,方法引用就是引用静态方法或者构造方法来实现接口中的方法的重写。
- 方法引用的语法说明:
即:“方法的隶属者::方法名”。方法的隶属者,即静态方法隶属者为类,非静态方法的隶属者是对象(隶属者不是接口,而是定义引用方法的类或者对象)。 - 注意事项:
1.被引用的方法的参数数量以及类型一定要和接口中的方法参数数目一致;
2.被引用的方法的返回值一定要和接口中的方法返回值一致,方法引用这个整体表达式可以返回函数式接口的实现对象,但其调用/引用的方法其返回类型绝不是接口实例对象;
3.方法名的后面没有括号“()”;
4.方法的引用是可以有多个参数入口的,虽然在::表达式中没有体现(由于没有小括号),但是接口中对其已有所规定了。
4.1、普通方法在Lambda表达式中的调用
public class Syntax3 {
public static void main(String[] args) {
/**
* 方法引用:可以快速将一个Lambda表达式的实现指向一个已经写好的方法
* 语法:方法的隶属者,静态方法隶属者为类,非静态方法的隶属者是对象
* 即:“法的隶属者(类(静态方法)/对象(非静态方法)) :: 方法名”
* 注意事项:
* 1.被引用的方法的参数数量以及类型一定要和接口中的方法参数数目一致
* 2.被引用的方法的返回值一定要和接口中的方法返回值一致
*
*
* 项目的开发中,我们为了减少重复冗余的代码,在方法引用时,如果多个Lambda表达式的实现是一样的,我们可以快
* 速的将一个Lambda表达式的实现指向一个已经实现的方法
* 缺点:如果将来要对方法进行改变,那么所有用Lambda表达式定义的对象都要更改,这在设计模式上就是有问题的
*/
//下面这种方式会造成代码重复冗余
LambdaSingleReturnSingleParameter lambda1 = a -> a * 2;
LambdaSingleReturnSingleParameter lambda2 = a -> a * 2;
/**
* 因此,我们一般是写一个通用的方法,并将其引用至Lambda表达式中
*/
//这里是引用静态方法来给接口中的方法进行重写
LambdaSingleReturnSingleParameter lambda3 = a -> change(a);//在Lambda表达式中使用一般方法的调用方式
LambdaSingleReturnSingleParameter lambda4 = Syntax3::change;//在Lambda表达式种使用方法引用(方法隶属于类)
System.out.println(lambda4.test(2));
Syntax3 syntax3 = new Syntax3();//非静态方法需要对象才能被调用
LambdaSingleReturnSingleParameter lambda5 = syntax3::change2;//在Lambda表达式种使用方法引用(方法隶属于对象)
LambdaSingleReturnMultipleParameter lambda6 = syntax3::change3;//多参数的引用方法使用
}
}
private static int change(int a) {
return a * 2;
}
private int change2(int a) {
return a * 2;
}
private int change3(int a, int b) {
return a * 2 + b * 3;
}
4.2、构造方法在Lambda表达式中的调用
- 定义一个类,构造方法创建的对象
public class Person {
public String name;
public int age;
public Person() {
System.out.println("Person类的无参构造方法执行了");//语句用于判断无参构造器是否执行了
}
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("方法的有参构造方法执行了");//语句用于判断有参构造器是否执行了
}
}
- Lambda表达式中引用构造方法的样例:
public class Syntax4 {
public static void main(String[] args) {
//普通的Lambda表达式表示构造方法
PersonCreater person = () -> new Person();
/**
* 构造方法的引用
* 注意:
* 1、当接口中的方法返回的是一个类时,就可以引用该类的构造方法
* 2、有参和无参构造器的调用区别在于所定义的接口中构造方法的参数区别
*/
//这里是引用Person类中的构造方法来给接口中的方法进行重写
PersonCreater creater1 = Person::new;
//无参
Person person1 = creater1.getPerson();
//有参
PersonCreater2 creater2=Person::new;
Person person2 = creater2.getPerson("Fisherman",18 );
}
}
//需求为:通过Lambda表达式返回有参或无参构造方法的实例
interface PersonCreater {
Person getPerson();
}
interface PersonCreater2 {
Person getPerson(String name, int age);
}
五、Lambda综合实例
- 定义一个Person类
public class Person {
public string name;
public int age;
}
5.1、使用Lambda表达式给ArrayList排序
public class Demo1 {
public static void main(String[] args) {
// 已知在一个ArrayList集合中有若干个Person对象,将这些Person对象按年龄进行降序排序
ArrayList<Person> list = new ArrayList<>();
list.add(new Pserson("zhang",19));
list.add(new Pserson("li",22));
list.add(new Pserson("wang",21));
list.add(new Pserson("wu",25));
list.add(new Pserson("liu",27));
/**
* sort中的参数是Comparator接口,并且是使用@FunctionalInterface修饰,
* 其中的方法 int compare(T o1,T o2);提供了对象大小比较的依据
*/
// 排序 sort()更改动态数组列表中元素的顺序。
list.sort((o1,o2) -> o2.age-o1.age);
System.out.println(list); //27,25,22,21,19
}
}
5.2、使用Lambda表达式给TreeSet排序
public class Demo2 {
public static void main(String[] args) {
// 使用Lambda表达式来实现Comparetor接口,并实例化TreeSet对象
TreeSet<Person> set = new TreeSet<>((o1, o2) -> {
// 直接相减返回0会被认为是同一个元素,set集合会去重,导致21的结果只有一列
//o2.age - o1.age;
//使用如下方式可以避免去重
if(o1.age >= o2.age){
return -1;
}else{
return 1;
}
});
set.add(new Pserson("zhang",19));
set.add(new Pserson("li",22));
set.add(new Pserson("wang",21));
set.add(new Pserson("jiang",21));
set.add(new Pserson("wu",25));
set.add(new Pserson("liu",27));
System.out.println(list); //..27..25..22...21..21...19
}
}
5.3、使用Lambda表达式集合遍历
public class Demo3 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
// 给集合添加元素
Collections.listAll(list,1,2,3,4,5,0);
// 遍历集合,将每一个元素都带入到accept中
list.forEach(System.out::println); // 方法引用,打印集合 1,2,3,4,5,0
// 输出集合中所有的偶数
list.forEach(ele -> {
if(ele % 2 == 0){
System.out.println(ele);
}
});
}
}
5.4、使用Lambda表达式对集合条件删除
public class Demo4 {
public static void main(String[] args) {
ArrayList<Person> list = new ArrayList<>();
list.add(new Pserson("zhang",19));
list.add(new Pserson("li",22));
list.add(new Pserson("wang",21));
list.add(new Pserson("wu",25));
list.add(new Pserson("liu",27));
//使用迭代器 删除集合中年龄大于25的元素
ListIterator<Person> it = list.listIterator();
while(it.hasNext()){
Person ele = it.next();
if(ele.age>25){
it.remove();
}
System.out.println(list); //19,22,21,25
/**
* Lambda实现 ,使用list.removeIf(Predicate),Predicate接口中的boolean test(T t)方法,
* 将集合每一个元素都带入test方法中,返回true,删除元素,false保留
*/
list.removeIf(ele -> ele.age > 25);
System.out.println(list); //19,22,21,25
}
}
5.5、使用Lambda表达式对线程实例化
public class Demo5 {
public static void main(String[] args) {
// 需求:开启一个线程,做一个数字的输出
//通过Lambda表达式实现无参无返回的Runnable接口
Thread t = new Thread(() -> {
for(int i=0;i<100;i++){
System.out.println(i);
}
});
t.start();
}
}
六、系统内置的函数式接口
public class Demo {
// 一些系统内置的函数式接口
Predicate<T>: 参数T 返回值boolean
一些扩展的函数式接口:
IntPredicate: int -> boolean
LongPredicate: long -> boolean
DoublePredicate: double -> boolean
Consumer<T>: 参数T 返回值void
一些扩展的函数式接口:
intConsumer: int -> void
LongConsumer: long -> void
DoubleConsumer: double -> void
Function<T,R>: 参数T 返回值R
一些扩展的函数式接口:
IntFunction<R>: int ->R
LongFunction<R>: long ->R
DoubleFunction<R>: double ->R1
IntToLongFunction: int ->long
IntToDoubleFunction: int -> double
LongToIntFunction: long -> int
LongToDoubleFunction: long -> double
DoubleToIntFunction: double -> int
DoubleToLongFunction: double -> long
Supplier<T>:
参数无 返回值T
Unaryoperator<T> :
参数T 返回值T
Binaryoperator<T>:
参数T,T 返回值T
BiFunction<T, U,R> :
参数T, U 返回值R
BiPredicate<T, U>:
参数T,U 返回值boolean
BiConsumer<T, U>:
参数T,U 返回值void
最常用的函数式接口:Predicate<T> , Consumer<T> , Function<T,R> Supplier<T>
}
七、Lambda表达式—闭包
- 闭包定义:
public class ClosureDemo {
public static void main(String[] args) {
//get方法为接口Supplier中的抽象方法
int n = getNumber().get();
//方法中的局部变量,在方法结束后就会被销毁,
//但这里成功赋值给了 n,这是因为闭包会提升被包围变量的生命周期,
//因此在getNumber执行完后,局部变量num不会被销毁
System.out.println(n); //10
}
//定义一个返回值为 Supplier 接口(无参有返回值)的方法
private static Supplier<Integer> getNumber(){
//定义一个局部变量
int num = 10;
//使用Lambda表达式实现Supplier接口,并整体作为接口的实现对象返回
//局部变量被Lambda表达式这个匿名方法包围起来了,这就是一个闭包
return () -> {
return num;
};
}
}
- 使用闭包的注意事项:
public class ClosureDemo2 {
public static void main(String[] args) {
int a = 10;
//有参无返回值接口Consumer<T>
Consumer<Integer> c = ele -> {
System.out.println(a); // 直接引用 a,
System.out.println(a+1); // 11
/* System.out.println(a++);会报错
Lambda表达式如果引用一个局部变量,那么这个变量必须是常量,虽然上面没有使用final
修饰 a,但是在编译的时候会自动加上final修饰符
*/
};
// a++; 依然会报错,一但在一个闭包中使用到局部变量,那么这个变量就会变成常量,无法做修改
c.accept(1); //参数随意,不影响结果
}
}