Java8-新特性


前言

哈喽!大家好,我是小简。今天开始学习《Java8-新特性》,此系列是我做的一个 “Java 从 0 到 1 ” 实验,给自己一年左右时间,按照我自己总结的 Java-学习路线,从 0 开始学 Java 知识,并不定期更新所学笔记,期待一年后的蜕变吧!<有同样想法的小伙伴,可以联系我一起交流学习哦!>

  • 🚩时间安排:预计3天更新完
  • 🎯开始时间:04-03
  • 🎉结束时间:04-05
  • 🍀总结:按时完成!

Lambda表达式

Lambda

读作λ表达式,它其实就是我们接口匿名实现的简化,Lambda 是一个匿名函数,Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)

Lambda 表达式的基础语法:Java8 中引入了一个新的操作符 “->“ 该操作符称为箭头操作符或 Lambda 操作符

箭头操作符将 Lambda 表达式拆分成两部分:

  • 左侧:Lambda 表达式的参数列表

  • 右侧:Lambda 表达式中所需执行的功能, 即 Lambda 体

语法格式一:无参数,无返回值
() -> System.out.println("Hello Lambda!");

语法格式二:有一个参数,并且无返回值
(String x) -> System.out.println(x)
  
语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
(x) -> System.out.println(x)

语法格式四:若只有一个参数,小括号可以省略不写
x -> System.out.println(x)

语法格式五:有两个以上的参数,有返回值,并且 Lambda 体中有多条语句
Comparator<Integer> com = (x, y) -> {
  System.out.println("函数式接口");
  return Integer.compare(x, y);
};

语法格式六:若 Lambda 体中只有一条语句, return 和大括号都可以省略不写
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);

从匿名类到 Lambda 的转换

@Test
public void test(){
  //匿名内部类
  Runnable r1 = new Runnable() {
    @Override
    public void run() {
      System.out.println("Hello World!");
    }
  };
  r1.run();
  
  //Lambda 表达式
  Runnable r2 = () -> System.out.println("He1lo Lambda!");
  r2.run();
}

参数传递

@Test
public void test2(){
  //原来使用匿名内部类作为参数传递
  TreeSet<String> ts = new TreeSet<>(new Comparator<String>(){
    @Override
    public int compare(String x, String y) {
      return Integer.compare(x.length(), y.length());
    }
  });

  // Lambda表达式作为参数传递
  Comparator<String> com = (x, y) -> Integer.compare(x.length(), y.length());
  TreeSet<String> ts2 = new TreeSet<>(com);
}

函数式接口

  • Lambda 表达式的本质:作为函数式接口的实例
  • 只包含一个抽象方法的接口,称为函数式接口
  • 函数式接口可以被隐式转换为 Lambda 表达式
  • 我们可以在任意函数式接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口,同时javadoc 也会包含一条声明,说明这个接口是一个函数式接口。

自定义一个函数式接口

@FunctionalInterface
interface GreetingService 
{
    void sayMessage(String message);
}

使用 Lambda 表达式来表示该接口的一个实现(JAVA 8 之前一般是用匿名类实现的):

GreetingService greetService1 = message -> System.out.println("Hello " + message);

Java内置四大核心函数式接口

函数式接口 参数类型 返回类型 用途
Consumer<T> 消费型接口 T void 对类型为T的对象应用操作,包含方法:void accept(T t)
Supplier<T> 供给型接口 T 返回类型为T的对象,包含方法:T get();
Function<T,R> 函数型接口 T R 对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t);
Predicate<T> 断定型接口 T boolean 确定类型为T的对象是否满足某约束,并返回boolean值。包含方法:boolean test(T t);

其他函数式接口

函数式接口 参数类型 返回类型 用途
BiFunction<T,U,R> T, U R 对类型为T,U参数应用操作,返回R类型的结果。包含方法为:Rapply(T t,U u);
UnaryOperator<T> (Function子接口) T T 对类型为T的对象进行一元运算,并返回T类型的结果。包含方法为:T apply(T t);
BinaryOperator<T>(BiFunction子接口) T, T T 对类型为T的对象进行二元运算,并返回T类型的结果。包含方法为:T apply(T t1,T t2);
BiConsumer<T,U> T, U void 对类型为T,U参数应用操作。 包含方法为:void accept(T t,U u)
BiPredicate<T,U> T,U boolean 包含方法为:boolean test(T t,U u)
ToIntFunction<T> ToLongFunction<T> ToDoubleFunction<T> T int long double 分别计算int、long、double值的函数
IntFunction<R> LongFunction<R> DoubleFunction<R> int long double R 参数分别为int、long、double类型的函数

方法引用

  • 方法引用本质上就是 Lambda 表达式

  • 当要传递给 Lambda 体的操作,已经有实现的方法了,可以使用方法引用 !

  • 要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!

  • 格式:使用操作符“::”将方法名和对象或类的名字分隔开来。

