JDK 8 下的Stream、Lambda、Optional

为什么我们要使用 Stream ?

使用 Stream 对 集合中存储的数据 进行操作,可以写出更加高效、简洁且易于使用的数据处理方式。

Stream 的执行流程

  1. 创建流
  2. 一系列中间操作
  3. 终止操作

注意: Stream 中的操作是延迟的,需要最后一个终止操作才会执行运算,如 collect();

1.创建流的几种方式

使用 Collection 接口的默认方法,处理集合中的数据

		List<String> collection=Collections.EMPTY_LIST;
        Stream<String> collectionStream = collection.stream();

使用 Arrays.stream(),处理数组类型的数据

		IntStream stream = Arrays.stream(new int[]{1, 2, 3, 4});

使用用例中的 Stream.of(),合并同类型的数据集生成流

创建无限流,生成需要的数据

		//迭代10次,添加以2开始的偶数到集合中
		List<Integer> collect = Stream.iterate(2, t -> t + 2).limit(10).collect(Collectors.toList());
        System.out.println(collect.toString()); 
        //生成10个随机数并打印
        Stream.generate(Math::random).limit(10).forEach(System.out::println);

2. 一系列中间操作

Ⅰ 筛选与切片

  1. filter( e->… …):从流中保留返回 true 的数据,eg:过滤空字符串filter(StringUtils::isNotEmpty)
  2. limit( n ):从流中拿到前 n 个元素
  3. skip( n ):从流中跳过前 n 个元素
  4. distinct():去除流中重复的元素。注意去重时,需要对象的 hashCode 和 equals 方法。

Ⅱ 映射

  1. map( e->…… ):将集合中的元素转换为其他类型的元素。
    与 mapToLong、mapToDouble、mapToInt 类似,但他们返回LongStream … ,而 map 返回 Stream<(lambda返回的类型)>
  2. flatMap( e->…… ):将流 e 转换为其他类型的流,并将所有的流合并为一个流

Ⅲ 排序

  1. sorted():按集合或者类中实现的 Comparable 接口进行排序
  2. sorted(Comparator com):按自定义的 com 对象进行排序
	 public static void main(String[] args) {
        ArrayList<ForTest> list = new ArrayList<>();
        list.add(new ForTest("3",3));
        list.add(new ForTest("c",2));
        list.add(new ForTest("a",2));
        list.add(new ForTest("11",1));

        list.stream()
                .sorted((e1,e2)->{
                    //前-后为升序
                    return e1.b==e2.b?e1.a.compareTo(e2.a):e1.b-e2.b;
                })
                .forEach(System.out::println);
    }
	static class ForTest {
        String a;
        int b;

        public ForTest(String s, int i) {
            a = s;
            b = i;
        }

        int getInt() {
            return b;
        }

        @Override
        public String toString() {
            return "ForTest{" +
                    "a='" + a + '\'' +
                    ", b=" + b +
                    '}';
        }
    }

3. 终止操作

Ⅰ 匹配查找

  1. allMatch( e->…… ):检查集合中所有元素是否匹配,返回一个 boolean 类型。有一个元素返回 false ,返回结果就为 false 。
  2. anyMatch( e->…… ):检查集合中至少有一个元素匹配,返回一个 boolean 类型。有一个元素返回 true ,返回结果就为 true 。
  3. noneMatch( e->…… ):检查集合中没有元素匹配,返回一个 boolean 类型。所有元素返回 flase 时,返回结果为 true。
  4. findFirst():找到第一个元素。给到该操作的数据必须不为空,否则抛出 NPE 异常。
    在这里插入图片描述
    当 map 的值为 NULL 时,findFirst 将抛出 NPE;当 list 为空是,不会执行到 findFirst 函数
  5. findAny():找到任意一个元素。NPE 的抛出同 findFirst 。
  6. count():找到流中元素的个数
  7. max():返回流中最大的元素
  8. min():返回流中最小的元素
  9. forEach():内部迭代

Ⅱ 归约

  1. reduce( identity, BinaryOperator ):将流中的元素反复结合起来,计算出一个值。返回一个 T
Integer reduce = Stream.of(1, 2, 3, 4)
                .reduce(5, Integer::sum);
System.out.println(reduce);//15
  1. reduce( BinaryOperator ):
//ForTest类在上面的例子中
Optional<Integer> optional = list.stream()
                .map(ForTest::getInt)
                .reduce(Integer::sum);
System.out.println(optional.get());//8

Ⅲ 收集

collect( Collectors. ):收集流为 toList()、toSet()、joining(delimiter,prefix,suffix) 等


eg: 传统 for 循环的函数式替代方案

