JDK15 is coming,Do you konw the new features of java8?(下)

简介: JDK15 is coming,Do you konw the new features of java8?(下)

六、Stream API


流 (Stream) 和 Java 中的集合类似。但是集合中保存的数据,而流中保存的是,对集合或者数组中数据的操作。

之所以叫流,是因为它就像一个流水线一样。从原料经过 n 道加工程序之后,变成可用的成品。

如果,你有了解过 Spark 里边的 Streaming,就会有一种特别熟悉的感觉。因为它们的思想和用法如此相似。


包括 lazy 思想,都是在需要计算结果的时候,才真正执行。类似 Spark Streaming 对 RDD 的操作,分为转换(transformation)和行动(action)。转换只是记录这些操作逻辑,只有行动的时候才会开始计算。


转换介绍:http://spark.apache.org/docs/latest/rdd-programming-guide.html#transformations


对应的,Stream API 对数据的操作,有中间操作和终止操作,只有在终止操作的时候才会执行计算。


所以,Stream 有如下特点,


1.Stream 自己不保存数据。

2.Stream 不会改变源对象,每次中间操作后都会产生一个新的 Stream。

3.Stream 的操作是延迟的,中间操作只保存操作,不做计算。只有终止操作时才会计算结果。

那么问题来了,既然 Stream 是用来操作数据的。没有数据源,你怎么操作,因此还要有一个数据源。


于是,stream操作数据的三大步骤为:数据源,中间操作,终止操作。


6.1 数据源


流的源可以是一个数组,一个集合,一个生成器方法等等


1、使用 Collection 接口中的 default 方法。


default Stream<E> stream()  //返回一个顺序流
default Stream<E> parallelStream() //返回一个并行流

由于 Collection 集合父接口定义了这些默认方法,所以像 List,Set 这些子接口下的实现类都可以用这种方式生成一个 Stream 流。

public class StreamTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("zhangzan");
        list.add("lisi");
        list.add("wangwu");
        //顺序流
        Stream<String> stream = list.stream();
        //并行流
        Stream<String> parallelStream = list.parallelStream();
        //遍历元素
        stream.forEach(System.out::println);
    }
}

2、 Arrays 的静态方法 stream()


static <T> Stream<T> stream(T[] array)


可以传入各种类型的数组,把它转化为流。如下,传入一个字符串数组。


String[] arr = {"abc","aa","ef"};
Stream<String> stream1 = Arrays.stream(arr);


3、Stream接口的 of() ,generate(),iterate()方法


注意,of() 方法返回的是有限流,即元素个数是有限的,就是你传入的元素个数。

而 generate(),iterate() 这两个方法,是无限流,即元素个数是无限个。

使用方法如下,

//of
Stream<Integer> stream2 = Stream.of(10, 20, 30, 40, 50);
stream.forEach(System.out::println);
//generate,每个元素都是0~99的随机数
Stream<Integer> generate = Stream.generate(() -> new Random().nextInt(100));
//iterate,从0开始迭代,每个元素依次增加2
Stream<Integer> iterate = Stream.iterate(0, x -> x + 2);

4、IntStream,LongStream,DoubleStream 的 of、range、rangeClosed 方法


它们的用法都是一样,不过是直接包装了一层。

实际,of()方法底层用的也是 Arrays.stream()方法。


以 IntStream 类为例,其他类似,

IntStream intStream = IntStream.of(10, 20, 30);

//从0每次递增1,到10,包括0,但不包括10

IntStream rangeStream = IntStream.range(0, 10);

//从0每次递增1,到10,包括0和10

IntStream rangeClosed = IntStream.rangeClosed(0, 10);


6.2 中间操作


一个流可以有零个或者多个中间操作,每一个中间操作都会返回一个新的流,供下一个操作使用。


1、筛选与切片

常见的包括:

filter

limit

skip

distinct

用法如下:

