JAVA Lambda 表达式

简介: JAVA8引入Lambda就是为了简化代码,允许把函数作为一个方法的参数传递进方法中。它是一个匿名函数,是一种没有声明的方法,即没有访问修饰符,返回值声明和名称。在仅使用一次方法的地方特别有用,方法定义很短。它为我们节省了,如包含类声明和编写单独方法的工作。

一、 Lambda简介

简单的来说,java8引入Lambda的目的是为了简化代码,允许把函数作为一个方法的参数传递进方法中。如果有JavaScript的编程经验,马上会想到这不就是闭包吗。是的,Lambda表达式也可以称作Java中的闭包。

Java8以前,如果想把某个接口的实现类作为参数传递给一个方法会怎么做?要么创建一个类实现该接口,然后new出一个对象,在调用方法时传递进去,要么使用匿名类,可以精简一些代码。以创建一个线程并打印一行日志为例,使用匿名函数写法如下:

newThread(newRunnable() { @Overridepublicvoidrun() { System.out.println("开启一个线程执行任务"); }
}).start();

再来看看使用Lambda表达式,上面的代码会变成什么样子。

newThread(() ->System.out.println("开启一个线程执行任务")).start();

Java 中的 Lambda 表达式通常使用语法是 (argument) -> (body),比如:

(arg1, arg2...) -> { body }
(type1arg1, type2arg2...) -> { body }

二、lambda表达式的基本使用

1、基本语法

在上面的例子中我们使用了这样一行() -> System.out.println("使用Lambda表达式");下面我们对lambda的格式进行一个介绍:

(1)左边括号:lambda的形参列表,就好比是我们定义一个接口,里面有一个抽象方法,这个抽象方法的形参列表。

(2)箭头:lambda的操作符,所以你看见这个箭头心中知道这是一个lambda表达式就可以了。

(3)右边lambda体:就好比是我们实现了接口中的抽象方法。

2、基本特点

  • Lambda 表达式可以具有零个,一个或多个参数。
  • 可以显式声明参数的类型,也可以由编译器自动从上下文推断参数的类型。例如 (int a) 与刚才相同 (a)
  • 参数用小括号括起来,用逗号分隔。例如 (a, b)(int a, int b)(String a, int b, float c)
  • 空括号用于表示一组空的参数。例如 () -> 42
  • 当有且仅有一个参数时,如果不显式指明类型,则不必使用小括号。例如 a -> return a*a
  • Lambda 表达式的正文可以包含零条,一条或多条语句。
  • 如果 Lambda 表达式的正文只有一条语句,则大括号可不用写,且表达式的返回值类型要与匿名函数的返回类型相同。
  • 如果 Lambda 表达式的正文有一条以上的语句必须包含在大括号(代码块)中,且表达式的返回值类型要与匿名函数的返回类型相同。

3、基本使用

3.1 无参无返回值

//这是一种最简单的情况//此时如果方法体比较复杂好几行代码,那么这个{}是不能省略的Runnablerunnable1= () -> {
System.out.println("第一行代码");
System.out.println("第二行代码");
};

3.2 有参数无返回值

@TestpublicvoidConsumerTest() {
//第一种:没有使用lambda表达式Consumer<String>consumer=newConsumer<String>() {
@Overridepublicvoidaccept(Strings) {
System.out.println(s);
            }
        };
consumer.accept("hello,world");
//第二种:使用lambda表达式Consumer<String>consumer1= (Strings)->//此时只有一行输出代码,因此可以省去外部的{}System.out.println(s);
consumer.accept("hello,world");
    }

3.3 有参数有返回值

@Testpublicvoidtest() {
//第一种:没有使用lambda表达式Comparator<Integer>comparator=newComparator<Integer>() {
@Overridepublicintcompare(Integero1, Integero2) {
returno1.compareTo(o2);
            }
        };
System.out.println(comparator.compare(1,2));
System.out.println("======================");
//第二种:使用lambda表达式Comparator<Integer>comparator2= (o1,o2)->o1.compareTo(o2);
System.out.println(comparator2.compare(1,2));
    }

三、FunctionalInterface介绍

在 Java 中,功能接口(Functional interface)指只有一个抽象方法的接口比如我们的Runnable就是一个函数式接口,我们可以到源码中看看:

@FunctionalInterfacepublicinterfaceRunnable {
/*** When an object implementing interface <code>Runnable</code> is used* to create a thread, starting the thread causes the object's* <code>run</code> method to be called in that separately executing* thread.* <p>* The general contract of the method <code>run</code> is that it may* take any action whatsoever.** @see     java.lang.Thread#run()*/publicabstractvoidrun();
}


1、函数式接口特点

(1)含有@FunctionalInterface注解

(2)只有一个抽象方法

2、函数式接口作用

函数式接口能够接受匿名内部类的实例化对象,换句话说,我们可以使用匿名内部类来实例化函数式接口的对象,而Lambda表达式能够代替内部类实现代码的进一步简化。并且java为我们提供了四个比较重要的函数式接口:

  1. 消费型接口:Consumer< T> void accept(T t)有参数,无返回值的抽象方法;
  2. 供给型接口:Supplier < T> T get() 无参有返回值的抽象方法;
  3. 断定型接口: Predicate< T> boolean test(T t):有参,但是返回值类型是固定的boolean
  4. 函数型接口: Function< T,R> R apply(T t)有参有返回值的抽象方法;

3、自定义函数式接口

@FunctionalInterfacepublicinterfaceWorkerInterface {
publicvoiddoSomeWork();
}

功能接口只能有一个抽象方法。如果我们尝试在其中添加一个抽象方法,则会抛出编译时错误。

classInterfaceTest {
publicstaticvoidmain(String[] args) {
// 通过匿名内部类调用WorkerInterfacework=newWorkerInterface() {
@OverridepublicvoiddoWork() {
System.out.println("通过匿名内部类调用");
            }
        };
work.doWork();
// 通过 Lambda 表达式调用// Lambda 表达式实际上是一个对象。// 我们可以将 Lambda 表达式赋值给一个变量,就可像其它对象一样调用。work= ()->System.out.println("通过 Lambda 表达式调用");
work.doWork();
    }
}

四、方法引用

1、方法引用简介

方法引用是lambda表达式的一种特殊形式,如果正好有某个方法满足一个lambda表达式的形式,那就可以将这个lambda表达式用方法引用的方式表示,但是如果这个lambda表达式比较复杂就不能用方法引用进行替换。实际上方法引用是lambda表达式的一种语法糖。

2、方法引用分类

为了演示以下代码,我们先自定义一个User类,有两个属性,name(String),age(Integer)

publicclassUser {
privateStringname;
privateIntegerage;
publicStudent(){
    }
publicUser(Stringname,Integerage){
this.name=name;
this.age=age;
    }
publicStringgetName() {
returnname;
    }
publicvoidsetName(Stringname) {
this.name=name;
    }
publicintgetAge() {
returnage;
    }
publicvoidsetAge(intage) {
this.age=age;
    }
publicstaticintcompareUserByAge(Useru1,Useru2){
returnu1.getAge() -u2.getAge();
    }
publicstaticintcompareUserByName(Useru1,Useru2){
returnu1.getName().compareToIgnoreCase(u2.getName());
    }
}

2.1、类名::静态方法名

User类有两个属性name和age并提供了初始化name和age的构造方法,并且在最下方提供了两个静态方法分别按age和name进行比较先后顺序。

接下来的需求是,按着分数由小到大排列并输出,在使用方法引用前,我们先使用lambda表达式的方式进行处理

Useru1=newUser("zhangsan",60);
Useru2=newUser("lisi",70);
Useru3=newUser("wangwu",80);
Useru4=newUser("zhaoliu",90);
List<User>userList=Arrays.asList(u1,u2,u3,u4);
userList.sort((o1, o2) ->o1.getAge() -o2.getAge());
userList.forEach(u->System.out.println(u.getAge()));

使用类名::静态方法名 方法引用替换lambda表达式

userList.sort(User::compareUserByAge);
userList.forEach(u->System.out.println(u.getAge()));

2.2、对象::实例方法名

我们再自定义一个用于比较User元素的类

publicclassComparatorUser {
publicintcompareUserByAge(Useru1,Useru2){
returnu2.getAge() -u1.getAge();
    }
}

ComparatorUser中定义了一个非静态的,实例方法compareUserByAge,同样该方法的定义满足Comparator接口的compare方法定义,所以这里可以直接使用 对象::实例方法名 的方式使用方法引用来替换lambda表达式

ComparatorUsercomparatorUser=newComparatorUser();
userList.sort(comparatorUser::compareUserByAge);
userList.forEach(u->System.out.println(u.getAge()));

2.3、类名::实例方法名

这种方法引用的方式较之前两种稍微有一些不好理解,因为无论是通过类名调用静态方法还是通过对象调用实例方法这都是符合Java的语法,使用起来也比较清晰明了。那我们带着这个疑问来了解一下这个比较特殊的方法引用。

现在再看一下Student类中静态方法的定义

public static int compareUserByAge(User u1,User u2){

   return u1.getAge() - u2.getAge();

}

虽然这个方法在语法上没有任何问题,可以作为一个工具正常使用,但是有没有觉得其在设计上是不合适的或者是错误的。这样的方法定义放在任何一个类中都可以正常使用,而不只是从属于User这个类,那如果要定义一个只能从属于User类的比较方法下面这个实例方法更合适一些

publicintcompareByAge(User u){

   returnthis.getAge() - u.getAge();

}

