22、函数式接口和方法引用
1、方法引用
1.1 应用场景
官方文档说明
使用lambda表达式创建匿名方法,有时除了调用现有方法外什么也不做,在这种情况下,通常按名称引用现有方法更容易。
方法引用可以实现此操作,它们是紧凑的,x对于已经具有名称的方法lambda表达式更易于阅读。
即在匿名内部类仅调用一条已经在别的类实现了的现有方法时,用方法引用,使之更容易阅读。
示例
public class Example01 {
public static void main(String[] args) {
//函数式编程思想,用lambda语法实现
Actor actor = s -> System.out.println(s);
actor.performance("演员表演节目");
}
}
interface Actor {
void performance(String s);
}
分析
在上面的例子中,Lambda表达式的作用就是调用System.out
对象中println(String msg)
方法,而这个方法已经有具体的实现,如果能够直接引用这个方法,那么代码将会变得更简洁。
1.2 方法引用符
双冒号::
为方法引用符,方法引用符所在的表达式称为方法引用。如果Lambda
表达式赋值的方法已经在某个类中有具体的实现,那么可以通过方法引用符来引用该方法作为Lambda表达式的替代者。
示例
public class Actor {
public static void main(String args[]) {
Actor actor = System.out::println;
actor.performance("演员表演节目");
}
}
interface Actor {
void performance(String s);
}
分析
Actor
接口中的performance(String msg)
方法在实现时用System.out
对象中println(String x)
方法,Lambda表达式可以根据已经实现的接口方法推导省略,方法引用也可以根据实现的接口方法进行推导省略。
void performance(String item)
方法中带有一个字符串类型的参数,public void println(String x)
方法来实现时就可以接收这个字符串参数。
方法引用与Lambda表达式一样,只能应用与函数式接口。方法有静态方法、成员方法和构造方法之分,方法引用因此也分为静态方法引用、成员方法引用和构造方法引用
1.3 静态方法引用
语法
类名::方法名
1.4 成员方法引用
语法
对象名::方法名
示例
public class Computer {
private Printer printer;
public Computer(Printer printer) {
this.printer = printer;
}
public void print(String msg) {
// Printable printable = new Printable() {
// @Override
// public void print(String msg) {
// printer.print(msg);
// }
// };
//lambda表达式
// Printable printable = message -> printer.print(msg);
//方法引用
Printable printable = printer::print;
printable.print(msg);
}
}
如果函数式接口的抽象方法只有一个引用数据类型的参数,且实现过程只需要调用该参数类中定义的成员方法,使用静态引用的方式直接引用该成员方法
示例
public class ActorTest {
public static void main(String[] args) {
//lambda表达式
// Actor actor = p -> p.dance();
Actor actor = Person::dance;
actor.perform(new Person());
}
}
public interface Actor {
//函数式接口的抽象方法只有一个引用数据类型的参数
void perform(Person p);
}
public class Person {
public void sing() {
System.out.println("唱歌");
}
public void dance() {
System.out.println("跳舞");
}
}
1.4.1 this引用成员方法
语法
this::方法名
示例
public class PersonTest {
public static void main(String[] args) {
Person person = new Person();
person.travel("万里长城");
}
}
public class Person {
public void takePhoto(String name) {
System.out.println("给" + name + "拍照");
}
public void travel(String name) {
// Camera camera = new Camera() {
// @Override
// public void takePhoto(String name) {
// Person.this.takePhoto(name);
// }
// };
//lambda表达式
Camera camera = str -> this.takePhoto(str);
camera.takePhoto(name);
//方法引用
Camera c = this::takePhoto;
c.takePhoto(name);
}
}
public interface Camera {
void takePhoto(String name);
}
1.4.2 super引用父类成员方法
语法
super::方法名
示例
public class Test {
public static void main(String[] args) {
JavaProgrammer javaProgrammer = new JavaProgrammer();
javaProgrammer.communicateWithCustomer();
}
}
public class JavaProgrammer extends SoftEngineer{
public void communicateWithCustomer() {
// Customer customer = new Customer() {
// @Override
// public void communicateBusyness() {
// JavaProgrammer.super.analysisBusyness();
// }
// };
//lambda表达式
// Customer customer = () -> JavaProgrammer.super.analysisBusyness();
Customer customer = super::analysisBusyness;
customer.communicateBusyness();
}
}
public class SoftEngineer {
public void analysisBusyness() {
System.out.println("分析业务");
}
}
public interface Customer {
//业务交流
void communicateBusyness();
}
1.4.3 构造方法的引用
语法
类名::new
示例
public class StudentBuilderTest {
public static void main(String[] args) {
// StudentBuilder studentBuilder = new StudentBuilder() {
// @Override
// public Student build(String name, String sex) {
// return new Student(name, sex);
// }
// };
// StudentBuilder studentBuilder = (String name, String sex) -> new Student(name, sex);
StudentBuilder studentBuilder = Student::new;
Student student = studentBuilder.build("张三", "男");
System.out.println(student);
}
}
public interface StudentBuilder {
Student build(String name, String sex);
}
public class Student {
private String name;
private String sex;
public Student(String name, String sex) {
this.name = name;
this.sex = sex;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
2、函数式接口
官方文档说明
函数式接口是仅包含一种抽象方法的任何接口,(一个函数式接口可能包含一个或多个默认方法或静态方法),因此在实现该方法时可以省略该方法的名称。
示例
//函数式接口
public interface Hello {
//一个抽象方法
vaid sayHello(String msg);
//普通方法
default void show() {
"show".sout;
}
//静态方法
static void print() {
"print".sout;
}
}
JDK8为函数式接口提供了一个注解标识符@FunctionalInterface
,该注解只能只用在接口类型的定义上,表明这是一个函数式接口,编译器在编译时就会对该接口进行检测:接口中是否只有一个抽象方法,如果有多个抽象方法或者一个抽象方法也没有,则将报编译错误
2.1 函数式编程
函数式编程就是一种编程方式,在Java中,简单来说就是一个变量能够存储一个函数。而能够实现这种赋值操作的只有Lambda表达式
2.2 Consumer接口
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
解释说明
Consumer
顾名思义就是消费者的意思。可以消费一个被接收到的数据,至于如何消费,取决于这个接口被如何实现
示例
public class ConsumerTest {
public static void main(String[] args) {
// Consumer<String> c = new Consumer<String>() {
// @Override
// public void accept(String s) {
// System.out.println(s);
// }
// };
// Consumer<String> c = s -> System.out.println(s);
Consumer<String> c = System.out::println;
c.accept("这是被消费的信息");
Consumer<String> c2 = s -> System.out.println(s.charAt(0));
c2.accept("this is a consumer");
Consumer<String> c3 = c.andThen(c2);
c3.accept("先打印再取第一个字符");
//将数组转换成集合
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// numbers.forEach(new Consumer<Integer>() {
// @Override
// public void accept(Integer integer) {
// System.out.println(integer);
// }
// });
// numbers.forEach(integer -> System.out.println(integer));
numbers.forEach(System.out::println);
}
}
2.3 BigConsumer接口
void accept(T t, U u);//接收两个被消费的数据
BigConsumer
也是一个消费者,只是这个消费者可以一次性消费两个数据(一般是键值对)。至于如何消费,就需要看这个接口被如何实现。
2.4 Predicate接口
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other);//条件之间的逻辑与衔接
default Predicate<T> negate();//条件取反
default Predicate or(Predicate<? super T> other);//条件之间的逻辑或衔接
Predicate
是条件的意思,可以检测给定数据是否满足条件,也可以与其他条件进行衔接。至于如何检测,取决于接口被如何实现
2.5 Function接口
R apply(T t);
default<V> Function<T, V> andThen(Function<? super R, ? extends V> after);
Function
是功能的意思,可以将一种数据类型的对象转换为另一种数据类型的对象,如何转换取决于如何实现