Java8中的lambda表达式入门教程

发布时间 - 2026-01-11 02:47:18    点击率:

1.基本介绍

lambda表达式,即带有参数的表达式,为了更清晰地理解lambda表达式,先上代码:

1.1 两种方式的对比

1.1.1 方式1-匿名内部类

class Student{
 private String name;
 private Double score;
 public Student(String name, Double score) {
  this.name = name;
  this.score = score;
 }
 public String getName() {
  return name;
 }
 public Double getScore() {
  return score;
 }
 public void setName(String name) {
  this.name = name;
 }
 public void setScore(Double score) {
  this.score = score;
 }
 @Override
 public String toString() {
  return "{"
    + "\"name\":\"" + name + "\""
    + ", \"score\":\"" + score + "\""
    + "}";
 }
}:
@Test
public void test1(){
 List<Student> studentList = new ArrayList<Student>(){
  {
   add(new Student("stu1",100.0));
   add(new Student("stu2",97.0));
   add(new Student("stu3",96.0));
   add(new Student("stu4",95.0));
  }
 };
 Collections.sort(studentList, new Comparator<Student>() {
  @Override
  public int compare(Student o1, Student o2) {
   return Double.compare(o1.getScore(),o2.getScore());
  }
 });
 System.out.println(studentList);
}

代码调用Collections.sort方法对集合进行排序,其中第二个参数是一个匿名内部类,sort方法调用内部类中的compare方法对list进行位置交换,因为java中的参数类型只能是类或者基本数据类型,所以虽然传入的是一个Comparator类,但是实际上可以理解成为了传递compare方法而不得不传递一个Comparator类 ,这种方式显得比较笨拙,而且大量使用的话代码严重冗余,这种情况在java8中通过使用lambda表达式来解决。

lambda表达式专门针对只有一个方法的接口(即函数式接口),Comparator就是一个函数式接口

@FunctionalInterface
public interface Comparator<T> {
 int compare(T o1, T o2);
}

@FunctionalInterface的作用就是标识一个接口为函数式接口,此时Comparator里只能有一个抽象方法,由编译器进行判定。

使用lambda表达式之后方式1 中的代码改造如下 

1.1.2 方式2-lambda表达式

public void test1_(){
  List<Student> studentList = new ArrayList<Student>(){
   {
    add(new Student("stu1",100.0));
    add(new Student("stu2",97.0));
    add(new Student("stu3",96.0));
    add(new Student("stu4",95.0));
   }
  };
  Collections.sort(studentList,(s1,s2)-> Double.compare(s1.getScore(),s2.getScore()));
  System.out.println(studentList);
 }

1.2 lambda语法

1.2.1 多参数

     (1). lambda表达式的基本格式为(x1,x2)->{表达式...};

     (2). 在上式中,lambda表达式带有两个参数,此时参数类型可以省略,但两边的括号不能省略

     (3). 如果表达式只有一行,那么表达式两边的花括号可以省略

1.2.2 无参数

一个常见的例子是新建一个线程,不使用lambda表达式的写法为

public void testThread(){
  new Thread(new Runnable() {
   @Override
   public void run() {
    System.out.println("hello, i am thread!");
   }
  }).start();
 }

其中Runnable接口也是一个函数式接口,源码如下

@FunctionalInterface
public interface Runnable {
 /**
  * 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()
  */
 public abstract void run();
}

将其转换为lambda表达式的写法为

public void testThread_(){
 new Thread(()-> System.out.println("hello, i am thread!")).start();
}

对于没有参数的情况 :

     (1).参数的括号不能省略,

     (2).其他语法同多参数

1.2.3 一个参数

我们构造一个只有一个参数的函数式接口

@FunctionalInterface
public interface MyFunctionalInterface {
 public void single(String msg);
}
 
/**
 * 需要单个参数
 */
public static void testOnePar(MyFunctionalInterface myFunctionalInterface){
 myFunctionalInterface.single("msg");
}
/**
  * 一个参数,可以省略参数的括号
  */
 @Test
 public void testOneParameter(){
  testOnePar(x-> System.out.println(x));
 }

对于一个参数的情况:

     (1).可以省略参数的括号和类型

     (2).其他语法同多参数

1.3 jdk提供的常用函数式接口

在这里我们为了演示只有一个参数的情况自己创建了一个函数式接口,其实java8中已经为我们提供了很多常见的函数式接口,截图如下:

常见的有

Function:提供任意一种类型的参数,返回另外一个任意类型返回值。 R apply(T t);

Consumer:提供任意一种类型的参数,返回空值。 void accept(T t);

Supplier:参数为空,得到任意一种类型的返回值。T get();

Predicate:提供任意一种类型的参数,返回boolean返回值。boolean test(T t);

