lambda

Lambda表达式的参数和返回值均可由编译器自动推断。

Lambda 表达式是 Java 8 中引入的一种新特性,它可以使代码更加简洁和易读。Lambda 表达式本质上是一个匿名函数,它可以被当做参数传递给方法或存储在变量中。

lambda表达式重写的必须是函数式接口(或只有一个方法的抽象类)

Lambda表达式

在Java程序中,我们经常遇到一大堆单方法接口,即一个接口只定义了一个方法:

  • Comparator

  • Runnable

  • Callable

Comparator为例,我们想要调用Arrays.sort()时,可以传入一个Comparator实例,以匿名类方式编写如下:

String[] array = ...
Arrays.sort(array, new Comparator<String>() {
    public int compare(String s1, String s2) {
        return s1.compareTo(s2);
    }
});

上述写法非常繁琐。从Java 8开始,我们可以用Lambda表达式替换单方法接口。改写上述代码如下:

// Lambda
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
        Arrays.sort(array, (s1, s2) -> {
            return s1.compareTo(s2);
        });
        System.out.println(String.join(", ", array));
    }
}

观察Lambda表达式的写法,它只需要写出方法定义:

(s1, s2) -> {
    return s1.compareTo(s2);
}

其中,参数是(s1, s2),参数类型可以省略,因为编译器可以自动推断出String类型。-> { ... }表示方法体,所有代码写在内部即可。Lambda表达式没有class定义,因此写法非常简洁。

如果只有一行return xxx的代码,完全可以用更简单的写法:

Arrays.sort(array, (s1, s2) -> s1.compareTo(s2));

返回值的类型也是由编译器自动推断的,这里推断出的返回值是int,因此,只要返回int,编译器就不会报错。

使用方式

方式一:

函数式接口 应用名 = lambda表达式

Lambda表达式可以用于函数式接口的实例化。函数式接口是指只有一个抽象方法的接口。我们可以使用Lambda表达式来实现这个抽象方法。

下面是一个使用Lambda表达式的示例:

// 定义一个函数式接口
interface MyInterface {
    void doSomething();
}

public class Main {
    public static void main(String[] args) {
        // 使用Lambda表达式实例化函数式接口
        MyInterface myInterface = () -> {
            System.out.println("Doing something...");
        };

        // 调用函数式接口的方法
        myInterface.doSomething();
    }
}

在上面的示例中,我们定义了一个函数式接口MyInterface,它只有一个抽象方法doSomething()。然后,我们使用Lambda表达式实例化了这个函数式接口。Lambda表达式( ) -> { System.out.println("Doing something..."); }表示实现了doSomething()方法的代码块。最后,我们调用了函数式接口的方法doSomething()

Lambda表达式的语法是(参数列表) -> { 方法体 }。在这个示例中,Lambda表达式没有参数,所以参数列表为空()。方法体是一个代码块{ },里面包含了要执行的代码。

使用Lambda表达式可以简化代码,使代码更加简洁和易读。

方式二:

将lambda表达式所代表的函数式接口,作为一个方法的参数存在

在Java中,lambda表达式可以用来表示函数式接口(Functional Interface)的实例。函数式接口是只包含一个抽象方法的接口。我们可以将lambda表达式作为一个方法的参数,以便在方法中使用该函数式接口的实例。

下面是一个示例代码,展示了如何将lambda表达式作为方法参数:

// 定义一个函数式接口
interface MyFunction {
    void doSomething();
}

// 接受函数式接口作为参数的方法
public static void execute(MyFunction function) {
    function.doSomething();
}

public static void main(String[] args) {
    // 使用lambda表达式作为方法参数
    execute(() -> System.out.println("Hello, World!"));
}

在上面的示例中,我们首先定义了一个函数式接口MyFunction,它只包含一个抽象方法doSomething()。然后,我们定义了一个方法execute(),它接受一个MyFunction类型的参数。在main()方法中,我们使用lambda表达式() -> System.out.println("Hello, World!")作为execute()方法的参数,该lambda表达式实现了MyFunction接口的抽象方法。

当我们调用execute()方法时,lambda表达式中的代码将被执行,输出"Hello, World!"。

通过将lambda表达式作为方法参数,我们可以更灵活地传递行为,实现更加通用和可复用的代码。

注意点

在使用Lambda表达式时,需要注意以下几点:

  1. Lambda表达式只能用于函数式接口,即只有一个抽象方法的接口。如果接口中有多个抽象方法,编译器会报错。

  2. Lambda表达式的参数类型可以省略,编译器可以根据上下文自动推断出参数类型。

  3. 如果Lambda表达式的方法体只有一行代码,可以省略大括号和return关键字。

  4. Lambda表达式可以访问外部的局部变量,但是这些变量必须是final或者是事实上的final(即不可修改的)。

  5. Lambda表达式可以作为方法的参数或者返回值,可以存储在变量中。

函数式接口

函数式接口

函数式接口是只包含一个抽象方法的接口。在Java中,函数式接口可以使用@FunctionalInterface注解来明确标识。函数式接口可以被Lambda表达式所实现。

下面是一个函数式接口的示例:

@FunctionalInterface
interface MathOperation {
    int operate(int a, int b);
}

在上面的示例中,MathOperation是一个函数式接口,它定义了一个抽象方法operate,接收两个整数参数并返回一个整数结果。

接下来,我们可以使用Lambda表达式来实现这个函数式接口:

public class Main {
    public static void main(String[] args) {
        MathOperation addition = (a, b) -> a + b;
        MathOperation subtraction = (a, b) -> a - b;

        System.out.println("10 + 5 = " + operate(10, 5, addition));
        System.out.println("10 - 5 = " + operate(10, 5, subtraction));
    }

    private static int operate(int a, int b, MathOperation mathOperation) {
        return mathOperation.operate(a, b);
    }
}

在上面的示例中,我们定义了两个Lambda表达式分别实现了MathOperation接口,分别表示加法和减法操作。然后通过operate方法来执行这些操作并输出结果。

通过函数式接口和Lambda表达式的结合,我们可以更加简洁地实现各种功能,提高代码的可读性和易维护性。

注意点:

  • 即使没有标注@Functionallnterface,但是只有一个抽象方法,也称之为函数式接口

  • 特殊情况:如果某个接口中有多个抽象方法,但只有一个抽象方法是本接口新定义的,其他抽象方法是本接口新新定义的,其他抽象方法和object中已有的方法重复,那么该接口任然是函数式接口

四大函数式接口

在 Java 中,有四大核心的函数式接口,它们分别是:

  1. Consumer:接收一个参数,无返回值。

import java.util.function.Consumer;

public class Main {
    public static void main(String[] args) {
        Consumer<String> consumer = (str) -> System.out.println(str);
        consumer.accept("Hello, World!");
    }
}
  1. Supplier:无参数,有返回值。

import java.util.function.Supplier;

public class Main {
    public static void main(String[] args) {
        Supplier<String> supplier = () -> "Hello, World!";
        System.out.println(supplier.get());
    }
}
  1. Function:接收一个参数,有返回值。

import java.util.function.Function;

public class Main {
    public static void main(String[] args) {
        Function<Integer, String> function = (num) -> "The number is: " + num;
        System.out.println(function.apply(42));
    }
}
  1. Predicate:接收一个参数,返回 boolean 值。

import java.util.function.Predicate;

public class Main {
    public static void main(String[] args) {
        Predicate<Integer> predicate = (num) -> num > 0;
        System.out.println(predicate.test(10)); // Output: true
    }
}

通过使用这四大函数式接口,我们可以更加灵活地处理不同的场景,简化代码逻辑,提高代码的可读性和可维护性。

接口的默认方法个静态方法

接口的默认方法

在Java 8中,接口可以包含默认方法。默认方法是在接口中定义的具有默认实现的方法。默认方法可以在接口中直接使用,也可以被实现该接口的类重写。

默认方法的定义语法如下:

public interface MyInterface {
    // 抽象方法
    void abstractMethod();

    // 默认方法
    default void defaultMethod() {
        // 默认实现
    }
}

在上面的示例中,defaultMethod()是一个默认方法,它有一个默认的实现。实现该接口的类可以直接使用默认方法,也可以选择重写默认方法。

下面是一个示例代码,展示了接口的默认方法的使用:

interface MyInterface {
    default void defaultMethod() {
        System.out.println("This is a default method.");
    }
}

class MyClass implements MyInterface {
    // 重写默认方法
    @Override
    public void defaultMethod() {
        System.out.println("This is a overridden default method.");
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        myClass.defaultMethod(); // Output: This is a overridden default method.
    }
}

在上面的示例中,我们定义了一个接口MyInterface,它包含一个默认方法defaultMethod()。然后,我们创建了一个实现了MyInterface接口的类MyClass,并重写了默认方法defaultMethod()。最后,我们创建了MyClass的实例并调用了defaultMethod()方法。

输出结果为"This is a overridden default method.",说明实现类重写了默认方法。

接口的默认方法可以为接口添加新的功能,而不会破坏已有的实现类。这使得接口的演化更加灵活。

接口的静态方法

在Java 8中,接口还可以包含静态方法。静态方法是在接口中定义的具有静态修饰符的方法。静态方法可以直接通过接口名调用,不需要实例化接口。

静态方法的定义语法如下:

public interface MyInterface {
    // 抽象方法
    void abstractMethod();

    // 默认方法
    default void defaultMethod() {
        // 默认实现
    }

    // 静态方法
    static void staticMethod() {
        // 静态方法实现
    }
}

在上面的示例中,staticMethod()是一个静态方法,它有一个静态的实现。静态方法可以直接通过接口名调用,不需要实例化接口。

下面是一个示例代码,展示了接口的静态方法的使用:

interface MyInterface {
    static void staticMethod() {
        System.out.println("This is a static method.");
    }
}

public class Main {
    public static void main(String[] args) {
        MyInterface.staticMethod(); // Output: This is a static method.
    }
}

在上面的示例中,我们定义了一个接口MyInterface,它包含一个静态方法staticMethod()。然后,我们直接通过接口名调用了静态方法。

输出结果为"This is a static method.",说明静态方法可以直接通过接口名调用。

接口的静态方法可以提供一些通用的功能,不需要实例化接口即可使用。这使得接口的使用更加灵活和方便。

方法引用

方法引用

方法引用是一种简化Lambda表达式的语法,它可以直接引用已经存在的方法。方法引用可以看作是Lambda表达式的一种特殊形式。

方法引用的语法如下:

对象::方法名

类名::静态方法名

类名::实例方法名

方法引用可以分为以下几种情况:

  1. 静态方法引用:引用静态方法。

  2. 实例方法引用:引用实例方法。

  3. 构造方法引用:引用构造方法。

下面是一些示例代码,展示了不同类型的方法引用的使用:

静态方法引用

import java.util.function.Function;

public class Main {
    public static void main(String[] args) {
        Function<Integer, String> function = String::valueOf;
        String result = function.apply(42);
        System.out.println(result); // Output: "42"
    }
}

在上面的示例中,我们使用静态方法引用String::valueOf来引用String类的静态方法valueOfvalueOf方法接收一个整数参数并返回一个字符串。通过静态方法引用,我们可以直接使用valueOf方法,而不需要编写Lambda表达式。

实例方法引用

import java.util.function.BiFunction;

public class Main {
    public static void main(String[] args) {
        BiFunction<String, String, Boolean> function = String::equals;
        boolean result = function.apply("Hello", "Hello");
        System.out.println(result); // Output: true
    }
}

在上面的示例中,我们使用实例方法引用String::equals来引用String类的实例方法equalsequals方法接收一个字符串参数并返回一个布尔值。通过实例方法引用,我们可以直接使用equals方法,而不需要编写Lambda表达式。

构造方法引用

import java.util.function.Supplier;

public class Main {
    public static void main(String[] args) {
        Supplier<String> supplier = String::new;
        String result = supplier.get();
        System.out.println(result); // Output: ""
    }
}

在上面的示例中,我们使用构造方法引用String::new来引用String类的构造方法。通过构造方法引用,我们可以直接创建一个新的字符串对象,而不需要编写Lambda表达式。

通过方法引用,我们可以更加简洁地使用已经存在的方法,提高代码的可读

重复注解

在 Java 8 中,引入了重复注解的功能,允许在同一个元素上多次使用相同的注解。重复注解使得我们可以更灵活地对代码进行注解,避免了繁琐的工作。

示例代码

下面是一个示例代码,展示了如何定义和使用重复注解:

import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

// 定义重复注解的容器注解
@Repeatable(Fruits.class)
@Retention(RetentionPolicy.RUNTIME)
@interface Fruit {
    String name();
}

// 定义重复注解的容器注解
@Retention(RetentionPolicy.RUNTIME)
@interface Fruits {
    Fruit[] value();
}

// 使用重复注解
@Fruit(name = "Apple")
@Fruit(name = "Banana")
public class Main {
    public static void main(String[] args) {
        // 获取重复注解
        Fruit[] fruits = Main.class.getAnnotationsByType(Fruit.class);
        for (Fruit fruit : fruits) {
            System.out.println("Fruit name: " + fruit.name());
        }
    }
}

在上面的示例中,我们首先定义了一个重复注解@Fruit,并指定了一个容器注解@Fruits。注解@Fruit用于标记水果的名称,注解@Fruits用于标记多个水果。

然后,在Main类中,我们使用了重复注解@Fruit来标记多个水果,如苹果和香蕉。通过Main.class.getAnnotationsByType(Fruit.class)方法,我们可以获取到所有的重复注解,并逐个输出水果的名称。

通过重复注解,我们可以更方便地对同一个元素进行多次注解,使得代码更加清晰和简洁。