Lambda语法

Lambda表达式语法

Lambda表达式的基本形式为:(Parameters) -> {Body},参数用括号括起来,这和方法的方法是一样的,而Lambda表达式主体是用括号括起来的代码块。Lambda表达式体中可以有局部变量、语句。我们可以在Lambda表达式体中使用break、continue、return;我们甚至可以从Lambda表达式体中抛出异常。但是Lambda表达式没有名字,因为它代表匿名的内部类,Lambda表达式的返回类型是由编译器推断的。一个Lambda表达式不能像方法一样有一个throws子句,且它不能是泛型的,而泛型是在函数接口中定义的。

显示与隐式声明

参数

一个没有声明其参数类型的Lambda表达式被称为隐式Lambda表达式。显式Lambda表达式是指声明了参数类型的Lambda表达式,对于隐式Lambda表达式,编译器会推断其参数类型。

public class Main {
  public static void main(String[] args) {
    // 显式
    MyIntegerCalculator myIntegerCalculator = (Integer s1) -> s1 * 2;
    // 隐式
    MyIntegerCalculator myIntegerCalculator = (s1) -> s1 * 2;

    System.out.println("1- Result x2 : " + myIntegerCalculator.calcIt(5));

  }
}
interface MyIntegerCalculator {
  public Integer calcIt(Integer s1);
}

我们可以选择省略Lambda表达式中的参数类型,如果我们选择省略参数的类型,我们必须省略所有参数的类型。

public class Main {
  public static void main(String[] argv) {
    Processor stringProcessor = (str) -> str.length();
    String name = "Java Lambda";
    int length = stringProcessor.getStringLength(name);
    System.out.println(length);//from   ww w.ja  v a 2  s .  c  om

  }
}

@FunctionalInterface
interface Processor {
  int getStringLength(String str);
}

对于单参数Lambda表达式,我们可以省略括号,因为我们省略了参数类型。

public class Main {
  public static void main(String[] argv) {
    Processor stringProcessor = str -> str.length();
    String name = "Java Lambda";
    int length = stringProcessor.getStringLength(name);
    System.out.println(length);/*  w  w  w. jav  a2s  .c  o  m*/

  }
}

@FunctionalInterface
interface Processor {
  int getStringLength(String str);
}

对于一个没有参数的Lambda表达式,我们仍然需要括号。

import java.util.function.BooleanSupplier;

public class Main {
  public static void main(String[] args) {
    BooleanSupplier bs = () -> true;
    System.out.println(bs.getAsBoolean());

    int x = 0, y= 1;
    bs = () -> x > y;
    System.out.println(bs.getAsBoolean());
  }
}

你可以在参数声明中使用final修饰符来表达显式lambda

public class Main {
  public static void main(String[] argv) {
    Processor stringProcessor = (final String str) -> str.length();
    String name = "Java Lambda";
    int length = stringProcessor.getStringLength(name);
    System.out.println(length);/* w w  w . ja  v  a  2s .co m*/

  }
}

@FunctionalInterface
interface Processor {
  int getStringLength(String str);
}

函数体

Lambda表达式主体可以是一个块状语句,也可以是一个单一表达式。块语句用大括号括起来,而单个表达式可以没有大括号。lambda是没有必要返回一个值的。下面两个Lambda表达式只是将参数输出到标准输出,并不返回任何东西。

(String msg)->{System.out.println(msg);}// a  block   statement
(String msg)->System.out.println(msg)   //an expression

public class Main {
  public static void main(String[] argv) {
    Processor stringProcessor = (String str) -> str.length();
    String name = "Java Lambda";
    int length = stringProcessor.getStringLength(name);
    System.out.println(length);// www . j a  va 2  s. co m

  }
}

@FunctionalInterface
interface Processor {
  int getStringLength(String str);
}

类型推导

一个Lambda表达式代表一个功能接口的实例。一个Lambda表达式可以根据上下文映射到不同的功能接口类型。编译器会推断Lambda表达式的类型。

public class Main {
  public static void main(String[] argv) {
    Processor stringProcessor = (String str) -> str.length();
    SecondProcessor secondProcessor = (String str) -> str.length();
    //stringProcessor = secondProcessor; //compile error
    String name = "Java Lambda";
    int length = stringProcessor.getStringLength(name);
    System.out.println(length);

  }
}