因此针对上面的情况,我们可以直接使用Consumer类,

/**
  * 需要单个参数
  */
 public static void testOnePar1(Consumer unaryOperator){
  unaryOperator.accept("msg");
 }

2.方法引用

lambda表达式用于替换函数式接口,方法引用也是如此,方法引用可以使代码更加简单和便捷

2.1 小试牛刀

上代码,根据List中字符串长度排序:

public static void test1_() {
 List<String> strLst = new ArrayList<String>() {
  {
   add("adfkjsdkfjdskjfkds");
   add("asdfasdfafgfgf");
   add("public static void main");
  }
 };
 Collections.sort(strLst, String::compareToIgnoreCase);
 System.out.println(strLst);
}

只要方法的参数和返回值类型与函数式接口中抽象方法的参数和返回值类型一致,就可以使用方法引用。

2.2 使用方式

方法引用主要有如下三种使用情况

     (1). 类::实例方法

     (2). 类::静态方法

     (3). 对象::实例方法

其中后两种情况等同于提供方法参数的lambda表达式,

如System.out::println 等同于(x)->System.out.println(x),

   Math::pow 等同于(x,y)->Math.pow(x,y).

第一种中,第一个参数会成为执行方法的对象,String::compareToIgnoreCase)等同于(x,y)->x.compareToIgnoreCase(y)

此外,方法引用还可以使用this::methodName及super::methodName表示该对象或者其父类对象中的方法

class Father {
 public void greet() {
  System.out.println("Hello, i am function in father!");
 }
}
 
class Child extends Father {
 @Override
 public void greet() {
  Runnable runnable = super::greet;
  new Thread(runnable).start();
 }
}
public static void main(String[] args){
  new Child().greet();
 }

最后打印的结果为:Hello, i am function in father!

3.构造器引用

构造器引用同方法引用类似,同样作用于函数式接口

构造器引用的语法为 ClassName::new

啥也不说,线上代码

List<String> labels = Arrays.asList("aaa","bbb","ccc","ddd");
Stream<Button> buttonStream = labels.stream().map(Button::new);

如上代码所示,map方法内需要一个Function对象

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

调用Button的构造器,接收一个String类型的参数,返回一个Button类型的对象

public class Button extends ButtonBase {
  /**
   * Creates a button with the specified text as its label.
   *
   * @param text A text string for its label.
   */
  public Button(String text) {
   super(text);
   initialize();
  }
 }

另外一个例子如下

Button[] buttons1 = buttonStream.toArray(Button[]::new);

toArray方法的申明如下

<A> A[] toArray(IntFunction<A[]> generator);

接收一个IntFunction类型的接口R apply(int value);该接口接收一个int型参数,返回指定类型

调用数组的初始化方法刚好适合。

有一个简单的构造器引用的例子如下:

public class LambdaTest3 {
 
 @Test
 public void test1_(){
  List<Integer> list = this.asList(ArrayList::new ,1,2,3,4,5);
  list.forEach(System.out::println);
 }
 public <T> List<T> asList(MyCrator<List<T>> creator,T... a){
  List<T> list = creator.create();
  for (T t : a)
   list.add(t);
  return list;
 }
}
interface MyCrator<T extends List<?>>{
 T create();
}

我们在项目中经常使用asList来创建一个ArrayList,但是也只能是ArrayList,

public static <T> List<T> asList(T... a) {
 return new ArrayList<>(a);
}

我们如何在asList中指定创建哪种类型的List的实例呢,使用构造器引用使得asList方法可以指定生成的List类型。

4.自由变量的作用范围

啥都不说,上代码先:

public class LambdaTest4 {
 public void doWork1(){
  Runnable runnable = ()->{
   System.out.println(this.toString());
   System.out.println("lambda express run...");
  };
  new Thread(runnable).start();
 }
 
 public void doWork2(){
  Runnable runnable = new Runnable() {
   @Override
   public void run() {
    System.out.println(this.toString());
    System.out.println("anony function run...");
   }
  };
  new Thread(runnable).start();
 }
 public static void main(String[] args) {
  new LambdaTest4().doWork1();
  new LambdaTest4().doWork2();
 }
}

代码中doWork1和doWork2分别使用lambda表达式和匿名内部类的方式实现了Runnable接口,最后打印的结果如下

com.java8.lambda.LambdaTest4@74f84cf
lambda express run...
com.java8.lambda.LambdaTest4$1@4295c176
anony function run...

可见使用lambda表达式的方式,表达式中的this指的是包含lambda表达式的类,而使用匿名内部类的方式,this指的是匿名内部类本身。

4.1 自由变量和闭包

lambda达式中的变量有几类,1.参数内的变量,2.lambda表达式中的内部变量,3.自由变量,自由变量指的是在lambda表达式之外定义的变量。

包含自由变量的代码则称为闭包,如果理解了lambda表达式会在编译阶段被转换为匿名内部类,那么可以很容易理解自由变量在lambda表达式中的作用范围,在lambda表达式中会捕获所有的自由变量,并且将变量定义为final类型,所以不能改变lambda表达式中自由变量的值,如果改变,那么首先就无法编译通过。

对于引用类型(如ArrayList),final指的是引用指向的类始终不变,进行add操作是允许的,但是应该保证变量的线程安全。

代码如下所示:

public class Outer {
 public AnnoInner getAnnoInner(int x) {
  int y = 100;
  return new AnnoInner() {
   int z = 100;
   @Override
   public int add() {
    return x + y + z;
   }
  };
 }
 public AnnoInner AnnoInnergetAnnoInner1(List<Integer> list1) {
  List<Integer> list2 = new ArrayList<>(Arrays.asList(1, 2, 3));
  return ()->{
   list2.add(123);
   int count = 0;
   Iterator<Integer> it = list1.iterator();
   while (it.hasNext()){
    count+=it.next();
   }
   Iterator<Integer> it1 = list2.iterator();
   while (it1.hasNext()){
    count+=it1.next();
   }
   return count;
  };
 }
 @Test
 public void test(){
  AnnoInner res = new Outer().AnnoInnergetAnnoInner1(new ArrayList<>(Arrays.asList(1,2,3)));
  System.out.println(res.add());
 }
}
interface AnnoInner {
 int add();
}

最后返回135

5.接口的静态方法和默认方法

java8对于接口做出了种种改进,使得我们可以在接口中实现默认方法和静态方法,见Comparator接口完整定义

@FunctionalInterface
public interface Comparator<T> {
 int compare(T o1, T o2);
 boolean equals(Object obj);
 default Comparator<T> reversed() {
  return Collections.reverseOrder(this);
 }
 default Comparator<T> thenComparing(Comparator<? super T> other) {
  Objects.requireNonNull(other);
  return (Comparator<T> & Serializable) (c1, c2) -> {
   int res = compare(c1, c2);
   return (res != 0) ? res : other.compare(c1, c2);
  };
 }
 default <U> Comparator<T> thenComparing(
   Function<? super T, ? extends U> keyExtractor,
   Comparator<? super U> keyComparator)
 {
  return thenComparing(comparing(keyExtractor, keyComparator));
 }
 default <U extends Comparable<? super U>> Comparator<T> thenComparing(
   Function<? super T, ? extends U> keyExtractor)
 {
  return thenComparing(comparing(keyExtractor));
 }
 default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) {
  return thenComparing(comparingInt(keyExtractor));
 }
 default Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor) {
  return thenComparing(comparingLong(keyExtractor));
 }
 default Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor) {
  return thenComparing(comparingDouble(keyExtractor));
 }
 public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
  return Collections.reverseOrder();
 }
 @SuppressWarnings("unchecked")
 public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
  return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
 }
 public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) {
  return new Comparators.NullComparator<>(true, comparator);
 }
 public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) {
  return new Comparators.NullComparator<>(false, comparator);
 }
 public static <T, U> Comparator<T> comparing(
   Function<? super T, ? extends U> keyExtractor,
   Comparator<? super U> keyComparator)
 {
  Objects.requireNonNull(keyExtractor);
  Objects.requireNonNull(keyComparator);
  return (Comparator<T> & Serializable)
   (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
            keyExtractor.apply(c2));
 }
 public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
   Function<? super T, ? extends U> keyExtractor)
 {
  Objects.requireNonNull(keyExtractor);
  return (Comparator<T> & Serializable)
   (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
 }
 public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {
  Objects.requireNonNull(keyExtractor);
  return (Comparator<T> & Serializable)
   (c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));
 }
 public static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor) {
  Objects.requireNonNull(keyExtractor);
  return (Comparator<T> & Serializable)
   (c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2));
 }
 public static<T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor) {
  Objects.requireNonNull(keyExtractor);
  return (Comparator<T> & Serializable)
   (c1, c2) -> Double.compare(keyExtractor.applyAsDouble(c1), keyExtractor.applyAsDouble(c2));
 }
}

在比较器接口中定义了若干用于比较和键提取的静态方法和默认方法,默认方法的使用使得方法引用更加方便,例如使用java.util.Objects类中的静态方法isNull和nonNull可以在Stream中很方便的进行null的判定(之后会有对于stream的介绍)。但是在接口中引入默认方法设计到一个问题,即

(1).接口中的默认方法和父类中方法的冲突问题

(2).接口之间引用的冲突问题

对于第一个冲突,java8规定类中的方法优先级要高于接口中的默认方法,所以接口中默认方法复写Object类中的方法是没有意义的,因为所有的接口都默认继承自Object类使得默认方法一定会被覆盖。

对于第二个冲突,java8强制要求子类必须复写接口中冲突的方法。如下所示:

public class LambdaTest5 implements myInterface1, myInterface2 {
 @Override
 public void getName() {
  myInterface1.super.getName();
 }
 public static void main(String[] args) {
  new LambdaTest5().getName();
 }
}
interface myInterface1 {
 default void getName() {
  System.out.println("myInterface1 getName");
 }
 ;
}
interface myInterface2 {
 default void getName() {
  System.out.println("myInterface2 getName");
 }
}

强制使用myInterface1中的getName方法

总结

以上所述是小编给大家介绍的Java8中的lambda表达式入门教程,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!


# java8  # lambda  # 表达式入门  # Java8深入学习系列(一)lambda表达式介绍  # java中Lambda常用场景代码实例  # Java8 lambda表达式2种常用方法代码解析  # 一文带你彻底搞懂Lambda表达式  # java常用Lambda表达式使用场景源码示例  # 返回值  # 类中  # 指的是  # 只有一个  # 所示  # 第一个  # 两种  # 我们可以  # 第二个  # 一个函数  # 另外一个  # 转换为  # 小编  # 有一个  # 的是  # 是一个  # 在这里  # 会有  # 出了  # 还可以 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 北京网页设计制作网站有哪些,继续教育自动播放怎么设置?  家族网站制作贴纸教程视频,用豆子做粘帖画怎么制作?  海南网站制作公司有哪些,海口网是哪家的?  如何在 Telegram Web View(iOS)中防止键盘遮挡底部输入框  1688铺货到淘宝怎么操作 1688一键铺货到自己店铺详细步骤  香港服务器建站指南:外贸独立站搭建与跨境电商配置流程  公司网站制作价格怎么算,公司办个官网需要多少钱?  西安专业网站制作公司有哪些,陕西省建行官方网站?  如何用狗爹虚拟主机快速搭建网站?  Google浏览器为什么这么卡 Google浏览器提速优化设置步骤【方法】  Swift中循环语句中的转移语句 break 和 continue  如何在VPS电脑上快速搭建网站?  如何用5美元大硬盘VPS安全高效搭建个人网站?  Linux虚拟化技术教程_KVMQEMU虚拟机安装与调优  Bootstrap整体框架之CSS12栅格系统  UC浏览器如何设置启动页 UC浏览器启动页设置方法  香港服务器建站指南:免备案优势与SEO优化技巧全解析  儿童网站界面设计图片,中国少年儿童教育网站-怎么去注册?  Laravel怎么做数据加密_Laravel内置Crypt门面的加密与解密功能  Gemini手机端怎么发图片_Gemini手机端发图方法【步骤】  网站建设保证美观性,需要考虑的几点问题!  php 三元运算符实例详细介绍  Python自然语言搜索引擎项目教程_倒排索引查询优化案例  JavaScript中的标签模板是什么_它如何扩展字符串功能  nodejs redis 发布订阅机制封装实现方法及实例代码  Java遍历集合的三种方式  HTML 中动态设置元素 name 属性的正确语法详解  佛山企业网站制作公司有哪些,沟通100网上服务官网?  如何快速重置建站主机并恢复默认配置?  如何在服务器上三步完成建站并提升流量?  Python企业级消息系统教程_KafkaRabbitMQ高并发应用  网站制作软件有哪些,制图软件有哪些?  PHP正则匹配日期和时间(时间戳转换)的实例代码  如何快速搭建FTP站点实现文件共享?  详解Android图表 MPAndroidChart折线图  为什么要用作用域操作符_php中访问类常量与静态属性的优势【解答】  北京的网站制作公司有哪些,哪个视频网站最好?  HTML5打空格有哪些误区_新手常犯的空格使用错误【技巧】  Laravel如何使用Seeder填充数据_Laravel模型工厂Factory批量生成测试数据【方法】  Laravel如何升级到最新版本?(升级指南和步骤)  Laravel storage目录权限问题_Laravel文件写入权限设置  使用豆包 AI 辅助进行简单网页 HTML 结构设计  CSS3怎么给轮播图加过渡动画_transition加transform实现【技巧】  使用spring连接及操作mongodb3.0实例  软银砸40亿美元收购DigitalBridge 强化AI资料中心布局  Laravel如何使用软删除(Soft Deletes)功能_Eloquent软删除与数据恢复方法  php在windows下怎么调试_phpwindows环境调试操作说明【操作】  网站视频制作书签怎么做,ie浏览器怎么将网站固定在书签工具栏?  Laravel如何自定义分页视图?(Pagination示例)  Win11搜索栏无法输入_解决Win11开始菜单搜索没反应问题【技巧】