Heim  >  Artikel  >  Java  >  Tipps zur Verwendung von Lambda-Ausdrücken in der Java-Programmierung

Tipps zur Verwendung von Lambda-Ausdrücken in der Java-Programmierung

高洛峰
高洛峰Original
2017-01-23 15:03:141418Durchsuche

Warum Lambda-Ausdrücke verwenden?
Sehen wir uns ein paar Beispiele an:

Das erste Beispiel ist die Ausführung einer Aufgabe in einem unabhängigen Thread. Wir implementieren sie normalerweise so:

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

Das zweite Beispiel besteht darin, die Zeichenfolgenvergleichsmethode (über die Zeichenfolgenlänge) anzupassen. Gehen Sie im Allgemeinen wie folgt vor:

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

Die Drittes Beispiel: Fügen Sie in JavaFX einen Rückruf zu einer Schaltfläche hinzu:

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

Diese Beispiele haben eines gemeinsam: Definieren Sie zunächst einen Codeblock und übergeben Sie ihn an einen Objekt oder Methode, dann wird es ausgeführt. Vor dem
-Ausdruck der Lambda-Tabelle darf Java keine Codeblöcke direkt übergeben, da Java objektorientiert ist. Daher muss ein Objekt übergeben werden, um den Codeblock zu kapseln, der
im Objekt ausgeführt werden soll.

Die Syntax des Lambda-Ausdrucks
Der Längenvergleicher im zweiten Beispiel oben wird als Lambda-Ausdruck ausgedrückt:

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

-> Liste, gefolgt vom Hauptteil der Ausdrucksanweisung;

Wenn der Hauptteil der Ausdrucksanweisung mehr als eine Zeile umfasst, schreiben Sie den Anweisungshauptteil in {}, genau wie bei einer gewöhnlichen Funktion:

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

Wenn keine Parameter vorhanden sind, muss () dennoch mitgebracht werden. Das erste Beispiel oben kann beispielsweise wie folgt ausgedrückt werden:

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

Wenn der Parameter Typ kann Wenn es automatisch aus dem Kontext abgeleitet wird, kann es weggelassen werden:

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

Wenn es nur einen Parameter gibt und der Typ automatisch abgeleitet werden kann, können die Klammern () dies tun auch weggelassen werden:

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

Der Typ des Rückgabewerts des Lambda-Ausdrucks wird automatisch abgeleitet, sodass es nicht erforderlich ist, ihn im Lambda-Ausdruck anzugeben einen
-Rückgabewert haben, während andere Zweige keinen Rückgabewert haben, ist nicht zulässig, wie zum Beispiel:

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

Darüber hinaus besteht der Unterschied zwischen Ausdrucks-Lambda und Anweisungs-Lambda Dieser Ausdruck Lambda erfordert nicht, dass
das Schlüsselwort return schreibt, und die Java-Laufzeit wird das Ergebnis des Ausdrucks als Rückgabewert zurückgeben, und die Anweisung Lambda ist ein von
in {} geschriebener Ausdruck und die Rückgabe Es muss ein Schlüsselwort verwendet werden, z. B.:

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

Funktionale Schnittstelle
Wenn eine Schnittstelle nur eine abstrakte Methode hat, wird sie als
Funktionale Schnittstelle bezeichnet, z. B. Runnable , Komparator usw.
Sie können Lambda-Ausdrücke überall dort verwenden, wo ein Functional Interface-Objekt erforderlich ist:

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

Hier erfordert der zweite Parameter von sort() ein Comparator-Objekt und Comparator ist ein
Funktionale Schnittstelle, damit der Lambda-Ausdruck direkt übergeben werden kann. Wenn die Compare()-Methode
des Objekts aufgerufen wird, wird der Anweisungskörper im Lambda-Ausdruck ausgeführt Wenn der Lambda-Ausdruck eine Ausnahme auslöst, muss die abstrakte Methode in der entsprechenden Funktionsschnittstelle

die Ausnahme auslösen, andernfalls muss die Ausnahme explizit im Lambda-Ausdruck abgefangen werden:


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

Methodenreferenz

Wenn die Parameter eines Lambda-Ausdrucks als Parameter an eine Methode übergeben werden und ihre Ausführungseffekte gleich sind, kann der Lambda-Ausdruck

mithilfe der Methodenreferenz ausgedrückt werden. Die folgenden beiden Methoden sind äquivalent:

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

wobei System.out::println als Methodenreferenz bezeichnet wird.

Methodenreferenz hat hauptsächlich drei Formen:

object::instanceMethod

Class::staticMethod

Class::instanceMethod

für Bei den ersten beiden Methoden stimmen die Parameter des entsprechenden Lambda-Ausdrucks mit den Parametern der Methode überein, zum Beispiel:

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

Bei der dritten Methode entspricht die entsprechende Anweisung der Lambda-Ausdruck Im Körper wird der erste Parameter als Objekt verwendet, die Methode aufgerufen und andere Parameter

als Parameter der Methode verwendet, zum Beispiel:


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

Constructor Reference ähnelt der Method Reference, außer dass es sich um eine spezielle Methode handelt: new Welcher Konstruktor aufgerufen wird, wird durch den Kontext bestimmt, zum Beispiel:

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

Button::new ist äquivalent zu (x) -> Button(x), daher lautet der aufgerufene Konstruktor: Button(x);

Zusätzlich zum Erstellen eines einzelnen Objekts können Sie auch ein Array von Objekten erstellen Zwei Methoden sind äquivalent:

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

Variablenbereich

Der Lambd-Ausdruck erfasst die im aktuellen Bereich verfügbaren Variablen, wie zum Beispiel:


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

Aber diese Variablen müssen unveränderlich sein. Ja, warum? Schauen Sie sich das folgende Beispiel an:

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

Da veränderliche Variablen in Lambda-Ausdrücken nicht threadsicher sind, entspricht dies nur den Anforderungen innerer Klassen Referenz

extern definierte endgültige Variablen;


Der Bereich des Lambda-Ausdrucks ist derselbe wie der Bereich des verschachtelten Codeblocks, daher ist der Parametername oder Variablenname im Lambd-Ausdruck nicht

Kann in Konflikt geraten Lokale Variablen, wie zum Beispiel:


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

Wenn auf diese Variable in einem Lambda-Ausdruck verwiesen wird, wird auf diese Variable der Methode verwiesen, die den Lambda-Ausdruck erstellt hat, wie zum Beispiel:

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

This.toString() ruft hier also toString() des Application-Objekts auf, nicht des Runnable

-Objekts.


Standardmethode

Schnittstellen können nur abstrakte Methoden haben. Wenn einer vorhandenen Schnittstelle eine neue Methode hinzugefügt wird, müssen alle Implementierungsklassen der Schnittstelle diese Methode implementieren.

Das Konzept der Standardmethode wurde in Java 8 eingeführt. Durch das Hinzufügen einer neuen Standardmethode zur Schnittstelle werden die vorhandenen Schnittstellenregeln nicht zerstört. Die Implementierungsklasse der Schnittstelle kann die Standardmethode überschreiben oder direkt erben.


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中文网!

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