Stream流、方法引用

Stream流、方法引用,第1张

Stream流、方法引用 一 Stream流

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

循环遍历的弊端 Java 8的Lambda让我们可以更加专注于做什么(What),而不是怎么做(How)

for循环的语法就是“怎么做”

for循环的循环体才是“做什么”

遍历是指每一个元素逐一进行处理,而并不是从第一个到最后一个顺次处理的循环。前者是目的,后者是方式。

Stream流的更优写法:

import java.util.ArrayList;
import java.util.List;
public class Demo03StreamFilter {
public static void main(String[] args) {
List 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(System.out::println);
}
}
1.1 流式思想概述

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

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

1、元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。

2、数据源 流的来源。 可以是集合,数组 等。

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

Pipelining: 中间 *** 作都会返回流对象本身。 这样多个 *** 作可以串联成一个管道, 如同流式风格(fluentstyle)。 这样做可以对 *** 作进行优化, 比如延迟执行(laziness)和短(shortcircuiting)。

内部迭代: 以前对集合遍历都是通过Iterator或者增强for的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法。

1.2 获取流

java.util.Stream是java 8新加入的最常用的流接口。(这并不是一个函数式接口。)

获取一个流非常简单,有以下几种常用的方式:

-所有的Collection集合都可以通过stream默认方法获取流;

default Stream stream ()

-Stream接口的静态方法of可以获取数组对应的流。

static  Stream  of (T...values)

参数是一个可变参数,那么我们就可以传递一个数组

java.util.Map 接口不是Collection 的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流需要分key、value或entry等情况:

java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public class Demo05GetStream {
public static void main(String[] args) {
Map map = new HashMap<>();
// ...
Stream keyStream = map.keySet().stream();
Stream valueStream = map.values().stream();
Stream> entryStream = map.entrySet().stream();
}
}

根据数组获取流 如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以Stream 接口中提供了静态方法of ,使用很简单

import java.util.stream.Stream;
public class Demo06GetStream {
public static void main(String[] args) {
String[] array = { "张无忌", "张翠山", "张三丰", "张一元" };
Stream stream = Stream.of(array);
}
}

Stream流属于管道流,只能别消费(使用)一次

第一个Stream流调用完毕方法,数据就会转到下一个Stream上,儿这个时候第一个Stream流已经使用完毕,关闭了。第一个流就不能再调用方法了。

1.3 Stream流常用方法 1.3.1 map

将一种数据类型转换为另一种类型,就叫映射。 R apply( T t)

import java.util.stream.Stream;
public class Demo08StreamMap {
public static void main(String[] args) {
//创建一个Stream流
Stream stream = Stream.of("1", "2", "3");
Stream stream2 = stream.map(str‐>Integer.parseInt(str));
}
}个数
1.3.2 统计:count

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

这事一个终结方法,返回值是一个Lang类型的整数,不能再继续调用Stream中的其他方法了。

1.3.3 limt方法

用于截取流中的元素。limit方法可以对流进行截取,只取用前n个,方法签名:Stream limit(Long maxSize); 参数是Long类型,如果集合当前长度大于参数则进行截取,不进行 *** 作。limit方法是一个延迟方法,只是对流中的元素进行截取返回的是一个新的流

import java.util.stream.Stream;
public class Demo10StreamLimit {
public static void main(String[] args) {
Stream original = Stream.of("张无忌", "张三丰", "周芷若");
Stream result = original.limit(2);
System.out.println(result.count()); // 2
}
}
1.3.4 skip

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

import java.util.stream.Stream;
public class Demo11StreamSkip {
public static void main(String[] args) {
//获取一个Strem流
Stream original = Stream.of("喜羊羊", "懒羊羊", "美羊羊","灰太狼", "红太狼");
//使用skip方法跳过前3个元素
Stream result = original.skip(2);
System.out.println(result.count()); // 1
}
}
1.3.5 组合:concat

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

备注:这是一个静态方法,与java.lang.String 当中的concat 方法是不同的。