如下三种主要使用情况:

  • 对象::实例方法

  • 类::静态方法

  • 类::实例方法

Consumer<String> con = (x) -> System.out.println(x);
//等同于
Consumer<String> con = System.out::println;


//对象的引用::实例方法名
Employee emp = new Employee(101, "张三", 18, 9999.99);
Supplier<String> sup = () -> emp.getName();
System.out.println(sup.get());
//等同于
Supplier<String> sup2 = emp::getName;
System.out.println(sup2.get());


//类名 :: 静态方法名
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
//等同于:
Comparator<Integer> com = Integer::compare;

//类名 :: 实例方法名
BiPredicate<String, String> bp = (x,y) -> x.equals(y);
//等同于:
BiPredicate< String,String> bp = String::equals;

//注意:当函数式接口方法的第-一个参数是需要引用方法的调用者,并且第二个参数是需要引用方法的参数(或无参数)时: ClassName::methodName

构造器引用

  • 与函数式接口相结合,自动与函数式接口中方法兼容。

  • 可以把构造器引用赋值给定义的方法

  • 要求:构造器参数列表要与接口中抽象方法的参数列表一致 !且方法的返回值即为构造器对应类的对象。

  • 格式:ClassName::new

Function<Integer, MyClass> fun = (n) -> new MyClass(n);
//等同于:
Function<Integer, MyClass> fun = MyClass::new;

数组引用

  • 格式:type[]::new
Function<Integer, Integer[]> fun = (n) -> newInteger[n];
//等同于
Function<Integer, Integer[]> fun = Integer[]::new;

Function<Integer, String[]> fun = (args) -> new String[args];
String[] strs = fun.apply(10);
System.out.println(strs.length);
//等同于
Function<Integer, Employee[]> fun2 = Employee[]::new;
Employee[] emps = fun2.apply(20);
System.out.println(emps.length);

Stream API

Stream 简介

  • 流式编程作为 Java 8 的亮点之一,是继 Java 5 之后对集合的再一次升级
  • Stream API 对集合数据进行操作,就类似于使用SQL 执行的数据库查询,Stream API 主要是通过 Lambda 表达式完成。
  • Stream 的工作过程像将一瓶水导入有很多过滤阀的管道一样,水每经过一个过滤阀,便被操作一次,比如过滤,转换等,最后管道的另外一头有一个容器负责接收剩下的水。
    • 首先通过 source 产生流,然后依次通过一些中间操作,比如过滤,转换,限制等,最后结束对流的操作。

  • Stream 也可以理解为一个更加高级的迭代器,主要的作用便是遍历其中每一个元素。
  • Stram API 中自带的并行流使得并发处理集合的门槛再次降低,使用 Stream API 编程可以非常方便的写出高性能的并发程序。

Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据结构,而Stream 是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向CPU,通过 CPU 实现计算。

在没有 Stream 之前,我们想提取出所有年龄大于18的学生,我们需要这样做:

List<Student> result=new ArrayList<>();
for(Student student:students){
    if(student.getAge()>18){
        result.add(student);
    }
}
return result;

使用Stream,我们可以参照上面的流程示意图来做,首先产生 Stream,然后 filter 过滤,最后归并到容器中。

return students.stream().filter(s->s.getAge()>18).collect(Collectors.toList());

Stream操作步骤

  • 1、创建 Stream
    • 一个数据源(如:集合、数组),获取一个流
  • 2、中间操作
    • 一个中间操作链,对数据源的数据进行处理
  • 3、终止操作
    • 一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用

创建流

在创建 Stream 前,先创建一个 Employee.java,供下面测试使用。

public class Employee {

    private int id;
    private String name;
    private int age;
    private double salary;

    public Employee() {
    }

    public Employee(String name) {
        this.name = name;
    }
    public Employee(Integer id) {
        this.id = id;
    }
    public Employee(Integer id,Integer age) {
        this.id = id;
        this.age = age;
    }

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Employee(int id, String name, int age, double salary) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public String show() {
        return "测试方法引用!";
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + id;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        long temp;
        temp = Double.doubleToLongBits(salary);
        result = prime * result + (int) (temp ^ (temp >>> 32));
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Employee other = (Employee) obj;
        if (age != other.age)
            return false;
        if (id != other.id)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        if (Double.doubleToLongBits(salary) != Double.doubleToLongBits(other.salary))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "Employee [id=" + id + ", name=" + name + ", age=" + age + ", salary=" + salary + "]";
    }

}

创建一个测试类 StreamAPI.java,并创建一个 test 方法。所有创建 Stream 的方式都在这个类的 test 方法中。

public class StreamaAPI {
  @Test
  public void test() {

  }
}

方式一:通过集合

Java8 中的 Collection 接口被扩展,提供了两个获取流的方法

  • default Stream<E> stream() : 返回一个顺序流

    • Collection.stream()
  • default Stream<E> parallelStream() : 返回一个并行流

    • Collection.parallelStream()
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();//获取一个顺序流
Stream<String> stream2 = list.parallelStream();//获取一个并行流

