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表达式时,需要注意以下几点:
Lambda表达式只能用于函数式接口,即只有一个抽象方法的接口。如果接口中有多个抽象方法,编译器会报错。
Lambda表达式的参数类型可以省略,编译器可以根据上下文自动推断出参数类型。
如果Lambda表达式的方法体只有一行代码,可以省略大括号和return关键字。
Lambda表达式可以访问外部的局部变量,但是这些变量必须是final或者是事实上的final(即不可修改的)。
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 中,有四大核心的函数式接口,它们分别是:
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!");
}
}
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());
}
}
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));
}
}
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表达式的一种特殊形式。
方法引用的语法如下:
对象::方法名
或
类名::静态方法名
或
类名::实例方法名
方法引用可以分为以下几种情况:
静态方法引用:引用静态方法。
实例方法引用:引用实例方法。
构造方法引用:引用构造方法。
下面是一些示例代码,展示了不同类型的方法引用的使用:
静态方法引用
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类的静态方法valueOf。valueOf方法接收一个整数参数并返回一个字符串。通过静态方法引用,我们可以直接使用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类的实例方法equals。equals方法接收一个字符串参数并返回一个布尔值。通过实例方法引用,我们可以直接使用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)方法,我们可以获取到所有的重复注解,并逐个输出水果的名称。
通过重复注解,我们可以更方便地对同一个元素进行多次注解,使得代码更加清晰和简洁。
Comments