@FunctionalInterface
interface Processor {
  int getStringLength(String str);
}

@FunctionalInterface
interface SecondProcessor {
  int noName(String str);
}

ProcessorSecondProcessor称为目标类型。推断Lambda表达式类型的过程称为目标类型。编译器使用以下规则来确定一个Lambda表达式是否可以分配到它的目标类型。

  • 它必须是一个函数接口
  • Lambda表达式的参数必须与函数式接口中的抽象方法相匹配。
  • Lambda表达式的返回类型与函数式接口中的抽象方法的返回类型兼容。
  • Lambda表达式中抛出的检查异常必须与函数式接口中抽象方法的声明的throws子句兼容。

编译器并不总是能够推断出Lambda表达式的类型。其中一种情况是将Lambda表达式传递给重载方法。在下面的代码中,有两个功能接口。一个是用于int值计算,另一个是用于long值。在Main类中,有称为engine的重载方法。一个是期望IntCalculator,另一个是用于LongCalculator。在main类中,我们必须指明Lambda表达式的参数,以指示编译器我们要使用哪个重载函数。

public class Main {
  public static void main(String[] argv) {

    // 指明参数
    engine((IntCalculator) ((x,y)-> x + y));

    // 指明类型
    IntCalculator iCal = (x,y)-> x + y;

    engine((int x,int y)-> x + y);
    engine((long x, long y)-> x * y);
    engine((int x,int y)-> x / y);
    engine((long x,long y)-> x % y);
  }

  private static void engine(IntCalculator calculator){
    int x = 2, y = 4;
    int result = calculator.calculate(x,y);
    System.out.println(result);
  }

  private static void engine(LongCalculator calculator){
    long x = 2, y = 4;
    long result = calculator.calculate(x,y);
    System.out.println(result);
  }
}

@FunctionalInterface
interface IntCalculator{
  int calculate(int x, int y);
}

@FunctionalInterface
interface LongCalculator{
  long calculate(long x, long y);
}

方法引用

一个Lambda表达式表示一个在函数接口中定义的匿名函数,方法引用使用现有方法创建一个Lambda表达式。方法引用的一般语法是 Qualifier::MethodName 两个连续的冒号作为分隔符。MethodName是方法的名称,Qualifier告诉在哪里可以找到方法的引用。方法参考有六种类型。

  • TypeName::staticMethod:类、接口或枚举的静态方法的引用。
  • objectRef::instanceMethod:对实例方法的引用
  • ClassName::instanceMethod:引用类的实例方法
  • TypeName.super::instanceMethod:从对象的超类型中引用一个实例方法。
  • ClassName::new:类的构造函数的引用
  • ArrayTypeName::new:引用指定数组类型的构造函数。

Static Method References

静态方法引用允许我们使用静态方法作为lambda表达式。静态方法可以定义在一个类、一个接口或一个枚举中。下面的代码定义了两个lambda表达式。第一个lambda表达式func1是通过定义一个输入参数x并提供lambda表达式body来创建的。基本上这是创建lambda表达式的正常方式。第二个lambda表达式func2是通过引用Integer类的静态方法创建的。

import java.util.function.Function;

public class Main {
  public static void main(String[] argv) {
    // Using  a  lambda  expression
    Function<Integer, String> func1  = x -> Integer.toBinaryString(x);
    System.out.println(func1.apply(10));

    // Using  a  method  reference
    Function<Integer, String> func2  = Integer::toBinaryString;
    System.out.println(func2.apply(10));
  }
}

我们可以在静态方法引用中使用重载静态方法。在重载方法的时候我们要多注意方法的签名和对应的功能接口。在下面的列表中,我们有三个版本的来自Integer类的valueOf()

static Integer valueOf(int i)
static Integer valueOf(String s)
static Integer valueOf(String s, int radix)

下面的代码显示了不同的目标功能接口如何与重载的Integer.valueOf()静态方法一起使用。

import java.util.function.BiFunction;
import java.util.function.Function;
public class Main{

