方法引用是Java 8中一种简化Lambda表达式的方式,通过直接引用现有方法来代替Lambda表达式。
方法引用使得代码更加简洁和易读,特别是在处理函数式接口时,可以替代相对冗长的Lambda表达式,提高了代码的可维护性和可读性。
方法引用通常可以分为以下几种类型:
ClassName::staticMethodName
。objectInstance::instanceMethodName
。ClassName::new
。定义一个函数式接口,定义一个抽象方法 convert
,接收一个类型为 F
的参数,并返回一个类型为 T
的结果。
@FunctionalInterface interface Converter { T convert(F from); }
有一个静态方法 StringHelper
中的静态方法 toUpperCase
。
class StringHelper { public static String toUpperCase(String str) { return str.toUpperCase(); } }
使用静态方法引用来创建一个 Converter
实例。
/** * StringHelper::toUpperCase 就是静态方法引用,它引用了 StringHelper 类的 toUpperCase 方法,该方法的签名与 Converter 接口中的 convert 方法兼容 */ Converter converter = StringHelper::toUpperCase; String convertedStr = converter.convert("hello"); System.out.println(convertedStr); // 输出: HELLO
定义一个类 StringUtils
,有一个实例方法 startsWithIgnoreCase
。
class StringUtils { public boolean startsWithIgnoreCase(String str, String prefix) { // 检查一个字符串是否以另一个字符串开头(忽略大小写) return str.toLowerCase().startsWith(prefix.toLowerCase()); } }
使用实例方法引用来创建一个 BiPredicate
实例:
/** * 方法引用允许我们在 BiPredicate 接口的上下文中使用 startsWithIgnoreCase 方法作为一个函数,第一个参数作为方法的调用者(即 stringUtils 对象),第二个参数作为方法的参数传递 */ StringUtils stringUtils = new StringUtils(); // 使用实例方法引用创建 BiPredicate 实例 BiPredicate startsWithIgnoreCase = stringUtils::startsWithIgnoreCase; // 测试 startsWithIgnoreCase 方法引用的效果,调用 test 方法来检查字符串 "Java" 是否以 "ja" 开头(忽略大小写) boolean result = startsWithIgnoreCase.test("Java", "ja"); System.out.println(result); // 输出: true
定义一个类 Person
,它有一个构造方法 Person(String name, int age)
。
class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } }
使用构造方法引用来创建一个 Supplier
实例。
// 使用构造方法引用创建 Supplier 实例 Supplier personSupplier = Person::new; // 获取 Person 对象 Person person = personSupplier.get(); // 设置 Person 对象的属性 person = new Person("Alice", 30); System.out.println(person.getName() + ", " + person.getAge()); // 输出: Alice, 30
在这里,Person::new
是构造方法引用,它引用了 Person
类的构造方法,根据 Supplier
的函数签名,Java 编译器会自动推断使用哪个构造方法。
是不是感觉构造方法引用有点多余,这样有什么好处吗?为什么不直接new一个构造函数,这样不显得多此一举吗 ?
使用构造方法引用和 Supplier
接口结合起来的好处:
Supplier
实例时,实际的对象并没有立即被创建。Supplier
的 get()
方法时,才会真正创建对象。这种方式可以延迟对象的实例化,直到真正需要使用它时。Supplier
是一个函数式接口,它通常用于表示一个供给型的操作,它不接受任何参数,返回一个指定类型的结果。Java 8 引入了接口的默认方法,这是一种在接口中定义具有默认实现的方法的方式。默认方法允许接口在不破坏现有实现的情况下,向现有的接口添加新的方法。
特点和用途:
interface Vehicle { // 抽象方法 void start(); // 默认方法 default void stop() { System.out.println("Vehicle stopped"); } } class Car implements Vehicle { @Override public void start() { System.out.println("Car started"); } // 可选:覆盖默认方法 @Override public void stop() { System.out.println("Car stopped"); } } public class Main { public static void main(String[] args) { Vehicle car = new Car(); // 输出: Car started car.start(); // 输出: Car stopped car.stop(); } }
一个类实现了多个接口,这些接口中有相同签名的默认方法(包括继承关系),那么编译器会要求显式地覆盖冲突的默认方法,以消除二义性。
冲突主要存在的情况:
interface InterfaceA { default void greet() { System.out.println("Hello from InterfaceA"); } } interface InterfaceB { default void greet() { System.out.println("Hello from InterfaceB"); } } // 实现类实现了多个接口,这些接口中有同名的默认方法 greet() class MyClass implements InterfaceA, InterfaceB { // 需要手动实现 greet() 方法,消除二义性 @Override public void greet() { // 可以选择调用 InterfaceA 的默认实现 InterfaceA.super.greet(); // 也可以自己实现 } } public class Main { public static void main(String[] args) { MyClass obj = new MyClass(); obj.greet(); } } // 输出: Hello from InterfaceA
接口静态方法是Java 8引入的一个重要特性,它们扩展了接口的功能,使得接口可以包含静态的方法实现。这种方法通常用于定义一些通用的工具方法,或者提供与接口实例无关的共享代码。静态方法的引入使得Java接口在语法上更加灵活和功能强大。
特点和用途:
static
关键字声明,并且可以有方法体。interface UtilityInterface { // 静态方法 static void printMessage(String message) { System.out.println("Message from interface: " + message); } // 抽象方法 void processData(String data); } class MyClass implements UtilityInterface { @Override public void processData(String data) { System.out.println("Processing data: " + data); } } public class Main { public static void main(String[] args) { // 调用静态方法 UtilityInterface.printMessage("Hello, world!"); MyClass obj = new MyClass(); obj.processData("Sample Data"); } }
集合类(如List、Set、Map等)引入了一个新的方法
forEach()
,用于简化集合的遍历操作。这个方法可以在集合类的实例上直接调用,接受一个函数式接口的实现作为参数,用来定义遍历集合时的操作。
import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List names = Arrays.asList("Alice", "Bob", "Charlie", "David"); // 使用 forEach() 遍历输出每个元素 names.forEach(name -> System.out.println(name)); // 也可以使用方法引用 names.forEach(System.out::println); } }
import java.util.HashSet; import java.util.Set; public class Main { public static void main(String[] args) { Set numbers = new HashSet<>(); numbers.add(1); numbers.add(2); numbers.add(3); // 使用 forEach() 遍历输出每个元素 numbers.forEach(number -> System.out.println(number)); } }
import java.util.HashMap; import java.util.Map; public class Main { public static void main(String[] args) { Map map = new HashMap<>(); map.put(1, "One"); map.put(2, "Two"); map.put(3, "Three"); // 使用 forEach() 遍历输出每个键值对 map.forEach((key, value) -> System.out.println(key + " -> " + value)); } }
注意事项:
forEach()
方法不能保证集合元素的顺序,具体取决于集合的实现类。ConcurrentModificationException
异常。好的木材并不在顺境中生长,风越强,树越壮