Heim  >  Artikel  >  Java  >  Ausführliche Erklärung und Beispielcode von Java Lambda-Ausdrücken

Ausführliche Erklärung und Beispielcode von Java Lambda-Ausdrücken

高洛峰
高洛峰Original
2017-01-23 13:24:521493Durchsuche

Java Lambda-Ausdruck ist eine neue Funktion, die in Java 8 eingeführt wurde. Man kann sagen, dass es sich um einen Syntaxzucker zur Simulation funktionaler Programmierung handelt. Er ähnelt Abschlüssen in Javascript, ist jedoch etwas anders. Der Hauptzweck besteht darin, eine funktionale Syntax bereitzustellen um unsere Codierung zu vereinfachen.

Lambda-Grundsyntax

Die Grundstruktur von Lambda ist (Argumente) -> Körper, es gibt die folgenden Situationen:

Wenn der Parametertyp abgeleitet werden kann, dort Es ist nicht erforderlich, den Typ anzugeben, z. B. (a) -> System.out.println(a)

Wenn nur ein Parameter vorhanden ist und der Typ abgeleitet werden kann, ist das Schreiben von () nicht obligatorisch. B. a -> System.out.println(a)

Bei der Angabe des Parametertyps müssen Klammern vorhanden sein, z. B. (int a) -> System.out.println(a)

Der Parameter kann leer sein, z. B. () -> System .out.println(“hello”)

Body muss {} verwenden, um Anweisungen einzuschließen , {} können weggelassen werden

Die übliche Schreibweise ist wie folgt:

(a) -> > a + b
(a, b) -> {return a - b;}
() ->

Funktionale Schnittstelle FunctionalInterface

Konzept

Java Lambda-Ausdruck Die funktionale Schnittstelle ist die Basis. Was ist eine funktionale Schnittstelle (FunctionalInterface)? Einfach ausgedrückt handelt es sich um eine Schnittstelle mit nur einer Methode (Funktion). Der Zweck dieser Art von Schnittstelle besteht darin, eine einzelne Operation durchzuführen, was einer einzelnen Funktion entspricht. Gängige Schnittstellen wie Runnable und Comparator sind funktionale Schnittstellen und werden mit @FunctionalInterface annotiert.

Beispiel

Nehmen Sie Thread als Beispiel, um zu veranschaulichen, dass es leicht zu verstehen ist. Die Runnable-Schnittstelle ist eine Schnittstelle, die häufig in unserer Thread-Programmierung verwendet wird. Sie enthält eine Methode void run(), die die laufende Logik des Threads darstellt. Gemäß der vorherigen Syntax verwenden wir beim Erstellen eines neuen Threads im Allgemeinen die anonyme Klasse Runnable wie folgt:

new Thread(new Runnable() {
  @Override
  public void run() {
    System.out.println(Thread.currentThread().getId());
  }
 
}).start();

Zu viel zu schreiben wäre langweilig, basiert aber auf Lambda Schreibregeln Es wird prägnant und klar wie folgt:

new Thread(() -> System.out.println(Thread.currentThread().getId())).start();


Achten Sie auf die Parameter von Thread. Die anonyme Implementierung von Runnable ist besser zu verstehen, wenn sie wie folgt geschrieben wird:

Runnable r = () -> ; System.out.println(Thread.currentThread() .getId());

new Thread(r).start();



Natürlich ist der Zweck von Lambda nicht nur prägnant zu schreiben, aber den übergeordneten Zweck zusammenzufassen, nachdem man ihn verstanden hat.

Sehen Sie sich ein weiteres Beispiel für einen Komparator an. Gemäß der traditionellen Schreibmethode lautet es wie folgt:

Integer[] a = {1, 8, 3, 9, 2, 0, 5};
Arrays.sort(a, new Comparator<Integer>() {
  @Override
  public int compare(Integer o1, Integer o2) {
    return o1 - o2;
  }
});

Der Lambda-Ausdruck wird wie folgt geschrieben:

Integer[ ] a = {1, 8, 3, 9, 2, 0, 5};

Arrays.sort(a, (o1, o2) -> o1 - o2);



Funktionale Schnittstellen im JDK

Damit vorhandene Klassenbibliotheken Lambda-Ausdrücke direkt verwenden können, gab es vor Java 8 einige Schnittstellen, die als funktionale Schnittstellen gekennzeichnet waren:

java .lang. Runnable

java.util.Comparator

java.util.concurrent.Callable

java.io.FileFilter

java.security.PrivilegedAction

java.beans.PropertyChangeListener


Java 8 hat ein neues Paket java.util.function hinzugefügt, das häufig verwendete funktionale Schnittstellen enthält:

Functionb6755583b82c4a7cd48100c3f618d43e - Funktion: Eingabe T Ausgabe R

BiFunction96b8c9ba1c8b330d622a8468a6113c2e - Funktion: Eingabe T und U Ausgabe R Objekt

Prädikat8742468051c85b06f0a0af9e3e506b5c Ausgabe boolean

BiPredicateabbd655bd3f9f929be0207abcc18a2ef – Behauptung/Beurteilung: Eingabe T und U Ausgabe boolean

Lieferant8742468051c85b06f0a0af9e3e506b5c – Produzent: Keine Eingabe, Ausgabe T

Verbraucher< ;T> – Verbraucher: Eingang T, kein Ausgang

BiConsumerabbd655bd3f9f929be0207abcc18a2ef – Verbraucher: Eingang T und U, kein Ausgang

UnaryOperator8742468051c85b06f0a0af9e3e506b5c T

BinaryOperator8742468051c85b06f0a0af9e3e506b5c - Binäroperation: Eingabe T und T Ausgabe T


Darüber hinaus werden spezifischere Funktionen für die grundlegende Typverarbeitung hinzugefügt, darunter: BooleanSupplier , DoubleBinaryOperator, DoubleConsumer, DoubleFunction0f1763a9dfcc95d54eac98034f0cdcdd, DoublePredicate, DoubleSupplier, DoubleToIntFunction, DoubleToLongFunction, DoubleUnaryOperator, IntBinaryOperator, IntConsumer, IntFunction0f1763a9dfcc95d54eac98034f0cdcdd, IntPredicate, IntSupplier, IntToDoubleFunction, IntToLongFunction, Int UnaryOperator, LongBinaryOperator, LongCon sumer,LongFunction

Funktionsschnittstelle erstellen

Manchmal müssen wir selbst eine Funktionsschnittstelle implementieren. Zuerst müssen Sie sicherstellen, dass diese Schnittstelle nur eine Funktionsoperation haben kann Markieren Sie den Schnittstellentyp einfach mit @FunctionalInterface.

Typableitung

Typableitung ist die Grundlage von Lambda-Ausdrücken, und der Prozess der Typableitung ist der Kompilierungsprozess von Lambda-Ausdrücken. Nehmen Sie den folgenden Code als Beispiel:

Function00c20620d278363633dd30e58ef30cbd strToInt = str -> Integer.parseInt(str);
编译期间,我理解的类型推导的过程如下:

先确定目标类型 Function

Function 作为函数式接口,其方法签名为:Integer apply(String t)

检测 str -> Integer.parseInt(str) 是否与方法签名匹配(方法的参数类型、个数、顺序 和返回值类型)

如果不匹配,则报编译错误

这里的目标类型是关键,通过目标类型获取方法签名,然后和 Lambda 表达式做出对比。

方法引用

方法引用(Method Reference)的基础同样是函数式接口,可以直接作为函数式接口的实现,与 Lambda 表达式有相同的作用,同样依赖于类型推导。方法引用可以看作是只调用一个方法的 Lambda 表达式的简化。

