赞
踩
在使用Lambda
表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿什么参数做什么操作。那么考虑一种情况:如果我们在Lambda
中所指定的操作方案,已经有地方存在相同方案,那是否还有必要再写重复逻辑?
来看一个简单的函数式接口以应用Lambda表达式:
public interface Printable {
void print(String s);
}
public class PrintableDemo {
public static void printString(Printable printable) {
printable.print("Hello World");
}
public static void main(String[] args) {
printString(str -> System.out.println(str));
}
}
Lambda
表达式的目的,打印参数传递的字符串把参数str
,传递给了System.out
对象,调用out
对象中的方法println
对字符串进行了输出
注意:
System.out
对象是已经存在的println
方法也是已经存在的所以我们可以使用方法引用来优化Lambda
表达式可以使用System.out
方法直接引用(调用)printin
方法
public class PrintableDemo {
public static void printString(Printable printable) {
printable.print("Hello World");
}
public static void main(String[] args) {
//printString(str -> System.out.println(str));
printString(System.out::println);
}
}
请注意其中的双冒号::
写法,这被称为“方法引用”,而双冒号是一种新的语法。
双冒号::
为引用运算符,而它所在的表达式被称为方法引用。如果Lambda
要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda
的替代者。
语义分析
例如上例中,System.out
对象中有一个重载的println(String)
方法恰好就是我们所需要的。那么对于printString
方法的函数式接口参数,对比下面两种写法,完全等效:
str->System.out.println(str);
System.out::println
第一种语义是指:拿到参数之后经Lambda
之手,继而传递给
System.out.println
方法去处理。
第二种等效写法的语义是指:直接让System.out
中的println
方法来取代Lambda
。两种写法的执行效果完全一样,而第二种方法引用的写法复用了已有方案,更加简洁。
注:
Lambda
中传递的参数一定是方法引用中的那个方法可以接收的类型,否则会抛出异常
推导与省略
如果使用Lambda
,那么根据”可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式——它们都
将被自动推导。而如果使用方法引用,也是同样可以根据上下文进行推导。函数式接口是Lambda
的基础,而方法
引用是Lambda
的李生兄弟。
这是最常见的一种用法,与上例相同。如果一个类中已经存在了一个成员方法:
public class MethodRefObject{
public void printUpperCase(String str){
System.out.printin(str.toUppercase());
}
}
函数式接口仍然定义为:
@FunctionalInterface
public interface Printable{
void print(String str);
}
那么当需要使用这个printUpperCase
成员方法来替代printable
接口的Lambda
的时候,已经具有了MethodRefobject
类的对象实例,则可以通过对象名引用成员方法,代码为:
/** * 通过对象名引用成员方法 * 使用前提是: * 1.对象名是已经存在的 * 2.成员方法也是已经存在的 * 就可以使用对象名来引用成员方法 * * @author guqin * @date 2019-07-23 21:21 */ public class MethodRefObject { public void printUpperCaseString(String str) { System.out.println(str.toUpperCase()); } public static void printString(Printable printable) { printable.print("Hello World..."); } public static void main(String[] args) { // 对已经存在的对象使用对象引用调用成员方法 MethodRefObject methodRefObject = new MethodRefObject(); // 使用methodRefObject的方法引用完成输出 printString(methodRefObject::printUpperCaseString); } }
由于在java.lang.Math
类中已经存在了静态方法abs
,所以当我们需要通过Lambda
来调用该方法时,有两种写法。首先是函数式接口:
@FunctionalInterface
public interface Calcable{
int calc(int num);
}
使用Lambda
表达式和静态方法引用:
/** * 通过类名引用静态成员方法 * 类已经存在,静态成员方法也已经粗壮乃 * 就可以通过类名直接引用静态成员方法 * * @author guqin * @date 2019-07-23 21:32 */ public class StaticMethodRefDemo { /** * 定义一个方法,参数传递要计算绝对值的整数和函数式接口 * @param num * @param calcable */ public static int absMethod(int num, Calcable calcable) { return calcable.calc(num); } public static void main(String[] args) { // Lambda表达式写法 // int number = absMethod(-10, num->Math.abs(num)); // System.out.println(number); // Math.abs是静态方法,使用静态方法引用 int absNumber = absMethod(-10, Math::abs); System.out.println(absNumber); } }
如果存在继承关系,当Lambda
中需要出现super
调用时,也可以使用方法引用进行替代。首先是函数式接口:
@FunctionalInterface
public interface Greetable{
void greet();
}
然后是父类Human
的内容:
public class Human {
public void sayHi() {
System.out.println("Hello 大家好我是周杰伦~");
}
}
最后是子类Man
的内容,其中使用了Lambda
的写法和supper
方法引用的方法:
public class Man extends Human { public void greet(Greetable greetable) { greetable.greet(); } public void showGreet() { // Lambda写法 greet(()->{ // 创建父类Human对象 Human human = new Human(); human.sayHi(); }); /** * 因为有字符类关系,所有有supper关键字 * 所以可以直接使用supper调用父类的成员变量方法 */ greet(super::sayHi); } public static void main(String[] args) { new Man().showGreet(); } }
this
代表当前对象,如果需要引用的方法就是当前类中的成员方法,那么可以使用this::成员方法
的格式来使用方法引用。首先患简单的函数式接口:
@FunctionalInterface
public interface Richable{
void buy();
}
下面是一个丈夫Husband
类:
public class Husband{
private void marry(Richable 1ambda){
1ambda.buy();
}
public void beHappy() {
marry(()->System.out.println("买套房子”));
}
}
开心方法beHappy
调用了结婚方法marry
,后者的参数为函数式接口Richable
,所以需要一个Lambda
表达式。
但是如果这个Lambda
表达式的内容已经在本类当中存在了,则可以对Husband
丈夫类进行修改:
public class Husband { public void buyHouse() { System.out.println("北京二环买一套四合院"); } public void marry(Richable richable) { richable.buy(); } public void veryHappy() { // 调用marry方法,使用this调用本来方法buyHouse //marry(()-> this.buyHouse()); // 使用this引用成员方法 marry(this::buyHouse); } public static void main(String[] args) { new Husband().veryHappy(); } }
由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用类名称::new
的格式表示。首先是一个简单的Person
类:
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
然后是用来创建person
对象的函数式接口:
/**
* 定义一个创建Person对象的函数式接口
* @author guqing
*/
@FunctionalInterface
public interface PersonBuilder {
/**
* 根据名字创建Person对象的方法
*
* @param name Person中的名称
* @return Person
*/
Person builderPerson(String name);
}
使用Lambda表达式和构造方法引用创建对象
public class PersonDemo {
public static void createPerson(String name, PersonBuilder personBuilder) {
System.out.println(personBuilder.builderPerson(name));
}
public static void main(String[] args) {
// 根据name创建一个Person对象
//createPerson("张三", (name)->new Person(name));
// 构造方法引用,使用Person引用new创建对象
createPerson("项羽",Person::new);
}
}
数组也是object
的子类对象,所以同样具有构造器,只是语法稍有不同。如果对应到Lambda
的使用场景中时,需要一个函数式接口:
/**
* 定义一个创建数组的函数式接口
* @author guqing
*/
public interface ArrayBuilder {
/**
* 通过长度构建一个数组
* @param length 数组长度
* @return 返回构建好的数组
*/
public int[] builderArray(int length);
}
在应用该接口的时候,可以通过Lambda
表达式:
public class ArrayBuilderDemo {
public static int[] createArray(int length, ArrayBuilder arrayBuilder) {
return arrayBuilder.builderArray(length);
}
public static void main(String[] args) {
int[] arr = createArray(3, length->new int[length]);
System.out.println(arr.length);
}
}
但是更好的写法是使用数组的构造器引用:
public class ArrayBuilderDemo {
public static int[] createArray(int length, ArrayBuilder arrayBuilder) {
return arrayBuilder.builderArray(length);
}
public static void main(String[] args) {
// 使用方法引用
int[] arr = createArray(4, int[]::new);
System.out.println(arr.length);
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。