Java8特性详解(二):lambda表达式与方法引用


通过行为参数化传递代码有助于应对不断变化的需求,而lambda表达式和方法引用便是Java8对行为参数化的新实践。Lambda表达式和方法引用不仅实践了行为参数化,也极大地简化了代码。

1 Lambda表达式

1.1 Lambda特性

Lambda表达式,是表示可传递的匿名函数的一种方式,具有这样几个特点:

  • 没有名称。
  • Lambda函数,不属于某个特定类(方法归属于特定类),有着参数列表、函数主体、返回类型以及异常列表。
  • Lambda表达式可以作为参数传递给方法或者赋值给某个变量。
  • 整个表达式就是返回值。

1.2 Lambda基本语法

Lambda表达式包含三部分:参数列表、箭头、Lambda主体。参数列表,即函数式接口方法的参数,Lambda主体是Lambda的返回值,箭头仅用于隔断两部分。

参数列表 --> Lambda主体
(parameters) -> expression

例
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
// 没有参数
() -> 42;
// Lambda主体有多条,并且有返回值情况
(Apple a1, Apple a2) -> { 
    return a1.getWeight().compareTo(a2.getWeight());
};

2 使用场景 – 函数式接口

2.1 函数式接口

只有在需要函数式接口的时候才可以传递Lambda。函数式接口是只定义了一个抽象方法的接口。例如:

public interface Runnable
public interface Comparator<T>
public interface Callable<V>
public interface FileFilter

需要注意的点:

  • 接口中可以有默认方法,默认方法的存在不会影响函数式接口。
  • 如果接口声明了一个抽象方法,用于覆盖Object类的public方法,该方法也不计入到函数式接口的抽象方法数中。Comparator接口中有两个抽象方法,但该接口仍然是函数式接口。
@FunctionalInterface   // 用于标注当前接口为函数式接口
public interface Comparator<T> {
    int compare(T o1, T o2);
    boolean equals(Object obj); // 覆盖Object的equals方法
}

Lambda是函数式接口的一个具体实现,它以内联的形式为接口的抽象方法提供的实现,并把整个Lambda表达式作为该接口的一个实例。因此Lambda表达式可以赋值给变量,也可以作为参数传递给方法。

2.2 Java8引入新的函数式接口

抽象方法的签名可以描述Lambda表达式的签名。函数式接口的抽象方法的签名称为函数描述符。
Java 8在java.util.function包引入了几类函数式接口。例如

函数式接口 函数描述符 注释 可以使用的场景
Predicate T -> boolean 定义test方法,接受泛型T对象,并返回一个boolean。 布尔表达式
BiPredicate<L,R> (L,R) -> boolean 定义test方法,接受泛型T和U类型对象,返回boolean
Consumer T -> void 定义accept方法,接受泛型T的对象,没有返回(void)。 消费对象
BiConsumer<T,U (T,U) -> void 定义accept方法,接受泛型T和U类型对象,没有返回(void)
Function<T,R> T -> R 定义apply方法,它接受一个泛型T的对象,并返回一个泛型R的对象。 从对象中选择或提取
BiFunction<T,U,R> (T,U) -> R 定义apply方法,它接受一个泛型T和U类型对象,并返回一个泛型R的对象。 比较对象
Supplier () -> T 定义get方法,没有输入,返回泛型T类型对象 创建新对象
UnaryOperator T -> T 继承于Function接口 合并值
BinaryOperator (T,T) -> T 继承于BiFunction接口

函数式接口通常会采用泛型,然而泛型又只能绑定到引用类型。对于基本数据类型,Java通过自动装箱机制转换成引用类型。装箱过程的本质是将原始类型包裹起来,保存着堆内存中,因此装箱操作消耗部分内存。为了避免输入和输出为原始类型时的自动装箱操作,java.util.function包提供了很多函数式接口的特殊版本,例如IntPredicate接口和ToIntFunction接口。

// 入参是int类型,出参是泛型
public interface IntFunction<R> {
    R apply(int value);
}
// 入参是泛型,出参是int类型
public interface ToIntFunction<T> {
    int applyAsInt(T value);
}

3 类型推断与类型检查

3.1 类型推断

Lambda表达式可以作为传递给方法的参数,也可赋值给某个变量。类型推断,是指通过这些上下文推断出Lambda表达式的类型。以上面的代码为例,分析类型判断

  1. 从filterApples方法调用找到方法定义。
  2. 定义中第二个参数为Predicate类型–目标类型。
  3. Predicate是函数式接口,只包含一个test抽象方法,接受T类型返回布尔值,其函数描述符是 T -> boolean。
  4. Lambda表达式与函数描述符匹配。
// 方法调用
List<Apple> heavyApples = filterApples(inventory, (Apple apple) -> "green".equals(apple.color) && apple.getWeight() > 150);

// Apple类方法定义
public static List<Apple> filterApples(List<Apple> inventory, Predicate<Apple> predicate) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : inventory) {
        if (predicate.test(apple)) {
            result.add(apple);
        }
    }
    return result;
}

// java.util.function包接口
public interface Predicate<T> {
    boolean test(T var1);
}

需要注意的有

  • void兼容规则。函数式接口返回类型兼容void类型,仍然要求参数列表兼容。
// Predicate返回boolean,但也兼容void
Predicate<String> p = s -> list.add(s);
  • Object类虽然是超级类,但不是函数式接口,不兼容Lambda表达式。

3.2 Lambda签名类型推断

如上,编译器能从Lambda表达式上下文推断出函数式接口,也可以推断出Lambda签名,即函数式接口抽象方法的签名。
改造上面的方法调用如下形式

List<Apple> heavyApples = filterApples(inventory, apple) -> "green".equals(apple.color) && apple.getWeight() > 150);
  • 参数列表中只有一个参数时,可以省略括号。

4 Lamda使用的限制

4.1 变量限制

Lambda可以没有限制地捕获(其主体中引用)实例变量和静态变量。但局部变量必须显式声明为final,或事实上是final(只被赋值过一次)。

实例变量和局部变量背后的实现有一个关键不同。实例变量都存储在堆中,而局部变量则保存在栈上。Java8的Lambda和匿名类可以做类似于闭包的事情:它们可以作为参数传递给方法,并且可以访问其作用域之外的变量。但有一个限制:它们不能修改定义Lambda的方法的局部变量的内容。这些变量必须是隐式最终的。可以认为Lambda是对值封闭,而不是对变量封闭。这种限制存在的原因在于局部变量保存在栈上,并且隐式表示它们仅限于其所在线程。如果允许捕获可改变的局部变量,就会引发造成线程不安全的新的可能性。

4.2 this用法

此处主要区分this在Lambda和匿名内部类的区别。在Lambda中,this指向Lamba所在的外部类,而匿名内部类中this指的是匿名内部类当前对象。

5 方法引用

方法引用让你可以重复使用现有的方法定义。方法引用可以被看作仅仅调用特定方法的Lambda的一种快捷写法,让你根据已有的方法实现来创建Lambda表达式。

目标引用::方法名称

方法引用一共有四种。

  • 类::实例方法
@Test
public void testClassMethod() {
    File[] hiddenFilesInnerClass = new File(".").listFiles(new FileFilter() {
        public boolean accept(File file) {
            return file.isHidden();
        }
    });
    File[] hiddenFilesLambda = new File(".").listFiles(file -> file.isHidden());
    File[] hiddenFilesMethodRef = new File(".").listFiles(File::isHidden);
}
  • 类::静态方法
@Test
public void testStaticMethod() {
    IntegerFunction function = Integer::parseInt;
    System.out.println(method(function, "12"));
}

public Integer method(IntegerFunction integerFunction, String str) {
    return integerFunction.apply(str);
}

public interface IntegerFunction {
    Integer apply(String str);
}
  • 实例引用::实例方法
@Test
public void testReferenceMethod(){
    // 创建一个实例
    MethodReferenceTest mrt = new MethodReferenceTest();
    System.out.println(method(mrt::parseInt,"42"));
}

public Integer parseInt(String str) {
    return new Integer(42);
}
  • 构造方法引用–类名:new
@Test
public void testConstructMethod(){
    Supplier<Student> supplier = Student::new;
    Function<String,Student> function = Student::new;
    Student student = function.apply("lzp");
}

public class Student{
    private String name;

    public Student() {
        this.name = "lzp";
    }

    public Student(String name) {
        this.name = name;
    }
    /*省略getter/setter*/
}

使用构造函数引用时,需要有与构造方法签名匹配的函数接口,如果没有现成的接口,需要自定义一个。

6 实践案例

定义Student类

public class Student {
    private String name;
    private Integer height;

    public Student(String name, Integer height) {
        this.name = name;
        this.height = height;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getHeight() {
        return height;
    }

    public void setHeight(Integer height) {
        this.height = height;
    }
}

优化过程

public class StudentSortDemo {

    public static void main(String[] args) {
        List<Student> students = Arrays.asList(new Student[]{new Student("Ming", 120), new Student("Ping", 160)});
        // 匿名内部类
        students.sort(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o1.getHeight().compareTo(o2.getHeight());
            }
        });
        // Lambda
        students.sort((o1, o2) -> o1.getHeight().compareTo(o2.getHeight()));
        // 使用Comparator静态辅助方法与Lambda
        students.sort(Comparator.comparing(o -> o.getHeight()));
        // 使用Comparator静态辅助方法与方法引用
        students.sort(Comparator.comparing(Student::getHeight));
    }
}
发布了72 篇原创文章 · 获赞 110 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/LIZHONGPING00/article/details/103691118