java8完全解读二

时间:2021-06-03 19:44:42

继续着上次的java完全解读一

1.强大的Stream API

Stream 是Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用SQL 执行的数据库查询。也可以使用Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

1.1什么是Stream

流(Stream)是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。

“集合讲的是数据,流讲的是计算!”

注意:

①Stream 自己不会存储元素。

②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。

③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

1.2 Stream操作的三大步骤

  • 创建Stream

    一个数据源(如:集合、数组),获取一个流
  • 中间操作

    一个中间操作链,对数据源的数据进行处理
  • 终止操作(终端操作)

    一个终止操作,执行中间操作链,并产生结果

java8完全解读二

1.2.1 创建Stream

创建Stream严格来说有三种方式

  • Java8 中的Collection 接口被扩展,提供了两个获取流的方法:
//Collection的方法
default Stream<E> stream() : 返回一个顺序流
default Stream<E> parallelStream() : 返回一个并行流
  • Java8 中的Arrays的静态方法stream() 可以获取数组流:
  • 可以使用静态方法Stream.of(), 通过显示值创建一个流。它可以接收任意数量的参数
//Arrays的方法
static <T> Stream<T> stream(T[] array);// 返回一个流
//Stream的方法
public static<T> Stream<T> of(T… values);// 返回一个流
  • 可以使用静态方法Stream.iterate()Stream.generate(), 创建无限流。
//迭代
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
//生成
public static<T> Stream<T> generate(Supplier<T> s) :
/*
*UnaryOperator是传入类型T返回类型T
* Supplier是无参,返回类型T
*/

实际运用

public void test1(){
        /**
        * list中的数据本来是用List包裹,创建stream后,将list中的数据换成了stream包裹了,
        * 同时对流中的数据操作变得更加方便
        */
        //1. Collection 提供了两个方法  stream() 与 parallelStream()
        List<String> list = new ArrayList<>();
        Stream<String> stream = list.stream(); //获取一个顺序流
        Stream<String> parallelStream = list.parallelStream(); //获取一个并行流

        //2. 通过 Arrays 中的 stream() 获取一个数组流
        Integer[] nums = new Integer[10];
        Stream<Integer> stream1 = Arrays.stream(nums);
        //通过 Stream 类中静态方法 of()底层也是调用
        Stream<Integer> stream2 = Stream.of(1,2,3,4,5,6);

        //3.创建无限流,这两个底层调用的都是一样的
        //迭代
        Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2).limit(10);
        stream3.forEach(System.out::println);
        //生成
        Stream<Double> stream4 = Stream.generate(Math::random).limit(2);
        stream4.forEach(System.out::println);
    }

1.2.2 Stream的中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。

筛选和切片

java8完全解读二

运用

//筛选年龄<=35岁的员工
@Test
public void test2(){
    //所有的中间操作不会做任何的处理
    Stream<Employee> stream = emps.stream()
            .filter((e) -> {
                System.out.println("测试中间操作");
                return e.getAge() <= 35;
         });
   //只有当做终止操作时,所有的中间操作会一次性的全部执行,称为“惰性求值”
   stream.forEach(System.out::println);
}

//查询薪水>=5000的前三个员工
@Test
public void test4(){
      emps.stream()
            .filter((e) -> {
             return e.getSal() >= 5000;
      }).limit(3)
            .forEach(System.out::println);
  }

//查询薪水>=5000的员工,去掉前两个
    @Test
    public void test5(){
        emps.stream()
                .filter((e) -> e.getSal()>= 5000)
                .skip(2)//跳过2元素,返回一个扔掉了前2个元素的流
                .forEach(System.out::println);
    }

//去重
@Test
public void test6(){
     emps.stream()
            .distinct()
            .forEach(System.out::println);
  }
映射

java8完全解读二

实例运用

  • map
@Test
    public void test1() {
        List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
        //将传入的String值转为大写并返回String
        Stream<String> stream = strList.stream()
                .map(String::toUpperCase);
        stream.forEach(System.out::println);
        //传入的String,通过方法将String转化成包裹字符的stream了
        Stream<Stream<Character>> stream2 = strList.stream()
                .map(Stream2::filterCharacter);
        stream2.forEach((sm) -> {
            sm.forEach(System.out::println);
        });
    }
    public static Stream<Character> filterCharacter(String str) {
        List<Character> list = new ArrayList<>();
        for (Character ch : str.toCharArray()) {
            list.add(ch);
        }
        return list.stream();
    }
  • flatMap
