搜索
首页Javajava教程Java编程中使用lambda表达式的奇技淫巧

为什么使用Lambda表达式
先看几个例子:

第一个例子,在一个独立的线程中执行某项任务,我们通常这么实现:

class Worker implements Runnable {
  public void run() {
    for (int i = 0; i < 100; i++)
      doWork();
  }
  ...
}
 
Worker w = new Worker();
new Thread(w).start();

   

第二个例子,自定义字符串比较的方法(通过字符串长度),一般这么做:

class LengthComparator implements Comparator<String> {
  public int compare(String first, String second) {
    return Integer.compare(first.length(), second.length());
  }
}
Arrays.sort(strings, new LengthComparator());

第三个例子,在JavaFX中,给一个button添加一个callback:

button.setOnAction(new EventHandler<ActionEvent>() {
  public void handle(ActionEvent event) {
    System.out.println("Thanks for clicking!");
  }
});

这些例子有一个共同点,就是:先定义一段代码块,传给某个对象或方法,然后被执行。在Lambda表
达式之前,Java是不允许直接传递代码块的,因为Java是面向对象的,因此必须传递一个对象,将要
执行的代码块封装到对象里。

Lambda表达式的语法
将上面第二个例子中的LengthComparator,用Lambda表达式表示为:

(String first, String second) -> Integer.compare(first.length(),
  second.length());

->前为参数列表,其后为表达式语句体;

如果表达式语句体不止一行,则将语句体写在{}中,与普通的函数一样:

(String first, String second) -> {
  if (first.length() > second.length()) {
    return 1;
  } else if (first.length() == second.length()) {
    return 0;
  } else {
    return -1;
  }
};

如果没有参数,()还是需要带上,比如上面的第一个例子,可以表示为:

() -> {
  for (int i = 0; i < 1000; i ++) {
    doWork();
  }
}

如果参数的类型可以从上下文自动推断,则可以省略:

Comparator<String> comp
  = (first, second) // Same as (String first, String second)
  -> Integer.compare(first.length(), second.length());

如果参数只有一个,且类型可以自动推断,则小括号()也可以省略:

// Instead of (event) -> or (ActionEvent event) ->
eventHandler<ActionEvent> listener =
  event -> System.out.println("Thanks for clicking!");

lambda表达式的返回值的类型是自动推断的,因此不需要指明;在lambda表达式,某些条件分支中有
返回值,而其它分支没有返回值,是不允许的,如:

(x) -> {
  if (x >= 0) {
    return 1;
  }
}

另外,expression lambda和statement lambda的区别是,expression lambda不需要
写return关键字,Java runtime会将表达式的结果作为返回值返回,而statement lambda是
写在{}中的表达式,需要使用return关键字,比如:

// expression lambda
Comparator<String> comp1 =
  (first, second) -> Integer.compare(first.length(), second.length());
 
// statement lambda
Comparator<String> comp2 = (first, second) ->
  { return Integer.compare(first.length(), second.length());};

Functional Interface
如果一个接口(interface)仅有一个抽象方法(abstract method),就称为
Functional Interface,比如Runnable、Comparator等。
在任何一个需要Functional Interface对象的地方,都可以使用lambda表达式:

Arrays.sort(words,
  (first, second) -> Integer.compare(first.length(), second.length()));

这里,sort()的第二个参数需要的是一个Comparator对象,而Comparator是
Functional Interface,因此可以直接传入lambda表达式,在调用该对象的compare()方法
时,就是执行该lambda表达式中的语句体;

如果lambda表达式的语句体会抛出异常,则对应的Functional Interface中的抽象方法必须抛
出了该异常,否则就需要在lambda表达式中显式捕获异常:

Runnable r = () -> {
  System.out.println("------");
  try {
    Thread.sleep(10);
  } catch (InterruptedException e) {
    // catch exception
  }
};
 
Callable<String> c = () -> {
  System.out.println("--------");
  Thread.sleep(10);
  return "";
};

Method Reference
如果将lambda表达式的参数作为参数传递给一个方法,他们的执行效果是相同的,则该lambda表达式
可以使用Method Reference表达,以下两种方式是等价的:

(x) -> System.out.println(x)
System.out::println

其中System.out::println被称为Method Reference。

Method Reference主要有三种形式:

object::instanceMethod

Class::staticMethod

Class::instanceMethod

对于前两种方式,对应的lambda表达式的参数和method的参数是一致的,比如:

System.out::println
(x) -> System.out.println(x)
 
Math::pow
(x, y) -> Math.pow(x, y)

对于第三种方式,对应的lambda表达式的语句体中,第一个参数作为对象,调用method,将其它参数
作为method的参数,比如:

String::compareToIgnoreCase
(s1, s2) -> s1.compareToIgnoreCase(s2)
1.5 Constructor Reference

Constructor Reference与Method Reference类似,只不过是特殊的method:new,具体调用的是哪个构造函数,由上下文环境决定,比如:

List<String> labels = ...;
Stream<Button> stream = labels.stream().map(Button::new);

Button::new等价于(x) -> Button(x),所以调用的构造函数是:Button(x);

除了创建单个对象,也可以创建对象数组,如下面两种方式等价:

int[]::new
(x) -> new int[x]

变量作用域
lambd表达式会捕获当前作用域下可用的变量,比如:

public void repeatMessage(String text, int count) {
  Runnable r = () -> {
    for (int i = 0; i < count; i ++) {
      System.out.println(text);
      Thread.yield();
    }
  };
  new Thread(r).start();
}

但是这些变量必须是不可变的,为什么呢?看下面这个例子:

int matches = 0;
for (Path p : files)
  new Thread(() -> { if (p has some property) matches++; }).start();
  // Illegal to mutate matches

因为可变的变量在lambda表达式中不是线程安全的,这和内部类的要求是一致的,内部类中只能引用
外部定义的final变量;

lambda表达式的作用域与嵌套代码块的作用域是一样的,所以在lambd表达式中的参数名或变量名不
能与局部变量冲突,如:

Path first = Paths.get("/usr/bin");
Comparator<String> comp = (first, second) -> Integer.compare(first.length(),
   second.length()); // Error: Variable first already defined

如果在lambda表达式中引用this变量,则引用的是创建该lambda表达式的方法的this变量,如:

public class Application() {
  public void doWork() {
    Runnable runner = () -> {
      ...;
      System.out.println(this.toString());
      ...
    };
  }
}

所以这里的this.toString()调用的是Application对象的toString(),而不是Runnable
对象的。

Default Method
接口中只能有抽象方法,如果在已有的接口中新增一个方法,则该接口所有的实现类都需要实现该方法。
Java 8中引入了Default Method的概念,在接口中新增一个default方法,不会破坏已有的接
口规则,接口的实现类可以选择重写或直接继承该default方法,比如:

interface Person {
  long getId();
  default String getName() { return "John Q. Public"; }
}

Java是允许多继承的,如果一个类的父类中定义的方法和接口中定义的default方法完全相同,或者
一个类的两个接口中定义了完全相同的方法, 则如何处理这种冲突呢?处理规则如下:

如果是父类和接口的方法冲突:以父类中的方法为准,接口中的方法被忽略;
如果两个接口中的default方法冲突,则需要重写该方法解决冲突;

Static Method
Java 8之前,接口中只能定义static变量,Java 8开始,接口中可以添加static方法,比如
Comparator接口新增了一系列comparingXXX的static方法,比如:

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));
}

使用这个static方法,以下两种方式也是等价的:

1、

Arrays.sort(cities, (first, second) -> Integer.compare(first.length(),
  second.length()));

2、

Arrays.sort(cities, Comparator.comparingInt(String::length));

所以,以后我们在设计自己的接口时,不需要再定义单独的工具类(如Collections/Collection),
在接口中使用static方法就行了。

匿名内部类

在 Java 世界中,匿名内部类 可以实现在应用程序中可能只执行一次的操作。例如,在 Android 应用程序中,一个按钮的点击事件处理。你不需要为了处理一个点击事件单独编写一个独立的类,可以用匿名内部类完成该操作:

