Home >Java >javaTutorial >Tips for using lambda expressions in Java programming

Tips for using lambda expressions in Java programming

高洛峰
高洛峰Original
2017-01-23 15:03:141448browse

Why use Lambda expressions
Let’s look at a few examples:

The first example is to perform a certain task in an independent thread. We usually implement it like this:

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

The second example is to customize the string comparison method (through string length), generally do this:

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

Three examples, in JavaFX, add a callback to a button:

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

These examples have one thing in common, which is: first define a code block and pass it to an object or method , and then executed. Before the Lambda expression
, Java is not allowed to pass the code block directly, because Java is object-oriented, so an object must be passed to encapsulate the code block to be
executed into the object.

The syntax of Lambda expression
The LengthComparator in the second example above is expressed as a Lambda expression:

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

-> is the parameter before list, followed by the expression statement body;

If the expression statement body is more than one line, write the statement body in {}, the same as an ordinary function:

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

If there are no parameters, () still needs to be brought. For example, the first example above can be expressed as:

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

If the type of the parameter can be automatically inferred from the context, then Can be omitted:

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

If there is only one parameter and the type can be automatically inferred, the parentheses () can also be omitted:

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

lambda The type of the return value of the expression is automatically inferred, so there is no need to specify it; in lambda expressions, some conditional branches have
return values, while other branches have no return values, which is not allowed, such as:

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

In addition, the difference between expression lambda and statement lambda is that expression lambda does not need to
write the return keyword. Java runtime will return the result of the expression as the return value, while statement lambda It is an expression written in {} and needs to use the return keyword, for example:

// 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

If an interface (interface) has only one abstract method (abstract method) is called
Functional Interface, such as Runnable, Comparator, etc.
You can use lambda expressions wherever a Functional Interface object is required:

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

Here, the second parameter of sort() requires a Comparator object, The Comparator is a

Functional Interface, so the lambda expression can be passed in directly. When the compare() method
of the object is called, the statement body in the lambda expression is executed;

If If the statement body of the lambda expression throws an exception, the abstract method in the corresponding Functional Interface must throw

the exception, otherwise the exception needs to be explicitly captured in the lambda expression:

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

If the parameters of the lambda expression are passed as parameters to a method, their execution effects are the same, then the lambda expression
can be expressed using Method Reference, in the following two ways are equivalent:

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

where System.out::println is called Method Reference.

Method Reference mainly has three forms:

object::instanceMethod

Class::staticMethod

Class::instanceMethod

For In the first two methods, the parameters of the corresponding lambda expression are consistent with the parameters of the method, for example:

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

For the third method, in the statement body of the corresponding lambda expression , the first parameter is used as an object, the method is called, and the other parameters

are used as parameters of the method, for example:

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

Constructor Reference is similar to Method Reference, but it is a special method : new, which constructor is called is determined by the context, for example:

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

Button::new is equivalent to (x) -> Button(x), so The constructor called is: Button(x);

In addition to creating a single object, you can also create an array of objects. The following two methods are equivalent:

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

Variable Scope

lambd expression will capture the variables available in the current scope, such as:

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

But these variables must be immutable, why? Look at the following example:

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

Because mutable variables are not thread-safe in lambda expressions, this is consistent with the requirements of inner classes, which can only be referenced

Externally defined final variables;

The scope of the lambda expression is the same as the scope of the nested code block, so the parameter name or variable name in the lambd expression cannot

Local variable conflict, such as:

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

If this variable is referenced in a lambda expression, the this variable of the method that created the lambda expression is referenced, such as:

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

So this.toString() here calls toString() of the Application object, not the Runnable

object.

Default Method

There can only be abstract methods in an interface. If a method is added to an existing interface, all implementation classes of the interface need to implement the method.
The concept of Default Method was introduced in Java 8. Adding a new default method to the interface will not destroy the existing interface rules. The implementation class of the interface can choose to override or directly inherit the default method. For example:

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

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn