zoukankan      html  css  js  c++  java
  • Stream和方法引用

    1.Stream流

    1.for循环带来的弊端

    • 在jdk8中,lambda专注于做什么,而不是怎么做
    • for循环的语法就是怎么做
    • for循环的循环体才是做什么

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

    集合存储案列:

    import java.util.ArrayList;
    import java.util.List;
    
    public class Demo{
        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 str:list){
                if(str.startsWidth("张")){
                    listA.add(str);
                }
            }
            //对listA集合进行过滤,只要姓名长度为3的人,存储到一个新集合中
            List<String> ListB = new ArrayList<>();
            for(String s:listA){
                if(s.length()==3){
                    listB.add(s);
                }
            }
        }
    }

    2.使用Stream的更优写法

        import java.util.ArrayList;
        import java.util.List;
        
        public class Demo{
            public static void main(String[] args){
                // 创建一个list集合,存储姓名
                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(System.out::println);
            }
        }
    

    流思想

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

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

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

    • Pipelining:中间操作都会返回流对象本省。着多个操作可以串联一个管道,如同流式风格,可以对操作进行优化, 比如延迟执行(laziness)和短路(short-circuiting)。

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

    • 使用流的步骤

      获取一个数据源(source)→ 数据转换→执行操作获取想要的结 果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以 像链条一样排列,变成一个管道。

    3.获取流

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

    • 所有的Collection集合都可以通过Stream默认方法获取流;
      • default Stream<E> stream()
    • Stream接口的静态方法of可以获取数据对应的流。
      • static <T> Stream<T> of(T.. values)
      • 参数是一个可变参数,那么可以传递一个数组
        public class Demo{
            public static void main(String[] args){
                // 把集合转为Stream流
                List<String> list = new ArrayList<>();
                Stream<String> stream1 = list.stream();
                
                Set<String> set = new HashSet<>();
                Stream<String> stream2 = set.stream();
                
                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);
                Strng[] arr2 = {"a", "bn", "cd"};
                Stream<String> stream8 = Stream.of(arr2);
            }
        }
    

    4.常用方法

    forEach方法

    Stream流常用方法 forEach

    • void forEach(Consumer<? super T> action):
    • 该方法节后一个Consumer接口函数,会将每一个流元素交给该函数进行处理。
    • Consumer接口是一个消费型的函数式接口, 可以传递Lambda表达式,消费数据

    简单记:

    • forEach方法,用来遍历流中的数据
    • 是一个终结方法,遍历之后就不能继续调用Stream流中的其他方法
        public class Demo{
            public class void main(String[] args){
                // 获取一个Stream流
                Stream<String> stream = Stream.of("张三","李四","王五","赵六","田七");
                // 使用stream流中的方法forEach对Stream流中的数据进行遍历
                /* stream.forEach((String name)->{
                	System.out.println(name);
                });*/
                stream.forEach(name->system.out.println(name));
            }
        }
    

    filter方法

    用于对Stream流中的数据进行过滤

    • Stream<T> filter(Predicate<? super T> predicate);
    • filter方法中的参数Predicate是一个函数式接口,所以可以传递Lambda表达式,对数据进行过滤
    • Predicate中的抽象方法:
      • boolean test(T t)
        public class Demo{
            public static void main(String[] args){
                // 创建一个Stream流
                Stream<String> stream = Stream.of("张三丰", "张翠山", "赵敏", "周芷若", "张无忌");
                // 对Stream中的元素进行过滤,只要姓张的人
                Stream<String> stream2 = stream.filter((String name)->{return name.stratsWith("张");});
                // 遍历Stream2
                stream2.forEach(name->System.out.println(name));
                /*
                 Stream流属于管道流,只能被消费(使用)一次
                    第一个Stream流调用完毕方法,数据就会流转到下一个Stream而这时第一个Stream流已经使用完毕,就会关闭了
                    所以第一个Stream流就不能再调用方法了
                    IllegalStateException: stream has already been operated upon or closed
                */
                // 遍历stream流
                stream.forEach(name-> System.out.println(name));
            }
        }
    

    map方法

    用于类型转换

    • 如果需要将流中的元素映射到另一个流中,可以使用map方法
    • <R> Stream<R> map(Function <? super T, ? extends R> mapper);
    • 该接口需要一个Function函数式接口参数,可以将当前流中的T类型转换为另一种R数据的流
    • Function的抽象方法
      • R apply(T t);
        public class Demo{
            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);});
                // 变量stream2
                stream2.forEach(i -> System.out.println(i));
            }
        }
    

    count 方法

    用于统计Stream流中元素的个数

    • long count();
    • count 方法是一个终结方法,返回值是一个long类型的整数,只能使用一次,使用完之后,不可以使用流的其他方法
        public class Demo{
            public static void main(String[] args){
                //获取一个Stream流
                ArrayList<Integer> list = new ArrayList<>();
                list.add(1);
                list.add(2);
                list.add(3);
                list.add(4);
                Stream<Integer> Stream = list.stream();
                long count = stream.count();
                System.out.println(count); // 4
            }
        }
    

    limit方法

    用于截取流中的元素

    • limit方法可以对流进行截取,支取前n个
    • Stream<T> limit(long maxSize);
      • 参数是一个int类型。如果当前的长度大于参数则进行截取, 否则不进行截取
    • limit方法是一个延迟方法,只是对流中的元素进行截取,返回的是一个新的流,所以可以继续调用Stream流中的其他方法
        public class Demo{
            public static void main(String[] args){
                // 获取一个Stream流
                String[] arr = {"美羊羊","喜洋洋","懒洋洋","灰太狼","红太狼"};
                Stream<String> stream = Stream.of(arr);
                // 使用limit放啊进行截取,只要前三个元素
                Stream<String> stream2 = stream.limit(3);
                // 遍历操作
                stream2.forEach(name->System.out.println(name));
            }
        }
    

    skip方法

    用于跳过元素,截取后面的元素

    • 如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流
    • Stream<T> skip(long n);
      • 如果流的当前长度大于n,则跳过钱n个, 否则得到一个长度为0的空流.
        public class Demo{
            public static void main(String[] args){
                // 获取一个Stream流
                String[] arr = {"美羊羊","喜洋洋","懒洋洋","灰太狼","红太狼"};
                Stream<String> stream = Stream.of(arr);
                // 使用skip方法跳过前面三个元素
                Stream<String> stream2 = stream.skip(3);
                // 遍历stream2
                stream2.forEach(name-> System.out.println(name));
            }
        }
    

    concat方法

    用于把流组合在一起

    • 如果有两个流,希望合并成一个流,那么可以使用Stream接口的静态方法concat
    • static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T>b)
        public class Demo{
            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.forEach(name->System.out.println(name));
            }
        }
    

    2.方法引用

    1.冗余的Lambda

    定义一个打印的函数式接口

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

    打印字符串

    public class Demo{
        // 定义一个方法,参数传递的是Printable的接口,对字符串进行打印
        public static void printString(Printable p){
            p.print("hello world");
        }
        public static void main(String[] args){
           //调用printString方法,方法的参数Printable是一个函数式的接口,所以可以传递lambda函数
            printString((s)->{
                System.out.println(s);
            });
        /*
                分析:
                    Lambda表达式的目的,打印参数传递的字符串
                    把参数s,传递给了System.out对象,调用out对象中的方法println对字符串进行了输出
                    注意:
                        1.System.out对象是已经存在的
                        2.println方法也是已经存在的
                    所以我们可以使用方法引用来优化Lambda表达式
                    可以使用System.out方法直接引用(调用)println方法
             */
            printString(System.out::println);    
        }  
    }

    2.分析

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

    3.使用方法引用

    public class Demo{
    	public static void printString(Printable p){
            p.print("Hello world");
        }
        public static void main(String[] args){
            // 调用方法使用方法的引用
            printString(System.out::println);
        }
    }

    4.方法引用符

    双冒号:: 为引用运算符,称为方法引用。如果Lambda要表达的函数方案已经存在于某个方法的实现中,name我们可以使用双冒号来引用该方法作为lambda的替代者。

    语法分析

    例如上例中, System.out 对象中有一个重载的 println(String) 方法恰好就是我们所需要的。那么对于

    printString 方法的函数式接口参数,对比下面两种写法,完全等效:

    • Lambda表达式写法: s -> System.out.println(s);
    • 方法引用写法: System.out::println

    第一种语义是指:拿到参数之后经Lambda之手,继而传递给 System.out.println 方法去处理。

    第二种等效写法的语义是指:直接让 System.out 中的 println 方法来取代Lambda。两种写法的执行效果完全一

    样,而第二种方法引用的写法复用了已有方案,更加简洁。

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

    5.通过对象名引用成员方法

    定义类

    public class MethodRefObject{
        public void printUpperCase(String str){
            System.out.println(str.toUpperCase());
        }
    }

    函数式接口定义

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

    那么当需要使用这个printUpperCase成员方法来代替Printable接口的Lambda的时候,已经具有了MethodRefObject类的对象实例。则可以通过对象名引用成员方法,代码:

    /*
    通过对象名引用成员方法
    使用前提:
    	对象名是已经存在的,成员方法也存在
    */
    public class Demo{
        // 定义一个方法,方法的参数传递Printable接口
        public static void printString(Ptrintable p){
            p.print("hello");
        }
        public static void main(String[] args){
            // 创建MethodRerObject对象
            MethodRerObject obj = new MethodRerObject();
            printString(obj::printUpperCaseString);
        }
    }

    6.通过类名称引用静态方法

    由于在java.lang.Math类中已经存在存在了静态方法abs,所以当我们需要通过Lambda来调用该方法时,有两种写法。

    函数式接口:

    @FunctionalInterface
    public interface Calcable{
        int calc(int num);
    }
    
    
    public class Demo{
        public static int method(int number, Calcable c){
            return c.calsAbs(number);
        }
        public static void main(String[] args){
            // 调用method方法,传递计算绝对值
            int number = method(-10, Math::abs);
            System.out.println(number2);
        }
    }

    7.通过super引用成员方法

    定义见面的函数式接口

    @FunctionalInterface
    public interface Greetable{
        //定义一个见面的方法
        void greet();
    }

    定义父类

    public class Human{
        //定义一个sayHello的方法
        public void sayHello(){
            System.out.println("hello,我是Human");
        }
    }

    定义子类

    public class Man extends Human{
        //子类重写父类的sayHello方法
        @Override
        public void sayHello(){
            System.out.println("hello 我是Man");
        }
        //定义一个方法参数传递Greetable接口
        public void method(Greetable g){
            g.greet();
        }
        public void show(){
            method(super::sayHello);
        }
        public static void main(String[] args){
            new Man().show();
        }
    }
    // hello 我是Human

    8.通过this引用成员方法

    定义购买的函数式接口

    @FunctionalInterface
    public interface Richable{
        // 定义一个想买什么就买什么的方法
        void buy();
    }

    定义一个类

    使用this引用本类的成员方法

    public class Husband{
        // 定义一个买房子的方法
        public void buyHouse(){
            System.out.println("北京二环买一套四合院");
        }
        // 定义一个结婚的方法参数传递Richable接口
        public void marry(Richable r){
            r.buy();
        }
        //定义一个非常高兴的方法
        public void soHappy(){
            marry(this::buyHouse);
        }
        public static void main(String[] args){
            new Husband().soHappy();
        }
    }

    9.类的构造器引用

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

    定义Person类

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

    Person对象的函数式接口

    @FunctionalInterface
    public interface PersonBuilder{
        Person builderPerson(String name);
    }

    Demo

    public class Demo{
        //定义一个方法,传递的是姓名和PersonBuilder接口,方法中通过姓名创建Person对象
        public static void printName(String name, PersonBuilder pb){
            Person person = pb.builderPersom(name);
            System.out.println(person.getName());
        }
        public static void main(String[] args){
            /*构造方法new Person(String name) 已知
                创建对象已知 new
                就可以使用Person引用new创建对象*/
            //使用Person类的带参构造方法,通过传递的姓名创建对象
            printName("古力娜扎", Person::new);
        }
    }

    10.数组的构造器引用

    数组也是 Object 的子类对象,所以同样具有构造器,只是语法稍有不同。

    定义一个创建数组的函数式接口

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

    Demo

    public class Demo{
        /*
            定义一个方法
            方法的参数传递创建数组的长度和ArrayBuilder接口
            方法内部根据传递的长度使用ArrayBuilder中的方法创建数组并返回
         */
        public static int[] createArray(int length, ArrayBuilder ab){
            return ab.builderArray(length);
        }
        public static void main(String[] args){
           /*
                使用方法引用优化Lambda表达式
                已知创建的就是int[]数组
                数组的长度也是已知的
                就可以使用方法引用
                int[]引用new,根据参数传递的长度来创建数组
             */
            int[] array = createArray(10, int[]::new);
            System.out.println(Arrays.toString(array));
            System.out.println(array.length);
        }
    }
  • 相关阅读:
    华南虎原图找到了
    电脑高手的7大标准
    科幻小说一代宗师阿瑟•克拉克过逝
    看英文片最容易误解的10个单词(感觉对大家很有用,转过来的)
    地震了,人跑的真快啊
    John Titor一个来自未来的人
    马云扮白雪公主
    世界上最冷的脑筋急转弯
    告别人肉刷,让房源自己送上门
    来测下你的浏览器对标准的支持情况吧
  • 原文地址:https://www.cnblogs.com/liudemeng/p/11363770.html
Copyright © 2011-2022 走看看