Button button = (Button) findViewById(R.id.button1);
button.setOnClickListener(new OnClickListener() {
  
  @Override
  public void onClick(View view) {
    Toast.makeText(MainActivity.this, "Button Clicked", Toast.LENGTH_SHORT).show();
  }
  
});

Lambda 示例

1.Runnable Lambda

来看几个示例, 下面是一个 Runnable 的示例:

public void runnableTest() {
    System.out.println("=== RunnableTest ===");
    // 一个匿名的 Runnable
    Runnable r1 = new Runnable() {
      @Override
      public void run() {
        System.out.println("Hello world one!");
      }
    };
    // Lambda Runnable
    Runnable r2 = () -> System.out.println("Hello world two!");
    // 执行两个 run 函数
    r1.run();
    r2.run();
  }
public void runnableTest() {
  System.out.println("=== RunnableTest ===");
  // 一个匿名的 Runnable
  Runnable r1 = new Runnable() {
    @Override
    public void run() {
      System.out.println("Hello world one!");
    }
  };
 
  // Lambda Runnable
  Runnable r2 = () -> System.out.println("Hello world two!");
 
  // 执行两个 run 函数
  r1.run();
  r2.run();
}

   

这两个实现方式都没有参数也没有返回值。Runnable lambda 表达式使用代码块的方式把五行代码简化为一个语句。
2.Comparator Lambda

在 Java 中,Comparator 接口用来排序集合。在下面的示例中一个 ArrayList 中包含了一些 Person 对象, 并依据 Person 对象的 surName 来排序。下面是 Person 类中包含的 fields:

public class Person {
  private String givenName;
  private String surName;
  private int age;
  private Gender gender;
  private String eMail;
  private String phone;
  private String address;
}
public class Person {
  private String givenName;
  private String surName;
  private int age;
  private Gender gender;
  private String eMail;
  private String phone;
  private String address;
}

下面是分别用匿名内部类和 Lambda 表达式实现 Comparator 接口的方式:

 
public class ComparatorTest {
  public static void main(String[] args) {
    List<Person> personList = Person.createShortList();
    // 使用内部类实现排序
    Collections.sort(personList, new Comparator<Person>() {
      public int compare(Person p1, Person p2) {
        return p1.getSurName().compareTo(p2.getSurName());
      }
    });
    System.out.println("=== Sorted Asc SurName ===");
    for (Person p : personList) {
      p.printName();
    }
    // 使用 Lambda 表达式实现
    // 升序排列
    System.out.println("=== Sorted Asc SurName ===");
    Collections.sort(personList, (Person p1, Person p2) -> p1.getSurName().compareTo(p2.getSurName()));
    for (Person p : personList) {
      p.printName();
    }
    // 降序排列
    System.out.println("=== Sorted Desc SurName ===");
    Collections.sort(personList, (p1, p2) -> p2.getSurName().compareTo(p1.getSurName()));
    for (Person p : personList) {
      p.printName();
    }
  }
}
public class ComparatorTest {
  public static void main(String[] args) {
    List<Person> personList = Person.createShortList();
  
    // 使用内部类实现排序
    Collections.sort(personList, new Comparator<Person>() {
      public int compare(Person p1, Person p2) {
        return p1.getSurName().compareTo(p2.getSurName());
      }
    });
  
    System.out.println("=== Sorted Asc SurName ===");
    for (Person p : personList) {
      p.printName();
    }
  
    // 使用 Lambda 表达式实现
  
    // 升序排列
    System.out.println("=== Sorted Asc SurName ===");
    Collections.sort(personList, (Person p1, Person p2) -> p1.getSurName().compareTo(p2.getSurName()));
    for (Person p : personList) {
      p.printName();
    }
  
    // 降序排列
    System.out.println("=== Sorted Desc SurName ===");
    Collections.sort(personList, (p1, p2) -> p2.getSurName().compareTo(p1.getSurName()));
    for (Person p : personList) {
      p.printName();
    }
  }
}

   

