java之Stream流和方法引用

目录

Stream流

两种获取流的方式

方法引用

通过各种方式引用成员方法


Stream流

用于解决已有集合类库既有的弊端

例如:List的循环遍历

循环是遍历的一种方式,而目的是遍历

 

循环遍历和Stream流方式遍历的对比

 

public class Demo9 {

    //for循环方式遍历,共使用了3个循环

    @Test

    public void test(){

        List<String> list=new ArrayList<>();

        list.add("张无忌");

        list.add("周芷若");

        list.add("赵敏");

        list.add("张强");

        list.add("张三丰");

        List<String> listA=new ArrayList<>();

        for (String s : list)

            if (s.startsWith("张"))

                listA.add(s);

        List<String> listB=new ArrayList<>();

        for (String s : listA)

            if (s.length()==3)listB.add(s);

        for (String s : listB) {

            System.out.println(s);

        }

    }

    //Stream的更优写法,1.8之后出现

    @Test

    public void test2(){

        List<String> list=new ArrayList<>();

        list.add("张无忌");

        list.add("周芷若");

        list.add("赵敏");

        list.add("张强");

        list.add("张三丰");

        list.stream().filter(name->name.startsWith("张"))

                .filter(name->name.length()==3)

                .forEach(System.out::println);

    }

    /*

结果一样:

    流式思想:

    拼接流式模型:建立一个生产线,按照生产线来生产商品

    上面的代码可以理解为:生产线上只要性张的且名字3个字的商品

    */

}

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

得益于Lambda表达式的延迟执行,只有当终结方法count执行时,整个模型才按照指定策略执行操作,也就是不会造成资源的浪费

元素是特点类型的对象,形成一个队列,Stream不会存储元素,是按需计算

数据源:可以是集合,数组等

Stream操作还有两个基本特征

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

内部迭代:以前对集合的遍历都是迭代器或增强for,在集合外部进行迭代,Stream提供了内部迭代的方式,流可以直接调用遍历方法

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

 

两种获取流的方式

java.util.stream.Stream<T>是java8新加入的常用流接口(非函数式接口)

 

获取流的方式:

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

default Stream<E> stream()

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

static <T> Stream<T> of (T… values)

参数是一个可变参数可以传递一个数组

 

 @Test

    public void test(){

        //把集合转换为Stream

        //先转List集合

        List<String> list=new ArrayList<>();

        Stream<String> stream1=list.stream();

        //Set集合

        Set<String> set=new HashSet<>();

        Stream<String> stream2 = set.stream();

        //Map集合

        Map<String,String> map=new HashMap<>();

        //间接将Map集合转化成Stream流

        //获取键

        Set<String> keySet = map.keySet();

        Stream<String> stream3 = keySet.stream();

        //获取值

        Collection<String> values = map.values();

        Stream<String> stream4 = values.stream();

        //获取键值对

        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);

        //可变参数可以传递数组

        Integer[] arr={1,2,3,4,5};

        Stream<Integer> stream7 = Stream.of(arr);

        //同理字符串也可以

        String [] arr2={"a","b","c"};

        Stream<String> stream8 = Stream.of(arr2);

    }

 

 

public class Demo10 {

    //这个管道流Stream可以理解为将数据向流水线一样一个一个的往前送

    /*

    常用方法

    分为:

        延迟方法:返回值任然是Stream接口自身类型的方法,因此支持链式调用。(除了终结方法其余方法都为延迟方法)

        终结方法:返回值不再是Stream接口自身类型的方法,因此不再支持类似stringBuilder那样的链式调用

        终结方法包括countforEach

    逐一处理:forEach(终结方法)

    void forEach(Consumer<? super T> action);

    该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理

    */

    //forEach演示

    @Test

    public void test2(){

        Stream<String> stream = Stream.of("张三", "李四", "王五", "赵六", "田七");

        //被代替的写法(String name)->System.out.println(name)

        stream.forEach(System.out::println);

    }

