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的优化产品

原文地址:https://www.cnblogs.com/mitoris/p/14175499.html