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月前
|
Java
【Java基础面试三十二】、new String(“abc“) 是去了哪里,仅仅是在堆里面吗?
这篇文章解释了Java中使用`new String("abc")`时,JVM会将字符串直接量"abc"存入常量池,并在堆内存中创建一个新的String对象,该对象会指向常量池中的字符串直接量。
|
4月前
|
Java Linux
java基础(3)安装好JDK后使用javac.exe编译java文件、java.exe运行编译好的类
本文介绍了如何在安装JDK后使用`javac.exe`编译Java文件,以及使用`java.exe`运行编译好的类文件。涵盖了JDK的安装、环境变量配置、编写Java程序、使用命令行编译和运行程序的步骤,并提供了解决中文乱码的方法。
102 2
|
2月前
|
安全 Java 编译器
JDK 10中的局部变量类型推断:Java编程的简化与革新
JDK 10引入的局部变量类型推断通过`var`关键字简化了代码编写,提高了可读性。编译器根据初始化表达式自动推断变量类型,减少了冗长的类型声明。虽然带来了诸多优点,但也有一些限制,如只能用于局部变量声明,并需立即初始化。这一特性使Java更接近动态类型语言,增强了灵活性和易用性。
120 53
|
1月前
|
Java
java do while 的语法怎么用?
java do while 的语法怎么用?
46 3
|
3月前
|
Java
让星星⭐月亮告诉你,jdk1.8 Java函数式编程示例:Lambda函数/方法引用/4种内建函数式接口(功能性-/消费型/供给型/断言型)
本示例展示了Java中函数式接口的使用,包括自定义和内置的函数式接口。通过方法引用,实现对字符串操作如转换大写、数值转换等,并演示了Function、Consumer、Supplier及Predicate四种主要内置函数式接口的应用。
33 1
|
4月前
|
Java API 调度
掌握Java线程状态:从NEW到TERMINATED
本文探讨了操作系统与Java中线程的状态及其转换。操作系统层面,线程状态包括初始、就绪、运行、阻塞和终止。Java线程状态则细分为NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING和TERMINATED,并详细介绍了各状态的特性和转换条件。此外,还列举了Java中常用的线程方法,如`wait()`、`notify()`、`start()`和`join()`等,帮助理解线程控制机制。
160 3
掌握Java线程状态:从NEW到TERMINATED
|
4月前
|
Oracle Java 关系型数据库
Linux下JDK环境的配置及 bash: /usr/local/java/bin/java: cannot execute binary file: exec format error问题的解决
如果遇到"exec format error"问题,文章建议先检查Linux操作系统是32位还是64位,并确保安装了与系统匹配的JDK版本。如果系统是64位的,但出现了错误,可能是因为下载了错误的JDK版本。文章提供了一个链接,指向Oracle官网上的JDK 17 Linux版本下载页面,并附有截图说明。
Linux下JDK环境的配置及 bash: /usr/local/java/bin/java: cannot execute binary file: exec format error问题的解决
|
4月前
|
安全 Java API
【性能与安全的双重飞跃】JDK 22外部函数与内存API:JNI的继任者,引领Java新潮流!
【9月更文挑战第7天】JDK 22外部函数与内存API的发布,标志着Java在性能与安全性方面实现了双重飞跃。作为JNI的继任者,这一新特性不仅简化了Java与本地代码的交互过程,还提升了程序的性能和安全性。我们有理由相信,在外部函数与内存API的引领下,Java将开启一个全新的编程时代,为开发者们带来更加高效、更加安全的编程体验。让我们共同期待Java在未来的辉煌成就!
93 11
|
4月前
|
监控 Java 大数据
【Java内存管理新突破】JDK 22:细粒度内存管理API,精准控制每一块内存!
【9月更文挑战第9天】虽然目前JDK 22的确切内容尚未公布,但我们可以根据Java语言的发展趋势和社区的需求,预测细粒度内存管理API可能成为未来Java内存管理领域的新突破。这套API将为开发者提供前所未有的内存控制能力,助力Java应用在更多领域发挥更大作用。我们期待JDK 22的发布,期待Java语言在内存管理领域的持续创新和发展。
|
4月前
|
Oracle Java 关系型数据库
【颠覆性升级】JDK 22:超级构造器与区域锁,重塑Java编程的两大基石!
【9月更文挑战第6天】JDK 22的发布标志着Java编程语言在性能和灵活性方面迈出了重要的一步。超级构造器和区域锁这两大基石的引入,不仅简化了代码设计,提高了开发效率,还优化了垃圾收集器的性能,降低了应用延迟。这些改进不仅展示了Oracle在Java生态系统中的持续改进和创新精神,也为广大Java开发者提供了更多的可能性和便利。我们有理由相信,在未来的Java编程中,这些新特性将发挥越来越重要的作用,推动Java技术不断向前发展。