Java8特性 - Lambda表达式

160 阅读6分钟

函数式接口

在Java中,函数式接口(Functional Interface)是只包含一个抽象方法的接口。这个概念是Java 8引入的,用于支持Lambda表达式和方法引用。函数式接口也可以有多个默认方法(default methods)和静态方法(static methods),但只能有一个抽象方法。 所以先把这块内容放在了前面。

定义函数式接口

函数式接口使用@FunctionalInterface注解进行标注,这个注解不是必须的,但建议使用它来表明该接口是一个函数式接口。如果接口中有多个抽象方法,编译器会报错。

@FunctionalInterface
public interface MyFunctionalInterface {
    void doSomething();  // 抽象方法

    // 可以有默认方法
    default void defaultMethod() {
        System.out.println("This is a default method");
    }

    // 可以有静态方法
    static void staticMethod() {
        System.out.println("This is a static method");
    }
}

函数式接口的出现是为了配合lambda一起使用的,常见的用法是lambda表达式实现函数式接口: 假设有一个函数式接口MyFunctionalInterface,它有一个抽象方法doSomething

@FunctionalInterface
interface MyFunctionalInterface {
    void doSomething();
}

可以使用Lambda表达式来实现这个接口:

MyFunctionalInterface myFunc = () -> System.out.println("Doing something!");
myFunc.doSomething();  // 输出: Doing something!

将上面内容合并一起:

public class Main {
    public static void main(String[] args) {
        MyFunctionalInterface myFunc = () -> System.out.println("Doing something!");
        myFunc.doSomething();  // 输出: Doing something!

        myFunc.defaultMethod();  // 输出: This is a default method
        MyFunctionalInterface.staticMethod();  // 输出: This is a static method
    }
}

标准函数式接口

Java 8还提供了一些标准的函数式接口,例如Function<T, R>Predicate<T>Consumer<T>Supplier<T>。这些接口都在java.util.function包中定义。

例子:Function<T, R>

import java.util.function.Function;

public class Main {
    public static void main(String[] args) {
        Function<String, Integer> stringLength = (str) -> str.length();
        int length = stringLength.apply("Hello, World!");  // length是13
        System.out.println(length);
    }
}

总结

  1. 函数式接口必须只有一个抽象方法。
  2. 函数式接口可以有任意数量的默认方法和静态方法。
  3. 使用@FunctionalInterface注解来标注函数式接口,以确保接口的合法性。

通过这些特性,Java的函数式接口提供了一种简洁而强大的方式来支持Lambda表达式和方法引用,使得代码更加简洁和易读。

Lambda表达式

Java 8引入了Lambda表达式(Lambda Expressions),和他相关的有函数式表达式(Functional Expression)。Lambda表达式是一个可以作为参数传递给方法存储在变量中的匿名函数(没有名称的方法)。使代码更加简洁、易读,特别配合使用函数式接口(Functional Interface)时。

Lambda表达式的语法

Lambda表达式的基本语法如下:

(parameters) -> expression

(parameters) -> { statements; }

带参数的Lambda表达式

Lambda表达式也可以接受参数:

@FunctionalInterface
interface MyFunctionalInterface {
    void doSomething(String message);
}

MyFunctionalInterface myFunc = (message) -> System.out.println(message);
myFunc.doSomething("Hello, Lambda!");  // 输出: Hello, Lambda!

返回值的Lambda表达式

如果Lambda表达式有返回值,可以这样写:

@FunctionalInterface
interface MyFunctionalInterface {
    int add(int a, int b);
}

MyFunctionalInterface addFunction = (a, b) -> a + b;
int result = addFunction.add(5, 3);  // result是8

Lambda表达式不仅极大地简化了代码(特别是在处理集合并发编程),而且是函数式编程(Java 8引入)的核心。

方法引用

在Java中,方法引用可以作为Lambda表达式的简洁替代方案,特别是在特定情况下,它可以使代码更具可读性、简洁性。方法引用有四种主要类型,每种类型都有其适用的情境:

  1. 引用静态方法
  2. 引用实例方法
  3. 引用特定对象的实例方法
  4. 引用构造方法

1. 引用静态方法

当Lambda表达式只是调用一个静态方法时,可以使用方法引用。

import java.util.function.Function;

public class Main {
    public static void main(String[] args) {
        // 使用Lambda表达式
        Function<String, Integer> lambda = str -> Integer.parseInt(str);

        // 使用方法引用
        Function<String, Integer> methodRef = Integer::parseInt;

        // 调用
        System.out.println(lambda.apply("123"));  // 输出: 123
        System.out.println(methodRef.apply("456"));  // 输出: 456
    }
}

2. 引用实例方法

当Lambda表达式只是调用一个对象的实例方法时,可以使用方法引用。

import java.util.function.Predicate;