方式二:通过数组

Java8 中的 Arrays 的静态方法stream() 可以获取数组流:

  • static <T> Stream<T> stream(T[] array): 返回一个流
    • Arrays.stream(T array)
Employee[] emps = new Employee[10];
Stream<Employee> stream3 = Arrays.stream(emps);

重载形式,能够处理对应基本类型的数组:

  • public static IntStream stream(int[] array)

  • public static LongStream stream(long[] array)

  • public static DoubleStream stream(double[] array)

方式三:通过Streamof()

可以调用Stream类静态方法of(), 通过显示值创建一个流。它可以接收任意数量的参数。

  • public static <T> Stream<T> of(T… values) : 返回一个流
    • Stream.of()
Stream<String> stream4 = Stream.of("aa", "bb", "cc");
stream4.forEach(System.out::println);

方式四:创建无限流

可以使用静态方法 Stream.iterate() 和Stream.generate(),创建无限流。

  • 迭代:public static <T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
    • Stream.iterate()
Stream<Integer> stream5 = Stream.iterate(0, (x) -> x + 2);
stream5.limit(5).forEach(System.out::println);
  • 生成:public static <T> Stream<T> generate(Supplier<T> s)
    • Stream.generate()
Stream<Double> stream6 = Stream.generate(() -> Math.random());
stream6.limit(5).forEach(System.out::println);

中间操作

一个流可以后面跟随零个或多个中间操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后会返回一个新的流,交给下一个操作使用。

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

在中间操作前,先在 StreamAPI.java 中创建一个 Employee 对象的集合,供下面测试使用。

List<Employee> emps = Arrays.asList(
  new Employee(102, "李四", 59, 6666.66), 
  new Employee(101, "张三", 18, 9999.99),
  new Employee(103, "王五", 28, 3333.33), 
  new Employee(104, "赵六", 8, 7777.77),
  new Employee(104, "赵六", 8, 7777.77), 
  new Employee(104, "赵六", 8, 7777.77),
  new Employee(105, "田七", 38, 5555.55)
);

筛选/切片

方法 描述
filter(Predicate p) 接收Lambda ,从流中排除某些元素
distinct() 筛选,通过流所生成元素的hashCode()和equals()去除重复元素
limit(long maxSize) 截断流,使其元素不超过给定数量
skip(long n) 跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n) 互补
filter——过滤
@Test
public void testFilter() {
  //这里加入了终止操作 ,不然中间操作不会执行
  emps.stream().filter((e) -> e.getAge() > 18)//年龄大于18岁
                         .forEach(System.out::println);//终止操作
}

/**
Employee [id=102, name=李四, age=59, salary=6666.66]
Employee [id=103, name=王五, age=28, salary=3333.33]
Employee [id=105, name=田七, age=38, salary=5555.55]
**/
distinct——去重
@Test
public void testDistinct() {
  emps.stream().distinct()//去除重复的元素,需要重写hashCode跟equals方法
                         .forEach(System.out::println);//终止操作
}

/**
Employee [id=102, name=李四, age=59, salary=6666.66]
Employee [id=101, name=张三, age=18, salary=9999.99]
Employee [id=103, name=王五, age=28, salary=3333.33]
Employee [id=104, name=赵六, age=8, salary=7777.77]
Employee [id=105, name=田七, age=38, salary=5555.55]
**/
limit——截断流
@Test
public void testLimit() {
  emps.stream().filter((e) -> e.getAge()> 18)
               .limit(2)
                         .forEach(System.out::println);//终止操作
}

/**
Employee [id=102, name=李四, age=59, salary=6666.66]
Employee [id=103, name=王五, age=28, salary=3333.33]
**/
skip——跳过元素
@Test
public void testSkip() {
  emps.stream().filter((e) -> e.getAge() > 18)
                         .skip(2)//这里可以查找filter过滤后的数据,前两个不要,要后面的,与limit相反
                         .forEach(System.out::println);//终止操作
}

/**
Employee [id=105, name=田七, age=38, salary=5555.55]
**/

映射

map操作能够将流中的每一个元素映射为另外的元素。

方法 描述
map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream。
mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream。
mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream。

@Test
public void testMapAndflatMap() {
  List<String> list=Arrays.asList("aaa","bbb");
  list.stream()
    .map((str)->str.toUpperCase())//里面是Function
    .forEach(System.out::println);

  System.out.println("======================================");

  //流中流
  Stream<Stream<Character>> stream = list.stream()
    .map(StreamAPI::filterCharacter);
  //map是一个个流(这个流中有元素)加入流中 {{a,a,a},{b,b,b}}
  stream.forEach(sm->{
    sm.forEach(System.out::println);
  });

  System.out.println("=============引进flatMap=============");
  
  //只有一个流
  Stream<Character> flatMap = list.stream()
    .flatMap(StreamAPI::filterCharacter);
  //flatMap是将一个个流中的元素加入流中 {a,a,a,b,b,b}
  flatMap.forEach(System.out::println);
}

public static Stream<Character> filterCharacter(String str){
  List<Character> list=new ArrayList<>();
  for (Character character : str.toCharArray()) {
    list.add(character);
  }
  return list.stream();
}

map 跟 flatMap的区别

  • 有点跟集合中的 add 和 addAll 方法类似
  • add 是将无论是元素还是集合,整体加到其中一个集合中去 [1,2,3,[2,3]]
  • addAll 是将无论是元素还是集合,都是将元素加到另一个集合中去 [1,2,3,2,3]
  • map是一个个流(这个流中有元素)加入流中,flatMap是将一个个流中的元素加入流中。

排序

方法 描述
sorted() 产生一个新流,其中按自然顺序排序
sorted(Comparator com) 产生一个新流,其中按比较器顺序排序
@Test
    public  void  testSorted() {
        List<String> list=Arrays.asList("ccc","aaa","bbb","ddd","eee");
        list.stream().sorted()
                     .forEach(System.out::println);

        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);
    }

终止操作

终止操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是void ,流进行了终止操作后,不能再次使用。

新建了一个Employee2.java,供下面测试使用。

public class Employee2 {

    private int id;
    private String name;
    private int age;
    private double salary;
    private Status status;

    public Employee2() {
    }

    public Employee2(String name) {
        this.name = name;
    }

    public Employee2(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Employee2(int id, String name, int age, double salary) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public Employee2(int id, String name, int age, double salary, Status status) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
        this.status = status;
    }

    public Status getStatus() {
        return status;
    }

    public void setStatus(Status status) {
        this.status = status;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public String show() {
        return "测试方法引用!";
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + id;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        long temp;
        temp = Double.doubleToLongBits(salary);
        result = prime * result + (int) (temp ^ (temp >>> 32));
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Employee2 other = (Employee2) obj;
        if (age != other.age)
            return false;
        if (id != other.id)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        if (Double.doubleToLongBits(salary) != Double.doubleToLongBits(other.salary))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "Employee2 [id=" + id + ", name=" + name + ", age=" + age + ", salary=" + salary + ", status=" + status
                + "]";
    }

    public enum Status {
        FREE, BUSY, VOCATION;
    }
}

创建一个测试类 TestShutdown.java,test 方法写在里面

public class TestShutdown {
    List<Employee2> emps = Arrays.asList(
            new Employee2(102, "李四", 59, 6666.66, Employee2.Status.BUSY),
            new Employee2(101, "张三", 18, 9999.99, Employee2.Status.FREE),
            new Employee2(103, "王五", 28, 3333.33, Employee2.Status.VOCATION),
            new Employee2(104, "赵六", 8, 7777.77, Employee2.Status.BUSY),
            new Employee2(104, "赵六", 8, 7777.77, Employee2.Status.FREE),
            new Employee2(104, "赵六", 8, 7777.77, Employee2.Status.FREE),
            new Employee2(105, "田七", 38, 5555.55, Employee2.Status.BUSY)
    );
  
    @Test
    public void test() {

    }
}

匹配

方法 描述
allMatch(Predicate p) 检查是否匹配所有元素
anyMatch(Predicate p) 检查是否至少匹配一个元素
noneMatch(Predicate p) 检查是否没有匹配所有元素
@Test
public void testMatch(){
  System.out.println("==========allMatch==============");
  boolean allMatch = emps.stream().allMatch((e) -> e.getAge() > 5);
  System.out.println(allMatch);

  System.out.println("==========anyMatch==============");
  boolean anyMatch = emps.stream().anyMatch((e)->e.getName().equals("张三"));
  System.out.println(anyMatch);

  System.out.println("==========noneMatch=============");
  boolean noneMatch = emps.stream().noneMatch((e) -> e.getAge() < 2);//检查是否没有一个小于2
  System.out.println(noneMatch);
}

查找

方法 描述
findFirst() 返回第一个元素
findAny() 返回当前流中的任意元素
count() 返回流中元素总数
max(Comparator c) 返回流中最大值
min(Comparator c) 返回流中最小值
forEach(Consumer c) 内部迭代(使用Collection接口需要用户去做迭代,称为外部迭代)
@Test
public void  testSearch(){
  System.out.println("==========findFirst==============");
  Optional<Employee2> findFirst = emps.stream()
    .sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))//按照工资排序并输出第一个
    .findFirst();
  System.out.println(findFirst);

  System.out.println("==========findAny==============");
  Optional<Employee2> findAny = emps.stream()
    .filter((e)->e.getStatus().equals(Employee2.Status.BUSY))
    .findAny();
  System.out.println(findAny);

  System.out.println("==========count==============");
  long count = emps.stream().count();
  System.out.println(count);

  System.out.println("==========max==============");
  Optional<Double> max = emps.stream()
    .map(Employee2::getSalary)
    .max(Double::compare);
  System.out.println(max);

  System.out.println("==========min==============");
  Optional<Employee2> min = emps.stream()
    .min((e1,e2)->Double.compare(e1.getSalary(), e2.getSalary()));
  System.out.println(min);
  
  System.out.println("==========forEach==============");
  emps.stream().forEach(System.out::println);
}

