标签:消费 extend ... content pre title 适用于 不为 表达式
12.1.1 概念
1.函数式接口在Java中是指:有且仅有一个抽象方法的接口。2.函数式接口,即适用于函数式编程场景的接口,而Java中的函数式编程体现就是Lambda,所有函数式接口就是可以适用于Lamnda使用的接口,只有确保接口中有且仅有一个抽象方法,java中的Lambda才能顺利地进行推导。3、语法糖的定义:是指使用更加方便,但是原理不变的代码语法,例如在遍历集合时使用的for-each语法,其实底层的实现原理 仍然是迭代器,这就是语法糖,从应用层讲, java中的Lambda可以被当做是匿名内部类的语法糖,但是二者原理上不同的4.复习之前Lambda的使用要求,使用条件1:必须拥有函数式接口,(java语言中已经提供了很多函数式接口)使用条件2:调用的方法必须拥有函数式接口作为方法的参数,(Java语言已经提供了很多方法,这些方法的参数都是函数式接口)
12.1.2 格式
只要确保接口中有且仅有一个抽象方法即可
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}
由于接口当中抽象方法的public abstract是可以省略的,所以定义一个函数式接口很简单:
public interface MyFunctionalInterface {
void myMethod();
}
public interface MyFunctionalInterface {
void myMethod();
}
12.1.3 @FunctionalInterface注解
与@Override注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解:@FunctionalInterface。该注解可用于一个接口的定义上:
// 函数式接口注解 : 该接口仅能拥有一个抽象方法
@FunctionalInterface
public interface MyFunctionalInterface {
// 抽象方法定义 :
void myMethod();
}
// 函数式接口注解 : 该接口仅能拥有一个抽象方法
@FunctionalInterface
public interface MyFunctionalInterface {
// 抽象方法定义 :
void myMethod();
}
12.2.1 Lambda 的延时执行
有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式是延迟执行的,这正好可以作为解决方案,提升性能。性能浪费的日志案例:一种典型的场景就是对参数进行有条件使用,例如对日志消息进行拼接后,在满足条件的情况下进行打印输出:
public static void main(String[] args) {
String s1 = "Hello";
String s2 = "World";
String s3 = "Java";
// 调用方法
log(0, s1 + s2 + s3);
}
// 方法 :
public static void log(int level, String msg) {
if (level == 1) {
System.out.println(msg);
}
}
结果:
说明 : 如果 level 不为 1, 则没有任何输出结果.
说明 : 如果 level 为 1, 输出结果如下 :
HelloWorldJava
public static void main(String[] args) {
String s1 = "Hello";
String s2 = "World";
String s3 = "Java";
// 调用方法
log(0, s1 + s2 + s3);
}
// 方法 :
public static void log(int level, String msg) {
if (level == 1) {
System.out.println(msg);
}
}
结果:
说明 : 如果 level 不为 1, 则没有任何输出结果.
说明 : 如果 level 为 1, 输出结果如下 :
HelloWorldJava
notes:这段代码存在问题:无论级别是否满足要求,作为log方法的第二个参数,三个字符串一定会首先被拼接并传入方法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费。体验Lambda的优化写法使用Lambda必然需要一个函数式接口:
@FunctionalInterface
public interface MessageBuilder {
// 抽象方法
String buildMsg();
}
public interface MessageBuilder {
// 抽象方法
String buildMsg();
}
然后对log方法进行改造
public static void main(String[] args) {
String s1 = "Hello";
String s2 = "World";
String s3 = "Java";
// 调用方法 :
log(1, () -> s1 + s2 + s3);
}
// 方法 :
public static void log(int level, MessageBuilder messageBuilder) {
if (level == 1) {
String result = messageBuilder.buildMsg();
System.out.println("result = " + result);
}
}
输出结果 :
result = HelloWorldJava
public static void main(String[] args) {
String s1 = "Hello";
String s2 = "World";
String s3 = "Java";
// 调用方法 :
log(1, () -> s1 + s2 + s3);
}
// 方法 :
public static void log(int level, MessageBuilder messageBuilder) {
if (level == 1) {
String result = messageBuilder.buildMsg();
System.out.println("result = " + result);
}
}
输出结果 :
result = HelloWorldJava
noets:这样一来,只有当级别满足要求的时候,才会进行三个字符串的拼接;否则三个字符串将不会进行拼接从结果中可以看出,在不符合级别要求的情况下,Lambda将不会执行。从而达到节省性能的效果。扩展:实际上使用内部类也可以达到同样的效果,只是将代码操作延迟到了另外一个对象当中通过调用方法来完成。而是否调用其所在方法是在条件判断之后才执行的。除此之外Lambda还可作为参数和返回值
public class MyFunctionalInterfaceTest1 {
public static void main(String[] args) {
// 重点 : 调用方法, 传递 Lambda 表达式作为函数式接口的实际参数
// 思考 : MyFunctionalInterface 函数式接口中的抽象方法长什么样 ??? void myMethod();
// () 小括号 : 函数式接口抽象方法的参数列表.
// {} 大括号 : 函数式接口抽象方法的方法实现体.
// Lambda 表达式的标准格式 :
doSomething(() -> { System.out.println("Lambda 表达式被执行 ..."); });
// Lambda 表达式的省略格式 :
doSomething(() -> System.out.println("Lambda 表达式被执行 ..."));
}
// 定义一个方法, 使用函数式接口作为方法的参数列表
public static void doSomething(MyFunctionalInterface inter) {
// 此处为 Lambda 表达式代码的调用
inter.myMethod();
}
}
public class MyFunctionalInterfaceTest1 {
public static void main(String[] args) {
// 重点 : 调用方法, 传递 Lambda 表达式作为函数式接口的实际参数
// 思考 : MyFunctionalInterface 函数式接口中的抽象方法长什么样 ??? void myMethod();
// () 小括号 : 函数式接口抽象方法的参数列表.
// {} 大括号 : 函数式接口抽象方法的方法实现体.
// Lambda 表达式的标准格式 :
doSomething(() -> { System.out.println("Lambda 表达式被执行 ..."); });
// Lambda 表达式的省略格式 :
doSomething(() -> System.out.println("Lambda 表达式被执行 ..."));
}
// 定义一个方法, 使用函数式接口作为方法的参数列表
public static void doSomething(MyFunctionalInterface inter) {
// 此处为 Lambda 表达式代码的调用
inter.myMethod();
}
}
/*
Lambda 表达式书写的前提要求 : (传递 Lambda 表达式作为方法的参数就是重要)
1. 必须要有 `函数式接口`. (自动推导) (Java提供)
2. 必须有方法使用 `函数式接口` 作为方法的参数列表. (Java提供)
*/
// 请问 : 如何让编译器该接口是否为函数式接口
@FunctionalInterface
public interface MyFunctionalInterface {
// 抽象方法 : 无参无返回值
void myMethod();
}
/*
Lambda 表达式书写的前提要求 : (传递 Lambda 表达式作为方法的参数就是重要)
1. 必须要有 `函数式接口`. (自动推导) (Java提供)
2. 必须有方法使用 `函数式接口` 作为方法的参数列表. (Java提供)
*/
// 请问 : 如何让编译器该接口是否为函数式接口
public interface MyFunctionalInterface {
// 抽象方法 : 无参无返回值
void myMethod();
}
public class MySupplierTest1 {
public static void main(String[] args) {
// 调用方法
// 思考 : 看一下 MySupplier 的抽象方法长什么样 ??? Object data();
printData(() -> { return "Hello ShangHai, I Love you."; });
// 调用方法
MySupplier mySupplier = getDataFromLambda();
// 请问 : 如何取出数据 ??? 调用函数式接口中的方法, 来获取数据
Object data = mySupplier.getData();
System.out.println("data = " + data);
}
// 方法 : 将函数式接口作为方法的参数
public static void printData(MySupplier mySupplier) {
Object data = mySupplier.getData();
System.out.println("data = " + data);
}
// 方法 : 将函数式接口作为方法的返回值
public static MySupplier getDataFromLambda() {
// 如何实现 : 返回一个 Lambda 表达式 Object getData();
// return () -> { return "你爱北京长城."; };
return () -> "你爱北京长城.";
}
}
public class MySupplierTest1 {
public static void main(String[] args) {
// 调用方法
// 思考 : 看一下 MySupplier 的抽象方法长什么样 ??? Object data();
printData(() -> { return "Hello ShangHai, I Love you."; });
// 调用方法
MySupplier mySupplier = getDataFromLambda();
// 请问 : 如何取出数据 ??? 调用函数式接口中的方法, 来获取数据
Object data = mySupplier.getData();
System.out.println("data = " + data);
}
// 方法 : 将函数式接口作为方法的参数
public static void printData(MySupplier mySupplier) {
Object data = mySupplier.getData();
System.out.println("data = " + data);
}
// 方法 : 将函数式接口作为方法的返回值
public static MySupplier getDataFromLambda() {
// 如何实现 : 返回一个 Lambda 表达式 Object getData();
// return () -> { return "你爱北京长城."; };
return () -> "你爱北京长城.";
}
}
@FunctionalInterface
public interface MySupplier {
// 抽象方法 : 无参, 有返回值
Object getData();
}
public interface MySupplier {
// 抽象方法 : 无参, 有返回值
Object getData();
}
12.3.2 冗余的Lambda场景
看一个简单的函数式接口以应用Lambda表达式
@FunctionalInterface
public interface Printable<T> {
void print(T t);
}
@FunctionalInterface
public interface Printable<T> {
void print(T t);
}
在Printable接口当中唯一的抽象方法print接收一个字符串参数,目的就是为了打印显示它。那么通过Lambda来使用它的代码很简单:
public static void main(String[] args) {
// 调用方法 :
printString("Hello beijing, 你好, 北京.", s -> System.out.println(s));
}
// 方法 :
public static void printString(String str, Printable<String> printable) {
printable.print(str);
}
输出结果:
Hello beijing, 你好, 北京.
public static void main(String[] args) {
// 调用方法 :
printString("Hello beijing, 你好, 北京.", s -> System.out.println(s));
}
// 方法 :
public static void printString(String str, Printable<String> printable) {
printable.print(str);
}
输出结果:
Hello beijing, 你好, 北京.
分析:这段代码的问题在于,对字符串进行控制台打印输出的操作方案,明明已经有了现成的实现,那就是System.out对象中的println(String)方法。既然Lambda希望做的事情就是调用println(String)方法,那何必自己手动调用呢?这时候引入了其孪生兄弟, 方法引用:改进之后的代码
public static void main(String[] args) {
// 调用方法 : Lambda 表达式
printString("Hello beijing, 你好, 北京.", s -> System.out.println(s));
// 调用方法 : 方法引用 -> 对象引用对象方法
PrintStream ps = System.out;
printString("Hello beijing, 你好, 北京.", ps::println);
// 调用方法 : 简化格式
printString("Hello beijing, 你好, 北京.", System.out::println);
}
// 方法 :
public static void printString(String str, Printable<String> printable) {
printable.print(str);
}
输出结果 :
Hello beijing, 你好, 北京.
Hello beijing, 你好, 北京.
Hello beijing, 你好, 北京.
public static void main(String[] args) {
// 调用方法 : Lambda 表达式
printString("Hello beijing, 你好, 北京.", s -> System.out.println(s));
// 调用方法 : 方法引用 -> 对象引用对象方法
PrintStream ps = System.out;
printString("Hello beijing, 你好, 北京.", ps::println);
// 调用方法 : 简化格式
printString("Hello beijing, 你好, 北京.", System.out::println);
}
// 方法 :
public static void printString(String str, Printable<String> printable) {
printable.print(str);
}
输出结果 :
Hello beijing, 你好, 北京.
Hello beijing, 你好, 北京.
Hello beijing, 你好, 北京.
notes:注意其中的双冒号::写法,这被称为“方法引用”,而双冒号是一种新的语法。12.3.3 方法引用符
双冒号::为引用运算符,而它所在的表达式被称为方法引用。如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。语义分析例如上例中,System.out对象中有一个重载的println(String)方法恰好就是我们所需要的。那么对于printString方法的函数式接口参数,对比下面两种写法,完全等效:- Lambda表达式写法:s -> System.out.println(s);- 方法引用写法:System.out::println第一种语义是指:拿到参数之后经Lambda之手,继而传递给System.out.println方法去处理。第二种等效写法的语义是指:直接让System.out中的println方法来取代Lambda。两种写法的执行效果完全一样,而第二种方法引用的写法复用了已有方案,更加简洁。推导与省略如果使用Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式——它们都将被自动推导。而如果使用方法引用,也是同样可以根据上下文进行推导。函数式接口是Lambda的基础,而方法引用是Lambda的孪生兄弟。
12.3.4 通过对象名引用成员方法
这是最常见的一种用法,与上例相同。如果一个类中已经存在了一个成员方法:思路历程:1. 接口Printable 中的抽象方法 void print(T t) 有参数 未指定类型,无返回值printString("Hello World. 你好, 世界!", s -> System.out.println(s));2,上面传递的Lambda 表达式 (s -> System.out,println(s) )的实现效果与 Sysetm.out 对象的println 方法实现一致既然效果一样,那么Lambda表达式可不可以不写了,直接引用别人的方法。方法引用的条件:引用的方法必须与函数式中的抽象方法匹配,(参数类型和返回值需要保持一致)// Printable : void print(T t); System.out 对象的 void println(String x);// 引用语法 : 对象名::对象方法名3.// 简化格式 : System.out::println 对象名::对象方法// 作用 : 已经有方法完成 Lambda 表达式需要实现的效果. 因此, 在程序中可以使用方法引用替代 Lambda 表达式.4.// 方法 : 使用函数式接口作为参数 (这是 Lambda 表达式的传递基本)
public class ObjectMethodRef {
// 对象方法 :
public void printUpperCase(String str) {
String s = str.toUpperCase();
System.out.println(s);
}
}
public class ObjectMethodRef {
// 对象方法 :
public void printUpperCase(String str) {
String s = str.toUpperCase();
System.out.println(s);
}
}
// 函数式接口仍然定义为
@FunctionalInterface
public interface Printable<T> {
void print(T t);
}
// 函数式接口仍然定义为
public interface Printable<T> {
void print(T t);
}
那么当需要使用这个printUpperCase成员方法来替代Printable接口的Lambda的时候,已经具有了ObjectMethodRef类的对象实例,则可以通过对象名引用成员方法,代码为:
public static void main(String[] args) {
// Lambda 表达式实现 :
printString("hello", s -> System.out.println(s.toUpperCase()));
// 对象方法引用 :
ObjectMethodRef obj = new ObjectMethodRef();
printString("hello", obj::printUpperCase);
}
public static void printString(String str, Printable<String> printable) {
printable.print(str);
}
输出结果 :
HELLO
HELLO
public static void main(String[] args) {
// Lambda 表达式实现 :
printString("hello", s -> System.out.println(s.toUpperCase()));
// 对象方法引用 :
ObjectMethodRef obj = new ObjectMethodRef();
printString("hello", obj::printUpperCase);
}
public static void printString(String str, Printable<String> printable) {
printable.print(str);
}
输出结果 :
HELLO
HELLO
12.3.5 通过类名称引用静态方法
由于在java.lang.Math类中已经存在了静态方法abs,所以当我们需要通过Lambda来调用该方法时,有两种写法。首先是函数式接口:
@FunctionalInterface
public interface Calculator {
// 抽象方法 :
int calc(int num);
}
public interface Calculator {
// 抽象方法 :
int calc(int num);
}
public class Test3 {
public static void main(String[] args) {
// 调用方法 : Lambda 表达式
printCalculator(-88, num -> Math.abs(num));
// 调用方法 : 静态方法引用
printCalculator(-88, Math::abs);
}
// 方法 :
public static void printCalculator(int num, Calculator calculator) {
int result = calculator.calc(num);
System.out.println("result = " + result);
}
}
输出结果 :
result = 88
result = 88
public class Test3 {
public static void main(String[] args) {
// 调用方法 : Lambda 表达式
printCalculator(-88, num -> Math.abs(num));
// 调用方法 : 静态方法引用
printCalculator(-88, Math::abs);
}
// 方法 :
public static void printCalculator(int num, Calculator calculator) {
int result = calculator.calc(num);
System.out.println("result = " + result);
}
}
输出结果 :
result = 88
result = 88
notes:
在这个例子中,下面两种写法是等效的:- Lambda表达式:n -> Math.abs(n)- 方法引用:Math::abs
12.3.6 通过super引用成员方法
如果存在继承关系,当Lambda中需要出现super调用时,也可以使用方法引用进行替代。首先是函数式接口
@FunctionalInterface
public interface Greetable {
void greet();
}
public interface Greetable {
void greet();
}
public class Human {
public void sayHello() {
System.out.println("Hello!");
}
}
public class Human {
public void sayHello() {
System.out.println("Hello!");
}
}
pubpublic class Man extends Human {
public void sayHi() {
// 调用方法 : Lambda 表达式
method(() -> System.out.println("Hello!"));
// 调用方法 : 执行父类中的 sayHello 方法.
method(() -> super.sayHello());
// 调用方法 : super引用, 使用父类中的 sayHello 方法.
method(super::sayHello);
}
public void method(Greeting greeting) {
greeting.greet();
System.out.println("I am a Man.");
}
}
pubpublic class Man extends Human {
public void sayHi() {
// 调用方法 : Lambda 表达式
method(() -> System.out.println("Hello!"));
// 调用方法 : 执行父类中的 sayHello 方法.
method(() -> super.sayHello());
// 调用方法 : super引用, 使用父类中的 sayHello 方法.
method(super::sayHello);
}
public void method(Greeting greeting) {
greeting.greet();
System.out.println("I am a Man.");
}
}
notes:
在这个例子中,下面两种写法是等效的:- Lambda表达式:() -> super.sayHello()- 方法引用:super::sayHello
12.3.7 通过this引用成员方法
this代表当前对象,如果需要引用的方法就是当前类中的成员方法,那么可以使用“this::成员方法”的格式来使用方法引用。首先是简单的函数式接口:
@FunctionalInterface
public interface Richable {
void buy();
}
@FunctionalInterface
public interface Richable {
void buy();
}
public class Husband {
// 行为 : 变得快乐
public void beHappy() {
// 结婚吧
merry(() -> System.out.println("买套房子."));
merry(() -> this.buyCar());
merry(this::changeWife);
}
// 行为 : 结婚 (需要变得有钱, 必须要买东西)
private void merry(Richable richable) {
richable.buy();
}
// 行为 : 买套方法
private void buyHouse() {
System.out.println("买套房子.");
}
// 行为 : 买辆车子
private void buyCar() {
System.out.println("买辆车子.");
}
}
public class Husband {
// 行为 : 变得快乐
public void beHappy() {
// 结婚吧
merry(() -> System.out.println("买套房子."));
merry(() -> this.buyCar());
merry(this::changeWife);
}
// 行为 : 结婚 (需要变得有钱, 必须要买东西)
private void merry(Richable richable) {
richable.buy();
}
// 行为 : 买套方法
private void buyHouse() {
System.out.println("买套房子.");
}
// 行为 : 买辆车子
private void buyCar() {
System.out.println("买辆车子.");
}
}
public class Test {
public static void main(String[] args) {
Husband husband = new Husband();
husband.beHappy();
}
}
输出结果 :
买套房子.
买辆车子.
public class Test {
public static void main(String[] args) {
Husband husband = new Husband();
husband.beHappy();
}
}
输出结果 :
买套房子.
买辆车子.
开心方法beHappy调用了结婚方法marry,后者的参数为函数式接口Richable:在这个例子中,下面两种写法是等效的:- Lambda表达式:() -> this.buyCar()- 方法引用:this::buyCar12.3.8 类的构造器引用
由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用类名称::new的格式表示。首先是一个简单的Person类:
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public Person() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public Person() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
在这个例子中,下面两种写法是等效的:- Lambda表达式:name -> new Person(name)- 方法引用:Person::new12.3.9 数组的构造器引用
数组也是Object的子类对象,所以同样具有构造器,只是语法稍有不同。需要一个函数式接口:
@FunctionalInterface
public interface ArrayBuilder {
// 抽象方法
int[] buildArray(int length);
}
@FunctionalInterface
public interface ArrayBuilder {
// 抽象方法
int[] buildArray(int length);
}
public class Test2 {
public static void main(String[] args) {
int[] arr1 = initIntArray(10, len -> new int[len]);
System.out.println("arr1.length = " + arr1.length);
int[] arr2 = initIntArray(10, int[]::new);
System.out.println("arr2.length = " + arr2.length);
}
// 初始化一个 int[] 数组
public static int[] initIntArray(int length, ArrayBuilder builder) {
return builder.buildArray(length);
}
}
输出结果 :
arr1.length = 10
arr2.length = 10
public class Test2 {
public static void main(String[] args) {
int[] arr1 = initIntArray(10, len -> new int[len]);
System.out.println("arr1.length = " + arr1.length);
int[] arr2 = initIntArray(10, int[]::new);
System.out.println("arr2.length = " + arr2.length);
}
// 初始化一个 int[] 数组
public static int[] initIntArray(int length, ArrayBuilder builder) {
return builder.buildArray(length);
}
}
输出结果 :
arr1.length = 10
arr2.length = 10
在这个例子中,下面两种写法是等效的:- Lambda表达式:length -> new int[length]- 方法引用:int[]::new
12.4.1 总结
第一步:定义函数式接口第二步: 定义方法,将函数式接口作为方法的参数类型总结:只要满足前两个步骤,我们就可以书写Lambda表达式了,问: 在使用Lambda表达式传送传输时,思考哪两个东西??思考1:函数式接口中华抽象方法的参数列表思考2:函数式接口中抽象方法的返回值类型核心点:要求程序员对函数式接口中的抽象方法,参数类型和返回值类型,非常清晰的理解。因此没有这一层的理解,Lambda表达式的语法就无从书写了Lambda表达式的语法格式:(抽象方法参数) -> { return 抽象方法的实现体 }12.4.2 4大函数式接口
Java 语言中提供了最重要的 "四大核心函数式接口" 特有的抽象方法1、消费型接口: 特点(有去无回)Consumer<T> void accept(T t)2、供给型接口 特点 (无中生有)Supplier<T> T get();3、函数型接口 特点(有去有回)Function<T, R> R apply (T t) T -> Type 参数类型 R -> Result 返回值类型4、断言型接口 特点(元芳,你怎么看)Predicate<T> boolean test (T t)说明:使用函数式接口作为方法的参数,这些API大部分都被定义到 StreamAPI 中StreamAPI 主要操作对象为 “集合” 对象,不是 “读写” 对象说明1:读写IO 主要是 “内存与硬盘” 实现数据交互说明2:StreamAPI 主要是 ”内存与内存“ 实现数据交互 (变量,数组,集合)
<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">
标签:消费 extend ... content pre title 适用于 不为 表达式
原文地址:https://www.cnblogs.com/zhengyuan/p/9362115.html