    /*

    过滤:filter,可以通过filter方法将一个流转换成另一个子集流

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

    接收一个Predicate函数式接口作为筛选条件

    */

    //filter演示

    @Test

    public void test3(){

        Stream<String> stream1 = Stream.of("张三丰", "张翠山", "张三", "赵敏", "周芷若", "张无忌");

        //过滤掉不姓张的人

        Stream<String> stream2 = stream1.filter((name) -> name.startsWith(""));

        stream2.forEach(System.out::println);

        /*

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

        第一个Stream流调用完毕方法,数据就会流转到下一个Stream上

        而这时第一个Stream流已经使用完毕,就会关闭了

        所以第一个Stream流就不能再调用方法了

        */

        //尝试用第一个Stream流再次调用方法

        stream1.forEach(System.out::println);

    }

 

    /*

    映射:map

    如果需要将流中的元素映射到另一个流中,可以使用map方法

    <R> Stream<R> map(Function<? super T,? extends R> mapper);

    该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换成另一种R类型的流

    */

    //map演示

    @Test

    public void test4(){

        Stream<String> stream = Stream.of("1", "2", "3", "4");

        //被代替的写法(s)->{ return Integer.parseInt(s); }

        Stream<Integer> stream2 = stream.map(Integer::parseInt);

        stream2.forEach(System.out::print);

    }

    /*

    统计个数:count(终结方法)

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

    long count();

    */

    //count演示

    @Test

    public void test5(){

        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);

    }

    /*

    limit方法可以对流进行截取,只取用前n

    Stream<T> limit(long maxSize);

    如果集合当前长度大于参数长度则进行截取,否则不进行截取,否则不进行操作

    */

    //limit演示

    @Test

    public void test6(){

        String [] arr={""};

        Stream<String> stream = Stream.of("1", "2", "3", "4", "5");

        stream.limit(3).forEach(System.out::println);

    }

    /*

    跳过前几个:skip

    如果希望跳过前几个元素,可以使用skip方法获取一个截取后的新流

    Stream<T> skip(long n);

    如果流的当前长度大于n,则跳过前n;否则将会得到一个长度为0的空流

    */

    //skip演示

    @Test

    public void test7(){

        String [] arr={""};

        Stream<String> stream = Stream.of("1", "2", "3", "4", "5");

        stream.skip(3).forEach(System.out::println);

    }

    /*

    组合:concat

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

    static <T> Stream concat(String<? extends T> a,Stream<? extends T> b)

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

    */

    //concat演示

    @Test

    public void test8(){

        Stream<String> stream1 = Stream.of("1", "2", "3", "4", "5");

        Stream<String> stream2 = Stream.of("张三丰", "张翠山", "张三", "赵敏", "周芷若", "张无忌");

        Stream<String> concat = Stream.concat(stream1, stream2);

        concat.forEach(System.out::println);

    }

    //练习

    @Test

    public void test9(){

        ArrayList<String> one=new ArrayList<>();

        one.add("迪丽热巴");

        one.add("宋远桥");

        one.add("苏星河");

        one.add("石破天");

        one.add("石中玉");

        one.add("老子");

        one.add("庄子");

        one.add("洪七公");

        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("张二狗");

        Stream<String> twoStream = two.stream().filter(name -> name.startsWith("张")).skip(2);

        //被代替的写法Stream.concat(oneStream,twoStream).map(name->new Person(name)).forEach(p->System.out.println(p));

        Stream.concat(oneStream, twoStream).map(Person::new).forEach(System.out::println);

    }

}

方法引用

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

考虑一种情况:若我们在Lambda中所指定的操作方案,已经有地方存在相同的方案,那么还有必要重复写吗?

 

 

public class Demo1 {

    public static void printString(Printable p){

        p.print("HelloWorld");

    }

    @Test

