Maison  >  Article  >  Java  >  Conseils pour utiliser les expressions lambda dans la programmation Java

Conseils pour utiliser les expressions lambda dans la programmation Java

高洛峰
高洛峰original
2017-01-23 15:03:141415parcourir

Pourquoi utiliser les expressions Lambda
Regardons quelques exemples :

Le premier exemple consiste à effectuer une tâche dans un thread indépendant. Nous l'implémentons généralement comme ceci :

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

Le deuxième exemple consiste à personnaliser la méthode de comparaison de chaînes (par la longueur de la chaîne), faites généralement ceci :

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

Le troisième exemple, dans JavaFX, ajouter un rappel à un bouton :

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

Ces exemples ont un point commun, à savoir : définir d'abord un bloc de code et le passer Donné à un objet ou méthode, il est ensuite exécuté. Avant l'expression

de la table Lambda, Java n'est pas autorisé à transmettre directement des blocs de code, car Java est orienté objet, donc un objet doit être transmis pour encapsuler le bloc de code à
exécuté dans l'objet.

La syntaxe de l'expression Lambda

Le lengthComparator dans le deuxième exemple ci-dessus est exprimé sous forme d'expression Lambda :

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

-> list, suivi du corps de l'instruction d'expression ;

Si le corps de l'instruction d'expression comporte plus d'une ligne, écrivez le corps de l'instruction en {}, comme pour une fonction ordinaire :

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

S'il n'y a pas de paramètres, () doit quand même être apporté. Par exemple, le premier exemple ci-dessus peut être exprimé comme suit :

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

Si le paramètre. type can S'il est automatiquement déduit du contexte, il peut être omis :

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

S'il n'y a qu'un seul paramètre et que le type peut être automatiquement déduit, les parenthèses () peuvent également être omis :

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

Le type de la valeur de retour de l'expression lambda est automatiquement déduit, il n'est donc pas nécessaire de le spécifier dans l'expression lambda, certaines branches conditionnelles ; avoir une valeur de retour

, alors que d'autres branches n'ont pas de valeur de retour, n'est pas autorisé, comme :

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

De plus, la différence entre l'expression lambda et l'instruction lambda est cette expression lambda ne nécessite pas

pour écrire le mot-clé return, et le runtime Java le fera. Le résultat de l'expression est renvoyé comme valeur de retour, et l'instruction lambda est une expression écrite en {} par
, et le retour un mot-clé doit être utilisé, tel que :

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

Interface fonctionnelle

Si une interface n'a qu'une seule méthode abstraite, elle est appelée
Interface fonctionnelle, telle que Runnable , Comparateur, etc.
Vous pouvez utiliser des expressions lambda partout où un objet Functional Interface est requis :

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

Ici, le deuxième paramètre de sort() nécessite un objet Comparator, et Comparator est un

Interface fonctionnelle, afin que l'expression lambda puisse être transmise directement. Lorsque la méthode compare()
de l'objet est appelée, le corps de l'instruction dans l'expression lambda est exécuté

Si le corps de l'instruction est exécuté ; de l'expression lambda lève une exception, la méthode abstraite dans l'interface fonctionnelle correspondante doit lever

l'exception, sinon l'exception doit être explicitement interceptée dans l'expression 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 "";
};

Référence de méthode

Si les paramètres d'une expression lambda sont passés en tant que paramètres à une méthode et que leurs effets d'exécution sont les mêmes, alors l'expression lambda
peut être exprimée en utilisant la référence de méthode, les deux méthodes suivantes sont équivalentes :

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

où System.out::println est appelé Référence de méthode.

Method Reference a principalement trois formes :

object::instanceMethod

Class::staticMethod

Class::instanceMethod

pour Pour les deux premières méthodes, les paramètres de l'expression lambda correspondante sont cohérents avec les paramètres de la méthode, par exemple :

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

Pour la troisième méthode, l'instruction correspondante de la expression lambda Dans le corps, le premier paramètre est utilisé comme objet, appelle la méthode et utilise d'autres paramètres

comme paramètres de la méthode, par exemple :

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

Constructor Reference est similaire à Method Reference, sauf qu'il s'agit d'une méthode spéciale : new Le constructeur appelé est déterminé par le contexte, par exemple :

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

Button::new. est équivalent à (x) -> Button(x), donc le constructeur appelé est : Button(x);

En plus de créer un seul objet, vous pouvez également créer un tableau d'objets. deux méthodes sont équivalentes :

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

Portée de la variable

L'expression lambd capturera les variables disponibles dans la portée actuelle, telles que :

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

Mais ces variables doivent être immuables Oui, pourquoi ? Regardez l'exemple suivant :

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

Étant donné que les variables mutables ne sont pas thread-safe dans les expressions lambda, cela est cohérent avec les exigences des classes internes, uniquement Reference <.> variables finales définies en externe ;


La portée de l'expression lambda est la même que la portée du bloc de code imbriqué, donc le nom du paramètre ou le nom de la variable dans l'expression lambd n'est pas

Peut entrer en conflit avec variables locales, telles que :


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

Si cette variable est référencée dans une expression lambda, elle est référencée à la variable this de la méthode qui a créé l'expression lambda, telle que :

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

Donc this.toString() appelle ici toString() de l'objet Application, pas l'objet Runnable

.


Méthode par défaut

Les interfaces ne peuvent avoir que des méthodes abstraites. Si une nouvelle méthode est ajoutée à une interface existante, toutes les classes d'implémentation de l'interface doivent implémenter cette méthode.

Le concept de méthode par défaut a été introduit dans Java 8. L'ajout d'une nouvelle méthode par défaut à l'interface ne détruira pas les règles d'interface existantes. La classe d'implémentation de l'interface peut choisir de remplacer ou d'hériter directement de la méthode par défaut.


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

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Article précédent:Java avancé -- IO descriptifArticle suivant:Java avancé -- IO descriptif