归约

方法 描述
reduce(T identity, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回T
reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回Optional<T>
  • identity : 起始值
  • BinaryOperator : 二元运算

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

@Test
public void testReduce() {
  List<Integer> list= Arrays.asList(1,2,3);
  Integer sum = list.stream().reduce(4,(x,y)->x+y);
  System.out.println(sum);

  Optional<Integer> sum2 = list.stream().reduce((x, y) -> x+y);
  System.out.println(sum2.get());
}

/**
10
6
**/

收集

方法 描述
collect(Collector c) 将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法

Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到List、Set、Map)。

另外,Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:

@Test
public void testCollect1() {
    System.out.println("==========toList==============");
    List<String> collect = emps.stream()
      .map(Employee2::getName)
      .collect(Collectors.toList());
    collect.forEach(System.out::println);

    System.out.println("==========toSet==============");
    Set<String> collect2 = emps.stream()
      .map(Employee2::getName)
      .collect(Collectors.toSet());//set不允许重复
    collect2.forEach(System.out::println);

    System.out.println("==========toCollection==============");
    HashSet<String> collect3 = emps.stream()
      .map(Employee2::getName)
      .collect(Collectors.toCollection(HashSet::new));
    collect3.forEach(System.out::println);
}

/**
==========toList==============
李四
张三
王五
赵六
赵六
赵六
田七
==========toSet==============
李四
张三
王五
赵六
田七
==========toCollection==============
李四
张三
王五
赵六
田七
**/
@Test
public void testCollect2() {
    System.out.println("==========counting==============");
    Long collect7 = emps.stream()
      .collect(Collectors.counting());
    System.out.println(collect7);

    System.out.println("==========summingDouble==============");
    Double collect5 = emps.stream()
      .collect(Collectors.summingDouble(Employee2::getSalary));
    System.out.println(collect5);

    System.out.println("==========averagingDouble==============");
    Double collect6 = emps.stream()
      .collect(Collectors.averagingDouble((e)->e.getSalary()));
    System.out.println(collect6);

    System.out.println("==========summarizingDouble==============");
    DoubleSummaryStatistics collect9 = emps.stream()
      .collect(Collectors.summarizingDouble(Employee2::getSalary));
    long count = collect9.getCount();
    double average = collect9.getAverage();
    double max = collect9.getMax();
    double min = collect9.getMin();
    double sum = collect9.getSum();
    System.out.println("count:"+count);
    System.out.println("average:"+average);
    System.out.println("max:"+max);
    System.out.println("min:"+min);
    System.out.println("sum:"+sum);
}
/**
==========counting==============
7
==========summingDouble==============
48888.840000000004
==========averagingDouble==============
6984.120000000001
==========summarizingDouble==============
count:7
average:6984.120000000001
max:9999.99
min:3333.33
sum:48888.840000000004
**/
@Test
public void testCollect3() {
  //组接字符串
  System.out.println("==========joining==============");
  String collect1 = emps.stream()
    .map((e)->e.getName())
    .collect(Collectors.joining());
  System.out.println(collect1);

  System.out.println("==========joining2==============");
  String collect2 = emps.stream()
    .map(Employee2::getName)
    .collect(Collectors.joining(","));
  System.out.println(collect2);

  System.out.println("==========joining3==============");
  String collect3 = emps.stream()
    .map(Employee2::getName)
    .collect(Collectors.joining(",", "前缀-", "-后缀"));
  System.out.println(collect3);


  System.out.println("==========maxBy==============");
  Optional<Double> collect4 = emps.stream()
    .map(Employee2::getSalary)
    .collect(Collectors.maxBy(Double::compare));
  System.out.println(collect4.get());


  System.out.println("==========minBy==============");
  Optional<Double> collect5 = emps.stream()
    .map(Employee2::getSalary)
    .collect(Collectors.minBy(Double::compare));
  System.out.println(collect5);
}
/**
==========joining==============
李四张三王五赵六赵六赵六田七
==========joining2==============
李四,张三,王五,赵六,赵六,赵六,田七
==========joining3==============
前缀-李四,张三,王五,赵六,赵六,赵六,田七-后缀
==========maxBy==============
9999.99
==========minBy==============
Optional[3333.33]
**/
 @Test