  public static void main(String[] argv){
    // Uses  Integer.valueOf(int)
    Function<Integer, Integer> func1  = Integer::valueOf;

    // Uses  Integer.valueOf(String)
    Function<String, Integer> func2  = Integer::valueOf;

    // Uses  Integer.valueOf(String, int)
    BiFunction<String, Integer,  Integer> func3  = Integer::valueOf;

    System.out.println(func1.apply(7));
    System.out.println(func2.apply("7"));
    System.out.println(func3.apply("101010101010", 2));
  }
}

实例方法引用

我们可以通过两种方式获取实例方法引用,从对象实例或从类名中获取。基本上我们有以下两种形式:instance::MethodName, ClassName::MethodName。这里的instance代表任何对象实例。ClassName是类的名称,如String、Integer。instanceClassName被称为接收器。更具体地说,instance 被称为有界接受者,而ClassName被称为无界接受者,我们称 instance 为有界接受者,因为接受者与 instance 是有界的,而ClassName为无界接受者,因为接受者是后来才有界的。

Bound Instance Method Reference

在他们下面的代码中,我们使用构建系统功能接口Supplier作为lambda表达式类型。首先我们以正常的方式定义一个lambda表达式。lambda表达式不接受任何参数,并返回一个字符串的长度,然后我们创建一个String实例,并使用其length方法作为实例方法引用。Bound意味着我们已经指定了实例。下面的例子展示了如何使用绑定接收器和无参数的方法来创建实例方法引用。

import java.util.function.Supplier;

public class Main{
  public static void main(String[] argv){
    Supplier<Integer> supplier  = () ->  "test".length();
    System.out.println(supplier.get());


    Supplier<Integer> supplier1  = "test"::length;
    System.out.println(supplier1.get());
  }
}

下面的例子展示了如何使用绑定接收器和带参数的方法来创建实例方法引用。

import java.util.function.Consumer;

public class Main{
  public static void main(String[] argv){
    Util util = new Util();

    Consumer<String> consumer  = str ->  util.print(str);
    consumer.accept("Hello");


    Consumer<String> consumer1  = util::print;
    consumer1.accept("test");

    util.debug();
  }
}

class Util{
  private int count=0;
  public void print(String s){
    System.out.println(s);
    count++;
  }
  public void debug(){
    System.out.println("count:" + count);
  }
}

Unbound Instance Method Reference

一个未绑定的接收器使用以下语法:ClassName::instanceMethod,这与我们用来引用静态方法的语法相同。从下面的代码中我们可以看到,输入类型是ClassName的类型。在下面的代码中,我们使用了 String:length,所以功能接口的输入类型是StringLambda表达式在使用的时候,得到的是输入。下面的代码使用String length方法作为unbind实例方法引用。String length方法通常在字符串值实例上调用,并返回字符串实例的长度。因此输入是String类型,输出是int类型,这与Buildin Function功能接口相匹配。我们每次调用strLengthFunc都会传入一个字符串值,长度方法就会从传入的字符串值中调用。

import java.util.function.Function;

public class Main{
  public static void main(String[] argv){
    Function<String,  Integer> strLengthFunc = String::length;
    String name ="test";
    int len = strLengthFunc.apply(name);
    System.out.println("name  = "  +  name + ", length = "  + len);

    name ="testtest";
    len = strLengthFunc.apply(name);
    System.out.println("name  = "  +  name + ", length = "  + len);

  }
}

下面的代码定义了一个带有静态方法append的类Utilappend方法接受两个String类型的参数,并返回一个String类型的结果,然后使用append方法创建一个lambda表达式,并分配给Java buildin BiFunction函数接口。然后append方法被用来创建一个lambda表达式,并分配给Java buildin BiFunction函数接口。append方法的签名与BiFunction功能接口中定义的抽象方法的签名一致。

import java.util.function.BiFunction;

public class Main{
  public static void main(String[] argv){
    BiFunction<String, String, String> strFunc = Util::append;
    String name = "test";
    String s = strFunc.apply(name, "hi");
    System.out.println(s);
  }
}
class Util{
  public static String append(String s1,String s2){
    return s1+s2;
  }
}

Supertype Instance Method References

