文章目录

  • 1. Stream流
    • 1.1 引言
    • 1.2 流式思想概述
    • 1.3 获取流
    • 1.4 常用方法
    • 1.5 练习:集合元素处理(传统方式)
    • 1.6 练习:集合元素处理(Stream方式)
  • 2. 方法引用
    • 2.1 冗余的Lambda场景
    • 2.2 问题分析
    • 2.3 用方法引用改进代码
    • 2.4 方法引用符
    • 2.5 通过对象名引用成员方法
    • 2.6 通过类名称引用静态方法
    • 2.7 通过super引用成员方法
    • 2.8 通过this引用成员方法
    • 2.9 类的构造器引用
    • 2.10 数组的构造器引用

1. Stream流

说到Stream便容易想到I/O Stream,而实际上,谁规定“流”就一定是“IO流”呢?在Java 8中,得益于Lambda所带
来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。

1.1 引言

传统集合的多步遍历代码
几乎所有的集合(如 Collection 接口或Map 接口等)都支持直接或间接的遍历操作。而当我们需要对集合中的元素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。例如:XXX 这是一段非常简单的集合遍历操作:对集合中的每一个字符串都进行打印输出操作。
循环遍历的弊端
Java 8的Lambda让我们可以更加专注于做什么(What),而不是怎么做(How),这点此前已经结合内部类进行了对比说明。现在,我们仔细体会一下上例代码,可以发现:

  • for循环的语法就是“怎么做”
  • for循环的循环体才是“做什么”

为什么使用循环?因为要进行遍历。但循环是遍历的唯一方式吗?遍历是指每一个元素逐一进行处理,而并不是从
第一个到最后一个顺次处理的循环。前者是目的,后者是方式。

试想一下,如果希望对集合中的元素进行筛选过滤:

  1. 将集合A根据条件一过滤为子集B;
  2. 然后再根据条件二过滤为子集C

那怎么办?在Java 8之前的做法可能为:
使用传统方式,遍历集合,对集合中的数据进行过滤:

public class Demo01List {public static void main(String[] args) {//创建一个List集合,存储姓名List<String> list = new ArrayList<>();list.add("张无忌");list.add("周芷若");list.add("赵敏");list.add("张强");list.add("张三丰");//对list集合中的元素进行过滤,只要以张开头的元素,存储到一个新的集合中List<String> listA = new ArrayList<>();for (String s : list) {if(s.startsWith("张")){listA.add(s);}}//对listA集合进行过滤,只要姓名长度为3的人,存储到一个新的集合中List<String> listB = new ArrayList<>();for (String s : listA) {if(s.length()==3){listB.add(s);}}//遍历listB集合、for (String s : listB) {System.out.println(s);}}
}

这段代码中含有三个循环,每一个作用不同:

  1. 首先筛选所有姓张的人;
  2. 然后筛选名字有三个字的人;
  3. 最后进行对结果进行打印输出。

每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?不是。循
环是做事情的方式,而不是目的。另一方面,使用线性循环就意味着只能遍历一次。如果希望再次遍历,只能再使
用另一个循环从头开始。

那,Lambda的衍生物Stream能给我们带来怎样更加优雅的写法呢?

Stream流的方式优化
使用Stream流的方式,遍历集合,对集合中的数据进行过滤:
Stream流逝JDK1.8之后出现的,关注的是做什么,而不是怎么做:其实就是解决集合过滤问题

public class Demo02Stream {public static void main(String[] args) {//创建一个List集合,存储姓名List<String> list = new ArrayList<>();list.add("张无忌");list.add("周芷若");list.add("赵敏");list.add("张强");list.add("张三丰");//对list集合中的元素进行过滤,只要以张开头的元素,存储到一个新的集合中//对listA集合进行过滤,只要姓名长度为3的人,存储到一个新的集合中//遍历listB集合、list.stream().filter(name->name.startsWith("张")).filter(name->name.length()==3).forEach(name-> System.out.println(name));}
}

1.2 流式思想概述

注意:请暂时忘记对传统IO流的固有印象!
整体来看,流式思想类似于工厂车间的“生产流水线”。

当需要对多个元素进行操作(特别是多步操作)的时候,考虑到性能及便利性,我们应该首先拼好一个“模型”步骤方案,然后再按照方案去执行它。

这张图中展示了过滤、映射、跳过、计数等多步操作,这是一种集合元素的处理方案,而方案就是一种“函数模
型”。图中的每一个方框都是一个“流”,调用指定的方法,可以从一个流模型转换为另一个流模型。而最右侧的数字3是最终结果。

这里的 filter 、 map 、 skip 都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法 count执行的时候,整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性。

备注:“Stream流”其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何
元素(或其地址值)。

Stream(流)是一个来自数据源的元素队列

  • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
  • 数据源 流的来源。 可以是集合,数组 等。

和以前的Collection操作不同, Stream操作还有两个基础的特征:

  • Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(flfluent
    style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
  • 内部迭代: 以前对集合遍历都是通过Iterator或者增强for的方式, 显式的在集合外部进行迭代, 这叫做外部迭
    代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法。

当使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)→ 数据转换→执行操作获取想要的结
果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。

1.3 获取流

java.util.stream.Stream 是Java 8新加入的最常用的流接口。
(这并不是一个函数式接口。) 获取一个流非常简单,有以下几种常用的方式:
- 所有的 Collection 集合都可以通过 stream 默认方法获取流;
default Stream stream()
- Stream 接口的静态方法 of 可以获取数组对应的流。
static Stream of (T… values)
参数是一个可变参数,那么我们就可以传递一个数组

public class Demo02GetStream {public static void main(String[] args) {/*根据Collection获取流*///把集合转换为Stream流List<String> list = new ArrayList<>();Stream<String> stream1 = list.stream();Set<String> set = new HashSet<>();Stream<String> stream2 = set.stream();/*根据Map获取流*/Map<String,String> map = new HashMap<>();//获取键,存储大一个Set集合中Set<String> keySet = map.keySet();Stream<String> stream3 = keySet.stream();//获取值,存储到Collection集合中Collection<String> values = map.values();Stream<String> stream4 = values.stream();//获取键值对(键与值映射关系 entrySet)Set<Map.Entry<String, String>> entries = map.entrySet();Stream<Map.Entry<String, String>> stream5 = entries.stream();/*根据数组获取流*///数组转换为Stream流Stream<Integer> stream6 = Stream.of(1, 2, 3, 4, 5, 6);//可变参数,可以传递数组Integer[] arr = {1,2,3,4,5,6};Stream<Integer> stream7 = Stream.of(arr);String[] arr2 = {"a","bb","ccc"};Stream<String> stream8 = Stream.of(arr2);}
}

1.4 常用方法

流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:

  • 延迟方法:返回值类型仍然是Stream接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方
    法均为延迟方法。)延迟方法,返回的是新的Stream流
  • 终结方法:返回值类型不再是Stream接口自身类型的方法,因此不再支持类似 StringBuilder 那样的链式调用。本小节中,终结方法包括count 和 forEach 方法。终极方法Stream流使用后就不能再使用了,已经关闭了

备注:本小节之外的更多方法,请自行参考API文档。

逐一处理:forEach

虽然方法名字叫 forEach ,但是与for循环中的“for-each”昵称不同。

Stream流中的常用方法_forEach
void forEach(Consumer<? super T> action);
该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理
Consumer接口是一个消费型的函数式接口,可以传递Lambda表达式,消费数据
Consumer接口中包含抽象方法void accept(T t),意为消费一个指定泛型的数据。

简单记:
forEach方法,用来遍历流中的数据
是一个终结方法,遍历之后就不能继续调用Stream流中的其他方法

public class Demo02Stream_forEach {public static void main(String[] args) {//获取一个Stream流Stream<String> stream = Stream.of("张三", "李四", "王五", "赵六", "田七");//使用Stream流中的方法forEach对Stream流中的数据进行遍历/*stream.forEach((String name)->{System.out.println(name);});*///优化Lambda表达式stream.forEach(name->System.out.println(name));}
}

过滤:fifilter

可以通过 filter 方法将一个流转换成另一个子集流。方法签名:


Stream<T> filter(Predicate<? super T> predicate);

该接口接收一个 Predicate函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nLWZRoxj-1658145205268)(en-resource://database/4657:1)]

Stream流中的常用方法_filter:用于对Stream流中的数据进行过滤。

Stream filter(Predicate<? super T> predicate);
filter方法的参数predicate是一个函数式接口,所以可以传递Lambda表达式,对数据进行过滤。

Predicate中的抽象方法:
boolean test(T t);

public class Demo03Stream_Filter {public static void main(String[] args) {//创建一个Stream流Stream<String> stream = Stream.of("张三丰", "张翠山", "赵敏", "周芷若", "张无忌");//对Stream流中的元素进行过滤,只要姓张的人/*Stream<String> stream2 = stream.filter((String name)->{return name.startsWith("张");});*///优化一下Stream<String> stream2 = stream.filter(name->name.startsWith("张"));//遍历一个Stream2流stream2.forEach(name-> System.out.println(name));}
}

Stream流的特点:只能使用一次
简单记:Stream流属于管道流,只能被消费(使用)一次
第一个Stream流,调用完毕方法,数据就会流转到下一个Stream上
而这时第一个Stream流已经使用完毕,就会关闭
所以第一个Stream流就不能再调用方法
IllegalStateException: stream has already been operated upon or closed

stream.forEach(name-> System.out.println(name));
//就会抛出异常,说这个流已经关闭

映射:map
如果需要将流中的元素映射到另一个流中,可以使用map方法,
Stream map(function<? super T, ? extends R> mapper);
该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一个R类型的流
Function中的抽象方法:
R apply(T t);

这可以将一种T类型转换成为R类型,而这种转换的动作,就称为“映射”。

public class Demo04Stream_Map {public static void main(String[] args) {//获取一个String类型的Stream流Stream<String> stream = Stream.of("1", "2", "3", "4");//使用map方法,把字符串类型的整数,转换(映射)为Integer类型的整数/*Stream<Integer> stream2 = stream.map((String s)->{return Integer.parseInt(s);});*///优化一下Stream<Integer> stream2 = stream.map(s->Integer.parseInt(s));//遍历一下stream2流stream2.forEach(i-> System.out.println(i));}
}

统计个数:count

正如旧集合 Collection 当中的 size 方法一样,流提供 count 方法来数一数其中的元素个数。

Stream流中的常用方法_count:用于统计Stream流中元素的个数
long count();
count方法是一个终结方法,返回值是一个long类型的整数,所以不能再继续调用Stream流中的其他方法了

public class Demo05Stream_count {public static void main(String[] args) {//获取一个Steam流ArrayList<Integer> list = new ArrayList<>();list.add(1);list.add(2);list.add(3);list.add(4);list.add(5);list.add(6);list.add(7);Stream<Integer> stream = list.stream();long count = stream.count();System.out.println(count);//7}
}

取用前几个:limit
Stream流中的常用方法_limit:用于截取流中的元素
limit方法可以对流进行截取,只取用前n个。方法签名:
Stream limit(long maxSize);
参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-30LmFFkN-1658145205268)(en-resource://database/4663:1)]
limit方法是一个延迟方法,只是对流中的元素进行截取,返回的是一个新的流,所以可以调用Stream流中的其他方法

public class Demo06Stream_limit {public static void main(String[] args) {//获取一个Stream流String[] arr = {"美羊羊","喜洋洋","懒洋洋","飞太郎","红太狼"};Stream<String> stream = Stream.of(arr);//使用limit方法对stream流中的元素进行截取,只要前三个元素Stream<String> stream2 = stream.limit(3);//进行遍历stream2流stream2.forEach(name-> System.out.println(name));}
}

跳过前几个:skip

Stream流中的常用方法_skip:用于跳过元素
如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流:
Stream skip(long n);
如果流的当前长度大于n,则跳过前n个,否则将会得到一个长度为0的空流

public class Demo07Stream_skip {public static void main(String[] args) {//获取一个Stream流Stream<String> stream = Stream.of("张三丰", "喜洋洋", "美羊羊", "红太狼", "灰太狼");//调用skip方法,跳过第一号元素张三丰Stream<String> stream2 = stream.skip(3);//遍历一下stream3流stream2.forEach(name-> System.out.println(name));}
}

组合:concat
Stream流中常用方法_concat:用于把流组合到一起

如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat :

static <T> Stream <T> concat(Stream<? extends T>a, Stream<? extends T> b)
public class Domo08Stream_concat {public static void main(String[] args) {//创建一个Stream流Stream<String> stream1 = Stream.of("张三丰", "张翠山", "赵敏", "周芷若", "张无忌");//获取一个Stream流String[] arr = {"美羊羊","喜洋洋","懒洋洋","飞太郎","红太狼"};Stream<String> stream2 = Stream.of(arr);//把以上两个流组合为一个流Stream<String> concat = Stream.concat(stream1, stream2);//遍历一下concat流concat.forEach(name-> System.out.println(name));}
}

1.5 练习:集合元素处理(传统方式)

练习:集合元素处理(传统方式)
题目现在有两个 ArrayList 集合存储队伍当中的多个成员姓名,
要求使用传统的for循环(或增强for循环)依次进行以 下若干操作步骤:

  1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
  2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
  3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
  4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
  5. 将两个队伍合并为一个队伍;存储到一个新集合中。
  6. 根据姓名创建 Person 对象;存储到一个新集合中。
  7. 打印整个队伍的Person对象信息。
public class Demo01StreamTest {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("洪七公");//1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。ArrayList<String> one2 = new ArrayList<>();for (String name : one) {if(name.length()==3){one2.add(name);System.out.println(name);}}System.out.println("--------------------");//2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。ArrayList<String> one3 = new ArrayList<>();for (int i = 0; i < 3; i++) {one3.add(one2.get(i));System.out.println(one2.get(i));//i的值 0 1 2}System.out.println("---------------------");//第二支队伍ArrayList<String> two = new ArrayList<>();two.add("古力娜扎"); two.add("张无忌");two.add("赵丽颖"); two.add("张三丰");two.add("尼古拉斯赵四"); two.add("张天爱");two.add("张二狗");//3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。ArrayList<String> two2 = new ArrayList<>();for (String name : two) {if(name.startsWith("张")){two2.add(name);System.out.println(name);}}System.out.println("---------------------");//4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。ArrayList<String> two3 = new ArrayList<>();for (int i = 2; i < two2.size(); i++) {two3.add(two2.get(i));System.out.println(two2.get(i));//i不包含0 1}System.out.println("---------------------");//5. 将两个队伍合并为一个队伍;存储到一个新集合中。ArrayList<String> all = new ArrayList<>();all.addAll(one3);all.addAll(two3);for (String s : all) {System.out.println(s);}//6. 根据姓名创建 Person 对象;存储到一个新集合中。ArrayList<Person> list = new ArrayList<>();for (String name : all) {list.add(new Person(name));}//7. 打印整个队伍的Person对象信息。System.out.println("--------------------");for (Person person : list) {System.out.println(person);}}
}

1.6 练习:集合元素处理(Stream方式)

public class Demo01StreamTest {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("洪七公");//1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。//2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。Stream<String> oneStream = one.stream().filter(name -> name.length() == 3).limit(3);//第二支队伍ArrayList<String> two = new ArrayList<>();two.add("古力娜扎"); two.add("张无忌");two.add("赵丽颖"); two.add("张三丰");two.add("尼古拉斯赵四"); two.add("张天爱");two.add("张二狗");//3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。//4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。Stream<String> twoStream = two.stream().filter(name -> name.startsWith("张")).skip(2);//5. 将两个队伍合并为一个队伍;存储到一个新集合中。//6. 根据姓名创建 Person 对象;存储到一个新集合中。//7. 打印整个队伍的Person对象信息。Stream.concat(oneStream,twoStream).map(name->new Person(name)).forEach(p-> System.out.println(p));}
}

2. 方法引用

在使用了Lambda表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿什么参数做什么操作。那么考虑一种情况:如果我们在Lambda中锁指定的操作方案,已经有地方存在相同的方案,那是否还有必要再写重复逻辑。

2.1 冗余的Lambda场景

来看一个简单的函数式接口以应用Lambda表达式

@FunctionalInterface
public interface Printable {//打印字符串的抽象方法void print(String s);
}

在Printable接口当中唯一的抽象方法print接收一个字符串参数,目的就是为了打印显示它,那么通过Lambda来使用它的代码很简单:

public class Demo01Printable {//定义一个方法,参数传递Printable接口,对字符串进行打印public static void printString(Printable p){p.print("HelloWorld");}public static void main(String[] args) {//调用printString方法,方法的参数Printable接口,是一个函数式接口,所以可以传递Lambda表达式printString(s -> System.out.println(s));}

其中printString方法只管调用Printable接口的print方法,而并不管print方法的具体实现逻辑会将字符串打印到什么地方去。而main方法通过Lambda表达式指定了函数式接口Printable的具体操作方案为:拿到String(类型可导,所以可省略)数据后,在控制台中输出它。

2.2 问题分析

这段代码的问题在于,对字符串进行控制台打印输出的操作方案,明明已经有了现成的实现,那就是System.out对象中的println(String)方法,既然Lambda希望做的事情就是调用println(String)方法,那何必自己手动调用呢?

2.3 用方法引用改进代码

分析:
Lambda表达式的目的,打印参数传递的字符串
把参数s,传递给了System.out对象,调用out对象中的方法println对字符串进行了输出。
注意:
1. System。out对象是已经存在的
2. println方法也是已经存在的
所以我们可以使用方法引用,来优化Lambda表达式
可以使用System.out方法直接引用(调用)println方法

public class Demo01Printable {//定义一个方法,参数传递Printable接口,对字符串进行打印public static void printString(Printable p){p.print("HelloWorld");}public static void main(String[] args) {//调用printString方法,方法的参数Printable接口,是一个函数式接口,所以可以传递Lambda表达式printString(s->System.out.println(s));printString(System.out::println);
}
}

请注意其中的双冒号 :: 写法,这被称为“方法引用”,而双冒号是一种新的语法。

2.4 方法引用符

双冒号 :: 为引用运算符,而它所在的表达式被称为方法引用。如果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的基础,而方法引用是Lambda的孪生兄弟。

下面这段代码将会调用 println 方法的不同重载形式,将函数式接口改为int类型的参数:


@FunctionalInterface
public interface PrintableInteger {void print(int str);
}

由于上下文变了之后可以自动推导出唯一对应的匹配重载,所以方法引用没有任何变化:


blic class Demo03PrintOverload {private static void printInteger(PrintableInteger data) {data.print(1024);
}
public static void main(String[] args) {printInteger(System.out::println);
}
}

这次方法引用将会自动匹配到 println(int) 的
重载形式。

2.5 通过对象名引用成员方法

通过对象名引用成员方法,
使用前提是对象名已经存在的,成员方法也是已经存在的
就可以使用对象名来引用成员方法

public class Demo01ObjectMethodReference {//定义一个方法,方法的参数传递Printable接口public static void printString(Printable p){p.print("Hello");}public static void main(String[] args) {//调用printString方法,方法的参数Printable是一个函数式接口,所以可以传递Lambda表达式printString((str)->{//创建MethodRerObject对象MethodRerObject obj = new MethodRerObject();//调用MethodRerObject对象中的成员方法printUpperCaseStringobj.printUpperCaseString(str);});/*使用方法引用来优化Lambda对象MethodRerObject是已经存在的,成员方法printUpperCaseString也是已经存在的所以,我们可以使用对象名来引用成员方法*/MethodRerObject obj = new MethodRerObject();printString(obj::printUpperCaseString);}
}

2.6 通过类名称引用静态方法

通过类名引用静态成员方法,
类已经存在,静态成员方法也已经存在
就可以通过类名直接引用静态成员方法

public class Demo01StaticMethodReference {//定义一个方法,方法的参数传递要计算的绝对值的整数,和函数式接口Calcable。public static int method(int number,Calcable c){return c.calsAbs(number);}public static void main(String[] args) {//调用method方法,传递计算绝对值的整数和Lambda表达式int num = method(-11,(int a)->{//对参数进行绝对值计算,并返回结果return Math.abs(a);});System.out.println(num);/*使用方法引用,优化Lambda表达式Math类是存在的,abs计算绝对值的静态方法也是存在的,所以我们可以直接通过类名引用静态方法*/int number = method(-10, Math::abs);System.out.println(number);}}

2.7 通过super引用成员方法

如果存在继承关系,当Lambda中需要出现super调用时,也可以使用方法引用进行替代。首先是函数式接口:

/*定义见面的函数式接口 */
@FunctionalInterface
public interface Greetable {void greet();
}

然后是父类 Human 的内容:

/*
定义父类*/
public class Human {//定义一个sayHello方法public void sayHello(){System.out.println("Hello,I'm Human");}
}

最后是子类 Man 的内容,其中使用了Lambda的写法:

/*
定义子类*/
public class Man extends Human{//子类重写父类sayHello方法@Overridepublic void sayHello() {System.out.println("Hello,I'm man");}//定义一个方法传递Greetable接口public void method(Greetable g){g.greet();}public void show(){//调用method方法,方法的参数Greetable接口是一个函数式接口,所以可以传递Lambda/*method(()->{//创建父类Human对象Human h = new Human();//调用父类的sayHello方法h.sayHello();});*///因为有子父类关系,所以存在一个关键字super,代表父类,所以我们可以直接使用super,调用父类的成员方法/*method(()->{super.sayHello();});*//*使用super,引用父类的成员方法super是已经存在的,父类的成员方法sayHello也是已经存在的所以,我们可以直接使用super引用父类的成员方法*/method(super::sayHello);}public static void main(String[] args) {new Man().show();}
}

在这个例子中,下面两种写法是等效的:

  • Lambda表达式: () -> super.sayHello()
  • 方法引用: super::say

2.8 通过this引用成员方法

this代表当前对象,如果需要引用的方法就是当前类中的成员方法,那么可以使用“this::成员方法”的格式来使用方法引用。首先是简单的函数式接口:

/*
定义一个富有函数式接口*/
@FunctionalInterface
public interface Richable {//定义一个想买什么就买什么的方法void buy();
}

下面是一个丈夫 Husband 类:

/*
通过this,引用本类的成员方法*/
public class Husband {//定义一个买房子的方法public void buyHouse(){System.out.println("北京二环内买一套四合院!");}//定义一个结婚的方法,参数传递Richable接口public void marry(Richable r){r.buy();}//定义一个非常高兴的方法public void soHappy(){//调用结婚的方法,方法参数Richable是一个函数式接口,传递Lambda表达式/*marry(()->{//使用this.成员方法,调用本类买房子的方法this.buyHouse();});*//*使用方法引用,优化Lambda表达式this是已经存在的本类的成员方法buyHouse也是已经存在的所以我们可以直接使用this引用本类的成员方法buyHouse*/marry(this::buyHouse);}public static void main(String[] args) {new Husband().soHappy();}
}

在这个例子中,下面两种写法是等效的:

  • Lambda表达式: () -> this.buyHouse()
  • 方法引用: this::buyHouse

2.9 类的构造器引用

由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用类名称::new 的格式表示。首先是一个简单的 Person 类:

public class Person {private String name;public Person() {}public Person(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}

然后是用来创建 Person 对象的函数式接口:

/*
定义一个创建Person对象的函数式接口*/
@FunctionalInterface
public interface PersonBuilder {//定义一个方法,根据传递的姓名,创建Person对象返回Person builderPerson(String name);
}

要使用这个函数式接口,可以通过Lambda表达式:

/*
类的构造器(构造方法)引用*/
public class Demo {//定义一个方法,参数传递姓名和PersonBuilder接口,方法中通过姓名创建Person对象public static void printName(String name,PersonBuilder pb){Person person = pb.builderPerson(name);System.out.println(person.getName());}public static void main(String[] args) {//调用printName方法,方法的参数PersonBuilder接口是一个函数式接口,可以传递LambdaprintName("迪丽热巴",(String name)->{return new Person(name);});/*使用方法引用,优化Lambda表达式构造方法new Person(String name)已知创建对象的方式已知 new就可以使用Person引用new创建对象*/printName("古力拉扎",Person::new);//使用Person类的带参构造方法,通过传递的姓名创建对象}
}

在这个例子中,下面两种写法是等效的:

  • Lambda表达式: name -> new Person(name)
  • 方法引用: Person::new

2.10 数组的构造器引用

数组也是 Object 的子类对象,所以同样具有构造器,只是语法稍有不同。如果对应到Lambda的使用场景中时,
需要一个函数式接口:

/*
定义一个创建数组的函数式接口*/
@FunctionalInterface
public interface ArrayBuilder {//定义一个创建int类型数组的方法,参数传递数组的长度,返回创建好的int类型数组int[] builderArray(int length);}

在应用该接口的时候,可以通过Lambda表达式:

/*
数组的构造器引用*/
public class Demo {/*定义一个方法方法的参数传递创建数组的长度和ArrayBuilder接口方法内部根据传递长度使用ArrayBuilder中的方法,创建数组并返回*/public static int[] createArray(int length,ArrayBuilder ab){return ab.builderArray(length);}public static void main(String[] args) {//调用creteArray方法,传递数组的长度和Lambda表达式int[] array1 = createArray(10,(len)->{//根据数组的长度,创建数组并返回return new int[len];});System.out.println(array1.length);//10/*使用方法引用,优化Lambda表达式已知创建的就是int[]数组数组的长度也是已知的就可以使用方法引用int[]引用new,根据参数传递的长度来创建数组*/int[] arr2 = createArray(10,int[]::new);System.out.println(Arrays.toString(arr2));System.out.println(arr2.length);//10}}

在这个例子中,下面两种写法是等效的:

  • Lambda表达式: length -> new int[length]

  • 方法引用: int[]::new

                                                                                   ——此文档为学习笔记!
    

2022/07/17、18 day10/11:Stream流、方法引用相关推荐

  1. Java基础 Stream流方法引用异常文件

    Stream流 引例 需求:按照下面要求完成集合的创建和遍历 创建一个集合,存储多个字符串元素 1. 把所有以"曹"开头的元素存储到新集合中 2. 把曹开头,长度为3的元素存储到新 ...

  2. Stream流方法引用

    一.对象存在,方法也存在,双冒号引用 1.方法引用的概念: 使用实例: 1.1先定义i一个函数式接口: 1.2定义一个入参参数列表有函数式接口的方法: 1.3调用这个入参有函数式接口的方法: lamb ...

  3. 18种女粉引流方法、效果、评估

    18种女粉引流方法.效果.评估 女粉推广过来的质量.成本.效果评估: 1.通过微信号站街主动加人 这个方法就是现在大家都在玩的群控配上加人软件.成本高.需要买很多手机+群控系统.人力少.流量多.转化率 ...

  4. Stream和方法引用

    Stream和方法引用 1. Stream流 1.1. Stream流引入 Stream流完全不是I/O流,按照流水线处理方式来考虑代码中的思想.JDK1.8 之后,我们拥有了Lambda表达式,让代 ...

  5. Steam流-方法引用

    多次循环,繁琐,过滤数组 1.8之后出的Steam流 List<String> list = new ArrayList<>();list.add("张顺" ...

  6. 2022/07/17 软件设计师错题日志 Day 1

    本次记录中题目来自希赛网2022/01/01软件设计师每日一练 错题1: 10个成员组成的开发小组,若任意两人之间都有沟通路径,则一共有()条沟通路径. A:100  B:90  C:50  D:45 ...

  7. 2022 07 17 第九组 韩文清 职业人生规划

    职业规划 首先找到实习工作,期望薪资在7000左右,在外地奋斗3年以后再回家,或者直接回家在家乡工作 1.如果在外地工作,希望有能力养活自己,并且在养活自己的路上活得越来越好,我自己多努力一些,父母以 ...

  8. Java学习day08--方法引用和Stream流

    Java学习day08--方法引用和Stream流 方法引用和Stream流 方法引用 获取Collection.Map.数组流 Stream流的常用操作方法 方法引用和Stream流 1).流思想: ...

  9. Sream流、方法引用

    目录 Stream流 方法引用 Stream流 Stream(流)是一个来自数据源的元素队列 元素是特定类型的对象,形成一个队列. Java中的Stream并不会存储元素,而是按需计算 数据源 流的来 ...

最新文章

  1. tf.matmul() 和tf.multiply() 的区别
  2. Day 4:PredictionIO——如何创建一个博客推荐器
  3. wordpress漏洞_WordPress XSS漏洞可能导致远程执行代码(RCE)
  4. android imageview 图片切换动画,在Android中以动画方式将ImageView移动到不同的位置...
  5. 2021.08.25学习内容torch.clamp(input, min, max, out=None) → Tensor,torch.mm(matrix multiply)
  6. Android 应用开发 --- popupwindow
  7. 此url不支持http方法get_HTTP 入门
  8. Android EditText的常用技巧
  9. http-server搭建web服务器
  10. 在控制台远程连接mysql数据库时,出现ERROR 2049 (HY000)错误
  11. 【原生JS】web原生文字轮播效果
  12. 28.开始画面和异形窗口
  13. Chrome 76.0.3809.100(正式版本) (64 位) 版本 77.0.3865.90不显示网址中的www怎么解决...
  14. 考研高等数学张宇30讲笔记——第十一讲 多元函数微分学
  15. php自带常量_php中的常量是什么 - php完全自学手册 - php中文网手册
  16. 惠普打印机USB安装成功,但断开USB重新连接时显示脱机无法打印问题(上一版本的驱动程序还在内存中,因此无法加载驱动程序)
  17. 信息系统规划方法-关键成功因素法(CSF)
  18. Strom完整攻略(一)
  19. JAVA图形编程Swing之——JPanel绘图
  20. git 申请合并冲突:rebase 解决合成一条再合并

热门文章

  1. centos7上用nginx安装nextcloud(PHP72)
  2. 扫地机器人测评云鲸_扫地机器人测评,谁才是最强扫地僧
  3. vue锚点链接,react锚点链接
  4. iOS 设置系统UINavigationBar返回按键不显示上一界面的标题。
  5. JQuery获取iframe中window对象的方法-contentWindow
  6. 基于VUE的考勤日历(带图标)
  7. 【AIO】使用ORACLE数据库存储过程发送企业微信群机器人消息
  8. Bootstrap4 导航栏navbar
  9. 堡垒机JumpServer(五):数据库应用权限管理
  10. 65动态版式标题Premiere Pro模板