接收一个User对象和当前调用该方法的User对象的分数进行比较即可。现在我们就可以使用 类名::实例方法名 这种方式的方法引用替换lambda表达式了

userList.sort(User::compareByAge);

userList.forEach(u-> System.out.println(u.getAge()));

这里非常奇怪,sort方法接收的lambda表达式不应该是两个参数么,为什么这个实例方法只有一个参数也满足了lambda表达式的定义(想想这个方法是谁来调用的)。这就是 类名::实例方法名 这种方法引用的特殊之处:当使用 类名::实例方法名 方法引用时,一定是lambda表达式所接收的第一个参数来调用实例方法,如果lambda表达式接收多个参数,其余的参数作为方法的参数传递进去。

结合本例来看,最初的lambda表达式是这样的

userList.sort((u1, u2) -> u1.getAge() - u2.getAge());

那使用 类名::实例方法名 方法引用时,一定是u1来调用了compareByAge实例方法,并将u2作为参数传递进来进行比较。是不是就符合了compareByAge的方法定义。

2.4、类名::new

也称构造方法引用,和前两种类似只要符合lambda表达式的定义即可,回想下Supplier函数式接口的get方法,不接收参数有返回值,正好符合无参构造方法的定义

@FunctionalInterfacepublicinterfaceSupplier<T> {
/*** Gets a result.** @return a result*/Tget();
}
Supplier<User>supplier=User::new;

上面就是使用了User类构造方法引用创建了supplier实例,以后通过supplier.get()就可以获取一个User类型的对象,前提是User类中存在无参构造方法。

相关文章
|
11天前
|
Java API 开发者
Java中的Lambda表达式与Stream API的协同作用
在本文中,我们将探讨Java 8引入的Lambda表达式和Stream API如何改变我们处理集合和数组的方式。Lambda表达式提供了一种简洁的方法来表达代码块,而Stream API则允许我们对数据流进行高级操作,如过滤、映射和归约。通过结合使用这两种技术,我们可以以声明式的方式编写更简洁、更易于理解和维护的代码。本文将介绍Lambda表达式和Stream API的基本概念,并通过示例展示它们在实际项目中的应用。
|
13天前
|
Java API 开发者
Java中的Lambda表达式:简洁代码的利器####
本文探讨了Java中Lambda表达式的概念、用途及其在简化代码和提高开发效率方面的显著作用。通过具体实例,展示了Lambda表达式如何在Java 8及更高版本中替代传统的匿名内部类,使代码更加简洁易读。文章还简要介绍了Lambda表达式的语法和常见用法,帮助开发者更好地理解和应用这一强大的工具。 ####
|
15天前
|
并行计算 Java 编译器
深入理解Java中的Lambda表达式
在Java 8中引入的Lambda表达式,不仅简化了代码编写,还提升了代码可读性。本文将带你探索Lambda表达式背后的逻辑与原理,通过实例展示如何高效利用这一特性优化你的程序。
|
20天前
|
搜索推荐 Java API
探索Java中的Lambda表达式
本文将深入探讨Java 8引入的Lambda表达式,这一特性极大地简化了代码编写,提高了程序的可读性。通过实例分析,我们将了解Lambda表达式的基本概念、使用场景以及如何优雅地重构传统代码。文章不仅适合初学者,也能帮助有经验的开发者加深对Lambda表达式的理解。
|
10天前
|
安全 Java API
Java中的Lambda表达式:简化代码的现代魔法
在Java 8的发布中,Lambda表达式的引入无疑是一场编程范式的革命。它不仅让代码变得更加简洁,还使得函数式编程在Java中成为可能。本文将深入探讨Lambda表达式如何改变我们编写和维护Java代码的方式,以及它是如何提升我们编码效率的。
|
13天前
|
安全 Java API
Java中的Lambda表达式与Stream API的高效结合####
探索Java编程中Lambda表达式与Stream API如何携手并进,提升数据处理效率,实现代码简洁性与功能性的双重飞跃。 ####
23 0
|
20天前
|
Java 开发者
探索Java中的Lambda表达式
【10月更文挑战第43天】本文将深入浅出地介绍Java中的Lambda表达式,通过实际代码示例,带领读者理解其背后的原理及应用场景。Lambda表达式不仅简化了代码,还提高了开发效率,是Java开发者必备的技能之一。
|
Java
QuartZ Cron表达式在java定时框架中的应用
CronTrigger CronTriggers往往比SimpleTrigger更有用,如果您需要基于日历的概念,而非SimpleTrigger完全指定的时间间隔,复发的发射工作的时间表。 CronTrigger,你可以指定触发的时间表如“每星期五中午”,或“每个工作日9:30时”,甚至“每5分钟一班9:00和10:00逢星期一上午,星期三星期五“。
1103 0
|
12天前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
10天前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####