可以看到 匿名内部类可以通过 Lambda 表达式实现。注意 第一个 Lambda 表达式定义了参数的类型为 Person;而第二个 Lambda 表达式省略了该类型定义。Lambda 表达式支持类型推倒,如果通过上下文可以推倒出所需要的类型,则可以省略类型定义。这里由于 我们把 Lambda 表达式用在一个使用泛型定义的 Comparator 地方,编译器可以推倒出这两个参数类型为 Person 。

更多Java编程中使用lambda表达式的奇技淫巧相关文章请关注PHP中文网!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
是否有任何威胁或增强Java平台独立性的新兴技术?是否有任何威胁或增强Java平台独立性的新兴技术?Apr 24, 2025 am 12:11 AM

新兴技术对Java的平台独立性既有威胁也有增强。1)云计算和容器化技术如Docker增强了Java的平台独立性,但需要优化以适应不同云环境。2)WebAssembly通过GraalVM编译Java代码,扩展了其平台独立性,但需与其他语言竞争性能。

JVM的实现是什么,它们都提供了相同的平台独立性?JVM的实现是什么,它们都提供了相同的平台独立性?Apr 24, 2025 am 12:10 AM

不同JVM实现都能提供平台独立性,但表现略有不同。1.OracleHotSpot和OpenJDKJVM在平台独立性上表现相似,但OpenJDK可能需额外配置。2.IBMJ9JVM在特定操作系统上表现优化。3.GraalVM支持多语言,需额外配置。4.AzulZingJVM需特定平台调整。

平台独立性如何降低发展成本和时间?平台独立性如何降低发展成本和时间?Apr 24, 2025 am 12:08 AM

平台独立性通过在多种操作系统上运行同一套代码,降低开发成本和缩短开发时间。具体表现为:1.减少开发时间,只需维护一套代码;2.降低维护成本,统一测试流程;3.快速迭代和团队协作,简化部署过程。

Java的平台独立性如何促进代码重用?Java的平台独立性如何促进代码重用?Apr 24, 2025 am 12:05 AM

Java'splatformindependencefacilitatescodereusebyallowingbytecodetorunonanyplatformwithaJVM.1)Developerscanwritecodeonceforconsistentbehavioracrossplatforms.2)Maintenanceisreducedascodedoesn'tneedrewriting.3)Librariesandframeworkscanbesharedacrossproj

您如何在Java应用程序中对平台特定问题进行故障排除?您如何在Java应用程序中对平台特定问题进行故障排除?Apr 24, 2025 am 12:04 AM

要解决Java应用程序中的平台特定问题,可以采取以下步骤:1.使用Java的System类查看系统属性以了解运行环境。2.利用File类或java.nio.file包处理文件路径。3.根据操作系统条件加载本地库。4.使用VisualVM或JProfiler优化跨平台性能。5.通过Docker容器化确保测试环境与生产环境一致。6.利用GitHubActions在多个平台上进行自动化测试。这些方法有助于有效地解决Java应用程序中的平台特定问题。

JVM中的类加载程序子系统如何促进平台独立性?JVM中的类加载程序子系统如何促进平台独立性?Apr 23, 2025 am 12:14 AM

类加载器通过统一的类文件格式、动态加载、双亲委派模型和平台无关的字节码,确保Java程序在不同平台上的一致性和兼容性,实现平台独立性。

Java编译器会产生特定于平台的代码吗?解释。Java编译器会产生特定于平台的代码吗?解释。Apr 23, 2025 am 12:09 AM

Java编译器生成的代码是平台无关的,但最终执行的代码是平台特定的。1.Java源代码编译成平台无关的字节码。2.JVM将字节码转换为特定平台的机器码,确保跨平台运行但性能可能不同。

JVM如何处理不同操作系统的多线程?JVM如何处理不同操作系统的多线程?Apr 23, 2025 am 12:07 AM

多线程在现代编程中重要,因为它能提高程序的响应性和资源利用率,并处理复杂的并发任务。JVM通过线程映射、调度机制和同步锁机制,在不同操作系统上确保多线程的一致性和高效性。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

螳螂BT

螳螂BT

Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。

EditPlus 中文破解版

EditPlus 中文破解版

体积小,语法高亮,不支持代码提示功能

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境

安全考试浏览器

安全考试浏览器

Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)