public void testCollect4() {
  System.out.println("==========reducing==============");
  Optional<Double> collect = emps.stream()
    .map(Employee2::getSalary)
    .collect(Collectors.reducing(Double::sum));
  System.out.println(collect.get());
  //分组
  System.out.println("==========groupingBy==============");
  Map<Employee2.Status, List<Employee2>> collect2 = emps.stream()
    .collect(Collectors.groupingBy(Employee2::getStatus));
  System.out.println(collect2);

  //多级分组
  System.out.println("==========groupingBy2==============");
  Map<Employee2.Status, Map<String, List<Employee2>>> collect3 = emps.stream()
    .collect(Collectors.groupingBy(Employee2::getStatus, Collectors.groupingBy((e)->{
      if(e.getAge() >= 60) {
        return "老年";
      } else if(e.getAge() >= 35) {
        return "中年";
      } else {
        return "成年";
      }
    })));
  System.out.println(collect3);
  //分区
  System.out.println("==========partitioningBy==============");
  Map<Boolean, List<Employee2>> collect4 = emps.stream()
    .collect(Collectors.partitioningBy((e)->e.getSalary()>5000));
  System.out.println(collect4);
}
/**
48888.84000000001
==========groupingBy==============
{BUSY=[Employee2 [id=102, name=李四, age=59, salary=6666.66, status=BUSY], Employee2 [id=104, name=赵六, age=8, salary=7777.77, status=BUSY], Employee2 [id=105, name=田七, age=38, salary=5555.55, status=BUSY]], VOCATION=[Employee2 [id=103, name=王五, age=28, salary=3333.33, status=VOCATION]], FREE=[Employee2 [id=101, name=张三, age=18, salary=9999.99, status=FREE], Employee2 [id=104, name=赵六, age=8, salary=7777.77, status=FREE], Employee2 [id=104, name=赵六, age=8, salary=7777.77, status=FREE]]}
==========groupingBy2==============
{BUSY={成年=[Employee2 [id=104, name=赵六, age=8, salary=7777.77, status=BUSY]], 中年=[Employee2 [id=102, name=李四, age=59, salary=6666.66, status=BUSY], Employee2 [id=105, name=田七, age=38, salary=5555.55, status=BUSY]]}, VOCATION={成年=[Employee2 [id=103, name=王五, age=28, salary=3333.33, status=VOCATION]]}, FREE={成年=[Employee2 [id=101, name=张三, age=18, salary=9999.99, status=FREE], Employee2 [id=104, name=赵六, age=8, salary=7777.77, status=FREE], Employee2 [id=104, name=赵六, age=8, salary=7777.77, status=FREE]]}}
==========partitioningBy==============
{false=[Employee2 [id=103, name=王五, age=28, salary=3333.33, status=VOCATION]], true=[Employee2 [id=102, name=李四, age=59, salary=6666.66, status=BUSY], Employee2 [id=101, name=张三, age=18, salary=9999.99, status=FREE], Employee2 [id=104, name=赵六, age=8, salary=7777.77, status=BUSY], Employee2 [id=104, name=赵六, age=8, salary=7777.77, status=FREE], Employee2 [id=104, name=赵六, age=8, salary=7777.77, status=FREE], Employee2 [id=105, name=田七, age=38, salary=5555.55, status=BUSY]]}
**/

Optional类