方法引用的语法为: Type::methodName 或者 instanceName::methodName , 构造函数对应的 methodName 为 new。

例如上面曾用到例子:

Function00c20620d278363633dd30e58ef30cbd strToInt = str -> Integer.parseInt(str);

对应的方法引用的写法为

Function00c20620d278363633dd30e58ef30cbd strToInt = Integer::parseInt;

根据方法的类型,方法引用主要分为一下几种类型,构造方法引用、静态方法引用、实例上实例方法引用、类型上实例方法引用等

构造方法引用

语法为: Type::new 。 如下面的函数为了将字符串转为数组

方法引用写法

Function00c20620d278363633dd30e58ef30cbd strToInt = Integer::new;

Lambda 写法

Function00c20620d278363633dd30e58ef30cbd strToInt = str -> new Integer(str);

传统写法

Function<String, Integer> strToInt = new Function<String, Integer>() {
  @Override
  public Integer apply(String str) {
    return new Integer(str);
  }
};

数组构造方法引用

语法为: Type[]::new 。如下面的函数为了构造一个指定长度的字符串数组

方法引用写法

Function440e1640c9faa3393c37ba0de3f32bfa fixedArray = String[]::new;

方法引用写法

Function440e1640c9faa3393c37ba0de3f32bfa fixedArray = length -> new String[length];

传统写法

Function<Integer, String[]> fixedArray = new Function<Integer, String[]>() {
  @Override
  public String[] apply(Integer length) {
    return new String[length];
  }
};

静态方法引用

语法为: Type::new 。 如下面的函数同样为了将字符串转为数组

方法引用写法

Function00c20620d278363633dd30e58ef30cbd strToInt = Integer::parseInt;

Lambda 写法

Function00c20620d278363633dd30e58ef30cbd strToInt = str -> Integer.parseInt(str);

传统写法

Function<String, Integer> strToInt = new Function<String, Integer>() {
  @Override
  public Integer apply(String str) {
    return Integer.parseInt(str);
  }
};

实例上实例方法引用

语法为: instanceName::methodName 。如下面的判断函数用来判断给定的姓名是否在列表中存在

Listf7e83be87db5cd2d9a8a0b8117b38cd4 names = Arrays.asList(new String[]{"张三", "李四", "王五"});
Predicatef7e83be87db5cd2d9a8a0b8117b38cd4 checkNameExists = names::contains;
System.out.println(checkNameExists.test("张三"));
System.out.println(checkNameExists.test("张四"));

类型上实例方法引用

语法为: Type::methodName 。运行时引用是指上下文中的对象,如下面的函数来返回字符串的长度

Function<String, Integer> calcStrLength = String::length;
System.out.println(calcStrLength.apply("张三"));
List<String> names = Arrays.asList(new String[]{"zhangsan", "lisi", "wangwu"});
names.stream().map(String::length).forEach(System.out::println);<br>

   

又比如下面的函数已指定的分隔符分割字符串为数组

BiFunction505cb6255f356d4ffe44ba9665547740 split = String::split;
String[] names = split.apply("zhangsan,lisi,wangwu", ",");
System.out.println(Arrays.toString(names));

Stream 对象

概念

什么是 Stream ? 这里的 Stream 不同于 io 中的 InputStream 和 OutputStream,Stream 位于包 java.util.stream 中, 也是 java 8 新加入的,Stream 只的是一组支持串行并行聚合操作的元素,可以理解为集合或者迭代器的增强版。什么是聚合操作?简单举例来说常见的有平均值、最大值、最小值、总和、排序、过滤等。

Stream 的几个特征:

单次处理。一次处理结束后,当前Stream就关闭了。
支持并行操作
常见的获取 Stream 的方式

从集合中获取

Collection.stream();
Collection.parallelStream();

静态工厂