@Test
public void test1(){
    ArrayList<Employee> list = new ArrayList<>();
    list.add(new Employee("张三",3000));
    list.add(new Employee("李四",5000));
    list.add(new Employee("王五",4000));
    list.add(new Employee("赵六",4500));
    list.add(new Employee("赵六",4500));
    // filter,过滤出工资大于4000的员工
    list.stream()
        .filter((e) -> e.getSalary() > 4000)
        .forEach(System.out::println);
    System.out.println("===============");
    // limit,限定指定个数的元素
    list.stream()
        .limit(3)
        .forEach(System.out::println);
    System.out.println("===============");
    // skip,和 limit 正好相反,跳过前面指定个数的元素
    list.stream()
        .skip(3)
        .forEach(System.out::println);
    System.out.println("===============");
    // distinct,去重元素。注意自定义对象需要重写 equals 和 hashCode方法
    list.stream()
        .distinct()
        .forEach(System.out::println);
}
// 打印结果:
Employee{name='李四', salary=5000}
Employee{name='赵六', salary=4500}
Employee{name='赵六', salary=4500}
===============
Employee{name='张三', salary=3000}
Employee{name='李四', salary=5000}
Employee{name='王五', salary=4000}
===============
Employee{name='赵六', salary=4500}
Employee{name='赵六', salary=4500}
===============
Employee{name='张三', salary=3000}
Employee{name='李四', salary=5000}
Employee{name='王五', salary=4000}
Employee{name='赵六', salary=4500}

2、映射


主要是map,包括:

map

mapToInt

mapToLong

mapToDouble

flatMap

用法如下:

@Test
public void test2(){
    int[] arr = {10,20,30,40,50};
    // map,映射。每个元素都乘以2
    Arrays.stream(arr)
          .map(e -> e * 2)
          .forEach(System.out::println);
    System.out.println("===============");
    //mapToInt,mapToDouble,mapToLong 用法都一样,不同的是返回类型分别是
    //IntStream,DoubleStream,LongStream.
    Arrays.stream(arr)
          .mapToDouble(e -> e * 2 )
          .forEach(System.out::println);
    System.out.println("===============");
    Arrays.stream(arr)
          .flatMap(e -> IntStream.of(e * 2))
          .forEach(System.out::println);
}
//打印结果:
20
40
60
80
100
===============
20.0
40.0
60.0
80.0
100.0
===============
20
40
60
80
100

这里需要说明一下 map 和 flatMap。上边的例子看不出来它们的区别。因为测试数据比较简单,都是一维的。


其实,flatMap 可以把二维的集合映射成一维的。看起来,就像把二维集合压平似的。( flat 的英文意思就是压平)

3、排序


sorted()

sorted(Comparator<? super T> comparator)

排序有两个方法,一个是无参的,默认按照自然顺序。一个是带参的,可以指定比较器。

@Test
public void test4(){
    String[] arr = {"abc","aa","ef"};
    //默认升序(字典升序)
    Stream.of(arr).sorted().forEach(System.out::println);
    System.out.println("=====");
    //自定义排序,字典降序
    Stream.of(arr).sorted((s1,s2) -> s2.compareTo(s1)).forEach(System.out::println);
} 

6.3 终止操作


一个流只会有一个终止操作。Stream只有遇到终止操作,它的源才开始执行遍历操作。注意,在这之后,这个流就不能再使用了。


1、查找与匹配


1.allMatch(Predicate p),传入一个断言型函数,检查是否匹配所有元素

2.anyMatch( (Predicate p) ),检查是否匹配任意一个元素

3.noneMatch(Predicate p),检查是否没有匹配的元素,如果都不匹配,则返回 true

4.findFirst(),返回第一个元素

5.findAny(),返回任意一个元素

6.count(),返回流中的元素总个数

7.max(Comparator c),按给定的规则排序后,返回最大的元素

8.min(Comparator c),按给定的规则排序后,返回最小的元素

9.forEach(Consumer c),迭代遍历元素(内部迭代

由于上边 API 过于简单,不再做例子。


收集


收集操作,可以把流收集到 List,Set,Map等中。而且,Collectors 类中提供了很多静态方法,方便的创建收集器供我们使用。


这里举几个常用的即可。具体的 API 可以去看 Collectors 源码(基本涵盖了各种,最大值,最小值,计数,分组等功能。)。

 @Test
public void test6() {
    ArrayList<Employee> list = new ArrayList<>();
    list.add(new Employee("张三", 3000));
    list.add(new Employee("李四", 5000));
    list.add(new Employee("王五", 4000));
    list.add(new Employee("赵六", 4500));
    //把所有员工的姓名收集到list中
    list.stream()
        .map(Employee::getName)
        .collect(Collectors.toList())
        .forEach(System.out::println);
    //求出所有员工的薪资平均值
    Double average = list.stream()
        .collect(Collectors.averagingDouble(Employee::getSalary));
    System.out.println(average);
}

七、日期时间新 API


JDK8 之前的时间 API 存在线程安全问题,并且设计混乱。因此,在 JDK8 就重新设计了一套 API。


7.1 如下,线程不安全的例子。


@Test
public void test1() throws Exception{
    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    List<Future<Date>> list = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        Future<Date> future = executorService.submit(() -> sdf.parse("20200905"));
        list.add(future);
    }
    for (Future<Date> future : list) {
        System.out.println(future.get());
    }
}


多次运行,就会报错 java.lang.NumberFormatException 。


接下来,我们就学习下新的时间 API ,然后改写上边的程序。


7.2 LocalDate,LocalTime,LocalDateTime


它们都是不可变类,用法差不多。以 LocalDate 为例。


7.2.1创建时间对象


now ,静态方法,根据当前时间创建对象

of,静态方法,根据指定日期、时间创建对象

parse,静态方法,通过字符串指定日期

LocalDate localDate1 = LocalDate.now();
System.out.println(localDate1);  //2020-09-05
LocalDate localDate2 = LocalDate.of(2020, 9, 5);
System.out.println(localDate2); //2020-09-05
LocalDate localDate3 = LocalDate.parse("2020-09-05");
System.out.println(localDate3); //2020-09-05

7.2.2、获取年月日周


getYear,获取年

getMonth ,获取月份,返回的是月份的枚举值

getMonthValue,获取月份的数字(1-12)

getDayOfYear,获取一年中的第几天(1-366)

getDayOfMonth,获取一个月中的第几天(1-31)

getDayOfWeek,获取一周的第几天,返回的是枚举值

LocalDate currentDate = LocalDate.now();
System.out.println(currentDate.getYear()); //2020
System.out.println(currentDate.getMonth()); // SEPTEMBER
System.out.println(currentDate.getMonthValue()); //9
System.out.println(currentDate.getDayOfYear()); //249
System.out.println(currentDate.getDayOfMonth()); //5
System.out.println(currentDate.getDayOfWeek()); // SATURDAY


7.2.3、日期比较,前后或者相等


isBefore ,第一个日期是否在第二个日期之前

isAfter,是否在之后

equals,日期是否相同

isLeapYear,是否是闰年

它们都返回的是布尔值。

LocalDate date1 = LocalDate.of(2020, 9, 5);
LocalDate date2 = LocalDate.of(2020, 9, 6);
System.out.println(date1.isBefore(date2)); //true
System.out.println(date1.isAfter(date2)); //false
System.out.println(date1.equals(date2)); //false
System.out.println(date1.isLeapYear()); //true


7.2.4、日期加减


plusDays, 加几天

plusWeeks, 加几周

plusMonths, 加几个月

plusYears,加几年

减法同理,


LocalDate nowDate = LocalDate.now();
System.out.println(nowDate);  //2020-09-05
System.out.println(nowDate.plusDays(1)); //2020-09-06
System.out.println(nowDate.plusWeeks(1)); //2020-09-12
System.out.println(nowDate.plusMonths(1)); //2020-10-05
System.out.println(nowDate.plusYears(1)); //2021-09-05

7.2.5.时间戳 Instant


Instant 代表的是到从 UTC 时区 1970年1月1日0时0分0秒开始计算的时间戳。


Instant now = Instant.now();
System.out.println(now.toString()); // 2020-09-05T14:11:07.074Z
System.out.println(now.toEpochMilli()); // 毫秒数, 1599315067074 


7.2.6.时间段 Duration


用于表示时间段 ,可以表示 LocalDateTime 和 Instant 之间的时间段,用 between 创建。

LocalDateTime today = LocalDateTime.now(); //今天的日期时间
LocalDateTime tomorrow = today.plusDays(1); //明天
Duration duration = Duration.between(today, tomorrow); //第二个参数减去第一个参数的时间差
System.out.println(duration.toDays()); //总天数,1
System.out.println(duration.toHours()); //小时,24
System.out.println(duration.toMinutes()); //分钟,1440
System.out.println(duration.getSeconds()); //秒,86400
System.out.println(duration.toMillis()); //毫秒,86400000
System.out.println(duration.toNanos()); // 纳秒,86400000000000

7.2.7日期段 Period


和时间段 Duration,但是 Period 只能精确到年月日。

有两种方式创建 Duration 。

LocalDate today = LocalDate.now(); //今天
LocalDate date = LocalDate.of(2020,10,1); //国庆节
//1. 用 between 创建 Period 对象
Period period = Period.between(today, date);
System.out.println(period); // P26D
//2. 用 of 创建 Period 对象
Period of = Period.of(2020, 9, 6);
System.out.println(of); // P2020Y9M6D
// 距离国庆节还有 0 年 0 月 26 天 
System.out.printf("距离国庆节还有 %d 年 %d 月 %d 天" , period.getYears(),period.getMonths(),period.getDays());