public class Main {
    public static void main(String[] args) {
        String str = "Hello";

        // 使用Lambda表达式
        Predicate<String> lambda = s -> str.equals(s);

        // 使用方法引用
        Predicate<String> methodRef = str::equals;

        // 调用
        System.out.println(lambda.test("Hello"));  // 输出: true
        System.out.println(methodRef.test("World"));  // 输出: false
    }
}

3. 引用特定对象的实例方法

当Lambda表达式只是调用一个特定对象的实例方法时,可以使用方法引用。

import java.util.function.Function;

public class Main {
    public static void main(String[] args) {
        Function<String, Integer> lambda = String::length;

        // 使用方法引用
        Function<String, Integer> methodRef = String::length;

        // 调用
        System.out.println(lambda.apply("Hello"));  // 输出: 5
        System.out.println(methodRef.apply("World"));  // 输出: 5
    }
}

4. 引用构造方法

当Lambda表达式只是调用一个构造方法时,可以使用方法引用。

import java.util.function.Supplier;

public class Main {
    public static void main(String[] args) {
        // 使用Lambda表达式
        Supplier<StringBuilder> lambda = () -> new StringBuilder();

        // 使用方法引用
        Supplier<StringBuilder> methodRef = StringBuilder::new;

        // 调用
        System.out.println(lambda.get().append("Hello"));  // 输出: Hello
        System.out.println(methodRef.get().append("World"));  // 输出: World
    }
}

总结

方法引用可以在以下情况下替代Lambda表达式:

  • 引用静态方法时:ClassName::methodName
  • 引用实例方法时:instance::methodName
  • 引用特定类型实例方法时:ClassName::methodName
  • 引用构造方法时:ClassName::new

这些方法引用可以使代码更简洁,更具可读性,但前提是它们适用于具体的上下文和目标接口的参数类型。

方法引用和Lambda对比

在Java中,方法引用(::)和普通的点操作符(.)都可以用来调用方法,但它们有不同的使用场景和优势。方法引用通常与函数式编程和Lambda表达式一起使用,能够使代码更简洁和可读。以下是方法引用相对于普通点操作符的一些优势:

1. 简洁性和可读性

方法引用可以减少样板代码,使代码更简洁和易读。它消除了Lambda表达式中重复的方法调用逻辑。 假设有一个列表,需要将每个元素打印出来:

import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("a", "b", "c");

        // 使用Lambda表达式
        list.forEach(s -> System.out.println(s));

        // 使用方法引用
        list.forEach(System.out::println);
    }
}

使用方法引用后,代码变得更简洁、易读。

2. 避免重复代码

方法引用避免了在Lambda表达式中重复编写相同的方法调用逻辑。

import java.util.function.Function;

public class Main {
    public static void main(String[] args) {
        // 使用Lambda表达式
        Function<String, Integer> lambda = str -> Integer.parseInt(str);

        // 使用方法引用
        Function<String, Integer> methodRef = Integer::parseInt;

        // 调用
        System.out.println(lambda.apply("123"));  // 输出: 123
        System.out.println(methodRef.apply("456"));  // 输出: 456
    }
}

方法引用消除了Lambda表达式中Integer.parseInt的重复调用。

3. 提高代码复用性

方法引用可以直接引用已有的方法,从而提高代码的复用性,而不需要在Lambda表达式中重新定义方法逻辑。 假设你有一个处理字符串的静态方法:

import java.util.function.Function;

public class StringUtils {
    public static int getLength(String str) {
        return str.length();
    }
}

public class Main {
    public static void main(String[] args) {
        // 使用Lambda表达式
        Function<String, Integer> lambda = str -> StringUtils.getLength(str);

        // 使用方法引用
        Function<String, Integer> methodRef = StringUtils::getLength;

        // 调用
        System.out.println(lambda.apply("Hello"));  // 输出: 5
        System.out.println(methodRef.apply("World"));  // 输出: 5
    }
}

4. 函数式编程的支持

方法引用与函数式接口一起使用时,非常方便,可以直接传递方法引用作为参数,而不需要创建新的Lambda表达式。

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

public class Main {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("a", "b", "c");

        // 使用Lambda表达式
        Consumer<String> lambda = s -> System.out.println(s);

        // 使用方法引用
        Consumer<String> methodRef = System.out::println;

        // 调用
        list.forEach(lambda);
        list.forEach(methodRef);
    }
}

总结

  1. 简洁性和可读性:减少样板代码,使代码更简洁。
  2. 避免重复代码:消除Lambda表达式中重复的方法调用逻辑。
  3. 提高代码复用性:可以直接引用已有的方法,提高代码复用性。
  4. 支持函数式编程:与函数式接口一起使用时,方便且高效。

尽管方法引用有这些优势,但它仅在适用的上下文中使用。当方法引用无法表达Lambda表达式的复杂逻辑时,Lambda表达式仍然是较好的选择。