侧边栏壁纸
博主头像
coydone博主等级

记录学习,分享生活的个人站点

  • 累计撰写 306 篇文章
  • 累计创建 51 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

Java8

coydone
2021-05-14 / 0 评论 / 0 点赞 / 328 阅读 / 24,463 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-05-02,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

尚硅谷Java8新特性:https://www.bilibili.com/video/BV14W411u7Ly

Java8介绍

Java8可谓是Java语言历史上变化最大的一个版本,其承诺要调整Java编程向着函数式风格迈进,这有助于编写出更为简洁、表达力更强,并且在很多情况下能够利用并行硬件的代码。

Java8的新特性主要有:Lambda表达式、 Stream API、全新时间日期 API、ConcurrentHashMap、MetaSpace(元空间)。

Java8新特性优点:速度更快,代码更少(增加了新的语法Lambda表达式),强大的Stream API,便于并行,最大化减少空指针异常Optional。其中最为核心的为Lambda表达式与Stream API。

其中Java8新特性中最突出的一部分是:

  • Lambda表达式

  • 函数式接口

  • 方法引用与构造器引用

  • Stream APl

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

  • 新时间日期API

  • 其他新特性

Lambda

使用Lambda的好处

Lambda表达式是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。

//匿名内部类代码与Lambda表达式比较
public class TestLambda {
    @Test
    //匿名内部类
    public void test1(){
        Comparator<Integer> com = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1,o2);
            }
        };
        TreeSet<Integer> ts = new TreeSet<>(com);
    }
    
    @Test
    //Lambda表达式
    public void test2(){
        Comparator<Integer> com = (x,y) -> Integer.compare(x, y);
        TreeSet<Integer> ts = new TreeSet<>(com);
    }
}

需求:我们要获取一定条件下的员工信息。

员工信息实体类

public class Employee {
    private String name;//姓名
    private int age ;//年龄
    private double salary;//工资
	//省略构造方法、getter()、setter()、toString()
}

获取需要的信息

import org.junit.Test;
import java.util.*;
public class TestLambda {
    //数组转集合实例化一系列员工
    List<Employee> employees = Arrays.asList(
            new Employee("张三",18,9999),
            new Employee("李四",36,5839),
            new Employee("王五",39,2899),
            new Employee("赵六",40,9392),
            new Employee("田七",50,5999)
    );
    //获取当前公司员工年龄大于35的员工信息
    public List<Employee> filterEmployees(List<Employee> list){
        List<Employee> emps = new ArrayList<>();
        for (Employee emp : list) {
            if (emp.getAge()>=35){
                emps.add(emp);
            }
        }
        return emps;
    }
    @Test
    public void test3(){
        List<Employee> list = filterEmployees(this.employees);
        for (Employee employee : list) {
            System.out.println(employee);
        }
    }
    //获取员工工资大于5000的员工信息
    public List<Employee> filterEmployees2(List<Employee> list){
        List<Employee> emps = new ArrayList<>();
        for (Employee emp : list) {
            if (emp.getSalary()>=5000){
                emps.add(emp);
            }
        }
        return emps;
    }
    @Test
    public void test4(){
        List<Employee> list = filterEmployees2(this.employees);
        for (Employee employee : list) {
            System.out.println(employee);
        }
    }
}

通过上述代码我们可以发现:我们只改了不同的条件,而大部分代码相似,只是条件不一样。

我们可以将相同的代码提取出来。

优化1:策略设计模式

将相同的步骤抽象成接口,然后实行接口的形式。

public interface MyPredicate<T> {
    public boolean test(T t);
}

通过年龄过滤员工信息

public class FilterEmployeeByAge implements MyPredicate<Employee>{
    @Override
    public boolean test(Employee employee) {
        return employee.getAge()>=35;
    }
}

通过工资过滤员工信息

public class FilterEmployeeBySalary implements MyPredicate<Employee> {
    @Override
    public boolean test(Employee employee) {
        return employee.getSalary()>=5000;
    }
}

测试类

//优化方式一:策略设计模式
public List<Employee> filterEmployee(List<Employee> list,MyPredicate<Employee> mp){
    List<Employee> emps = new ArrayList<>();
    for (Employee employee : list) {
        if (mp.test(employee)){
            emps.add(employee);
        }
    }
    return emps;
}
@Test
public void test5(){
    List<Employee> list = filterEmployee(employees, new FilterEmployeeByAge());
    for (Employee employee : list) {
        System.out.println(employee);
    }
    System.out.println("---------------------");
    List<Employee> list2 = filterEmployee(employees, new FilterEmployeeBySalary());
    for (Employee employee : list2) {
        System.out.println(employee);
    }
}

优化2:匿名内部类

也需要MyPredicate接口。

//优化方式二:匿名内部类
@Test
public void test6(){
    List<Employee> list = filterEmployee(employees, new MyPredicate<Employee>() {
        @Override
        public boolean test(Employee employee) {
            return employee.getAge() >= 35;
        }
    });
    for (Employee employee : list) {
        System.out.println(employee);
    }
}

优化3:Lambda

//优化方式三:Lambda表达式
@Test
public void test7(){
    List<Employee> list = filterEmployee(employees, (e) -> e.getAge() >= 35);
    list.forEach(System.out::println);
}

优化4:Stream API

//优化方式四:Stream API
@Test
public void test8(){
    employees.stream()
            .filter((e)->e.getAge()>=35)
            .forEach(System.out::println);
}

所以,使用Java8的新特性来处理问题,可以写出更简洁、更灵活的代码。

基本使用

Lambda表达式(也称为闭包)是整个Java8发行版中最受期待的在Java语言层面上的改变,Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中),或者把代码看成数据。Lambda表达式用于简化Java中接口式的匿名内部类。被称为函数式接口的概念。函数式接口就是一个具有一个方法的普通接口。像这样的接口,可以被隐式转换为lambda表达式。

语法:(参数1,参数2,…)-> {...}

左侧是Lambda表达式的参数列表,右侧是Lambda表达式中所需执行的功能,即Lambda体。

//上联:左右遇一括号省
//下联:左侧推断类型省
//横批:能省则省

//语法格式一:无参数,无返回值
//() -> System.out.println("hello lambda");
@Test
public void test1(){
	//jdk1.7前,在局部内部类中运用了局部变量,变量必须是final,jdk8默认加上了
    int num = 0;
    Runnable r = new Runnable() {
        @Override
        public void run() {
            System.out.println("Hello World!" + num);
        }
    };
    r.run();
    System.out.println("-------------------------------");

    Runnable r1 = () -> System.out.println("Hello Lambda!");
    r1.run();
}

//语法格式二:有一个参数,并且无返回值
// (x) -> System.out.println(x)
//语法格式三:若只有一个参数,小括号可以省略不写
// x -> System.out.println(x)
@Test
public void test2(){
    Consumer<String> con = x -> System.out.println(x);
    con.accept("Hello Lambda!");
}

//语法格式四:有两个以上的参数,有返回值,并且Lambda体中有多条语句
@Test
public void test3() {
    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表达式的参数列表的数据类型可以省略不写,
//  因为JVM编译器通过上下文推断出数据类型,即类型推断
// (Integer x, Integer y) -> Integer.compare(x, y);

类型推断:上述Lambda表达式中的参数类型都是由编译器推断得出的。Lambda表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的 “类型推断”。

函数式接口

定义

只包含一个抽象方法的接口,称为函数式接口。

可以通过Lambda表达式来创建该接口的对象。(若 Lambda表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)。

我们可以在任意函数式接口上使用@FunctionalInterface注解,这样做可以检查它是否是一个函数式接口,同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。

自定义函数式接口

//Lambda 表达式需要 函数式接口 的支持
//  函数式接口:接口中只有一个抽象方法的接口,称为函数式接口。
//  可以使用注解 @FunctionalInterface 修饰,可以检查是否是函数式接口

//需求:对一个数进行运算
@FunctionalInterface
public interface MyFun {
    Integer getValue(Integer num);
}

@Test
public void test6(){
    Integer num = operation(100, (x) -> x * x);
//类型推断
    System.out.println(num);
    System.out.println(operation(200, (y) -> y + 200));
}

public Integer operation(Integer num, MyFun mf){
    return mf.getValue(num);
}

四大核心函数式接口

Consumer<T>:消费者接口
	void accept(T t);
Function<T,R>:函数型接口,表示接受一个参数并产生结果的函数。
	R apply(T t);
Supplier<T>:供给型接口,代表结果供应商。
	R apply(T t);
Predicate<T>:断言型接口。
	boolean test(T t);
//Consumer<T> 消费型接口
@Test
public void test1(){
   happy(10000, (m) -> System.out.println("消费:" + m + "元"));
} 
//作为参数传递Lambda表达式:为了将Lambda表达式作为参数传递,
//接收Lambda表达式的参数类型必须是与该Lambda表达式兼容的函数式接口的类型。
public void happy(double money, Consumer<Double> con){
   con.accept(money);
}

//Supplier<T> 供给型接口
@Test
public void test2(){
   List<Integer> numList = getNumList(10, () -> (int)(Math.random() * 100));
   
   for (Integer num : numList) {
      System.out.println(num);
   }
}
//需求:产生指定个数的整数,并放入集合中
public List<Integer> getNumList(int num, Supplier<Integer> sup){
   List<Integer> list = new ArrayList<>();
   
   for (int i = 0; i < num; i++) {
      Integer n = sup.get();
      list.add(n);
   } 
   return list;
}

//Function<T, R> 函数型接口
@Test
public void test3(){
   //去除收尾空格
   String newStr = strHandler("\t\t\t test   ", (str) -> str.trim());
   System.out.println(newStr);

   //截取字符串 cde
   String subStr = strHandler("abcdef", (str) -> str.substring(2, 5));
   System.out.println(subStr);
}
//需求:用于处理字符串
public String strHandler(String str, Function<String, String> fun){
   return fun.apply(str);
}

//Predicate<T> 断言型接口
@Test
public void test4(){
   List<String> list = Arrays.asList("Hello", "coydone", "Lambda", "www", "ok");
   //将长度大于3的字符串放入集合中
   List<String> strList = filterStr(list, (s) -> s.length() > 3);
   for (String str : strList) {
      System.out.println(str);
   }
}
//需求:将满足条件的字符串,放入集合中
public List<String> filterStr(List<String> list, Predicate<String> pre){
   List<String> strList = new ArrayList<>();
   for (String str : list) {
      if(pre.test(str)){ //测试是否符合要求
         strList.add(str);
      }
   }
   return strList;
}

其它接口

方法引用与构造器引用

方法引用

当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用。(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致。)

方法引用:使用操作符 “::” 将方法名和对象或类的名字分隔开来。如下三种主要使用情况:

对象::实例方法
类::静态方法
类::实例方法

(x) -> System.out.println(x);
//等同于
System.out::println

BinaryOperator<Double> bo = (x, y) -> Math.pow(x,y);
//等同于
BinaryOperator<Double> bo = Math::pow;

compare((x,y) -> x.equals(y, "abc", "abc");
//等同于
compare(String::equals,"abc", "abc");

注意:当需要引用方法的第一个参数是调用对象,并且第二个参数是需要引用方法的第二个参数(或无参数)时: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) -> new Integer[n];
//等同于
Function<Integer,Integer[]> fun =Integer[]::new;

Stream API

了解Stream

Java8中有两大最为重要的改变。第一个是Lambda表达式;另外一个则是Stream API(java.util.stream.*)。

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

Stream是元素的集合,这点让Stream看起来有些类似Iterator,可以支持顺序和并行的对原Stream进行汇聚的操作。Stream接口提供了强大的集合处理功能,相比较于Iterator迭代器一个一个的处理元素。

我们可以把Stream当成一个高级版本的Iterator。原始版本的Iterator,用户只能一个一个的遍历元素并对其执行某些操作;高级版本的Stream,用户只要给出需要对其包含的元素执行什么操作,比如”过滤掉长度大于10的字符串”、“获取每个字符串的首字母”等,具体这些操作如何应用到每个元素上,就给Stream就好了。

流(Stream)到底是什么呢?是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。集合讲的是数据,流讲的是计算。

注意:

①Stream 自己不会存储元素。

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

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

Stream的操作步骤

1、创建Stream。一个数据源(如:集合、数组),获取一个流;

2、中间操作。一个中间操作链,对数据源的数据进行处理;

3、终止操作(终端操作)。一个终止操作,执行中间操作链,并产生结果。

创建Stream

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

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

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

2、由数组创建流

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

static <T> Stream<T> stream(T[] array)//返回一个流
//重载形式,能够处理对应基本类型的数组
public static IntStream stream(int[] array)
public static LongStream stream(long[] array)
public static DoubleStream stream(double[] array)

//案例
Integer[] nums = new Integer[10];
Stream<Integer> stream1 = Arrays.stream(nums);

3、由值创建流

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

public static<T> Stream<T> of(T... values)//返回一个流

//3. 通过 Stream 类中静态方法 of()
Stream<Integer> stream2 = Stream.of(1,2,3,4,5,6);

4、由函数创建流:创建无限流

可以使用静态方法 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)


//迭代
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);

Stream的中间操作

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

1、筛选与切片

filter(Predicate p):接收Lambda,从流中排除某些元素。
limit(long maxSize):截断流,使其元素不超过给定数量。
skip(long n):跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补
distinct():筛选,通过流所生成元素的hashCode()和equals()去除重复元素

//内部迭代:迭代操作 Stream API 内部完成
public class Employee {
    private int id;
    private String name;
    private int age;
    private double salary;
}
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)
	);
@Test
public void test2(){
   //所有的中间操作不会做任何的处理
   Stream<Employee> stream = emps.stream()
      .filter((e) -> {
         System.out.println("测试中间操作");
         return e.getAge() <= 35;
      });
   
   //只有当做终止操作时,所有的中间操作会一次性的全部执行,称为“惰性求值”
   stream.forEach(System.out::println);
}

//外部迭代
@Test
public void test3(){
   Iterator<Employee> it = emps.iterator();
   
   while(it.hasNext()){
      System.out.println(it.next());
   }
}

@Test
//短路:只要找到满足条件的值,后面的操作不在继续
public void test4(){
   emps.stream()
      .filter((e) -> {
         System.out.println("短路!"); // &&  ||
         return e.getSalary() >= 5000;
      }).limit(3)
      .forEach(System.out::println);
}

@Test
public void test5(){
   emps.parallelStream()
      .filter((e) -> e.getSalary() >= 5000)
      .skip(2)
      .forEach(System.out::println);
}

@Test
public void test6(){
   emps.stream()
      .distinct() //需要Employee重写hashCode()和equals()
      .forEach(System.out::println);
}

2、映射

//获取员工的姓名
Stream<String> str = emps.stream()
   .map((e) -> e.getName());

@Test
public void test1(){
   List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
   Stream<Stream<Character>> stream2 = strList.stream()
         .map(TestStreamAPI1::filterCharacter);
   stream2.forEach((sm) -> {
      sm.forEach(System.out::println);
   });
   
   System.out.println("---------------------------------------------");
   
   Stream<Character> stream3 = strList.stream()
         .flatMap(TestStreamAPI1::filterCharacter);
   stream3.forEach(System.out::println);
}
//这是TestStreamAPI1类中的静态方法
public static Stream<Character> filterCharacter(String str){
   List<Character> list = new ArrayList<>();
   for (Character ch : str.toCharArray()) {
      list.add(ch);
   }
   return list.stream();
}

3、排序

//sorted():自然排序,按照Comparable方式排序
//sorted(Comparator com):定制排序
@Test
public void test2(){
   //自然排序
   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);
}

Stream的终止操作

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

1、查找与匹配

@Test
public void test1() {
    //查看员工状态是否都为Busy
    boolean bl = emps.stream()
            .allMatch((e) -> e.getStatus().equals(Status.BUSY));

    //查看员工状态是否至少有一个为Busy
    boolean bl1 = emps.stream()
            .anyMatch((e) -> e.getStatus().equals(Status.BUSY));

    //查看员工状态是否没有一个为Busy
    boolean bl2 = emps.stream()
            .noneMatch((e) -> e.getStatus().equals(Status.BUSY));
}

@Test
public void test2() {
    //获取工资最低的员工信息,Optional用于防止空指针
    Optional<Employee> op = emps.stream()
            .sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
            .findFirst();

    System.out.println(op.get());

    //找一个状态为Free的员工
    Optional<Employee> op2 = emps.parallelStream()
            .filter((e) -> e.getStatus().equals(Status.FREE))
            .findAny();
    System.out.println(op2.get());
}