import java.util.stream.Stream;
public class Demo12StreamConcat {
public static void main(String[] args) {
Stream streamA = Stream.of("张无忌");
Stream streamB = Stream.of("张翠山");
Stream result = Stream.concat(streamA, streamB);
}
}
1.4 练习
public class DemoStream {
public static void main(String[] args) {
    //第一支队伍
    ArrayList one = new ArrayList<>();
    one.add("迪丽热巴");
    one.add("宋院桥");
    one.add("苏炳添");
    one.add("王中王");
    one.add("刘德华");
    one.add("老子");
    one.add("庄子");
    one.add("郭富城");
    //,第一个队伍只要名字为三个字的呈圆形姓名,存储到一个新的集合中
    ArrayList one1 = new ArrayList<>();
    for (String s:one)
          {
              if (s.length()==3){
                  one1.add(s);
              }
          }
    ArrayList one2 = new ArrayList<>();
    for (int i = 0; i < 3; i++) {
        one2.add(one1.get(i));
    }

    //第二支队伍
    ArrayList two = new ArrayList<>();
    two.add("古力娜扎");
    two.add("张无忌");
    two.add("赵丽颖");
    two.add("张三丰");
    two.add("尼古拉斯");
    two.add("张天爱");
    two.add("张够大");
    //第二支队伍只要姓张的成员姓名,存储到新的集合中
    ArrayList two1 = new ArrayList<>();
    for (String s:two) {
    if(s.startsWith("张"))   {
        two1.add(s);
    }
    }
    //4,第二支队伍筛选之后不要前两个人,存储到一个新的集合中。

ArrayList two2 = new ArrayList<>();
    for (int i = 2; i  all = new ArrayList<>();
    all.addAll(one2);
    all.addAll(two2);
    //6根据姓名创建Person对象,存储到一个新的集合中。
    ArrayList  list = new ArrayList<>();
    for (String name:all)
    {
     list.add(new Person(name));
    }
   //打印每每一个Person
    for (Person p:list) {
        System.out.println(p);
    }
    }
    }

用Stream流实现,优化

public class Demo01Stream {
public static void main(String[] args) {
    //第一支队伍
    ArrayList one = new ArrayList<>();
    one.add("迪丽热巴");
    one.add("宋院桥");
    one.add("苏炳添");
    one.add("王中王");
    one.add("刘德华");
    one.add("老子");
    one.add("庄子");
    one.add("郭富城");
    //1 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
    //2,第一个队伍筛选之后只要前3个人;存储到一个新集合中
    Stream oneS = one.stream().filter(name->name.length()==3).limit(3);

    //第二支队伍
    ArrayList two = new ArrayList<>();
    two.add("古力娜扎");
    two.add("张无忌");
    two.add("赵丽颖");
    two.add("张三丰");
    two.add("尼古拉斯");
    two.add("张天爱");
    two.add("张够大");
    //第二支队伍只要姓张的成员姓名,存储到一个新的集合中
    //不要前两个人,存储到一个新的集合中
    Stream twoS = two.stream().filter(name->name.startsWith("张")).skip(2);
    //两只队伍合并,存储
    //根据姓名创建Person对象;存储到一个新的集合中
    //打印Person对象
    Stream.concat(oneS,twoS).map(name->new Person(name)).forEach(s-> System.out.println(s));
    };
    }
二 方法引用

在使用Lambda表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿什么参数做什么 *** 作。

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

@FunctionalInterface
public interface Printable {
void print(String str);
}

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

public class Demo01PrintSimple {
private static void printString(Printable data) {
data.print("Hello, World!");
}
public static void main(String[] args) {
printString(s ‐> System.out.println(s));
}
}

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

分析: Lambda表达式的目的,打印参数传递的字符串 把参数s,传递给了System.out对象,调用out对象中的方法println对字符串进行了输出

注意:

1,System.out对象是已经存在的 2,println方法也是已经存在的

所以我们可以使用方法引用来优化Lambda表达式 可以使用System.out直接引用(调用)println方法

public class Demo02PrintRef {
private static void printString(Printable data) {
data.print("Hello, World!");
}
public static void main(String[] args) {
printString(System.out::println);
}

方法引用符

双冒号:: 为引用运算符,而它所在的表达式被称为方法引用。如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 中 传递的参数 一定是方法引用中 的那个方法可以接收的类型,否则会抛出异常

2.1 通过对象名引用成员方法
public class Demo04MethodRef {
//成员方法存在
private static void printString(Printable lambda) {
lambda.print("Hello");
}
public static void main(String[] args) {
//创建对象
MethodRefObject obj = new MethodRefObject();
//方法引用
printString(obj::printUpperCase);
}
}
2.2 通过类名引用静态方法

通过类名引用静态成员方法。

类已经存在,静态方法也已经存在,就可以使用方法引用

例子,去绝对值

public class Demo06MethodRef {
private static void method(int num, Calcable lambda) {
System.out.println(lambda.calc(num));
}
public static void main(String[] args) {
method(‐10, Math::abs);
}
}
2.3 通过super引用成员方法

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

Lambda表达式: () -> super.sayHello()

方法引用: super::sayHello

2.4 通过this引用本类方法

在这个例子中,下面两种写法是等效的: Lambda表达式: () -> this.buyHouse() 方法引用: this::buyHouse

2.5 类的构造器引用

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

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

2.6 数组的构造器引用

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

等效的写法:

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

方法引用: int[]::new

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5695303.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存