zoukankan      html  css  js  c++  java
  • day33_Stream&方法引用

    • Stream
    • 方法引用

    Stream流

    ​ JDK1.8引入的新特性。用于解决已有集合类库既有的一些弊端(繁琐的操作)。依赖于Lambda表达式。

    ​ 从集合中获取需要的元素时,传统做法需要穷举、多次遍历集合。

    ​ 如果希望对集合中的元素按多个条件进行筛选过滤:

    • ​ 将集合A中的元素根据条件1过滤拿到子集合B

    • ​ 再将子集合B根据条件2过滤拿到子集合C

      使用Steam流的优秀写法

      //需要字符串中包含数字1的元素取出
      Stream<String> stream = list.stream();//相当于原集合A
      //Stream<T> filter(Predicate<? super T> predicate)
      Stream<String> stream2 = stream.filter(str -> str.contains("1"));//相当于子集合B
      //将元素长度不超过6个的元素取出
      Stream<String> stream3 = stream2.filter(str -> str.length()<=6);//相当于子集合C
      //forEach()进行遍历  
      //forEach(Consumer<? super T> action)   需要借助Consumer中的accept(T t)方法
      steam3.forEach(str -> System.out.println(str));//遍历打印输出集合C
      
      可以使用链式调用优化代码
      stream.filter(str -> str.contains("1"))
          .filter(str -> str.length()<=6)
          .forEach(str -> System.out.println(str));
      
      

    流式思想

    • 整体来看,流式思想类似于工厂的生产 " 流水线 " 。
    • 当需要对多个元素进行操作的时候,尤其是多步操作,考虑到性能问题以及便利性。首先需要搭建好一个范式(模型步骤方案),然后再去执行。
    • 比如:对某类商品进行操作,诸如:过滤、映射、跳过、计数等,这也是我们对集合中的元素操作的步骤,这一套流程,称之为一种处理方案,而处理方案就是一种 "函数模型" 。
    • 处理方案中操作的每一个步骤,我们都可以称为一个 "流" ,这个流需要调用指定的api方法,从一个流转换为另一个流。这些操作对应的都有api方法,filter / map / skip / count。

    当我们使用 "流" 时,总有三个步骤:

    1. ​ 获取数据源
    2. ​ 进行数据转换
    3. ​ 执行需要的操作、获取相应结果

    每次转换原有的Stream对象会返回新的Stream对象,这样我们就可以像链条一样进行操作。

    Stream流和以往的Collection集合有所不同。Stream操作有两个基础的特征:

    1. ​ 中间操作都会返回流对象本身,这样多个操作可以串联成一个管道,如同流式风格,对中间操作可以进行优化,比如可以进行延迟执行或短路操作等
    2. ​ 内部迭代:以前对集合遍历都是通过迭代器iterator或者增强for循环,显式的在集合外部进行迭代(外部迭代),Stream流提供了内部迭代的方式,这个流可以直接调用遍历的方法。forEach()

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

    Steam流是一个来自数据源的元素队列:

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

    ​ 数据源是流的来源,可以是集合也可以是数组等容器。

    获取一个流对象

    java.util.stream.Stream<T>是JDK1.8引入的新特性,较为常用的接口(并不是函数式接口)

    ​ 获取一个流对象,有以下常见的操作:

    • ​ 所有的Collection集合都可以通过stream()这个默认方法来获取
    • ​ Stream接口中含有一个静态方法 of()也可以获取对应的流对象
    //把集合转换为Stream流
    		List<String> list = new ArrayList<>();
    		Stream<String> stream = list.stream();
    		
    		HashMap<String, Integer> mao = new HashMap<String, Integer>();
    		Set<String> keySet = mao.keySet();
    		Stream<String> stream2 = keySet.stream();//key
    		
    		Collection<Integer> values = mao.values();
    		Stream<Integer> stream3 = values.stream();//value
    		
    		Set<Entry<String,Integer>> entrySet = mao.entrySet();	
    		Stream<Entry<String, Integer>> stream4 = entrySet.stream();//entry
    		
    		//数组转换Steam
    		Stream<Integer> stream5 = Stream.of(1,2,3,4,5,6);
    		//可变参数可以是一个数组
    		String []arr = {"a","b","c","d"};
    		Stream<String> stream6 = Stream.of(arr);
    
    Stream中的常用方法

    ​ 两个大类:延迟方法 / 终结方法

    延迟方法:返回值类型都是Stream接口自身,支持链式操作

    终结方法:返回值类型不是Steam接口本身,因此不能进行链式操作。count方法和forEach方法

    forEach方法

    void forEach(Consumer<T> consumer);//借助于该函数式接口中的方法accept方法
    //Consumer<T> 是一个消费型接口,用来消费一个指定泛型的数据
    
    public class TestforEach{
        public static void main(String args []){
            //获取一个数据源
            Stream<String> stream = Stream.of("abc","aaa","abd","ddd");
            //转换操作, 获取想要的结果
            stream.forEach(str ->{
                if(str.conntains("a")){
                    System.out.println(str);    //abc  aaa  abd
                }
            });
        }
    }
    
    过滤:filter

    可以通过filter方法将一个流转换成为另外一个子集流

    Stream<T> filter(Predicate<? super T> predicate) 返回由与此给定谓词匹配的此流的元素组成的流
    //借助于Predicate函数式接口当中的抽象方法test(T t)  对数据进行过滤
    

    该方法接收一个函数式接口Predicate,可以使用Lambda表达式进行条件的筛选

    Predicate接口

    java.util.stream.Predicate函数式接口。

    boolean test(T t):返回一个布尔值,代表指定条件是否满足,如果条件满足返回true,那么Stream流的方法filter就会将集合或数组(数据源)的元素保留;如果条件不满足,filter方法会丢弃该元素

    //1.准备数据源
    		//获取该数据源
    		String[] arr = {"小孙","小赵","小王","小张","大牛","狗蛋"};
    		//2.数据转换
    		//使用Stream流中的方法filter
    		Stream<String> stream1 = Stream.of(arr);
    		//3.筛选
    		stream1.filter(name->name.contains("小")).forEach(name->System.out.println(name));
    
    映射:map

    如果你需要将流中的数据映射到另外一个流中,可以使用map方法

    <R> Stream<R> map(Function<? super T,? extends R> mapper)
    返回一个流,该流包含将给定函数应用于此流的元素的结果
    

    该方法接收一个Function接口作为参数,可以将当前流中的T数据转换成另外一种R类型的数据

    Function接口

    java.util.stream.Function函数式接口,其中唯一的抽象方法

    R apply(T t)
    //可以将一种T类型的数据转换成R类型的数据,这种转换的动作,我们称之为"映射"
    
    //1.准备一个数据源并获取  将字符串的整数转换为int类型的整数
    		Stream<String> stream = Stream.of("123","234","456","897");
    		//2.数据转换    apply(T t)
    		Stream<Integer> stream2 = stream.map(str -> Integer.valueOf(str));
    		//3.遍历
    		stream2.forEach(num -> System.out.println(num));
    
    统计个数:count

    可以像Collection集合当中的size()方法一样,统计流中的元素个数。通过count方法来实现

    long count();//返回此流中元素的数量
    

    该方法返回一个long类型的值代表流中的元素个数(区别于int size())

    Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
    long count = stream.count();
    System.out.println(count);//6
    
    取用流中前几个元素 :limit

    ​ limit()方法可以对流中的数据进行限制 --->截取操作,需要一个参数,设定取用流中前max个数。

    Stream<T> limit(long maxSize)
    返回由此流的元素组成的流,截断长度不超过maxSize
    

    参数是long类型的,截取的长度不能超过流中最大元素个数,否则不进行操作。

    Stream<Integer> stream = Stream.of(12,13,14,15,17,88);
    //截取流中前五个元素
    Stream<Integer> stream2 = stream.limit(5);
    //查看
    System.out.println(stream2.count);//5
    
    跳过前几个元素:skip

    如果你想要跳过前几个元素,取用后几个元素,请使用skip

    Stream<T> skip(long n)//在丢弃流的第一个n元素后,返回由此流的其余元素组成的流。 
    //如果此流包含少于n元素,则将返回空流,长度为0
    
    String []arr = {"123","112","abc","fdh","455"};
    		Stream<String> stream = Stream.of(arr);
    		//跳过前三个元素
    		Stream<String> stream2 = stream.skip(3);
    		//System.out.println(stream2.count()); //2
    		stream2.forEach(str -> System.out.println(str)); //fdh 455
    
    组合:concat

    如果有两个流并且希望合成为一个流,可以使用concat()静态方法

    static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) :
        //创建一个延迟连接的流,其元素是第一个流的所有元素,后跟第二个流的所有元素
    
    Stream<Integer> stream = Stream.of(52,94,37,45,64,66);
    Stream<Integer> stream1 = Stream.of(12,13,14,15,17,88);
    Stream<Integer> stream2 = Stream.concat(stream,stream1);
    //把两个流合为一个流
    

    方法引用 -> 优化Lambda冗余现象

    ​ 使用Lambda表达式的时候,传递的是一段解决问题的代码,给什么参数做什么操作。

    Lambda冗余场景:

    ​ 比如,需要打印一个文本内容

    //函数式接口
    @FunctionalInterface
    public interface Printable {
    	//定义唯一的抽象方法
    	public abstract void print(String str);
    }
    
    /*测试类  定义一个静态方法,静态方法传入函数式接口作为参数,函数式接口有唯一的抽象方法print(),通常我们使用Lambda表达式实现以上需求。
    	经观察,对字符串进行打印输出的操作方案中,明明已经有了现成的执行方案,System.out对象中有一个println方法,所以引出了方法引用的概念---直接通过对象名引用方法
    	
    		PrintStream out = System.out;
    		printString(out::println);
    	
    	*/
    public class MethodReference {
    	//定义一个静态方法,参数传入函数式接口Printable
    	public static  void printString(Printable p) {
    		p.print("Hello World!!!!");
    	}
    	
    	public static void main(String []args) {
    		printString( (String str) ->{
    			System.out.println(str.toUpperCase());//传统Lambda表达式写法
    			//new MethodReference02().printUppercase(str);//调用对象中的方法
    		});
    		
    		PrintStream out = System.out;
    		//通过对象来引用对应的成员方法
    		printString(out::println);
    		
    		/*
    		 * 使用方法引用优化Lambda
    		 * 前提:   	 对象必须是已经存在的
    		 * 		 	 成员方法已经存在   
    		 * 		所以可以使用对象名来引用成员方法
    		 * 
    		 */
    		MethodReference02 method = new MethodReference02();
    		printString(method::printUppercase);
    	}
    }
    
    //提供方法的类
    public class MethodReference02 {
        
    	//定义一个成员方法,传递一个字符串,把字符串转换为大写输出
    	public void printUppercase(String str) {
    		System.out.println(str.toUpperCase());
    	}
    }
    

    注意:其中的双冒号 ': :'就是方法引用,JDK1.8新特性

    方法引用符号

    ​ 双冒号::也被归置为引用运算符

    方法引用的使用场景:

    1. ​ 通过对象名来引用成员方法
    2. ​ 通过类名引用静态方法

    ​ 比如: java.lang,Math全部都是静态方法

    (方法内容都被封装到了其他类中)

    ​ Lambda表达式写法: d -> Math.abs(d)

    ​ 方法引用写法:Math ::abs

    通过super来引用成员方法

    ​ 如果存在继承关系,当Lambda中需要使用super调用时,也可以使用方法引用来优化Lambda表达式。super::成员方法

    通过this来引用成员方法

    ​ this指代当前对象,如果需要引用的方法就是本类当中的成员方法,那么可以使用this::成员方法

    类的构造器引用

    ​ 由于构造器的名称与类名完全相同,所以构造器的引用使用类名称::new的格式表示

    数组的构造器引用

    ​ 数组也是Object子类对象,所以同样具有构造器,只不过语法稍微有些特殊。数组数据类型 [ ]::new

    为什么能够如此引用?

    可推导即可省略,在Lambda中,无需指定参数类型,无需指定重写的形式 ---> 它们都可以被推导出来,所以就可以省略掉。能够使用方法引用,同样也是可以根据上下文环境进行推导出来的

    函数式接口是Lambda的基础,而方法引用是Lambda的优化产品

  • 相关阅读:
    oracle 失效对象自动重新编译
    Apache commons 工具集简介
    正则表达式 元字符
    LayUI之弹出层
    Js和JQuery基础
    单点登录
    java算法题
    SpringBoot自定义注解
    SpringBoot基础
    java面试题
  • 原文地址:https://www.cnblogs.com/mitoris/p/14175499.html
Copyright © 2011-2022 走看看