@Test
public void test3() {
    //统计状态为Free员工的个数
    long count = emps.stream()
            .filter((e) -> e.getStatus().equals(Status.FREE))
            .count();

    //获取公司工资最高是多少
    Optional<Double> op = emps.stream()
            .map(Employee::getSalary)
            .max(Double::compare);
    System.out.println(op.get());
}

2、归约

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

@Test
public void test1(){
   List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
   //将0作为初始值放入x,从数组中取出1放入y中,计算得1
   //将计算出的1放入x,取出2放入y,计算得3
   //将计算出的3放入x,取出3放入y,计算得6,依次进行
   Integer sum = list.stream()
      .reduce(0, (x, y) -> x + y);
   System.out.println(sum);//55
   
   //统计员工工资总和
   Optional<Double> op = emps.stream()
      .map(Employee::getSalary)
      .reduce(Double::sum);
   System.out.println(op.get());
}

3、收集

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

@Test
public void test4(){
   //工资最高的是多少
   Optional<Double> max = emps.stream()
      .map(Employee::getSalary)
      .collect(Collectors.maxBy(Double::compare));
   System.out.println(max.get());//9999.99

   //工资最低的员工信息
   Optional<Employee> op = emps.stream()
      .collect(Collectors.minBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
   System.out.println(op.get());

   //总和
   Double sum = emps.stream()
      .collect(Collectors.summingDouble(Employee::getSalary));


   //平均数
   Double avg = emps.stream()
      .collect(Collectors.averagingDouble(Employee::getSalary));
   
   //统计总人数
   Long count = emps.stream()
      .collect(Collectors.counting());

   DoubleSummaryStatistics dss = emps.stream()
      .collect(Collectors.summarizingDouble(Employee::getSalary));
   System.out.println(dss.getMax());
   System.out.println(dss.getAverage());
   System.out.println(dss.getSum());
}

//分组
@Test
public void test5(){
   Map<Status, List<Employee>> map = emps.stream()
      .collect(Collectors.groupingBy(Employee::getStatus));
   System.out.println(map);
}

//多级分组
@Test
public void test6(){
   //按照状态和年龄进行分组
   Map<Status, Map<String, List<Employee>>> map = emps.stream()
      .collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy((e) -> {
         if(e.getAge() >= 60)
            return "老年";
         else if(e.getAge() >= 35)
            return "中年";
         else
            return "成年";
      })));
   
   System.out.println(map);
}

//分区
@Test
public void test7(){
   Map<Boolean, List<Employee>> map = emps.stream()
      .collect(Collectors.partitioningBy((e) -> e.getSalary() >= 5000));
   System.out.println(map);//满足条件和不满足条件的进行分区
}

//将姓名连接成字符串
@Test
public void test8(){
   String str = emps.stream()
      .map(Employee::getName)
      .collect(Collectors.joining("," , "----", "----"));
   System.out.println(str);
   //----李四,张三,王五,赵六,赵六,赵六,田七----
}

//归约工资总和
@Test
public void test9(){
   Optional<Double> sum = emps.stream()
      .map(Employee::getSalary)
      .collect(Collectors.reducing(Double::sum));
   System.out.println(sum.get());
}

并行流与串行流

并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。

Java8中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过 parallel() (并行流)与 sequential() (顺序流)在并行流与顺序流之间进行切换。

了解Fork/Join框架

Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行join汇总。

Fork/Join框架与传统线程池的区别

采用 “工作窃取”模式(work-stealing):当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。

相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上。在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行,那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行。这种方式减少了线程的等待时间,提高了性能。

Fork/Join与并行流案例

import java.util.concurrent.RecursiveTask;

public class ForkJoinCalculate extends RecursiveTask<Long>{
   private static final long serialVersionUID = 13475679780L;
   private long start;
   private long end;
   
   private static final long THRESHOLD = 10000L; //临界值
   
   public ForkJoinCalculate(long start, long end) {
      this.start = start;
      this.end = end;
   }
   
   @Override
   protected Long compute() {
      long length = end - start;
      if(length <= THRESHOLD){
         long sum = 0;
         for (long i = start; i <= end; i++) {
            sum += i;
         }
         return sum;
      }else{
         long middle = (start + end) / 2;
         ForkJoinCalculate left = new ForkJoinCalculate(start, middle);
         left.fork(); //拆分,并将该子任务压入线程队列
         ForkJoinCalculate right = new ForkJoinCalculate(middle+1, end);
         right.fork();
         return left.join() + right.join();
      }
   }
}

public class TestForkJoin {
	@Test
	public void test1(){
		long start = System.currentTimeMillis();
		ForkJoinPool pool = new ForkJoinPool();
		ForkJoinTask<Long> task = new ForkJoinCalculate(0L, 10000000000L);
		long sum = pool.invoke(task);
		System.out.println(sum);
		long end = System.currentTimeMillis();
		System.out.println("耗费的时间为: " + (end - start)); 
	}
	
	@Test
	public void test2(){
		long start = System.currentTimeMillis();
		long sum = 0L;
		for (long i = 0L; i <= 10000000000L; i++) {
			sum += i;
		}
		System.out.println(sum);
		long end = System.currentTimeMillis();
		System.out.println("耗费的时间为: " + (end - start)); 
	}
	
	@Test
	public void test3(){
		long start = System.currentTimeMillis();
		Long sum = LongStream.rangeClosed(0L, 10000000000L)
							 .parallel()
							 .sum();
		System.out.println(sum);
		long end = System.currentTimeMillis();
		System.out.println("耗费的时间为: " + (end - start));
	}
}

Optional类

Optional 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。

Optional容器类(JDK1.8):这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

常用方法

Optional.of(T t):为非null的值创建一个Optional实例。

Optional.empty():创建一个空的 Optional实例。

ofNullable():为指定的值创建一个Optional,如果指定的值为null,则返回一个空的Optional。

isPresent():如果值存在返回true,否则返回false。

get():如果Optional有值则将其返回,否则抛出NoSuchElementException。

ifPresent():如果Optional实例有值则为其调用Consumer,否则不做处理

orElse():如果有值则将其返回,否则返回指定的其它值。

orElseGet():orElseGet()与orElse()方法类似,区别在于得到的默认值。orElse()方法将传入的字符串作为默认值,orElseGet()方法可以接受Supplier接口的实现用来生成默认值。

orElseThrow():如果有值则将其返回,否则抛出Supplier接口创建的异常。

map():如果有值,则对其执行调用mapping()函数得到返回值。如果返回值不为null,则创建包含mapping()返回值的Optional作为map()方法返回值,否则返回空Optional 。

flatMap():如果有值,为其执行mapping()函数返回Optional类型返回值,否则返回空Optional。flatMap与map(Funtion)方法类似,区别在于flatMap()中的mapper()返回值必须是Optional。调用结束时,flatMap()不会对结果用Optional封装。

filter():如果有值并且满足断言条件返回包含该值的Optional,否则返回空Optional。

import java.util.Optional;
public class Test {
    public static void main(String[] args){
        //创建Optional对象的方式
        Optional<String> opt1 = Optional.empty();
        Optional<String> opt2 = Optional.of("aaabbb");
        Optional<String> opt3 = Optional.ofNullable("test");

        System.out.println(opt3.get()); //test
        System.out.println(opt3.isPresent()); //true
        opt3.ifPresent(System.out::println);  //如果有值就打印 test
        System.out.println(opt3.orElse("noValue")); //如果有就返回值,没有返回其它值
        try {
            opt1.orElseThrow(Exception::new);
        } catch (Exception e) {
            e.printStackTrace();
        }
        Optional<String> opt4 = opt3.map(v -> v.toUpperCase());
        System.out.println(opt4.orElse("not found"));
    }
}

接口中的默认/静态方法

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

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

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

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

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

interface A {
    default void print() {}
    static void method() {}
}

默认方法与静态方法并不影响函数式接口的契约,可以任意使用。

集合新方法

Map接口的新方法

在JDK8中Map接口提供了一些新的便利的方法。如下的Map方法都是以默认值方法的方式实现的,所以现有的Map接口的实现可以直接拥有这些在默认值方法中定义的默认行为,而不需要新增一行代码。

getOrDefault(Object,V)
putlfAbsent(K,V)
remove(Object key, Object value)
replace(K,V)
replace(K,V,V) //替换(key,oldValue,newValue)
compute(K key,BiFunction<? super K,? super V,? extends V> remappingFunction)
computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)
merge(K key, V value, BiFunction<? super v,? super v,? extends V> remappingFunction)
import java.util.HashMap;
import java.util.Map;

public class Test {
    public static void main(String[] args){
        Map<Integer,String> map = new HashMap<>();
        map.put(1,"Tom");
        map.put(2,"Jack");
        map.put(3,"Mary");

        //获取不到值时返回一个默认值
        map.getOrDefault(4,"Xxx");

        //如果k为null才put(),没有添加进去返回原来的值,添加进去返回新的值
        map.putIfAbsent(4,"Bob");
        map.forEach((k,v) -> System.out.println(k + "==" + v));

        //根据k和v都匹配时才删除
        map.remove(3,"Tom");

        //把oldValue计算成一个newValue
        map.compute(3,(k,v) -> v + "1"); //3 Mary1

        //如果k的值为空,则进行计算
        map.computeIfAbsent(2,val -> val + "test");

        //合并,将一个newValue与oldValue进行合并,如果k不存在,直接新增
        map.merge(1,"new",(oldVal,newVal) -> oldVal.concat(newVal));
    }
}

集合遍历foreach()

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

public class Test {
    public static void main(String[] args){
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
        numbers.add(4);
        //no.1
        numbers.forEach((Integer i) -> {
            System.out.println(i);
        });
        //no.2
        numbers.forEach(i -> {System.out.println(i);});
        //no.3
        numbers.forEach(i -> System.out.println(i));
        //no.4
        numbers.forEach(System.out::println);
        //no.5 没必要使用这个
        numbers.forEach(new MyConsumer());
    }
}

class MyConsumer implements Consumer<Integer> {
    @Override
    public void accept(Integer integer) {
        System.out.println(integer);
    }
}

新时间日期API

使用LocalDate、LocalTime、LocalDateTime

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

注:ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法。

Instant时间戳

用于“时间戳”的运算。它是以Unix元年(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的描述进行运算。

Duration 和 Period

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

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

//Duration : 用于计算两个“时间”间隔
//Period : 用于计算两个“日期”间隔
@Test
public void test3(){
   Instant ins1 = Instant.now();
   try {
      Thread.sleep(1000);
   } catch (InterruptedException e) {
   }
   Instant ins2 = Instant.now();
   //getSeconds() 秒
   System.out.println("所耗费时间为:" + Duration.between(ins1, ins2).toMillis());//毫秒
   
   LocalDate ld1 = LocalDate.now();
   LocalDate ld2 = LocalDate.of(2011, 1, 1);
   Period pe = Period.between(ld2, ld1);
   System.out.println(pe.getYears());
   System.out.println(pe.getMonths());
   System.out.println(pe.getDays());
}

日期的操纵

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

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

//例如获取下个周日
LocalDate nextSunday = LocalDate.now().with(
	TemporalAdjusters.next(DayOfweek.SUNDAY)
);

解析与格式化

java.time.format.DateTimeFormatter类:该类提供了三种格式化方法:

  • 预定义的标准格式;

  • 语言环境相关的格式;

  • 自定义的格式。

@Test
public void test5(){
	//DateTimeFormatter dtf = DateTimeFormatter.ISO_LOCAL_DATE;
	DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss E");
	
	LocalDateTime ldt = LocalDateTime.now();
	String strDate = ldt.format(dtf);
	System.out.println(strDate);
	LocalDateTime newLdt = ldt.parse(strDate, dtf);
	System.out.println(newLdt);
}

时区的处理

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

例如 :Asia/Shanghai 等。

ZoneId:该类中包含了所有的时区信息。

  • getAvailableZoneIds() : 可以获取所有时区时区信息;

  • of(id) : 用指定的时区信息获取ZoneId 对象。

@Test
public void test7(){
   LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
   System.out.println(ldt);
   
   ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("US/Pacific"));
   System.out.println(zdt);
}

@Test
public void test6(){
   //获取所有支持的时区
   Set<String> set = ZoneId.getAvailableZoneIds();
   set.forEach(System.out::println);
}

与传统日期处理的转换

重复注解与类型注解

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

0

评论区