参考链接: 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法) | lucida java8 lambda表达式 - 傻不拉几猫 - 博客园 深入理解 Java 中的 Lambda - 知乎 Java-Lambda表达式和“方法引用”的对比和详解_li_xunhuan的博客-CSDN博客_lambda和方法引用
在Java代码中写Lambda表达式是种怎样的体验? - 知乎
一、Lambda表达式简介
使用Lambda时,要记住的就两点:
- Lambda返回的是接口的实例对象
- 有没有参数、参数有多少个、需不需要有返回值、返回值的类型是什么---->选择自己合适的函数式接口
Lambda表达式的标准格式 由三部分组成:a.一些参数 b.一个箭头 c.一段代码 格式: (参数列表)->{一些重写方法的代码} 解释说明格式: ():接口中抽象方法的参数列表,没有参数就空着,有参数就写出参数,传多个参数使用逗号分隔 —>:把参数传递给方法体即传递给大括号,读作goes to ,用于分割参数列表和方法体 {}:来描述一个方法体(或者重写接口抽象方法的方法体)
简化方式:
1、省略参数:
由于在接口中已经定义了参数,所以在Lambda表达式中参数的类型可以省略;
(如果需要进行省略类型,那么所有参数的类型都必须都得省略,省略部分会报错)
匿名内部类中省略参数类型是不可取的,这是Lambda表达式的优势;
2、小括号的精简:
如果参数列表中,参数的个数有且只有一个(多了少了都不行),那么小括号可以省略,且仍然可以省略参数的类型
3、方法大括号的精简:
类似于if,while语句,如果语句块只有一条语句,那么此时大括号可以省略
4、return的省略:
如果出现接口只有唯一方法且方法中只有唯一语句,且是返回语句,那么如果要省略,只能一起省略掉大括号以及return,不能省略其中之一,否则会报错。
Lambda对接口的要求?
虽然Lambda表达式对某些接口进行简单的实现,但是并不是所有的接口都可以使用Lambda表达式来实现,要求接口种定义的必须要实现的抽象方法只能是一个(注意:具体方法可以多个或者没有)。
在Java 8 中对接口增加了新特性:default,提供了一个默认的抽象方法,但是Lambda对此没有特殊的影响,方法可以按Lambda所表达的来。
1.@FunctionalInterface
这个注解用于修饰函数式接口,即意味着接口中的抽象方法只能有一个,否则编译器会报错。
2.我们总是需要对象来实现接口,Lambda表达式就是帮助我们简化这个过程,而对象中的单独的方法在对象的创建接口对象的创建过程中并不会执行。4.2小节中构造方法在Lambda表达式中的调用,其更像一种工厂方法返回一个对象的引用,在创建实现接口的对象的时候工厂方法并不被执行。
接口实现的不同方式
实现接口的对象创建方式有三种,如下示例所示,分为:
- 使用接口实现类(接口对象指向已实现了接口的类对象)
- 使用匿名内部类实现接口
- 使用lambda表达式来实现接口
public class Program {
public static void main(String[] args) {
/***
* 1.使用接口实现类
*/
Comparator comparator = new MyComparator();
/**
* 2.使用匿名内部类实现接口
*/
Comparator comparator1 = new Comparator() {
@Override
public int compare(int a, int b) {
return a - b;
}
};
/**
* 3.使用lambda表达式来实现接口
*
*/
Comparator comparator2 = (a, b) -> a - b;
/**
* 测试部分 若不出错,则显示三个-1
*/
System.out.println(comparator.compare(1, 2));
System.out.println(comparator1.compare(1, 2));
System.out.println(comparator2.compare(1, 2));
}
}
class MyComparator implements Comparator {
@Override
public int compare(int a, int b) {
return a - b;
}
}
@FunctionalInterface
interface Comparator {
int compare(int a, int b);
}
我们用Lambda表达式的多种形式,分为有无返回值的普通方法(构造方法后面再讲),无参、一参、多参方法,总共有6个方法。所以定义了多个拥有不同方法的接口。从接口的命名方式就可以知道其意味着的含义(如果不在同一包种,注意import)。
二、Lambda表达式的基础语法和用法示例
6种不同的接口定义:
//1.无返回值的多参接口
@FunctionalInterface
public interface LambdaNoneReturnMultipleParameter {
void test(int a, int b);
}
//2.无返回值的无参接口
@FunctionalInterface
public interface LambdaNoneReturnNoneParameter {
void test();
}
//3.无返回值的一参接口
@FunctionalInterface
public interface LambdaNoneReturnSingleParameter {
void test(int n );
}
//4.有返回值的多参接口
@FunctionalInterface
public interface LambdaSingleReturnMultipleParameter {
int test(int a, int b);
}
//5.有返回值的无参接口
@FunctionalInterface
public interface LambdaSingleReturnNoneParameter {
int test();
}
//6.有返回值的一参接口
@FunctionalInterface
public interface LambdaSingleReturnSingleParameter {
int test(int n);
}
6种不同的接口的Lambda表达式应用:
/**
* Lambda表达式的基础语法
*/
public class Syntax1 {
public static void main(String[] args) {
/**
* 1.无参无返回的Lambda表达式使用样例
*/
LambdaNoneReturnNoneParameter lambda1 = () -> {
System.out.println("lambda1:" + "Hello World!");
};
lambda1.test();
/**
* 2.无返回值的单参数的Lambda表达式使用样例
*/
LambdaNoneReturnSingleParameter lambda2 = (int i) -> {
System.out.println("lambda2:" + i);
};
lambda2.test(1024);
/**
* 3.无返回值的多参数的Lambda表达式使用样例
*/
LambdaNoneReturnMultipleParameter lambda3 = (int a, int b) ->
{
System.out.println("lambda3:" + (a + b));
};
lambda3.test(1000, 24);
/**
* 4.有返回值的无参数的Lambda表达式使用样例
*/
LambdaSingleReturnNoneParameter lambda4 = () -> {
return 1024;
};
int res = lambda4.test();
System.out.println("lambda4:" + res);
/**
* 5.有返回值,单个参数的Lambdad的表达式使用
*/
LambdaSingleReturnSingleParameter lambda5 = (int a) -> {
return a;
};
int res2 = lambda5.test(1024);
System.out.println("lambda5:" + res2);
/**
* 6.有返回值,多个参数的Lambdad的表达式使用
*/
LambdaSingleReturnMultipleParameter lambda6 = (int a, int b) -> {
int sum = a + b;
return sum;
};
int res3 = lambda6.test(1000, 24);
System.out.println("lambda6:" + res3);
}
}
三、Lambda表达式语法精简
四种接口精简的方式代码案例:
/**
* 此类用于语法精简的Lambda表达式演示
*/
public class Syntax2 {
/**
* 参数精简
* 1.参数的精简
* 由于在接口中已经定义了参数,所以在Lambda表达式中参数的类型可以省略
* 备注:如果需要进行省略类型,但是所有参数的类型必须都得省略,省略部分会报错
* 匿名内部类中省略参数类型是不可取的
*/
LambdaNoneReturnMultipleParameter lambda1 = (a, b) -> {
System.out.println(a + b);
};
/**
* 2.精简参数小括号
* 如果参数列表中,参数的个数有且只有一个(多了少了都不行),那么小括号可以省略
* 且仍然可以省略参数的类型
*/
LambdaNoneReturnSingleParameter lambda2 = a -> {
System.out.println(a);
};
/**
* 3.方法大括号的省略
* 类似于if,while语句,如果语句块只有一条语句,那么此时大括号可以省略、
* 前面的省略方式仍然成立
*/
LambdaNoneReturnSingleParameter lambda3 = a ->
System.out.println(a);
/**
* 4.如果接口的唯一方法只有唯一返回语句,那么可以省略大括号,但是在省略大号的同时必须省略return
*/
LambdaSingleReturnNoneParameter lambda4 = () -> 10;
}
四、Lambda表达式进阶之函数引用
方法引用的提出: 由于如果存在一种情况,我们新建了多个接口的实现对象,其方法都是相同的,但是如果方法需要修改,那么修改的复杂度就随着对象数量的上升而上升。
方法引用的定义: 快速将一个Lambda表达式的实现指向一个已经写好的方法
方法引用可以看作是lambda表达式的特殊形式,或者称之为语法糖。一般方法已经存在才可以使用方法引用,而方法若未存在,则只能使用lambda表达式。
我们可以采用两种方式来在Lambda表达式中调用其他方法,第一种如一般的方法调用,第二种就是方法引用。
方法引用的语法说明:
即:“方法的隶属者::方法名”。方法的隶属者,即静态方法隶属者为类,非静态方法的隶属者是对象(隶属者不是接口,而是定义引用方法的类或者对象)。
注意事项:
1.被引用的方法的参数数量以及类型一定要和接口中的方法参数数目一致;
2.被引用的方法的返回值一定要和接口中的方法返回值一致,方法引用这个整体表达式可以返回函数式接口的实现对象,但其调用/引用的方法其返回类型绝不是接口实例对象;
3.方法名的后面没有括号“()”;
4.方法的引用是可以有多个参数入口的,虽然再::表达式中没有体现(由于没有小括号),但是接口中对其已有所规定了;
4.1、普通方法在Lambda表达式中的调用
2种不同的普通方法调用的样例说明:
public class Syntax3 {
public static void main(String[] args) {
/**
*方法引用:可以快速将一个Lambda表达式的实现指向一个已经写好的方法
*语法:方法的隶属者,静态方法隶属者为类,非静态方法的隶属者是对象
* 即:“方法的隶属者:方法名”
* 注意事项:
* 1.被引用的方法的参数数量以及类型一定要和接口中的方法参数数目一致
* 2.被引用的方法的返回值一定要和接口中的方法返回值一致
*
*
* 假如我们在程序中对于某个接口方法需要调用许多次,那么用以下的方法创建对象,来调用方法就是不太好的
* 缺点:如果将来要对方法进行改变,那么所有用Lambda表达式定义的对象都要更改,这在设计模式上就是有问题的;
* */
LambdaSingleReturnSingleParameter lambda1 = a -> a * 2;
LambdaSingleReturnSingleParameter lambda2 = a -> a * 2;
/**
* 我们一般是写一个通用的方法,并将其引用至Lambda表达式中
*
* */
LambdaSingleReturnSingleParameter lambda3 = a -> change(a);//在Lambda表达式中使用一般方法的调用方式
LambdaSingleReturnSingleParameter lambda4 = Syntax3::change;//在Lambda表达式种使用方法引用(方法隶属于类)
System.out.println(lambda4.test(2));
Syntax3 syntax3 = new Syntax3();//非静态方法需要对象才能被调用
LambdaSingleReturnSingleParameter lambda5 = syntax3::change2;//在Lambda表达式种使用方法引用(方法隶属于对象)
LambdaSingleReturnMultipleParameter lambda6 = syntax3::change3;//多参数的引用方法使用
}
private static int change(int a) {
return a * 2;
}
private int change2(int a) {
return a * 2;
}
}
private int change3(int a, int b) {
return a * 2 + b * 3;
}
4.2、构造方法在Lambda表达式中的调用
Person类具有无参和有参构造方法。
定义一个类,构造方法创建的对象
public class Person {
public String name;
public int age;
public Person() {
System.out.println("Person类的无参构造方法执行了");//语句用于判断无参构造器是否执行了
}
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("方法的有参构造方法执行了");//语句用于判断有参构造器是否执行了
}
}
Lambda表达式中引用构造方法的样例:
public class Syntax4 {
public static void main(String[] args) {
PersonCreater person = () -> new Person();
/**构造方法的引用
* 有参和无参构造器的调用区别在于所定义的接口中构造方法的参数区别
*/
PersonCreater creater1 = Person::new;
//无参
Person person1 = creater1.getPerson();
//有参
PersonCreater2 creater2=Person::new;
Person person2 = creater2.getPerson("Fisherman",18 );
}
}
//需求为:一个返回一个Person类的接口
interface PersonCreater {
Person getPerson();
}
interface PersonCreater2 {
Person getPerson(String name, int age);
}
数组的构造方法引用:
语法格式:TypeName[]::new
等价于lambda表达式:x -> new int[x]
IntFunction<int[]> arrayMaker = int[]::new;//假设有一个返回int类型数组的函数式接口
int[] array = arrayMaker.apply(10) // 创建数组 int[10]
注意:
在Lambda表达式中,一个接口要么对应一个无参构造方法,要么含有一个有参构造方式,其在接口中所定义的抽象返回Person对象的方法已经决定有参还是无参了。
构造方法和静态方法一样都是隶属于类的方法
构造方法不同于一般静态方法“类名::方法名一样调用”,而是采用"类名::new"的方式来进行构造方法的调用。
使用new关键字是为了明确地知道调用的是构造函数。而其并不需要入口参数的原因是因为编译器完全可以通过接口的定义推断出参数类型和个数。构造方法的方法引用和普通方法引用并没有本质的区别,比如在CodeBlock-5中用change(a)来实现原接口中定义的返回整形数据的test方法,而new关键字使用对应形参的构造器来实现接口中定义的返回Person对象的getPerson方法。
::在IDE(比如Intllij IDEA)中总是指向当前方法引用实现的函数式接口,以此可以方便地确定方法引用所实现的函数式接口为哪个。
4.3方法引用的格式总结:
引用方法的类型 | 格式规定 | 方法的发出者 | 等价lambda表达式 |
引用静态方法 | ClassName::staticMethodName | 类 | (s) -> String.valueOf(s) |
引用某个对象的实例(非静态)方法 | ObjectName::instanceMethodName | 当前对象 | ObjectName.instanceMethodName |
引用某个类型的任意对象的实例方法 | ClassName::methodName | 任意此类或子类的对象 | (任意对象,s) -> methodName(任意对象,s) |
引用构造方法 | ClassName::new | 类 | (s) -> new ClassName(s); |
上述s代表形参,限于篇幅,只象征性地写了一个。其数目可以不为1,为0,2,3…都可以。
4.4方法引用和Lambda表达式的对比:
方法引用比Lambda表达式更加简洁,但同时也更难理解其语法,所以我们以下用做对比的方法来理解表达式。
4.4.1、静态方法引用
组成语法格式:ClassName::staticMethodName
静态方法引用比较容易理解,和静态方法调用的lambda表达式相比,只是把 .换为 ::
在目标类型兼容的任何地方,都可以使用静态方法引用。此时,类是静态方法动作的发起者。
例子:
String::valueOf等价于lambda表达式 (s) -> String.valueOf(s)
Math::pow等价于lambda表达式 (x, y) -> Math.pow(x, y);
4.4.2 特定实例对象的方法引用
实例方法引用又分以下三种类型:
1.实例上的实例方法引用
这种语法与用于静态方法的语法类似,只不过这里使用对象引用而不是类名。此时,对象是方法动作的发起者。
语法格式:instanceReference::methodName
例子:
instanceReference::methodName相当于(无参或有参)->instanceReference.methodName(数目相同的参数)
由于对象需要构造,故在下面给出代码示例。
public class Test {
public static void main(String[] args) {
Power powerObject = new Power();
Function<Integer,Integer> function1 = a->powerObject.power(a);
Function<Integer,Integer> function2 = powerObject::power;
/**
*不管哪种实现,方法的调用是相同的,都用接口的已实现抽象方法名调用。
*/
System.out.println(function1.apply(2));
System.out.println(function2.apply(3));
}
}
class Power {
public int power (int a ){
return a*a;
}
}
2.超类上的实例方法引用
语法格式:super::methodName
方法的名称由methodName指定,通过使用super,可以引用方法的超类版本。
例子:
还可以捕获this 指针,this :: equals 等价于lambda表达式 x -> this.equals(x);
3.类上的实例方法引用(特定类的任意对象的方法引用)
语法格式:ClassName::methodName
这里和类调用静态方法以及对象调用实例都不相同, ::前的类不在是实例方法的发出者,那发出者是谁呢?我们凭借此方法引用的格式也找不到究竟谁是动作的发起者?实际上真正的发起者是ClassName类锁创建的任意一个对象,只不过,在方法调用的时候需要将引用对象作为参数输入到方法中,并且规定,此对象一定要位于方法参数的第一个。
代码案例:
注意:
若类型的实例方法是泛型的,就需要在::分隔符前提供类型参数,或者(多数情况下)利用目标类型推导出其类型,并不需要加类型参数,但是遇到分隔符前使用了强制类型转换应当看得懂其用意。
例子:
String::toString等价于lambda表达式 (s) -> s.toString()而绝不等于String.toString(),因为非静态方法只允许对象调用,而不能是类。但是即使这样讲,你可能还是不明白,那我给以下浅显易懂的例子,你一定都能够掌握它。
我们定义一个计算平方的方法,输入参数为”几“次方,而对于Power接口的不同实现,实现了求幂的底数规定,比如:PowerOfTwo类实现power(i)方法是求2的 i 次幂,PowerOfThree类实现power(i)方法是求3的 i 次幂。
类::实例方法的CodeBlock-1:
import java.net.InterfaceAddress;
import java.util.function.BiFunction;
import java.util.function.Function;
public interface Power {
int power(int i);
}
class PowerOfTwo implements Power {
public int power(int i) {
return (int) Math.pow(2, i);
}
}
class PowerOfThree implements Power {
public int power(int i) {
return (int) Math.pow(3, i);
}
}
class Test {
public static void main(String[] args) {
/**
* 之所以使用BiFunction作为函数式接口,是因为其为2输入参数,1个返回值。
* 正好符合此例中,类名.实例方法的调用规则
*/
Power powerObject1 = new PowerOfTwo();
Power powerObject2 = new PowerOfThree();
BiFunction<Power, Integer, Integer> function = Power::power;
System.out.println(function.apply(powerObject1, 4));//输出"2"的4次方:16
System.out.println(function.apply(powerObject2, 4));//输出"3"的4次方:81
}
}
只需要一个BiFunction接口的实现类,就能实现方法调用的多态(同一的方法名,由于对象名的不同而有不一样的操作)。这里多态的形成也是由于父类接口指向不同子类对象实现形成的。
我们应当将BiFunction<Power, Integer, Integer> function = Power::power;以及function.apply(powerObject1, 4)这两个步骤结合起来看,而不是孤立的。前者规定了方法输入参数类型,返回值类型,且规定了方法第一个参数必须得是实际调用该方法的对象,实现了后者的apply方法(代码实现,并未执行)。后者则输入了对应参数,执行相关程序。
如果你问我为什么需要这样的方法引用形式,那么最大的原因就其在具体方法调用时将对象作为参数输入到方法调用中呢,这增加了多态在方法引用中的便利性,如上述例子所示。如果采用其他方法引用方式,将产生多个接口的实例,而此方式只需要一个接口的实例。类上实例方法的引用的第二个例子:
类::实例方法的CodeBlock-2:
public class LowercaseToUppercase {
public static void main(String[] args) {
List<String> list = Arrays.asList("hello", "world", "hello world");
list.stream().map(String::toUpperCase).forEach(System.out::println);
}
}
如果你还没学过流,不影响这里的理解。我们Crtl+左键+: :,可以看到map类所需实现的是一个Function接口,即一个输入,一个输出。但是String::toUpperCase作为一个方法引用,返回值是确定的,因为toUpperCase方法显然会返回一个String类型对象:大写的String对象;但是输入参数是哪个呢,这似乎难以判断了,因为toUpperCase方法是一个无参的函数。但是JVM模型告诉我们:所有实例的实例方法,第一个参数都是一个隐式的this。map方法,对流的元素进行了映射(小写至大写),而方法中隐式的参数this即这每一个对象。
分步骤结合JDK源码解释:
1、map(String::toUpperCase),向map方法内传入一个方法引用所形成的接口实现对象
2、<R> Stream<R> map(Function<? super T, ? extends R> mapper);,map是一个返回Stream对象,输入为实现Function接口的对象即1中的方法引用
3、R apply(T t);此是Function接口所要实现的方法,即使用toUpperCase()方法去实现它。
4、public String toUpperCase() { return toUpperCase(Locale.getDefault());},这是toUpperCase()方法的源码,其是一个无参返回String类对象的非静态方法。
5、接口所需实现的方法的形参T t则对应任意一个String对象。
如果你有关于“为何例子1中的apply方法调用中方法的第一个参数为执行操作对象的引用,而第二个例子中却从未输入过对象的引用”的疑惑,那么说明你理解到关键位置了。实际上后者是map调用Function接口对象返回给stream,最后被流中的迭代方法调用了apply方法,其最终也是将对象引用作为第一个输入参数调用了apply方法,所以两个例子通过同一种方式达成了类名::实例方法,只不过一个显式,一个隐式。
5总结
总结: Lambda表达式和方法引用的目的都是使用具体的方法来代替接口中抽象的方法,但是在实际使用中,调用的是接口中被实现的方法名
,lambda表达式和方法引用只应用于接口实例的方式实现了的构造过程,例如:
//1.这是需要被实现的抽象方法,方法名:getPerson
interface PersonCreater {
Person getPerson();
}
//2.这是使用方法引用实现了抽象方法的对象(返回的是一个被实现了抽象方法的接口的实例)
PersonCreater creater1 = Person::new;
//3.这是调用接口实例的实现方法,返回一个Person对象,分为有参和无参。
Person person1 = creater1.getPerson();
6常用函数式接口
java8中一个非常重要的特性就是lambda表达式,我们可以把它看成是一种闭包,它允许把函数当做参数来使用,是面向函数式编程的思想,一定程度上可以使代码看起来更加简洁。例如以前我们使用匿名内部类来实现代码:
使用Labmda表达式需要函数式编程接口,比如在Runnable接口上我们可以看到@FunctionalInterface
注解(标记着这个接口只有一个抽象方法)
我们使用Lambda表达式创建线程的时候,并不关心接口名,方法名,参数名。我们只关注他的参数类型,参数个数,返回值。
//匿名内部类写法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("内部类写法");
}
}).start();
使用lambda则更加简洁:
//lambda 写法
new Thread(() -> System.out.println("lambda写法")).start();
1、lambda表达式语法
(paramters) -> expression;
或者:
(paramters) -> {statements;}
展开如:
(Type1 param1, Type2 param2, Type2 param2, ...) -> {
statement1;
statement2;
statement3;
...
return statementX;
}
2、lambda表达式特征
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
//入参为空
TestDemo no_param = () -> "hi, no param";
TestDemo no_param2 = () -> { return "hi, no param"; };
System.out.println(no_param.hi());
//单个参数
TestDemo2 param = name -> name;
TestDemo2 param2 = name -> { return name;};
System.out.println(param.hei("hei, grils"));
//多个参数
TestDemo3 multiple = (String hello, String name) -> hello + " " + name;
//一条返回语句,可以省略大括号和return
TestDemo3 multiple2 = (hello, name) -> hello + name;
//多条处理语句,需要大括号和return
TestDemo3 multiple3 = (hello, name) -> {
System.out.println("进入内部");
return hello + name;
};
System.out.println(multiple.greet("hello", "lambda"));
3、方法引用
------------------------------------------------------------------------------------------------------------------------
part1:
方法引用写法
Consumer<String> consumer = System.out::println;
consumer.accept("Java3y");
如果按正常Lambda的写法可能是这样的:
普通的Lambda写法
Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("Java3y");
显然使用方法引用比普通的Lambda表达式又简洁了一些。
如果函数式接口的实现恰好可以通过调用一个方法来实现,那么我们可以使用方法引用
Demo
作者:Java3y
链接:https://www.zhihu.com/question/37872003/answer/1009015660
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
public class Demo {
public static void main(String[] args) {
// 静态方法引用--通过类名调用
Consumer<String> consumerStatic = Java3y::MyNameStatic;
consumerStatic.accept("3y---static");
//实例方法引用--通过实例调用
Java3y java3y = new Java3y();
Consumer<String> consumer = java3y::myName;
consumer.accept("3y---instance");
// 构造方法方法引用--无参数
Supplier<Java3y> supplier = Java3y::new;
System.out.println(supplier.get());
}
}
class Java3y {
// 静态方法
public static void MyNameStatic(String name) {
System.out.println(name);
}
// 实例方法
public void myName(String name) {
System.out.println(name);
}
// 无参构造方法
public Java3y() {
}
}
Demo的 运行结果:
------------------------------------------------------------------------------------------------------------------------
part2:
Lambda虽然代码看起来是简洁,但是如果复杂的话还是比较难看明白的。
在学习Lambda的时候,首先我们得知道有哪些常用函数式编程接口,这些函数式编程接口的有什么区别(参数个数、返回值类型)
Lambda表达式返回的是接口对象实例,如果函数式接口的实现恰好可以通过调用一个方法来实现,那么我们可以使用方法引用来替代Lambda表达式
------------------------------------------------------------------------------------------------------------------------
part3:
3.1 对象::实例方法,将lambda的参数当做方法的参数使用
objectName::instanceMethod
Consumer<String> sc = System.out::println;
//等效
Consumer<String> sc2 = (x) -> System.out.println(x);
sc.accept("618, 狂欢happy");
3.2 类::静态方法,将lambda的参数当做方法的参数使用
ClassName::staticMethod
//ClassName::staticMethod 类的静态方法:把表达式的参数值作为staticMethod方法的参数
Function<Integer, String> sf = String::valueOf;
//等效
Function<Integer, String> sf2 = (x) -> String.valueOf(x);
String apply1 = sf.apply(61888);
3.3 类::实例方法,将lambda的第一个参数当做方法的调用者,其他的参数作为方法的参数。开发中尽量少些此类写法,减少后续维护成本。
ClassName::instanceMethod
//ClassName::instanceMethod 类的实例方法:把表达式的第一个参数当成instanceMethod的调用者,其他参数作为该方法的参数
BiPredicate<String, String> sbp = String::equals;
//等效
BiPredicate<String, String> sbp2 = (x, y) -> x.equals(y);
boolean test = sbp.test("a", "A");
4、构造函数
无参的构造方法就是类::实例方法模型,如:
- 在学习Lambda的时候,首先我们得知道有哪些常用函数式编程接口,这些函数式编程接口的有什么区别(参数个数、返回值类型)
- Lambda表达式返回的是接口对象实例,如果函数式接口的实现恰好可以通过调用一个方法来实现,那么我们可以使用方法引用来替代Lambda表达式
-
// Supplier是一个无入参带返回的值的函数式编程接口 // () -> new Java3y()这整句Lambda表达式,返回的是Supplier接口的实例。从Lambda表达式可以看出无参数,带返回值 Supplier<Java3y> supplier = () -> new Java3y(); // 由于这个“() -> new Java3y()”Lambda表达式可以通过调用一个方法就实现了,那么我们可以优化成方法引用 Supplier<Java3y> supplier2 = Java3y::new;
Supplier<User> us = User::new;
//等效
Supplier<User> us2 = () -> new User();
//获取对象
User user = us.get();
当有参数时:
//一个参数,参数类型不同则会编译出错
Function<Integer, User> uf = id -> new User(id);
//或加括号
Function<Integer, User> uf2 = (id) -> new User(id);
//等效
Function<Integer, User> uf3 = (Integer id) -> new User(id);
User apply = uf.apply(61888);
//两个参数
BiFunction<Integer, String, User> ubf = (id, name) -> new User(id, name);
User 狂欢happy = ubf.apply(618, "狂欢happy");
5、继承及实现具有相同默认方法的父类或接口问题
接口A:
public interface A {
String hi();
String greet();
default void hello() {
System.out.println("A.hello");
}
}
接口B:
public interface B {
String hi();
String hh();
default void hello() {
System.out.println("B.hello");
}
}
类C实现A,B:
public class C implements A, B{
@Override
public String hi() {
return "C.hi";
}
@Override
public String greet() {
return "C.greet";
}
@Override
public String hh() {
return "C.hh";
}
/**
* 子类优先继承父类的方法, 如果父类没有相同签名的方法,才继承接口的默认方法。
* 编译报错解决1:覆盖法
*/
@Override
public void hello() {
System.out.println("C.hello");
}
/**
* 编译报错解决2:指定实现的父接口
*/
// @Override
// public void hello() {
// A.super.hello();
B.super.hello();
// }
}
此时若不处理hello方法时,类C将编译出错,解决方式要么覆盖,要么指定实现父接口的该方法。
进一步测试继承具有相同方法的父类:
类D:
public class D {
public void hello() {
System.out.println("D.hello");
}
}
类C继承类D:
public class C extends D implements A, B{
@Override
public String hi() {
return "C.hi";
}
@Override
public String greet() {
return "C.greet";
}
@Override
public String hh() {
return "C.hh";
}
/**
* 子类优先继承父类的方法, 如果父类没有相同签名的方法,才继承接口的默认方法。
* 编译报错解决1:覆盖法
*/
// @Override
// public void hello() {
// System.out.println("C.hello");
// }
/**
* 编译报错解决2:指定实现的父接口
*/
// @Override
// public void hello() {
// A.super.hello();
B.super.hello();
// }
}
此时若不覆盖或指定父接口的方法时,类C将继承类D的hello方法。
7. JDK原生就给我们提供了一些函数式编程接口方便我们去使用,下面是一些常用的接口:
简单说明一下:
- 表格中的一元接口表示只有一个入参,二元接口表示有两个入参
Demo
// Consumer 一个入参,无返回值
Consumer<String> consumer = s-> System.out.println(s);
consumer.accept("Java3y");
// Supplier 无入参,有返回值
Supplier<String> supplier = () -> "Java4y";
String s = supplier.get();
System.out.println(s);
//.....
使用Lambda时,要记住的就两点:
- Lambda返回的是接口的实例对象
- 有没有参数、参数有多少个、需不需要有返回值、返回值的类型是什么---->选择自己合适的函数式接口
6、总结
java8引入lambda表达式是接收了函数式编程语言的思想,例如scala之类的,它将函数视为一等公民,可以使用高阶函数等。
和指令式编程相比,函数式编程强调函数的计算比指令的执行重要。
和过程化编程相比,函数式编程里函数的计算可随时调用。
写在最后,lambda表达式可以使代码看起来简洁,但一定程度上增加了代码的可读性以及调试的复杂性,所以在使用时应尽量是团队都熟悉使用,要么干脆就别用,不然维护起来是件较痛苦的事。