//flatmap
    @Test
    public void Test3() {
        List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
        Stream<Character> stream3 = strList.stream()
                .flatMap(Stream2::filterCharacter);
        stream3.forEach(System.out::println);
    }
排序

java8完全解读二

实际运用

@Test
    public void test3() {
        emps.stream()
                .map(Employee::getName)
                .sorted()
                .forEach(System.out::println);
        emps.stream()//按年龄排序,如果年龄相同:按名字排序
                .sorted((x, y) -> {
                    if (x.getAge() == y.getAge()) {
                        return x.getName().compareTo(y.getName());
                    } else {
                        return Integer.compare(x.getAge(), y.getAge());
                    }
                }).forEach(System.out::println);
    }

1.2.3 Stream 的终止操作

终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是void 。

查找与匹配

java8完全解读二

归约

java8完全解读二

备注:map 和reduce 的连接通常称为map-reduce 模式,因Google 用它来进行网络搜索而出名。

使用

    @Test
    public void test1(){
        //将数组中的值加起来
        List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        Integer sum = list.stream()
                .reduce(0, (x, y) -> x + y);
        System.out.println(sum);
        //使用map将员工的工资提取出来,使用reduce将工资累加
        Optional<Double> op = emps.stream()
                .map(Employee::getSal)
                .reduce(Double::sum);
        System.out.println(op.get());
    }
    //需求:搜索名字中 “六” 出现的次数
    @Test
    public void test2(){
        Optional<Integer> sum = emps.stream()
                .map(Employee::getName)
                .flatMap(Stream2::filterCharacter)
                .map((ch) -> {
                    if(ch.equals('六'))
                        return 1;
                    else
                        return 0;
                }).reduce(Integer::sum);

        System.out.println(sum.get());
    }
收集

java8完全解读二

Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到List、Set、Map)。但是Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:

java8完全解读二

java8完全解读二

2 新时间日期API

2.1 使用LocalDate、LocalTime、LocalDateTime

LocalDate、LocalTime、LocalDateTime 类的实例是不可变的对象,分别表示使用ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。

提供的方法:

java8完全解读二

运用

@Test
    public void test() {
        LocalDate localDate = LocalDate.now();//现在的日期
        LocalTime localTime = LocalTime.now();//现在的时间
        LocalDateTime localDateTime = LocalDateTime.now();//现在的时间和日期
        System.out.println(localDate+"==>"+localTime+"==>"+localDateTime);
    }
    @Test
    public void test2() {
        LocalDate localDate = LocalDate.of(1996, 02, 03);//指定现在的日期
        LocalTime localTime = LocalTime.of(16, 04);//指定现在的时间
        LocalDateTime localDateTime = LocalDateTime.of(1996, 02, 03, 16, 04);//指定现在的日期时间
        System.out.println(localDate+"==>"+localTime+"==>"+localDateTime);
    }
    @Test
    public void test3() {
        LocalDate localDate = LocalDate.now();
        LocalDate localDate1 = localDate.plusDays(5);//在当前时间上加5天
        LocalDate localDate2 = localDate.plusMonths(2);//加2个月
        LocalDate localDate3 = localDate.plusWeeks(1);//加一周
        LocalDate localDate4 = localDate.plusYears(1);//加一年
        LocalDate localDate5 = localDate.minusMonths(5);//减5天
        System.out.println(localDate4);
    }

    @Test
    public void test5() {
        LocalDate localDate = LocalDate.now();
        LocalDate localDate1 = localDate.withDayOfMonth(12);//将日期中的,这个月的天数修改为12
        LocalDate localDate2 = localDate.withYear(2015);//将日期中的年修改为2015
        LocalDate localDate3 = localDate.withMonth(8);//将日期中的月修改为8月
        System.out.println(localDate3);
    }
    @Test
    public void test6() {
        LocalDate localDate = LocalDate.now();
        int year = localDate.getYear();//获取当前的年份
        System.out.println(year);
    }
    @Test
    public void test7() {
        LocalDate localDate = LocalDate.now();
        LocalDate localDate1 = LocalDate.now();
        Period period = localDate.until(localDate1);//获取两个日期间的日期间隔
        boolean before = localDate.isBefore(localDate1);//比较两个localData
        boolean leapYear = localDate.isLeapYear();//是否是闰年
        System.out.println(before);
    }