7.2.8.时区 ZoneId


ZoneId 表示不同的时区。

getAvailableZoneIds() ,获取所有时区信息,大概40多个时区

of(id),根据时区id获得对应的 ZoneId 对象

systemDefault,获取当前时区

Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
availableZoneIds.forEach(System.out::println); //打印所有时区
ZoneId of = ZoneId.of("Asia/Shanghai");   //获取亚洲上海的时区对象
System.out.println(of);  
System.out.println(ZoneId.systemDefault()); //当前时区为:Asia/Shanghai

7.2.9.日期时间格式化


JDK1.8 提供了线程安全的日期格式化类 DateTimeFormatter。


DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 1. 日期时间转化为字符串。有两种方式
String format = dtf.format(LocalDateTime.now());
System.out.println(format); // 2020-09-05 23:02:02
String format1 = LocalDateTime.now().format(dtf); //实际上调用的也是 DateTimeFormatter 类的format方法
System.out.println(format1); // 2020-09-05 23:02:02
// 2. 字符串转化为日期。有两种方式,需要注意,月和日位数要补全两位
//第一种方式用的是,DateTimeFormatter.ISO_LOCAL_DATE_TIME ,格式如下
LocalDateTime parse = LocalDateTime.parse("2020-09-05T00:00:00");
System.out.println(parse); // 2020-09-05T00:00
//第二种方式可以自定义格式
LocalDateTime parse1 = LocalDateTime.parse("2020-09-05 00:00:00", dtf);
System.out.println(parse1); // 2020-09-05T00:00

7.2.10.改为线程安全类


接下来,就可以把上边线程不安全的类改写为新的时间 API 。


@Test
public void test8() throws Exception{
    // SimpleDateFormat 改为 DateTimeFormatter
    DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd");
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    // Date 改为 LocalDate
    List<Future<LocalDate>> list = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        //日期解析改为 LocalDate.parse("20200905",dtf)
        Future<LocalDate> future = executorService.submit(() -> LocalDate.parse("20200905",dtf));
        list.add(future);
    }
    for (Future<LocalDate> future : list) {
        System.out.println(future.get());
    }
}


目录
相关文章
|
5天前
|
Cloud Native Java 编译器
Java生态系统的进化:从JDK 1.0到今天
Java生态系统的进化:从JDK 1.0到今天
|
5天前
|
Java 编译器 测试技术
滚雪球学Java(03):你知道JDK、JRE和JVM的不同吗?看这里就够了!
【2月更文挑战第12天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,助你一臂之力,带你早日登顶🚀,欢迎大家关注&&收藏!持续更新中,up!up!up!!
111 4
|
5天前
|
监控 安全 Java
探索Java的未来:JDK 18新特性全览
探索Java的未来:JDK 18新特性全览
84 0
|
5天前
|
缓存 安全 Java
JDK 14全景透视:每个Java开发者必知的新特性
JDK 14全景透视:每个Java开发者必知的新特性
56 0
|
5天前
|
JavaScript 前端开发 安全
Java新纪元:一探JDK 15的全新特性
Java新纪元:一探JDK 15的全新特性
49 0
|
5天前
|
JSON 编解码 Java
Java升级:JDK 9新特性全面解析“
Java升级:JDK 9新特性全面解析“
75 0
|
5天前
|
Java 开发框架 XML
JDK、JRE、Java SE、Java EE和Java ME有什么区别?
JDK、JRE、Java SE、Java EE和Java ME有什么区别?
|
5天前
|
Java
JDK环境下利用记事本对java文件进行运行编译
JDK环境下利用记事本对java文件进行运行编译
16 0
|
5天前
|
IDE Java 应用服务中间件
JDK1.6.0+Tomcat6.0的安装配置(配置JAVA环境)
JDK1.6.0+Tomcat6.0的安装配置(配置JAVA环境)
20 1
|
5天前
|
Java 编译器 对象存储
java一分钟之Java入门:认识JDK与JVM
【5月更文挑战第7天】本文介绍了Java编程的基础——JDK和JVM。JDK是包含编译器、运行时环境、类库等的开发工具包,而JVM是Java平台的核心,负责执行字节码并实现跨平台运行。常见问题包括版本不匹配、环境变量配置错误、内存溢出和线程死锁。解决办法包括选择合适JDK版本、正确配置环境变量、调整JVM内存参数和避免线程死锁。通过代码示例展示了JVM内存管理和基本Java程序结构,帮助初学者更好地理解JDK和JVM在Java编程中的作用。
22 0