Optional 类 (java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在用 Optional 可以更好的表达这个概念;Optional 类主要解决的问题就是空指针异常(NullPointerException)

常用方法:

  • Optional.of(T t):创建一个 Optional 实例
  • Optional.empty(T t):创建一个空的 Optional 实例
  • Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则空实例
  • isPresent():判断是否包含某值
  • orElse(T t):如果调用对象包含值,返回该值,否则返回 t
  • orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回 s 获取的值
  • map(Function f):如果有值对其处理,并返回处理后的 Optional,否则返回 Optional.empty()
  • flatmap(Function mapper):与 map 相似,要求返回值必须是 Optional
@Test
public void test01(){
    Optional<Employee> op = Optional.of(new Employee());
    Employee employee = op.get();
  
    Optional<Employee> op = Optional.empty();
    Employee employee = op.get();
  
    Optional<Employee> op = Optional.ofNullable(new Employee());
    Employee employee = op.get();
  
    Optional<Employee> op = Optional.ofNullable(new Employee());
    if (op.isPresent()) {
        Employee employee = op.get();
    }
}

详细请看:Optional类 - 简书 (jianshu.com)

新时间日期API

本地时间/日期

  • LocalDate、LocalTime、LocalDateTime 分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。

  • 它们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。

常用方法

@Test
public void test01(){
    //获取当前时间日期 now
    LocalDateTime ldt1 = LocalDateTime.now();
    System.out.println(ldt1);

    //指定时间日期 of
    LocalDateTime ldt2 = LocalDateTime.of(2020, 05, 17, 16, 24, 33);
    System.out.println(ldt2);

    //加 plus
    LocalDateTime ldt3 = ldt2.plusYears(2);
    System.out.println(ldt3);

    //减 minus
    LocalDateTime ldt4 = ldt2.minusMonths(3);
    System.out.println(ldt4);

    //获取指定的年月日时分秒... get
    System.out.println(ldt2.getDayOfYear());
    System.out.println(ldt2.getHour());
    System.out.println(ldt2.getSecond());
}

Instant时间戳

它是以 Unix 元年 1970-01-01 00:00:00 到某个时间之间的毫秒值

@Test
public void test02(){
    // 默认获取 UTC 时区 (UTC:世界协调时间)
    Instant ins1 = Instant.now();
    System.out.println(ins1);

    //带偏移量的时间日期 (如:UTC + 8)
    OffsetDateTime odt1 = ins1.atOffset(ZoneOffset.ofHours(8));
    System.out.println(odt1);

    //转换成对应的毫秒值
    long milli1 = ins1.toEpochMilli();
    System.out.println(milli1);

    //构建时间戳
    Instant ins2 = Instant.ofEpochSecond(60);
    System.out.println(ins2);
}

时间差/日期差

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

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

@Test
public void test03(){
    //计算两个时间之间的间隔 between
    Instant ins1 = Instant.now();
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    Instant ins2 = Instant.now();
    Duration dura1 = Duration.between(ins1, ins2);
    System.out.println(dura1.getSeconds());
    System.out.println(dura1.toMillis());
}

@Test
public void test04(){
    LocalDate ld1 = LocalDate.of(2016, 9, 1);
    LocalDate ld2 = LocalDate.now();
    Period period = Period.between(ld1, ld2);  // ISO 标准
    System.out.println(period.getYears());
    System.out.println(period.toTotalMonths());
}

时间校正器

  • TemporalAdjuster:时间校正器。有时我们可能需要获取例如:将日期调整到“下个周日”等操作。

  • TemporalAdjusters:该类通过静态方法提供了大量的常用TemporalAdjuster的实现。

@Test
public void test01(){
    //TemporalAdjusters:时间校正器
    LocalDateTime ldt1 = LocalDateTime.now();
    System.out.println(ldt1);

    //指定日期时间中的 年 月 日 ...
    LocalDateTime ldt2 = ldt1.withDayOfMonth(10);
    System.out.println(ldt2);

    //指定时间校正器
    LocalDateTime ldt3 = ldt1.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
    System.out.println(ldt3);

    //自定义时间校正器
    LocalDateTime ldt5 = ldt1.with((ta) -> {
        LocalDateTime ldt4 = (LocalDateTime) ta;
        DayOfWeek dow1 = ldt4.getDayOfWeek();
        if (dow1.equals(DayOfWeek.FRIDAY)) {
            return ldt4.plusDays(3);
        } else if (dow1.equals(DayOfWeek.SATURDAY)) {
            return ldt4.plusDays(2);
        } else {
            return ldt4.plusDays(1);
        }
    });
    System.out.println(ldt5);
}

解析与格式化

  • DateTimeFormatter:格式化时间 / 日期
@Test
public void test01(){
    //默认格式化
    DateTimeFormatter dtf1 = DateTimeFormatter.ISO_DATE_TIME;
    LocalDateTime ldt1 = LocalDateTime.now();
    String str1 = ldt1.format(dtf1);
    System.out.println(str1);

    //自定义格式化 ofPattern
    DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    LocalDateTime ldt2 = LocalDateTime.now();
    String str2 = ldt2.format(dtf2);
    System.out.println(str2);

    //解析
    LocalDateTime newDate = ldt1.parse(str1, dtf1);
    System.out.println(newDate);
}

时区

  • ZonedDate 、ZonedTime、ZonedDateTime 分别为带时区的日期、时间、日期和时间。
@Test
public void test02(){
    //查看支持的时区
    Set<String> set = ZoneId.getAvailableZoneIds();
    set.forEach(System.out::println);

    //指定时区
    LocalDateTime ldt1 = LocalDateTime.now(ZoneId.of("Europe/Tallinn"));
    System.out.println(ldt1);
  
    // 上海时间
    ZoneId shanghaiZoneId = ZoneId.of("Asia/Shanghai");
    ZonedDateTime shanghaiZonedDateTime = ZonedDateTime.now(shanghaiZoneId);

    //在已构建好的日期时间上指定时区
    LocalDateTime ldt2 = LocalDateTime.now(ZoneId.of("Europe/Tallinn"));
    ZonedDateTime zdt1 = ldt2.atZone(ZoneId.of("Europe/Tallinn"));
    System.out.println(zdt1);
}

接口默认方法与静态方法

默认方法

  • 在 Java8 以前,接口中只能有抽象方法(public abstract 修饰的方法)跟全局静态常量(public static final 常量 )
  • 在 Java8 中,允许接口中包含具有具体实现的方法,称为 “默认方法”,默认方法使用 default 关键字修饰
public interface MyFun {
    T func(int a);

    default Integer getAge(){
        return 22;
    }
}

默认方法的”类优先”原则

若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时

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

1、新建一个接口 MyInterface1.java,里面有个默认实现方法 method1

public interface MyInterface1 {
    default void method1() {
        System.out.println("MyInterface1中的默认方法");
    }
}

2、再建一个类 FatherClass.java,里面同样有一个同名的实现方法 method1

public class FatherClass {
    public void method1() {
        System.out.println("FatherClass中的方法method1");
    }  
}

3、创建一个子类 SonClass.java,这个类继承 FatherClass 父类,并且实现 MyInterface1 接口。

public class SonClass extends FatherClass implements MyInterface1{

}

4、测试这个子类创建后,调用 method1 方法,会调用哪个类或者接口中的实现方法?

@Test
public void test01() {
  SonClass sc = new SonClass();
  sc.method1();
}
/**
FatherClass中的方法method1
**/

5、结果:调用父类中的方法,并不是接口中的实现方法。

(2)接口冲突。如果一个接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突,子类必须指定覆盖哪个父接口中的方法。

1、新建一个 MyInterface2 接口,同 MyInterface1 接口中方法同名。

public interface MyInterface2 {
    default void method1() {
        System.out.println("MyInterface2中的默认方法");
    }
}

2、新建 SonClass2 类,实现了 MyInterface1,MyInterface2 两个接口,这时会提醒你要实现哪个接口中的默认方法,如下:

public class SonClass2 implements MyInterface1,MyInterface2{
    @Override
    public void method1() {
        MyInterface1.super.method1();//覆盖MyInterface1
    }

}

3、测试

@Test
    public void test02() {
        SonClass2 sc = new SonClass2();
        sc.method1();
    }

4、结果:调用 MyInterface1 中的默认方法

静态方法

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

public interface MyInterface1 {
  
    default void method1() {
        System.out.println("MyInterface1中的默认方法");
    }
    
    public static void say() {
        System.out.println("这是MyInterface1中的静态方法");
    }
}

调用方式:接口名.方法名();

MyInterface1.say();

注解

Java8 对注解处理提供了两点改进:可重复的注解及可用于类型的注解。

重复注解

Java8 以前的版本使用注解有一个限制是相同的注解在同一位置只能使用一次,不能使用多次。

Java 8 引入了重复注解机制,这样相同的注解可以在同一地方使用多次。重复注解机制本身必须用 @Repeatable 注解。

public class Test01 {
    //重复注解
    @Test
    @MyAnnotation("Hello")
    @MyAnnotation("World")
    public void test01() throws NoSuchMethodException {
        Class<Test01> clazz = Test01.class;
        Method test01 = clazz.getMethod("test01");
        MyAnnotation[] mas = test01.getAnnotationsByType(MyAnnotation.class);
        for (MyAnnotation ma : mas) {
            System.out.println(ma.value());
        }
    }
}

类型注解

  • 1、Java 8 的类型注解扩展了注解使用的范围。在 Java 8之前,注解只能是在声明的地方所使用,Java8 开始,注解可以应用在任何地方。

    • 1.创建类实例

    • new @Interned MyObject();
      
    • 2.类型映射

    • myString = (@NonNull String) str;
      
    • 3.implements 语句中

    • class UnmodifiableList<T> implements@Readonly List<@Readonly T> { ... }
      
    • 4.throw exception声明

    • void monitorTemperature() throws@Critical TemperatureException { ... }
      
  • 2、新增 ElementType.TYPE_USE 和 ElementType.TYPE_PARAMETER(在Target上)

    • ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中。

    • ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中(例如:声明语句、泛型和强制转换语句中的类型)。

例如,下面的示例。

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}
  • 3、类型注解的作用:类型注解被用来支持在Java的程序中做强类型检查。配合第三方插件工具Checker Framework(注:此插件so easy,这里不介绍了),可以在编译的时候检测出runtime error(例如:UnsupportedOperationException;NumberFormatException;NullPointerException异常等都是runtime error),以提高代码质量。这就是类型注解的作用。

参考

❤️Sponsor

您的支持是我不断前进的动力,如果您恰巧财力雄厚,又感觉本文对您有所帮助的话,可以考虑打赏一下本文,用以维持本博客的运营费用,拒绝白嫖,从你我做起!🥰🥰🥰

支付宝支付 微信支付

文章作者: 简简
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 简简 !
评论
填上邮箱会收到评论回复提醒哦!!!
 上一篇
Java-反射 Java-反射
前言哈喽!大家好,我是小简。今天开始学习《Java-反射》,此系列是我做的一个 “Java 从 0 到 1 ” 实验,给自己一年左右时间,按照我自己总结的 Java-学习路线,从 0 开始学 Java 知识,并不定期更新所学笔记,期待一年后
2022-04-06
下一篇 
数据结构:布隆/布谷鸟 数据结构:布隆/布谷鸟
布隆过滤器原理论文名称:《Space/time trade-offs in hash coding with allowable errors》(在允许错误的哈希编码中,空间/时间的权衡) ——该篇论文是布隆过滤器的开
2022-03-24
  目录