场景:现需要将三个拥有共同父类的 Enum 对象中的值,封装到另外一个对象的 List 集合中去
解决方案

  1. 使用传统的外部迭代 for 循环解决:第一层 for 循环三个 Enum 对象,第二层循环每一个 Enum 对象,完成对象的转换。缺点显而易见,嵌套 for 循环带来的时间复杂度非常高,可读性差等。
  2. 使用流进行内部迭代,配合 Lambda 完成遍历功能,如下代码所示。优点:代码量少,可读性增强。
//三个 Enum 类是需要读取信息的类
 List<FourDetection> list = Stream.of(GdDetectionProjectMapping.values(), YJDetectionProjectMapping.values(), BCDetectionProjectMapping.values())
                .flatMap(e -> Arrays.stream(e))//合并流,合并后,其类型还是DetectionMapping
                .map(e->toFour(e))//转化DetectionMapping为FourDetection流
                .filter(i->isAddToList(i,set))//过滤元素是否添加到list中
                .collect(Collectors.toList());//收集流转为list集合
                
//读取 DetectionMapping 的信息封装到 FourDetection 中
private FourDetection toFour(DetectionMapping value) {
        FourDetection fourDetection = new FourDetection();
        fourDetection.setDetectionProject(value.toString());
        fourDetection.setDetectionCode(value.getName());
        fourDetection.setFourDetection(value.getDetectionClass().getValue());
        fourDetection.setDetectionType(value.getDetectionType().getValue());
        fourDetection.setDetectionDescription(value.getDescription());
        fourDetection.setCheckProject("true");
        fourDetection.setLinkType(value.getDetectionLinkType().getValue());

        return fourDetection;
}
//Set 为已有的 FourDetection 集合
private boolean isAddToList(FourDetection fourDetection, Set<FourDetection> set) {
        if (!set.contains(fourDetection)) {
            return true;
        }
        return false;
}

什么是外部迭代?什么是内部迭代?谁的效率高?
外部迭代:意为显式地进行迭代操作,即集合中的元素访问是由一个处于集合外部的东西来控制的,如控制着循环的迭代器
内部迭代:遍历将在集合内部进行,我们不会显式地去控制这个循环

/**
size 	 1000		10_000 		1_000_000 		
外部迭代	   1		   	3			102
内部迭代   73		  109			140
*/
public static void main(String[] args) {
        final long count = 10_000_000;
        List<Long> list = new ArrayList<>((int)count);
        for (long i = 0; i < count; i++) {
            list.add(i);
        }
        
        //=========传统方式进行外部迭代=========
        Instant begin = Instant.now();
        for (Long i : list) {
            System.out.print("");
        }
        System.out.println("--------------------------");
        Instant end = Instant.now();
        System.out.println("传统方式进行外部迭代" + count + "次,耗时(ms):" + Duration.between(begin, end).toMillis());

        //=========java8内部迭代,用lambda处理=========
        begin = Instant.now();
        list.stream().forEach(i -> System.out.print(""));
        System.out.println("--------------------------");
        end = Instant.now();
        System.out.println("内部迭代forEach" + count + "次,耗时(ms):" + Duration.between(begin, end).toMillis());
    }

函数式编程的更多讲解(共10部分)

Lambda 中使用外部局部变量,为什么要 final?

结论:
  为了防止 Lambda 内外部使用数据不一致而规定的。

  Lambda 中使用的局部变量是通过值拷贝方式得到的(引用类型拷贝的是指向堆中的地址值),为避免在声明 Lambda 后(即完成了数据拷贝)修改变量值,导致 Lambda 真正执行时,内外部数据不同步的问题。所以规定 Lambda 使用的外部局部变量为 final 类型,禁止修改其值。

JDK 8中,Lambda 使用的外部局部变量为什么没有显示的声明 final ?

		int i=888;
        new Thread(
                () -> {
                    System.out.println("lambda runnable interface"+i);//此处访问i后,i的就变为final类型了
                }
        ).start();
        //i=1;//此处会出现编译错误

  局部变量未声明为 final 类型,一旦在 Lambda 中使用了,就会被强行加上了 final 属性,所以后面就不允许修改该 局部变量 了

声明的变量为 final,如果想要要从 Lambda 内向外传值该怎么做?

  		int[] i = {8};
        new Thread(
                () -> {
                    System.out.println("lambda runnable interface "+ i[0]);//lambda runnable interface 8
                    i[0] =10;
                     //i={8};//由于i已经是final类型了,所以此处会出现编译错误
                }
        ).start();
        TimeUnit.SECONDS.sleep(1);//保障thread中的代码被执行到
        System.out.println(i[0]);//10

  如上例所示,使用引用类型的变量,操作引用类型指向堆中地址的值,就可以把 Lambda 中的值传递到外部了。

Optional

orElse 与 orElseGet 的区别

   orElse 方法传入的应该是默认值,所以如果在 orElse 中如果传入执行语句,那么该函数一定会被执行。
   但如果使用 orElseGet ,如果不会走到 orElseGet,那么其中的函数不会别执行。


  

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值