1. Lambda表达式
1.1 函数式编程思想概述
传统的面向对象编程,函数的执行更注重的是根据对象的某种方法执行某个函数,比较依赖于对象。而函数式编程,将对象与函数的这种联系给优化了,不需要我们自己来创建对象和调用函数,换句话说JVM内部会帮我推导出对象于函数之间的联系,这样可以简化我们的编码量,提高开发效率。
1.2 多线程开发中遇到的冗余代码
回顾传统线程创建的三种方式:
- 继承Thread,重写run()
public class TestExtends extends Thread{
@Override
public void run() {
System.out.println("线程1....");
}
}
调用:
//1.继承Thread
TestExtends anExtends = new TestExtends();
anExtends.start();
- 实现Runnable接口,重写run()
public class TestRunnable implements Runnable{
@Override
public void run() {
System.out.println("线程2....");
}
}
调用:
//2.实现Runnable
TestRunnable runnable = new TestRunnable();
new Thread(runnable).start();
- 匿名内部类实现
//3. 使用匿名内部类的实现
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程3....");
}
}).start();
可以发现,三种方式同样做了一件事,重写run方法,而且它们需要我们手都去创建Runnable的实现类,此时我们想,我们应该更注重线程体里面的内容,而不是去关注其它的细节,引出lambda表达式。
1.3 使用lambda表达式优化
使用 ()——>表达式来简化代码,只需要3行代码即可,()根据情况传参可以传参
//4.lambda表达式
new Thread(()->{
System.out.println("线程4....");
}).start();
1.4 几个练习
1.4.1 使用lambda表达式(无参无返回)
题目:给定一个厨子Cook
接口,内含唯一的抽象方法makeFood
,且无参无返回值
如下:
public interface Cook {
public abstract void makeFood();
}
在下面的代码中,使用lambda表达式的标准格式调用invokeCook
方法,打印字符串
public class InvokeCook {
public static void main(String[] args) {
// TODO 请在此使用Lambda【标准格式】调用invokeCook方法
}
public static void invokeCook(Cook cook){
cook.makeFood();
}
}
解答:
public class InvokeCook {
public static void main(String[] args) {
// TODO 请在此使用Lambda【标准格式】调用invokeCook方法
InvokeCook.invokeCook(()->{
System.out.println("该吃饭了....");
});
}
public static void invokeCook(Cook cook){
cook.makeFood();
}
}
1.4.2 使用lambda表达式(有参无返回)
回顾对象比较大小,使用Comparator接口
public class PersonComparator {
public static void main(String[] args) {
Person[] persons = {
new Person("周杰伦",45),
new Person("林俊杰",33),new Person("成龙",56)};
Comparator<Person> comparator = new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
};
//排序
Arrays.sort(persons,comparator);
for (Person person : persons) {
System.out.println(person);
}
}
}
使用lambda表达式优化:
public class LambdaComparator {
public static void main(String[] args) {
Person[] persons = {
new Person("周杰伦",45),
new Person("林俊杰",33),new Person("成龙",56)};
Arrays.sort(persons, ((Person o1,Person o2)->{
return o1.getAge() - o2.getAge();
}) );
//遍历
for (Person person : persons) {
System.out.println(person);
}
}
}
1.4.3 使用lambda表达式(有参有返回)
问题:定义一个计算器接口类,实现两数之和
public interface Calculator {
public int sum(int a,int b);
}
在下面的代码中,请使用Lambda的标准格式调用 invokeCalc 方法,完成120和130的相加计算:
public class InvokeCalculator {
public static void main(String[] args) {
// TODO 请在此使用Lambda【标准格式】调用invokeCalc方法来计算120+130的结果ß
}
public static void calculator(int a,int b,Calculator calculator){
int result = calculator.sum(a, b);
System.out.println(result);
}
}
解答:
public class InvokeCalculator {
public static void main(String[] args) {
// TODO 请在此使用Lambda【标准格式】调用invokeCalc方法来计算120+130的结果ß
InvokeCalculator.calculator(120,130,(int a,int b)->{
return a+b;
});
}
public static void calculator(int a,int b,Calculator calculator){
int result = calculator.sum(a, b);
System.out.println(result);
}
}
1.5 继续优化lambda表达式
依据可推导即可省略
如省略了{}和;
InvokeCook.invokeCook(()->
System.out.println("该吃饭了....")
);
省略规则:
- 小括号内的参数类型可以省略
- 如果小括号内只有一个参数,那么小括号也可以省略
- 如果大括号内只有一行,无论是否有返回值,均可以省略{},return关键字和尾部分号。
上面代码的一些省略:
InvokeCalculator.calculator(120,130,( a, b)->
a+b);
}
Arrays.sort(persons, (( o1, o2)->
o1.getAge() - o2.getAge()) );
1.6 lambda表达式的使用前提
lambda表达式的语法非常简洁,完全没有面向对象复杂的束缚,但是使用时有几个问题需要注意:
- 使用lambda表达式必须具有接口,要求接口中有且只有一个抽象方法。无论是JDK内置的Runnable,Comparator还是自定义的接口,当接口中的抽象方法唯一时,才可以使用lambda表达式。
- 使用lambda表达式必须具有上下文推断,也就是方法的参数或局部变量必须为lambda对应的接口类型,才可以使用lambda作为该接口的实例。
备注:有且只有一个抽象方法的接口,称为函数式接口。
1.7 函数式接口
1.7.1 概念
有且只有一个抽象方法的接口,称为函数式接口。
函数式接口,即适用于函数式编程场景的接口,而Java函数式编程体现就是lambda,所以函数式接口可以适应于lambda使用的接口,只有确保接口中只有一个抽象方法,lambda才能顺序进行推导。
1.7.2 语法糖
语法糖是指使用更加方便,但原理不变的代码语法,如我们在遍历集合的时候可以使用迭代器,也可以使用for-each遍历,但是迭代器的语法相对复杂,for-each的遍历底层也是使用迭代器,所以可以成for-each是迭代器的语法糖。从应用层面上讲,lambda表达式是匿名内部类的语法糖,但是<mark>两者在原理上又有点不同</mark>。
1.7.3 格式
修饰符 interface 接口名{
pubic abstract 返回值 方法名(参数列表);
//其它非非抽象函数
}
其中pubic abstract也可以省略。
1.7.3 注解@FunctionalInterface
作用是被FuncationalInterface标注的接口是一个函数式接口,即接口内只能包含一个抽象的方法,否则编译失败。
包含两个:
2. 函数式编程
2.1 Lambda的延迟执行
由一个日志日志案例来引出:
public class TestLogger {
public static void logger(int leave,String msg){
System.out.println(msg); //liuzeyuduyanting
if( leave == 1){
System.out.println(msg);
}
}
public static void main(String[] args) {
String msg1 = "liuzeyu";
String msg2 = "duyanting";
logger(2,msg1 + msg2);
}
}
上面是一个打印日志信息的案例,目的是有且只有leave = 1的时候,才打印输出拼接字符串。但是我们输入leave = 2的时候,也进行了字符串的拼接,性能浪费了。
使用lambda表达式优化:
@FunctionalInterface
public interface LoggerInterface {
public String logger();
}
public class TestLogger {
public static void logger(int leave,LoggerInterface myInterface){
if(leave == 1){
System.out.println(myInterface.logger());
}
}
public static void main(String[] args) {
String msg1 = "liuzeyu";
String msg2 = "duyanting";
logger(2,()->{
System.out.println("lambda延迟了...");
return msg1 + msg2;
});
}
}
此时字符串拼接的地方在lambda表达式的内部,可以做到延迟拼接的作用,提高了性能。
2.2 常用的函数式接口
JDK提供了大量常用的函数式接口,主要在java.util.function
包中被提供,下面式几个常用的接口。
2.2.1 Supplier接口
java.util.function.Supplier接口中只包含了一个无参的抽象方法,T get(),泛型传什么,get()就返回什么,常适用于生产环境下。
public class SupplierDemo1 {
public static String fun1(Supplier<String> supplier){
return supplier.get();
}
public static void main(String[] args) {
// String value = fun1(() -> {
// return "liuzeyu" + "duyanting";
// });
//简化代码:
String value = fun1(() ->
"liuzeyu" + "duyanting");
System.out.println(value); //liuzeyuduyanting
}
}
练习:求数组中最大值
public class SupplierDemo2 {
public static Integer getMax(Supplier<Integer> supplier){
return supplier.get();
}
public static void main(String[] args) {
int[] arr = {
11,343,42,547,12};
int maxValue = getMax(()->{
int max = arr[0];
for (int i:
arr) {
if( i > max){
max = i;
}
}
return max;
});
System.out.println(maxValue); //547
}
}
2.2.2 Consumer接口
- 接口概述
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> {
accept(t); after.accept(t); };
}
}
Consumer接口的作用刚好了Supplier相反,Supplier的作用是泛型接收一个参数类型,就可以返回数据,主要用于生产环境下,因为它不会对数据进行修改,而是直接返回。而Consumer接口可以修改数据,使用数据,常用于消费环境。
- 抽象方法accept
accept方法作用是可以消费一个接收泛型的数据
public class ConsumerDemo1 {
public static void fun1(String str,Consumer<String> con){
con.accept(str);
}
public static void main(String[] args) {
fun1("Hello",(str)->{
String s = new StringBuilder(str).reverse().toString();
System.out.println(s); //olleH
});
}
}
- 默认方法andThen
如果一个方法的参数和返回值都是Consumer类型,那么就可以实现效果,首先做一个动作,然后再做一个动作,实现两个动作的组合,这个时候就需要用到Consumer接口中的andThen方法。
实现组合需要用到多个lambda表达式,andThen方法的语义是进行一步一步操作,例如:
public class ConsumerDemo2 {
public static void convert(String string, Consumer<String> con1, Consumer<String> con2){
con1.andThen(con2).accept(string); //先执行动作con1再执行动作con2
}
public static void main(String[] args) {
convert("Hello",(string)->{
String lowerCase = string.toLowerCase();
System.out.println(lowerCase); //hello
},(string)->{
String upperCase = string.toUpperCase();
System.out.println(upperCase); //HELLO
});
}
}
- 实现格式化数据
需求:将数据String[] info = {"刘泽煜,22","杜丫头,20"};
数组中的每个信息,格式化为姓名:年龄,例如 刘泽煜:22
public class ConsumerDemo3 {
public static void form(String[] strs, Consumer<String>con1,Consumer<String> con2){
//将数组中的每一个元素进行
for (String str : strs) {
con1.andThen(con2).accept(str);
}
}
public static void main(String[] args) {
String[] info = {
"刘泽煜,22","杜丫头,20"};
form(info,(string)->{
String name = string.split(",")[0];
System.out.print(name+":");
},(string)->{
String age = string.split(",")[1];
System.out.println(age);
});
}
}
2.2.3 Predicate接口
我们需要对某个数据进行判断,使用抽象方法test(),然后得到布尔值,这个时候可以使用Predicate接口,Predicate也是java.util.function
报下的类
- 抽象方法test()
public class PredicateDemo1 {
public static void testLong(String str, Predicate<String> predicate){
boolean test = predicate.test(str);
System.out.println(test); //true
}
public static void main(String[] args) {
testLong("ILoveJava",(String str)->{
return str.length() > 5;
});
}
}
- 默认方法and
and函数表示并且,如果两个条件都满足就返回true,否则false
举例:判断字符串的长度是否大于5,第一个字符是不是为L
public class PredicateDemo2 {
public static void testAnd(String str, Predicate<String> p1,Predicate<String> p2){
boolean test = p1.and(p2).test(str);
System.out.println(test);
}
public static void main(String[] args) {
testAnd("Liuzeyu",(String str)->{
return str.length() > 5;
},(String str)->{
return str.charAt(0) == 'L';
});
}
}
- 默认方法or
或者的意思,满足条件之一即可返回true - 默认方法negate
取反。
练习:
数组中有多条姓名+性别信息,请通过Predicate接口实习信息的筛选,筛选到ArrayList集合中,需要同时满足以下条件:
- 必须为女生
- 名字为4个字
public class PredicateDemo3 {
public static void main(String[] args) {
String[] infos = {
"迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
ArrayList<String> list = getArrayList(infos, (String str) -> {
return str.split(",")[0].length() == 4;
}, (String str) -> {
return str.split(",")[1].equals("女");
});
System.out.println(list);
}
public static ArrayList<String> getArrayList(String[] strs, Predicate<String> p1,Predicate<String> p2){
ArrayList<String> info = new ArrayList<>();
for (String str : strs) {
boolean test = p1.and(p2).test(str);
if(test){
info.add(str);
}
}
return info;
}
}
2.2.4 Function接口
Function接口实现的功能是将一种类型转换成另一种类型,使用的是内部的抽象方法R apply(T t)
; t是要转换的对象类型,R是转换后的对象类型
- 抽象方法apply
举例:使用Function接口将String类型转成Integer类型
public class FunctionDemo1 {
public static Integer convert(String str, Function<String,Integer> function){
Integer apply = function.apply(str);
return apply;
}
public static void main(String[] args) {
Integer integer = convert("123213", (String str) -> {
return Integer.parseInt(str);
});
System.out.println(integer+10);
}
}
- 默认方法andThen
使用andThen进行组合操作
举例:将String类型的123转换成Integer类型后,将结果加10,结果再转换成String类型
public class FunctionDemo2 {
public static String convert(String str,
Function<String,Integer> fun1,Function<Integer,String> fun2){
return fun1.andThen(fun2).apply(str);
}
public static void main(String[] args) {
String convert = convert("123", (String str) -> {
return Integer.parseInt(str) + 10;
}, (Integer integer) -> {
return String.valueOf(integer);
});
System.out.println(convert);
}
}
-
练习:
请使用 Function 进行函数模型的拼接,按照顺序需要执行的多个函数操作为:String str = “赵丽颖,20”;
- 将字符串截取数字年龄部分,得到字符串;
- 将上一步的字符串转换成为int类型的数字;
- 将上一步的int数字累加100,得到结果int数字。
public class FunctionDemo3 {
public static String convert(String str,
Function<String,String> fun1,
Function<String,Integer> fun2,
Function<Integer,String> fun3){
return fun1.andThen(fun2).andThen(fun3).apply(str);
}
public static void main(String[] args) {
String str = "赵丽颖,20";
String convert = convert(str, (String s) -> {
return s.split(",")[1];
}, (String s) -> {
return Integer.parseInt(s) + 100;
}, (Integer i) -> {
return String.valueOf(i);
});
System.out.println(convert); //120
}
}
3. Stream流
谈到流,可能第一时间想到的是我们之前学过的IO流,但实际上Stream流和IO流并不是一个东西,Stream流是JDK 1.8引入的,得益于lambda表达式所带来的函数式编程,目的是解决集合类存在的弊端。
3.1 传统集合类存在的弊端
需求:如果要完成:
- 首先筛选所有姓张的人;
- 然后筛选名字有三个字的人;
- 最后进行对结果进行打印输出。
使用传统的集合类操作的话:
public class StreamDemo1 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
//首先筛选所有姓张的人;
List<String> list2 = new ArrayList<>();
for (String s : list) {
if(s.startsWith("张")){
list2.add(s);
}
}
//然后筛选名字有三个字的人;
List<String> list3 = new ArrayList<>();
for (String s : list2) {
if(s.length() == 3){
list3.add(s);
}
}
//最后进行对结果进行打印输出。
for (String s : list3) {
System.out.println(s);
}
}
}
可见,代码量繁重,而且冗余,用到了多个循环,很影响性能。
3.2 Stream流可以解决的弊端
public class StreamDemo2 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
list.stream().filter((s)->s.startsWith("张"))
.filter((s)->s.length() == 3)
.forEach(s -> System.out.println(s));
}
}
从代码量来看,确实少了很多,变得更加简洁了。
3.3 Stream流思想的简单概述
切勿将此处的流与IO流混为一谈,这是两个不同的东西,Stream流类似于车间的流水线,每一个物品经历流水线可能会经过一些步骤:如加工步骤,包装,拆分…等等步骤。集合元素在Stream上也会同样尽力一些处理步骤,这个过程集合元素本身没有发生改变,类似于copy一个集合的副本放在流水线上
进行一些操作,在Stream中不会存储任何元素,它也类似于来自于数据源额元素队列,数据源可以是集合,数组等。
与之前的Collection操作不同,Stream流还有两个基础的特征:
- Pipelining:中间操作每次都会返回流本身,这样多个操作可以串联成一个管道,如同流式风格。
- 内部迭代:以前对集合的遍历一般都是采用迭代器或增强for循环,采用的的外部迭代方式,而Stream流提供的是内部迭代的方式,流可以直接调用遍历方法。
当你使用一个流的时候,包括3个步骤:获取数据源->数据转换->执行操作获取想要的结果,每次转换原有的Stream对象不变,返回一个新的Stream对象。可以有多次转换,这就允许对其操作可以像链条一样排列,变成一个管道。
3.4 根据不同集合类型获取流
public class StreamDemo1 {
public static void main(String[] args) {
//根据Collectioin获取的流
List<String> list = new ArrayList<String>();
Stream<String> stream1 = list.stream();
Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();
//根据Map获取流
Map<String,String> map = new HashMap<>();
Set<String> keySet = map.keySet();
Stream<String> stream3 = keySet.stream();
Set<Map.Entry<String, String>> entrySet = map.entrySet();
Stream<Map.Entry<String, String>> stream4 = entrySet.stream();
Collection<String> values = map.values();
Stream<String> stream5 = values.stream();
//根据数组获取流
String[] names = new String[3];
names[0]="xxx";
names[1]="yyy";
Stream<String> stream6 = Arrays.stream(names);
}
}
运行结果:
3.5 流的常用方法
流模型的操作很丰富,主要的操作方法可以分为两类:
- 延迟方法:返回值类型仍然是Stream类型,因此支持链式调用,除了终结方法外,其它的方法都属于延迟方法。
- 终结方法:返回值不再是Stream类型的方法,不支持链式调用,终结方法有foreach和count。
3.5.1 forEach:逐一处理
public class StreamDemo2 {
public static void main(String[] args) {
String[] names = {
"liuzeyu","duyanting","jay"};
Stream<String> stream = Stream.of(names);
// stream.forEach((String name)->{
// System.out.println(name);
// });
stream.forEach(name-> System.out.println(name));
}
}
运行结果:
3.5.2 count:统计个数
public class StreamDemo3 {
public static void main(String[] args) {
Integer[] ids = {
1,2,3,5,6};
Stream<Integer> stream = Stream.of(ids);
long count = stream.count();
System.out.println(count); //5
}
}
3.5.3 filter:过滤
用到Predicate<T>
接口函数
public class StreamDemo4 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
Stream<String> stream = list.stream();
stream.filter((String name)->{
return name.startsWith("张");})
.filter((String name)->{
return name.length() == 3;})
.forEach((String name)->{
System.out.println(name);
});
}
}
filter每个操作都会返回一个新的Stream对象。
运行结果:
3.5.4 map:映射
用到Function<T,R>
函数式接口
public class StreamDemo5 {
public static void main(String[] args) {
Integer[] ids = {
1,2,3,5,6};
//将Integer类型转换成String
Stream<Integer> stream = Stream.of(ids);
Stream<String> stringStream = stream.map((Integer id) -> {
return String.valueOf(id);
});
stringStream.forEach(name-> System.out.println(name));
}
}
运行结果:
3.5.5 limit:提取前几个
public class StreamDemo6 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
Stream<String> stream = list.stream();
Stream<String> limit = stream.limit(3);
limit.forEach(name-> System.out.println(name));
}
}
运行结果:
3.5.6 skip:跳过前几个
public class StreamDemo7 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
Stream<String> stream = list.stream();
Stream<String> skip = stream.skip(2);
skip.forEach(name-> System.out.println(name));
}
}
运行结果:
3.5.7 concat:组合
public class StreamDemo8 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
Integer[] ids = {
1,2,3,5,6};
Stream<String> stream1 = list.stream();
Stream<Integer> stream2 = Stream.of(ids);
Stream<? extends Serializable> concat = Stream.concat(stream1, stream2);
concat.forEach(name-> System.out.println(name));
}
}
运行结果:
3.6 练习
需求:
- 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
- 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
- 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
- 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
- 将两个队伍合并为一个队伍;存储到一个新集合中。
- 根据姓名创建 Person 对象;存储到一个新集合中。
- 打印整个队伍的Person对象信息。
public class Person {
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
....代码省略
public class ListDemo {
public static void main(String[] args) {
//第一支队伍
ArrayList<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("石破天");
one.add("石中玉");
one.add("老子");
one.add("庄子");
one.add("洪七公");
//第二支队伍
ArrayList<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("赵丽颖");
two.add("张三丰");
two.add("尼古拉斯赵四");
two.add("张天爱");
two.add("张二狗");
Stream<String> stream1 = one.stream();
Stream<String> stream2 = two.stream();
//利用流思想
//队伍1
Stream<String> limit = stream1.filter(name -> name.length() == 3).limit(3);
//队伍2
Stream<String> skip = stream2.filter(name -> name.startsWith("张")).skip(2);
//合为一只队伍
Stream<String> concat = Stream.concat(limit, skip);
//存储到Person对象中
List<Person> persons = new ArrayList<>();
concat.forEach(name->persons.add(new Person(name)));
for (Person person : persons) {
System.out.println(person);
}
}
}
运行结果:
4. 方法引用(不好理解)
在使用lambda表达式的时候,我们实际传递进去的代码就是一种解决方案,拿什么参数就做什么操作,那么考虑另外一种情况,如果lambda中所指定的操作方案,已经有地方存在相同的方案,那是否有必要重复再写一份呢?
4.1 方法引用
public class MethodRef {
public static void printString(Printable printable){
printable.printMsg("Hello Java");
}
public static void main(String[] args) {
// printString((String str)->{
// System.out.println(str);
// });
//使用方法引用
printString(System.out::print);
}
}
interface Printable{
void printMsg(String msg);
}
printString(System.out::print);
传递的参数System.out::print
,很奇怪是吧,为什么传递这样一个参数,就可以替代(String str)->{ System.out.println(str); }
- 使用lambda表达式获取到传递的参数后,再将它传递给System.out.println(str);
- 而使用方法引用,是直接让System.out中的println来取代lambda表达式,两种写法执行效果完全一致,而第二种方法引用的写法复用了已有的方案,更加简洁。
注意:lambda表达式中传递的参数<mark>一定是方法引用中那个方法可以接收的参数类型</mark>,否则会抛出异常。
再看一个例子:
public class MethodRef {
public static void printString(Printable printable){
printable.printMsg(3434);
}
public static void main(String[] args) {
// printString((Integer i)->{
// System.out.println(i);
// });
//使用方法引用
printString(System.out::print);
}
}
interface Printable{
void printMsg(Integer msg);
}
可见无论使用lambda表达式还是使用方法引用,都是根据上下文推导出,接收的相应参数类型,来输出相应的参数值。
4.2 对象名引用成员方法
这也是一种常见的用法,准备好MethodRefObject类
public class MethodRefObject {
public void printUpperCase(String msg){
System.out.println(msg);
}
}
函数式接口
interface Printable2{
void print(String msg);
}
那么当需要使用printUpperCase方法来替代Printable2 接口的lambda表达式的时候,就已经具有了MethodRefObject类的对象实例,所以可以通过对象类引用方法名:
public class MethodRef2 {
public static void pringString(Printable2 printable2){
printable2.print("Hello");
}
public static void main(String[] args) {
//1.使用lambda表达式
// pringString((String msg)->{
// MethodRefObject mfo = new MethodRefObject();
// mfo.printUpperCase(msg);
// });
//2.使用对象的方法引用
MethodRefObject mfo = new MethodRefObject();
pringString(mfo::printUpperCase);
}
}
4.3 通过类型引用静态方法
同样的方法,当需要使用abs来替代Calcable 接口lambda表达式的时候,由于abs是静态方法,于是可以使用类名调用
public class MethodRef3 {
public static void method(int num,Calcable cal){
System.out.println(cal.calc(num));
}
public static void main(String[] args) {
//1.使用lambda表达式
// method(10,(int num)->{
// return Math.abs(num);
// });
//method(-10, num-> Math.abs(num));
//2. 使用方法引用
method(-1,Math::abs);
}
}
@FunctionalInterface
interface Calcable{
int calc(int sum);
}
4.4 super引用父类的方法
super引用父类的方法:super:父类方法
准备父类:
public class Human {
void sayHello(){
System.out.println("Hello....");
}
}
准备函数式接口:
@FunctionalInterface
public interface Greetable {
void greet();
}
准备子类:
public class Man extends Human {
@Override
void sayHello() {
super.sayHello();
}
//定义method,传递参数
public void method(Greetable g){
g.greet();
}
public void show(){
//调用method方法,使用lambda表达式
method(()->{
//创建父类Human对象,调用sayHello方法
new Human().sayHello();
});
//使用方法引用
method(super::sayHello);
}
public static void main(String[] args) {
new Man().show();
}
}
super::sayHello
的作用和使用lambda表达式一样。
4.5 this引用本类的成员方法
this引用本类的成员方法采用: this:方法名
函数式接口:
@FunctionalInterface
public interface Richeable {
void buy();
}
Husband 类:
package methodreference.thisRef;
public class Husband {
private void marry(Richeable r){
r.buy();
}
private void byHouse(){
System.out.println("买套新房...");
}
public void beHappy(){
marry(()->{
byHouse(); //买套新房...
});
marry(()->{
this.byHouse(); //买套新房...
});
//使用方法引用
marry(this::byHouse); //买套新房...
}
public static void main(String[] args) {
new Husband().beHappy();
}
}
4.6 类的构造器的引用
构造器的引用采用的是:类名: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;
}
}
准备函数式接口:
@FunctionalInterface
public interface PersonBuilder {
public Person buildPerson(String name);
}
测试类:
public class Test {
public static void printName(String name,PersonBuilder pb){
System.out.println(pb.buildPerson(name).getName());
}
public static void main(String[] args) {
printName("张三丰",(String name)->{
return new Person(name);
});
//使用方法引用
printName("张无忌",Person::new);
}
}
4.7 数组构造器的引用
数组也是Object的子类,所以同样具有构造器,只是引用的方法有所不同,采用的是:数组类型[]:new
函数式接口:
@FunctionalInterface
public interface ArrayBuilder {
//根据数组长度创建整形数组
int[] buidArray(int len);
}
测试类:
public class Test {
private static int[] initArray(int len,ArrayBuilder ab){
return ab.buidArray(len);
}
public static void main(String[] args) {
int[] array1 = initArray(10, (len) -> {
return new int[len];
});
int[] array2 = initArray(12, int[]::new);
System.out.println(array1.length);
System.out.println(array2.length);
}
}
参考:黑马视频+笔记