关键字super只在实例上下文中使用,它引用的是被覆盖的方法。我们可以使用下面的语法来创建一个方法引用,引用父类型中的实例方法:ClassName.super::instanceMethod。下面的代码定义了一个名为ParentUtil的父类。在ParentUtil中,有一个名为append的方法,它将两个String值追加在一起。

然后创建了一个名为Util的子类并扩展了ParentUtil。在Util类中,append方法被覆盖。在Util的构造函数中,我们创建了两个lambda表达式,一个是使用Utilappend方法,另一个是使用ParentUtil类的append方法。我们使用this::append来引用当前类,而使用Util.super::append来引用父类的方法。

import java.util.function.BiFunction;

public class Main{
  public static void main(String[] argv){
    new Util();
  }
}

class Util extends ParentUtil{

  public Util(){
    BiFunction<String,  String,String> strFunc = this::append;
    String name = "test";
    String s = strFunc.apply(name," hi");
    System.out.println(s);

    strFunc = Util.super::append;
    name ="test";
    s = strFunc.apply(name," Java Lambda Tutorial");
    System.out.println(s);

  }

  @Override
  public String append(String s1,String s2){
    System.out.println("child append");
    return s1+s2;
  }
}
class ParentUtil{
  public String append(String s1,String s2){
    System.out.println("parent append");
    return s1+s2;
  }
}

Constructor Reference

我们可以使用构造函数来创建一个Lambda表达式。使用构造函数引用的语法是:ClassName::new。关键字new指的是类的构造函数。编译器根据上下文选择构造函数。

import java.util.function.Function;
import java.util.function.Supplier;

public class Main{
  public static void main(String[] argv){
    Supplier<String> func1  = () ->  new String();
    System.out.println("Empty String:"+func1.get());

    Function<String,String> func2  = str ->  new String(str);

    System.out.println(func2.apply("test"));

    Supplier<String> func3  = String::new;
    System.out.println("Empty String:" + func3.get());

    Function<String,String> func4  = String::new;
    System.out.println(func4.apply("test"));
  }
}

Array Constructor References

我们可以使用数组构造函数创建一个数组,如下所示:ArrayTypeName::new。int[]::new是调用new int[]new int[]需要一个int类型的值作为数组长度,因此int[]:new需要一个int类型的输入值。下面的代码使用数组构造函数引用来创建一个int数组。

import java.util.Arrays;
import java.util.function.IntFunction;

public class Main{
  public static void main(String[] argv){
    IntFunction<int[]> arrayCreator1 = size ->  new int[size];
    // Creates an  int array of  five  elements
    int[] intArray1  = arrayCreator1.apply(5);
    System.out.println(Arrays.toString(intArray1));

    IntFunction<int[]> arrayCreator2 = int[]::new;
    int[] intArray2 = arrayCreator2.apply(5);
    System.out.println(Arrays.toString(intArray2));
  }
}

通过使用 Function<Integer,ArrayType>,我们可以在声明中指定数组类型。

import java.util.Arrays;
import java.util.function.Function;

public class Main{
  public static void main(String[] argv){
    Function<Integer, int[]> arrayCreator3 = int[]::new;
    int[] intArray = arrayCreator3.apply(5);
    System.out.println(Arrays.toString(intArray));
  }
}

在创建二维数组时,我们可以指定第一维的长度。

import java.util.Arrays;
import java.util.function.IntFunction;

public class Main{
  public static void main(String[] argv){
    IntFunction<int[][]> TwoDimArrayCreator  = int[][]::new;
    int[][] intArray = TwoDimArrayCreator.apply(5);
    // Creates an  int[5][]  array
    intArray[0] = new int[5];
    intArray[1] = new int[5];
    intArray[2] = new int[5];
    intArray[3] = new int[5];
    intArray[4] = new int[5];

    System.out.println(Arrays.deepToString(intArray));
  }
}

Generic Method Reference

我们可以在方法引用中通过指定实际类型参数来使用通用方法。其语法如下:ClassName::<TypeName>methodName,通用构造函数引用的语法:ClassName<TypeName>::new

import java.util.Arrays;
import java.util.List;
import java.util.function.Function;

public class Main{
  public static void main(String[] argv){
    Function<String[],List<String>> asList = Arrays::<String>asList;

    System.out.println(asList.apply(new String[]{"a","b","c"}));
  }
}
下一页