    public void test(){

        printString((s)->System.out.println(s));

 

    /*

    分析:该Lambda表达式的目的,打印参数传递的字符串

    把s传递给了System.out对象,调用out对象中的方法println对字符串进行了输出

    注意:

        1、System.out对象是已经存在的

        2、println方法也是已经存在的

    所以我们可以使用方法引用来优化Lambda

    */

        printString(System.out::println);

        //::是方法引用的运算符,如果Lambda要表达的函数方案已经存在于某个方法的实现中

        //那么通过::来引用该方法作为Lambda的替代者

    //注意:Lambda中传递的参数一定是方法引用中的那个方法可以接收的类型,否则会抛出异常

    }

}

两次打印结果一致

 

上面例子中使用的函数式接口:

/*

打印的函数式接口

*/

@FunctionalInterface

public interface Printable {

    void print(String s);

}

通过各种方式引用成员方法

/*引用对象方法案例*/

public class MethodRerObj {

    //定义一个成员方法,传递字符串,把字符串按照大写输出

    public void  printUpperCase(String str){

        System.out.println(str.toUpperCase());

    }

    //不想再写一个接口了,直接使用Consumer接口

    public static void printString(Consumer<String> con){

        String s="hello";

        con.accept(s);

    }

    public static void main(String[] args) {

        MethodRerObj obj=new MethodRerObj();

        printString(obj::printUpperCase);

    }

    //通过类名引用静态方法

    //该方法的参数传递要计算绝对值的整数,利用Function接口实现

    public static int method(int number, Function<Integer,Integer> fun){

        return fun.apply(number);

    }

    @Test

    public void test1(){

        int number2= method(-10,Math::abs);

        System.out.println(number2);

    }

 

//此例子有点抽象!,其中写了一个函数式接口,一个父类包含一个见面方法,一个子类继承自父类

//其中子类重写了父类的见面方法,子类中还包含一个show方法测试super关键字的引用

//在类的外部使用@Test注解,创建子类对象进行运行,结果表名super::sayHello调用的是父类的方法

    //通过super引用成员方法

    //当存在继承关系时,当Lambda中需要出现super调用时,也可以使用方法引用进行代替

    //定义一个父类

    class Fu{

        //该构造方法用于测试构造器引用

        public Fu(){

            System.out.println("我是构造方法");

        }

        //用于测试构造器引用

        public  void showFuConstruct(GreetInterface greetInterface){

            greetInterface.greet();

        }

        public void sayHello(){

            System.out.println("hello!,我是父类");

        }

    }

   //定义一个函数式接口

    @FunctionalInterface

    public interface GreetInterface {

        //定义一个见面方法

        void greet();

    }

    //定义子类

    class Zi extends Fu{

        //重写sayHello

        @Override

        public void sayHello(){

            System.out.println("hello!,我是子");

        }

        public void  method2(GreetInterface greetInterface){

            greetInterface.greet();

        }

        //用于测试super引用

        public void show(){

            method2(super::sayHello);

        }

        //该方法用于测试this引用

        public void show2(){

            method2(this::sayHello);

        }

    }

    @Test

    public void test2(){

        Zi zi =new Zi();

        zi.show();

    }

//通过this引用本类的成员方法

    @Test

    public void test3(){

        Zi zi =new Zi();

        zi.show2();

    }

    //类的构造器引用(构造方法)

    //使用方法:类名::new

    @Test

    public void test4(){

        new Fu().showFuConstruct(Fu::new);

    }

    /*

    数组的构造器引用

    */

    //定义一个方法参数传递创建数组的长度和Function接口

    public static int[] creatArray(int length,Function<Integer,int[]> fun){

       return fun.apply(length);

    }

    @Test

    public void test5(){

        int[] arr1=creatArray(10,(len)->{

            return new int[len];

        });

        /*使用方法引用优化Lambda表达式

          一直创建的就是int[] 数组

          数组的长度也是已知的

          就可以使用方法引用

          int[]::new,根据参数的长度来创建数组

        */

        creatArray(10, int[]::new);

        System.out.println(arr1.length);

    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值