2.2 Duration 和Period

  • Duration:用于计算两个“时间”间隔
  • Period:用于计算两个“日期”间隔

使用

@Test
    public void test4() {
        //时间戳
        Instant start = Instant.now();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        Instant end = Instant.now();
        //Duration:用于计算两个“时间”间隔
        Duration duration = Duration.between(start, end);
        // Period:用于计算两个“日期”间隔
        LocalDate localDate = LocalDate.now();
        LocalDate localDate1 = LocalDate.of(2017, 12, 1);
        Period period = Period.between(localDate, localDate1);
        //给日期加一个间隔
        LocalDate localDate3 = localDate.plus(period);
        System.out.println(localDate3);
    }

2.3 解析与格式化

使用java.time.format.DateTimeFormatter对时间和日期进行格式化

@Test
    public void test8() {
        LocalDateTime localDateTime = LocalDateTime.now();
        //第一种对时间日期的格式化方式
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yy年MM月dd日 HH:mm:ss");
        String format = formatter.format(localDateTime);
        System.out.println(format);
        //第二种对时间日期的格式化方式
        String format1 = localDateTime.format(DateTimeFormatter.ofPattern("yy年MM月dd日"));
        System.out.println(format1);
    }

2.4 时区的处理

Java8 中加入了对时区的支持,带时区的时间为分别为:ZonedDate、ZonedTime、ZonedDateTime其中每个时区都对应着ID,地区ID都为“{区域}/{城市}”的格式例如:Asia/Shanghai 等

@Test
    public void test9() {
        //时区
        LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
        System.out.println(ldt);
    }

3 接口中的默认方法与静态方法

3.1 接口中的默认方法

Java 8中允许接口中包含具有具体实现的方法,该方法称为“默认方法”,默认方法使用default关键字修饰.

3.1.1 默认方法的使用

public interface MyInterface {
    default String getName(){
        return "呵呵呵";
    }
}

一个实现这个接口的类

public class SubClass implements MyInterface{
}

在主函数中调用

public static void main(String[] args) {
        SubClass sc = new SubClass();
        System.out.println(sc.getName());
    }

打印结果呵呵呵

3.1.2 接口默认方法的”类优先”原则

这时假如这个子类有一个父类,父类中也有一个getName()方法

public class MyClass {
    public String getName(){
        return "嘿嘿嘿";
    }
}

子类此时是

public class SubClass extends MyClass implements MyInterface{
}

这时在主类中掉用

public static void main(String[] args) {
        SubClass sc = new SubClass();
        System.out.println(sc.getName());
    }

结果为嘿嘿嘿

类优先原则:若一个接口中定义了一个默认方法,而另外一个父类中又定义了一个同名的方法时.

选择父类中的方法。如果父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。

3.1.3 接口默认方法的"接口冲突"

"接口冲突":假如子类实现的多个接口中有相同的方法和参数(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突

如,子类的另一个实现的接口

public interface MyFun {
    default String getName(){
        return "哈哈哈";
    }
}

这时子类必须覆盖方法,通过接口名来指定到底调用哪一个

public class SubClass implements MyFun,MyInterface{
    @Override
    public String getName() {
        return MyInterface.super.getName();//指定调用的接口
    }
}

在主类中调用

public static void main(String[] args) {
        SubClass sc = new SubClass();
        System.out.println(sc.getName());
    }

执行结果:呵呵呵

3.2 接口中的静态方法

Java8 中,接口中允许添加静态方法。

使用

public interface MyInterface {
    static void show(){
        System.out.println("接口中的静态方法");
    }
}

直接使用类名调用

public static void main(String[] args) {
        MyInterface.show();
    }

输出结果:接口中的静态方法