Arrays.stream(array)
Stream.of(T …)
IntStream.range()
这里只对 Stream 做简单的介绍,下面会有具体的应用。要说 Stream 与 Lambda 表达式有什么关系,其实并没有什么特别紧密的关系,只是 Lambda 表达式极大的方便了 Stream 的使用。如果没有 Lambda 表达式,使用 Stream 的过程中会产生大量的匿名类,非常别扭。

举例

以下的demo依赖于 Employee 对象,以及由 Employee 对象组成的 List 对象。

public class Employee {
 
  private String name;
  private String sex;
  private int age;
 
  public Employee(String name, String sex, int age) {
    super();
    this.name = name;
    this.sex = sex;
    this.age = age;
  }
  public String getName() {
    return name;
  }
 
  public String getSex() {
    return sex;
  }
  public int getAge() {
    return age;
  }
  @Override
  public String toString() {
    StringBuilder builder = new StringBuilder();
    builder.append("Employee {name=").append(name).append(", sex=").append(sex).append(", age=").append(age)
        .append("}");
    return builder.toString();
  }
}
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("张三", "男", 25));
employees.add(new Employee("李四", "女", 24));
employees.add(new Employee("王五", "女", 23));
employees.add(new Employee("周六", "男", 22));
employees.add(new Employee("孙七", "女", 21));
employees.add(new Employee("刘八", "男", 20));

   

打印所有员工

Collection 提供了 forEach 方法,供我们逐个操作单个对象。

employees.forEach(e -> System.out.println(e)); 
或者
employees.stream().forEach(e -> System.out.println(e));

按年龄排序

Collections.sort(employees, (e1, e2) -> e1.getAge() - e2.getAge());
employees.forEach(e -> System.out.println(e));
或者
employees.stream().sorted((e1, e2) -> e1.getAge() - e2.getAge()).forEach(e -> System.out.println(e));
打印年龄最大的女员工

max/min 返回指定排序条件下最大/最小的元素

Employee maxAgeFemaleEmployee = employees.stream()
    .filter(e -> "女".equals(e.getSex()))
    .max((e1, e2) -> e1.getAge() - e2.getAge())
    .get();
System.out.println(maxAgeFemaleEmployee);

   

打印出年龄大于20 的男员工

filter 可以过滤出符合条件的元素

employees.stream()
        .filter(e -> e.getAge() > 20 && "男".equals(e.getSex()))
        .forEach(e -> System.out.println(e));
打印出年龄最大的2名男员工

limit 方法截取有限的元素

employees.stream()
    .filter(e -> "男".equals(e.getSex()))
    .sorted((e1, e2) -> e2.getAge() - e1.getAge())
    .limit(2)
    .forEach(e -> System.out.println(e));

   

打印出所有男员工的姓名,使用 , 分隔

map 将 Stream 中所有元素的执行给定的函数后返回值组成新的 Stream

String maleEmployeesNames = employees.stream()
    .map(e -> e.getName())
    .collect(Collectors.joining(","));
System.out.println(maleEmployeesNames);

   

统计信息

IntSummaryStatistics, DoubleSummaryStatistics, LongSummaryStatistics 包含了 Stream 中的汇总数据。

IntSummaryStatistics stat = employees.stream()
    .mapToInt(Employee::getAge).summaryStatistics();
System.out.println("员工总数:" + stat.getCount());
System.out.println("最高年龄:" + stat.getMax());
System.out.println("最小年龄:" + stat.getMin());
System.out.println("平均年龄:" + stat.getAverage());

   

总结

Lambda 表达式确实可以减少很多代码,能提高生产力,当然也有弊端,就是复杂的表达式可读性会比较差,也可能是还不是很习惯的缘故吧,如果习惯了,相信会喜欢上的。凡事都有两面性,就看我们如何去平衡这其中的利弊了,尤其是在一个团队中。

以上就是对Java8 JavaLambda 的资料整理,后续继续补充相关资料谢谢大家对本站的支持!

更多Java Lambda 表达式详解及示例代码相关文章请关注PHP中文网!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn