全网详解(波哥)Java8新特性(Lambda、Stream、LocalDate等)新特性

发布于:2023-01-04 ⋅ 阅读:(398) ⋅ 点赞:(0)

Java8新特性

全网详解(波哥)Java8新特性(Lambda、Stream、LocalDate等)新特性

一、Lambda表达式

1.需求分析

​ 创建一个新的线程,指定线程要执行的任务

public static void main(String[]args){
  // 开启一个新的线程
  // new Runnable() 匿名内部类
  new Thread(new Runnable(){
    @Override
    public void run(){
      System.out.println("新线程中执行的代码:"+Thread.currentThread().getName());
    }
  }).start();
  System.out.println("主线程中的代码:"+Thread.currentThread().getName());
}

2.代码分析:

1.Thread类需要一个Runnable接口作为参数,其中的抽象方法run方法是用来指定线程任务内容的核心
2.为了指定run方法体,不得不需要Runnable的实现类
3.为了省去定义一个Runnable的实现类,不得不使用匿名内部类
4.必须覆盖重写抽象的run方法,所有的方法名称,方法参数,方法返回值不得不都重写一遍,而且不能出错
5.实际上,我们只在乎方法体上的代码

3.Lambda表达式初体验

​ Lambda表达式是一个匿名函数,可以理解为一段可以传递的代码

public static void main(String[] args) {
    // 开启一个新线程
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("新线程中执行的代码" + Thread.currentThread().getName());
        }
    }).start();
    System.out.println("主线程代码:" + Thread.currentThread().getName());
    System.out.println("------------------------");
    new Thread(() -> System.out.println("新线程Lamdba表达式"+ Thread.currentThread().getName());}).start();
}

Lambda表达式的优点:简化了匿名内部类的使用,语法更加简单。
匿名内部类语法冗余,体验了Lambda表达式后,发现Lambda表达式是简化匿名内部类的一种方式。

4.Lambda的语法规则

​ Lambda省去了面向对象的条条框框,标准格式由3个部分组成:

(参数类型 参数名称) -> {
代码体;
}

格式说明:

  • (参数类型 参数名称):参数列表
  • {代码体;}:方法体
  • ->:箭头,分割参数列表和方法体

4.1 Lambda练习1

​ 练习无参无返回值的Lambda

定义一个接口

package com.king.service;

public interface UserService  {
    void show() ;
}

创建主方法使用

package com.king;

import com.king.service.UserService;

public class Thread_Demo {
  public static void main(String[] args) {
    goShow(new UserService() {
      @Override
      public void show() {
        System.out.println("show方法执行了。。。。。。");
      }
    });
    System.out.println("------------------");
    goShow(()->{
      System.out.println("Lambda方法执行了。。。。。");
    });
  }

  public static void goShow(UserService userService){
    userService.show();
  }
}

输出:

show方法执行了。。。。。。
------------------
Lambda方法执行了。。。。。

4.2 Lambda练习2

​ 完成一个有参且有返回值的Lambda表达式案例

创建一个Person对象

package com.king.pojo;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
    private String name;

    private Integer age;

    private String heigt;
}

创建主方法启动

package com.king;

import com.king.pojo.Person;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class List_Demo {
  public static void main(String[] args) {
    List<Person> list = new ArrayList<>();

    list.add(new Person("巢瘠薄",22,"175"));
    list.add(new Person("曹瘠薄",25,"185"));
    list.add(new Person("冯小狗",32,"187"));
    list.add(new Person("方鸿俊",21,"176"));

    Collections.sort(list, new Comparator<Person>() {
      @Override
      public int compare(Person o1, Person o2) {
        return o1.getAge()-o2.getAge();
      }
    });
    list.forEach(System.out::println);
    System.out.println("------------------");
  }
    
  @Test
  public void test_Lambda(){
    List<Person> list = new ArrayList<>();

    list.add(new Person("巢瘠薄",22,"175"));
    list.add(new Person("曹瘠薄",25,"185"));
    list.add(new Person("冯小狗",32,"187"));
    list.add(new Person("方鸿俊",21,"176"));
    list.sort((Person p1, Person p2) -> {
      return p1.getAge() - p2.getAge();
    });

    list.forEach(System.out::println);
  }
}

输出;

Person(name=方鸿俊, age=21, heigt=176)
Person(name=巢瘠薄, age=22, heigt=175)
Person(name=曹瘠薄, age=25, heigt=185)
Person(name=冯小狗, age=32, heigt=187)

5.@FunctionalInterface注解

/**
 * @FunctionalInterface
 * 这是一个标志注解,被该注解修饰的接口只能声明一个抽象方法
   */
@FunctionalInterface
public interface UserService {
  void show();
}

6.Lambda表达式的原理

匿名内部类的本质是在编译时生成一个Class文件---Xxxx$1().class
Lambda表达式在程序运行的时候会形成一个类。
	1.在类中新增了一个方法,这个方法的方法体就是Lambda表达式中的代码
	2.还会形成一个匿名内部类,实现接口,重写实现方法
	3.在借口中重写方法会调用新生成的方法

7.Lambda表达式的省略写法 简写

在Lambda表达式的标准写法基础上,可以使用省略写法的规则为:
	1.小括号内的参数类型可以省略
	2.如果小括号内有且仅有一个参数,则小括号可以省略
	3.如果大括号内有且仅有一个语句,可以同时省略大括号,return关键字及语句分号
package com.king;

import com.king.service.OrderSercice;
import com.king.service.StudentService;

public class Simplify_Demo {
    public static void main(String[] args) {

        goStudent((String name,Integer age)->{
            System.out.println(name+age);
            return name+age+"666..";
        });
        System.out.println("--------------");
        //省略写法
        String s = goStudent((name, age) -> name + age + "6666.。。");
        System.out.println(s);
        System.out.println("------------------------");
        Integer i = goOrder((String name) -> {
            System.out.println("---->" + name);
            return 6666;
        });
        System.out.println(i);
        System.out.println("------------------------");
        goOrder(name -> {
            System.out.println(name);
            return 666;
        });
    }

    public static String goStudent(StudentService studentService){
        return studentService.show("张三",1231);
    }

    public static Integer goOrder(OrderSercice orderSercice){
       return orderSercice.show("小明");
    }
}

8.Lambda表达式的使用前提

Lambda表达式的语法是非常简洁的,但是Lambda表达式不是随便使用的,使用时有几个条件要特别注意
1.方法的参数或者局部变量类型必须为接口才能使用
2.接口中有且仅有一个抽象方法 @FunctionalInterface

9.Lambda和匿名内部类的对比

1.所需类型不一样
a.匿名内部类的类型可以是类、抽象类、接口
b.Lambda表达式需要的类型必须是接口
2.实现方法数量不一样
a.匿名内部类所需的接口中抽象方法的数量是随意的
b.Lambda表达式所需的接口中只能有一个抽象方法
3.实现原理不一样
a.匿名内部类是在变以后形成一个class文件
b.Lamb表达式是在程序运行的时候动态生成class

二、接口中新增的方法

1.JDK8中接口的新增

在jdk中正对接口有做增强,在jdk8之前

interface 接口名{
  静态常量;
  抽象方法;
}

在jdk8之后对接口做出了增强,接口中可以有默认方法静态方法

interface 接口名{
  静态常量;
  抽象方法;
  默认方法;
  静态方法;
}

2.默认方法

2.1 为什么要增强默认方法

在jdk8以前接口中只能有抽象方法和静态常量,会存在一下的问题

如果接口中新增抽象方法,那么实现类都必须要抽象这个抽象方法,非常不利于接口的扩展

package com.king.inter;

public interface Demo01Interface {
  public static void main(String[] args) {
    A b = new B();
    A c = new C();
  }
}

interface A{
  void test1();

  //  接口中新增抽象方法,所有实现类都需要重写这个方法,不利于接口的扩展
  //void test2();

}

class B implements A{
  @Override
  public void test1() {
  }
}
class C implements A{
  @Override
  public void test1() {
  }
}

2.2 接口默认方法的格式

​ 接口中默认方法的语法格式是:

interface 接口名{
  修饰符	default	返回值类型	方法名{
    方法体;
  }
}

2.3 接口中默认方法的使用

package com.king.inter;

public interface Demo01Interface {
  public static void main(String[] args) {
    A b = new B();
    b.test3();
    A c = new C();
    c.test3();
  }
}

interface A{
  void test1();

  //  接口中新增抽象方法,所有实现类都需要重写这个方法,不利于接口的扩展
  //void test2();
  public default String  test3(){
    System.out.println("接口中的默认方法执行了。。。。");
    return "hello";
  }
}

class B implements A{
  @Override
  public void test1() {
  }

  @Override
  public String test3() {
    System.out.println("B 实现类中重写了A的默认方法");
    //       return A.super.test3(); //接口中的默认方法执行了。。。。
    return "ok...";
  }

}
class C implements A{
  @Override
  public void test1() {

  }
}

接口中默认方法有两种的使用方式

  1. 实现类的默认方法用接口的默认方法
  2. 实现类重写接口的默认方法

3.静态方法

JDk8中为接口新增了静态方法,作用也是为了接口的扩展

3.1语法规则

interface 接口名{
  修饰符	static 返回值类型 方法名{
    方法体;
  }
}
package com.king.inter;

public interface Demo01Interface {
    public static void main(String[] args) {
        A b = new B();
        b.test3();
        A.test4();
        System.out.println("------------------");
        A c = new C();
        c.test3();
    }
}

interface A{
    void test1();

    //  接口中新增抽象方法,所有实现类都需要重写这个方法,不利于接口的扩展
    //void test2();
    public default String  test3(){
        System.out.println("接口中的默认方法执行了。。。。");
        return "hello";
    }

    public static String test4(){
        System.out.println("接口中的静态方法");
        return "hello";
    }
}

class B implements A{
    @Override
    public void test1() {
    }

    @Override
    public String test3() {
        System.out.println("B 实现类中重写了A的默认方法");
//       return A.super.test3(); //接口中的默认方法执行了。。。。
        return "ok...";
    }

}
class C implements A{
    @Override
    public void test1() {
    }
}

3.2 静态方法的使用

接口中的静态方法在实现类中是不能被重写的,调用的话只能通过接口类型来实现:接口名.静态方法名();

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ha6QaZDB-1661781169548)(img/image-20220804152419586.png)]

4.两者的区别

  1. 默认方法通过实例调用,静态方法通过接口名调用
  2. 默认方法可以被继承,实现类可以直接调用接口默认方法,也可以重写接口方法
  3. 静态方法不能被继承,实现类不能重写接口的静态方法,只能使用接口名调用

三、函数式接口

+++

1.函数式接口的由来

+++

​ 我们知道使用lambda表达式的前提是需要有函数式接口,而Lambda表达式使用时不关心接口名,抽象方法名,只关心抽象方法的参数列表和返回值类型,因此,为了让我们使用Lambda表达式更加的方便,在JDK中提供了大量常用的函数式接口

package com.king.fun;

public class Demo01Fun {

  public static void main(String[] args) {
    fun1((arr) -> {
      int sum = 0;
      for (int i : arr) {
        sum += arr[i];
      }
      return sum;
    });
  }

  public static void fun1(Operator operator) {
    int[] arr = {1, 2, 3, 4};
    int sum = operator.getSum(arr);
    System.out.println("sum=" + sum);
  }
}

/*
* 函数式接口
* */
interface Operator {
  int getSum(int[] arr);
}

2.函数式接口介绍

+++

在jdk中帮我们提供的函数式接口,主要是在java.util.function包中

2.1 Supplier

​ 无参有返回值的接口

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

使用:

package com.king.fun;

import java.util.Arrays;
import java.util.function.Supplier;

/*
 * Suplier   函数式接口的使用
 * */
public class SupplierTest {
    public static void main(String[] args) {
        fun1(()->{
            int arr[]={22,33,44,55,12,312};
            //计算出数组的最大值
            Arrays.sort(arr);
            return arr[arr.length-1];
        });
    }
    public static void fun1(Supplier<Integer> supplier) {
        //get()是一个无参有返回值的抽象方法
        Integer max = supplier.get();
        System.out.println("max=" + max);
    }
}

2.2 Consumer

​ 有参无返回值的接口,前面介绍的supplier接口是用来身缠生产数据的,而Consumer是用来消费数据的,使用的时候需要指定一个泛型来定义参数类型

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);
}

使用:将输入的数据统一的转换为大写输出

package com.king.fun;

import java.util.Locale;
import java.util.function.Consumer;

public class ConsumerTest {

    public static void main(String[] args) {
        test(msg->{
            System.out.println(msg+"->装换为大写"+msg.toUpperCase(Locale.ROOT));
        });
    }
    public static void test(Consumer<String> consumer){
        //accept()是有参数无返回类型额接口
        consumer.accept("hello,world");
    }
}

默认方法:andThen

​ 如果一个方法的参数和返回值全部是Consumer类型,那么就可以实现效果,消费一个数据的时候,首先做一个操作,然后在做一个操作,实现组合,而这个方法的Consumer接口中的default方法 andThen()方法

 default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }

具体操作:

package com.king.fun;

import java.util.Locale;
import java.util.function.Consumer;

public class ConsumerAndThenTest {
  public static void main(String[] args) {
    test2(msg1->{
      System.out.println(msg1+"->装换为小写"+msg1.toLowerCase(Locale.ROOT));
    },msg2->{
      System.out.println(msg2+"->装换为大写"+msg2.toUpperCase(Locale.ROOT));
    });
  }
  public static void test2(Consumer<String> consumer1, Consumer<String> consumer2){
    String str="Hello,Wolrd";
    //consumer1.accept(str);//转小写
    //consumer2.accept(str);//转大写
    //consumer1.andThen(consumer2).accept(str);
    consumer2.andThen(consumer1).accept(str);
  }
}

2.3 Function

​ 有参有返回值的接口, Function接口是根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件,有参数有返回值。

@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
}

使用:传递进去一个字符串,返回一个数字

package com.king.fun;

import java.util.function.Function;

public class FunctionTest {
  public static void main(String[] args) {
    test(msg->{
      return Integer.parseInt(msg);
    });
  }

  public static void test(Function<String, Integer> function) {
    Integer apply = function.apply("666");
    System.out.println("apply=" + apply);
  }
}

默认方法:andThen,也会用来做组合操作

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
  Objects.requireNonNull(after);
  return (T t) -> after.apply(apply(t));
}

具体操作:

package com.king.fun;

import java.util.function.Function;

public class FunctionAndThenTest {
  public static void main(String[] args) {
    test(msg1 -> {
      return Integer.parseInt(msg1);
    }, msg2 -> {
      return msg2 * 10;
    });
  }

  public static void test(Function<String, Integer> function1, Function<Integer, Integer> function2) {
    //Integer apply = function1.apply("666");
    //Integer integer = function2.apply(apply);
    Integer apply1 = function1.andThen(function2).apply("666");
    //Integer apply = function2.apply(function1.apply("666"));
    //System.out.println("integer = " + integer);
    System.out.println("---------------");
    System.out.println("apply= "+apply1);
  }
}

默认的compose方法的作用顺序和andThen方法刚好相反

而静态方法的identity则是,输入什么参数就返回值什么参数

2.4 Predicate

​ 有参且返回值为boolean类型的接口

@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
}

使用;

package com.king.fun;

import java.util.function.Predicate;

public class PredicateTest {
  public static void main(String[] args) {
    test(msg -> {
      return msg.length() > 3;
    }, "hello");
  }

  private static void test(Predicate<String> predicate, String msg) {
    boolean b = predicate.test(msg);
    System.out.println("b:" + b);
  }
}

在Predicate中的默认方法提供了逻辑关系操作 and,or,negate,isEqual方法

package com.king.fun;

import java.util.function.Predicate;

public class PredicateDefaultTest {
  public static void main(String[] args) {
    test(msg1 -> {
      return msg1.contains("H");
    }, msg2 -> {
      return msg2.contains("W");
    });
  }

  private static void test(Predicate<String> p1, Predicate<String> p2) {
    //b1 包含H b2包含W

    //b2 包含H 同时  p2 包含W
    boolean b1 = p1.and(p2).test("Hello");
    //  p1包含H 或者 p2 包含W
    boolean b2 = p1.or(p2).test("Hello");
    //p1    不包含H
    boolean b3 = p1.negate().test("Hello");
    System.out.println(b1);//false
    System.out.println(b2);//true
    System.out.println(b3);//false
  }
}

四、方法引用

1.为什么要用方法引用

1.1 lambda表达式冗余

在使用lambda表达式的时候,也会出现代码冗余的情况,比如:用lambda表达式求一个数组的和

package com.king.funref;

import java.util.function.Consumer;

public class FunctionRefTest01 {

  public static void main(String[] args) {
    printMax(a -> {
      int sum = 0;
      for (int i : a) {
        sum += i;
      }
      System.out.println("数组求和:" + sum);
    });
  }

  private static void printMax(Consumer<int[]> consumer) {
    int[] a = {10, 20, 30, 40, 50, 60};
    consumer.accept(a);
  }
}

1.2 解决方案

因为在Lambda表达式中要执行的代码和我们另一个方法中的代码是一样的,这时就没有必要重写一份逻辑了,这时我们就可以“引用重复代码“

:: 方法引用,jdk8中新的语法

package com.king.funref;

import java.util.function.Consumer;
import java.util.function.Function;

public class FunctionRefTest01 {

  public static void main(String[] args) {
    //  ::  方法引用,jdk8中新的语法
    printMax(FunctionRefTest01::getSum);
  }
  /*
    * 求数组中所有元素的和
    * */
  public static void getSum(int[] a){
    int sum = 0;
    for (int i : a) {
      sum += i;
    }
    System.out.println("数组求和:" + sum);
  }

  private static void printMax(Consumer<int[]> consumer) {
    int[] a = {10, 20, 30, 40, 50, 60};
    consumer.accept(a);
  }
}

2.方法引用的格式

符号表示:::

符号说明:双冒号为方法引用运算符,而它所在的表达式被称为方法引用

应用场景:如果lambda表达式所要实现的方案,已经有其它方法存在相同的方案,那么则可以使用方法引用

常见的应用方式

方法引用在jdk8中的使用时相当灵活的,有以下几种形式

  1. InstanceName::methodName 对象::方法名
  2. ClassName::staticMethodName 类名::静态方法
  3. ClassName::methodName 类名::普通方法
  4. ClassName::new 类名::new 调用的构造器
  5. TypeName[]::new String[]::new 调用数组构造器

2.1 对象名::方法名

这是最常见的一种用法。如果一个类中已经存在了一个成员方法,即可通过类名引用成员方法

package com.king.funref;

import java.util.Date;
import java.util.function.Supplier;

public class FunctionRefTest02 {
  public static void main(String[] args) {
    Date date = new Date();
    Supplier<Long> supplier = () -> {
      return date.getTime();
    };
    System.out.println(supplier.get());
    System.out.println("-------------------");
    //然后我们通过 方法引用 的方式类处理
    Supplier<Long> time = date::getTime;
    System.out.println(time.get());
  }
}

方法引用的注意事项:

  1. 被引用的方法,参数要和接口中的抽象方法的参数一致
  2. 当接口抽象方法有返回值时,被引用的方法也必须有返回值

2.2 类名::静态方法名

也是比较常用的方式

package com.king.funref;

import java.util.function.Supplier;

public class FunctionRefTest03 {
    public static void main(String[] args) {
        Supplier<Long> supplier = () -> {
            return System.currentTimeMillis();
        };
        System.out.println(supplier.get());
        //通过    方法引用    来实现
        Supplier<Long> longSupplier= System::currentTimeMillis;
        System.out.println(longSupplier.get());
    }
}

2.3 类名::引用实例方法

​ Java面向对象的时候,类名只能调用静态方法,类名引用实例方法是有前提的,实际上是拿第一个参数作为方法的调用者

package com.king.funref;

import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

public class FunctionRefTest04 {
  public static void main(String[] args) {
    Function<String,Integer> function=(msg)->{
      return msg.length();
    };
    System.out.println(function.apply("hello"));

    //通过    方法引用    来实现
    Function<String,Integer> function1=String::length;
    System.out.println(function1.apply("hello"));
    System.out.println("---------------------------------");
    BiFunction<String,Integer,String>   biFunction=String::substring;
    String msg = biFunction.apply("helloworld", 3);
    System.out.println(msg);
  }
}

2.4 类名::构造器

​ 由于构造器的名称和类名完全一致,所以构造器引用使用::new的格式使用,

package com.king.funref;

import com.king.pojo.Person;
import com.king.pojo.Student;

import java.util.function.BiFunction;
import java.util.function.Supplier;

public class FunctionRefTest05 {
  public static void main(String[] args) {
    Supplier<Person> supplier = () -> {
      return new Person();
    };
    System.out.println(supplier.get());
    System.out.println("------------");
    //通过    方法引用    来实现
    Supplier<Person> personSupplier = Person::new;
    System.out.println(personSupplier.get());
    System.out.println("------------");
    BiFunction<String, Integer, Student> biFunction = Student::new;
    System.out.println(biFunction.apply("张丹",12));
  }
}

2.5 数组::构造器

​ 数组是怎么构造出来的呢?

package com.king.funref;

import com.king.pojo.Person;
import com.king.pojo.Student;

import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

public class FunctionRefTest06 {
  public static void main(String[] args) {
    Function<Integer, String[]> function = (len) -> {
      return new String[len];
    };
    String[] apply = function.apply(12);
    System.out.println("数组的长度是:" + apply.length);
    System.out.println("--------------");
    Function<Integer, String[]> function1 = String[]::new;
    String[] s = function1.apply(5);
    System.out.println("数组的长度是" + s.length);
  }
}

小结:方法引用是对Lambda表达式符合特定情况的一种缩写方式,它使得我们的Lambda表达式更加的精简,也可以理解为Lambda表达式的缩写形式,不过要注意的是方法引用只能引用已经存在的方法。

五、Stream API

1.集合处理数据的弊端

​ 当我们在需要对集合中的元素进行操作的时候,除了必需的添加,删除,获取外,最典型的操作是集合的遍历

package com.king.stream;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class StreamTest01 {
  public static void main(String[] args) {
    //定义一个集合
    List<String> list= Arrays.asList("张三","张三丰","周新民");
    //1.获取所有姓张的信息
    List<String> list1=new ArrayList<>();
    for (String s:list){
      if (s.startsWith("张")){
        list1.add(s);
      }
    }

    //2.获取名称长度为3的用户
    List<String> list2=new ArrayList<>();
    for (String s : list1) {
      if (s.length() == 3) {
        list2.add(s);
      }
    }

    //3.输出符合要求的用户信息
    for (String s : list2) {
      System.out.println(s);
    }
  }
}

​ 上面的代码针对与我们不同的需求总是一次次的循环循环循环,这时我们希望有更加高效的处理方式,这是我们就可以通过jdk8中提供的Stream API来解决这个问题了。

package com.king.stream;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class StreamTest02 {
  public static void main(String[] args) {
    //定义一个集合
    List<String> list = Arrays.asList("张三", "张三丰", "周新民");
    //1.获取所有姓张的信息
    //2.获取名称长度为3的用户
    //3.输出符合要求的用户信息
    list.stream()
      .filter(s -> s.startsWith("张"))
      .filter(s -> s.length() == 3)
      .forEach(s -> {
        System.out.println(s);
      });
    System.out.println("---------------");
    list.stream()
      .filter(s -> s.startsWith("张"))
      .filter(s -> s.length() == 3)
      .forEach(System.out::println);
  }
}

​ 上面的Stream代码的含义:获取流,过滤张,过滤长度,逐一打印。代码相比于上面的案例更加的简洁直观

2.Stream流式思想概述

注意:Stream 和 IO(InputStream/OutStream) 没有任何关系,请暂时忘记对传统IO流的固有影响!Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理,Stream可以看作是流水线上的一个工序。在流水线上,通过多个工序让一个原材料加工成一个商品

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

Stream API能让我们快速完成许多复杂的操作,如筛选,切片,映射,查找,去重,统计,匹配和归约。

3.Stream流的获取方式

3.1 根据Collection获取

​ 首先,java.util.Collection接口中加入了default方法stream。也就是说Collection接口下的所有食安县实现都可以通过Stream方法来获取Stream流

package com.king.stream;

import java.util.*;

public class StreamTest03 {
  public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.stream();
    Set<String> set=new HashSet<>();
    set.stream();
    Vector vector=new Vector();
    vector.stream();
  }
}

​ 但是map接口没有实现Collection接口,那这时怎么办呢?这是我们可以根据Map获取对应的key value集合。

public class StreamTest04 {
  public static void main(String[] args) {
    Map<String, Object> map = new HashMap<>();
    Stream<String> stream = map.keySet().stream();//key
    Stream<Object> stream1 = map.values().stream();//value
    Stream<Map.Entry<String, Object>> entryStream = map.entrySet().stream();
  }
}

3.2 通过Stream的of方法

​ 在实际开发中,我们不可避免的还是会操作到数组中的数据,由于数组对象不可能添加默认方法,所有Stream接口中提供了静态方法of

public class StreamTest05 {
  public static void main(String[] args) {
    Stream<String> stream = Stream.of("a1", "a2", "a3", "a4");
    String[] arr1={"b1", "b2", "b3", "b4"};
    Stream<String> arr11 = Stream.of(arr1);
    Integer[] arr2={1,2,3,4,5};
    Stream<Integer> arr21 = Stream.of(arr2);
    arr21.forEach(System.out::println);
    System.out.println("------------------------");

    //注意:基本数据类型是不行的
    int[] arr3={1,2,3,4};
    Stream.of(arr3).forEach(System.out::println);
  }
}

4.Stream常用方法介绍

Stream常用方法

Stream流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分为两种:

方法名 方法作用 返回值类型 方法种类
count 统计个数 long 终结
forEach 逐一处理 void 终结
filter 过滤 Stream 函数拼接
limit 取用前几个 Stream 函数拼接
skip 跳过前几个 Stream 函数拼接
map 映射 Stream 函数拼接
concat 组合 Stream 函数拼接

终结方法:返回值类型不再是Stream类型的方法,不在支持链式调用。

非终结方法:返回值类型仍然是Stream类型的方法,支持链式调用。(除了终结方法外,其余方法均为非终结方法)

Stream注意事项(重要)

  1. Stream只能操作一次
  2. Stream方法返回的是新的流
  3. Stream不能调用终结方法,中间的操作不会执行
public class StreamTest06{
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("a1", "a2", "a3", "a4");
        stream.filter(s -> {
            System.out.println("----------");
            return s.contains("a");
        }).forEach(System.out::println);
        System.out.println("------------");
    }
}

4.1 forEach

forEach用来遍历流中的数据

void forEachOrdered(Consumer<? super T> action);

该方法接受一个Consumer接口,会将每一个流元素交给函数处理

public class StreamTest07ForEach {
    public static void main(String[] args) {
        Stream.of("a1", "a2", "a3", "a4").forEach(System.out::println);
    }
}

4.2 count

Stream流中的count方法用来统计其中的元素个数的

long count();

该方法返回一个long值,代表元素个数。

public class StreamTest07Count {
    public static void main(String[] args) {
        long count = Stream.of("a1", "a2", "a3").count();
        System.out.println(count);
    }
}

4,3 filter

filter方法的作用是用来过滤数据的,返回符合条件的数据

可以通过filter方法讲一个流转换成另一个子集流

Stream<T> filter(Predicate<? super T> predicate);

该接口接受一个Predicate函数式接口参数作为筛选条件

public class StreamTest07Filter {
  public static void main(String[] args) {
    Stream.of("a1", "a2", "a3","bb","cc","dd").filter(s -> s.contains("a"))
      .forEach(System.out::println);
  }
}

输出:

a1
a2
a3

4.4 limit

limit方法可以对流进行截取处理,支取前n个数据

Stream<T> limit(long maxSize);

参数是一个long类型的数值,如果集合长度大于参数就进行截取,否则不操作:

public class StreamTest08Limit {
  public static void main(String[] args) {
    Stream.of("a1", "a2", "a3", "bb", "cc", "aa", "dd").limit(5)
      .forEach(System.out::println);
  }
}

输出:

a1
a2
a3
bb
cc

4.5 skip

在这里插入图片描述

如果希望跳过前面几个元素,可以使用skip方法获取一个截取之后的新流:

  Stream<T> skip(long n);

操作:

public class StreamTest09Skip {
    public static void main(String[] args) {
        Stream.of("a1", "a2", "a3","bb","cc","aa","dd").skip(3)
                .forEach(System.out::println);
    }
} 

输出:

bb
cc
aa
dd

4.6 map

如果我们需要将流中的元素映射到另一个流中可以使用map方法;

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

在这里插入图片描述

该接口需要一个Function函数式的接口参数,可以将当前的流中的T类型数据转换为另一种R类型的数据

public static void main(String[] args) {
  Stream.of("1", "2", "3","4","5","6","7")
    //.map(s -> Integer.parseInt(s))
    .map(Integer::parseInt)
    .forEach(System.out::println);
}

4.7 sorted

如果需要将数据排序,可以使用sorted方法:

Stream<T> sorted();

在使用的时候可以根据自然规则排序,也可以通过比较器来指定对应的排序规则

public class StreamTest11Sorted {
  public static void main(String[] args) {
    Stream.of("1", "9", "3","4","22","6","7")
      .map(Integer::parseInt)
      //.sorted() //根据数据的自然顺序排序
      .sorted((o1,o2)->o2-o1) //根据比较指定排序规则
      //.sorted(Integer::compareTo)
      .forEach(System.out::println) ;
  }
}

4.8 distinct

如果要去掉重复的数据,可以使用distinct方法:

Stream<T> distinct();
package com.king.stream;
import com.king.pojo.Person;
import java.util.stream.Stream;
public class StreamTest12Distinct {
  public static void main(String[] args) {
    Stream.of("1", "9", "3","4","3","6","7")
      .distinct() //去掉重复数据
      .forEach(System.out::println);
    System.out.println("---------------------------------");
    Stream.of(
      new Person("张三",18,"198"),
      new Person("李四",22,"199"),
      new Person("张三",18,"167")
    ).distinct()
      .forEach(System.out::println);
  }
}

Stream流中的 方法对于基本数据类型是可以直接去重的,但是对于自定义类型,我们是需要重写 hashCode和equals方法来移除重复元素

在这里插入图片描述

使用;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
  private String name;
  private Integer age;
  private String heigt;

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Person person = (Person) o;
    return Objects.equals(name, person.name) && Objects.equals(age, person.age);
  }

  @Override
  public int hashCode() {
    return Objects.hash(name, age);
  }
}

4.9 match

如果需要判断数据是否匹配指定的条件,可以使用match相关的方法

boolean anyMatch(Predicate<? super T> predicate);// 元素是否有任意一个满足条件
boolean allMatch(Predicate<? super T> predicate);// 元素是否都满足条件
boolean noneMatch(Predicate<? super T> predicate);// 元素是否都不满足条件

使用:

public class StreamTest13Match {
  public static void main(String[] args) {
    boolean b = Stream.of("1", "9", "3", "4", "3", "6", "7")
      .map(Integer::parseInt)
      //.allMatch(s -> s > 0)
      //.anyMatch(s -> s > 4)
      .noneMatch(s-> s > 4);
    System.out.println(b);
  }
}

4.10 find

如果我们需要找到某些元素,可以使用find方法来实现

Optional<T> findFirst();
Optional<T> findAny();

在这里插入图片描述

使用:

public class StreamTest14Find {
    public static void main(String[] args) {

        Optional<String> first = Stream.of("1", "9", "3", "4", "3", "6", "7").findFirst();
        System.out.println(first.get());

        Optional<String> any = Stream.of("1", "9", "3", "4", "3", "6", "7").findAny();
        System.out.println(any.get());
    }
}

4.11 max和min

如果我们想要获取最大值和最小值没那么可以使用max和min方法

Optional<T> max(Comparator<? super T> comparator);
Optional<T> min(Comparator<? super T> comparator);

在这里插入图片描述

使用:

public class StreamTest15MaxMin {
  public static void main(String[] args) {

    Optional<Integer> first = Stream.of("1", "9", "3", "4", "3", "6", "7")
      .map(Integer::parseInt)
      .max((o1,o2)->o1-o2);
    System.out.println(first.get());

    Optional<Integer> any = Stream.of("1", "9", "3", "4", "3", "6", "7")
      .map(Integer::parseInt)
      .min(((o1, o2) -> o1-o2));
    System.out.println(any.get());
  }
}

4.12 reduce方法

如果需要将所有数据归纳得到一个数据,使用reduce方法

T reduce(T identity, BinaryOperator<T> accumulator);

使用:

public class StreamTest16Reduce {
  public static void main(String[] args) {
    Integer total = Stream.of(4, 5, 3, 9)
      // identity是默认值
      // 第一次的时候会将默认值赋给x
      // 之后每次会将上一次的操作结果赋值给x y就是每次从数据中获取的元素
      .reduce(1, (x, y) -> {
        System.out.println("x=" + x + ",y=" + y);
        return x * y;
      });
    System.out.println(total);

    // 获取最大值
    Integer max = Stream.of(4, 5, 3, 9)
      .reduce(0, (x, y) -> {
        return x > y ? x : y;
      });
    System.out.println(max);

    // 获取最小值
    Optional<Integer> reduce2 = Stream.of(4, 5, 3, 9)
      .reduce((x, y) -> {
        return x < y ? x : y;
      });
    System.out.println(reduce2.get());
  }
}

4.13 map和reduce的组合

在实际开发中,我们经常会将map和reduce组合使用

public class StreamTest17MapReduce {
  public static void main(String[] args) {
    //求年龄总和
    Integer sumAge = Stream.of(
      new Person("张三", 18, 198),
      new Person("李四", 22, 199),
      new Person("张三", 18, 167),
      new Person("赵六", 19, 176),
      new Person("张三", 33, 180)
    ).map(Person::getAge)
      .reduce(0, Integer::sum);
    System.out.println(sumAge);
    // 求出年龄最大值
    Integer maxAge = Stream.of(
      new Person("张三", 18, 198),
      new Person("李四", 22, 199),
      new Person("张三", 18, 167),
      new Person("赵六", 19, 176),
      new Person("张三", 33, 180)
    ).map(Person::getAge)
      .reduce(0, Math::max);
    System.out.println(maxAge);
    //统计 字符 a 出现的次数
    Integer count = Stream.of("a", "b", "c", "d", "a", "c", "a")
      .map(s -> "a".equals(s) ? 1 : 0)
      .reduce(0, Integer::sum);
    System.out.println(count);
  }
}

4.14 mapToInt

如果需要将Stream中的Integer类型转换成int类型,可以使用mapToInt方法来实现

public class StreamTest18MapToInt {
  public static void main(String[] args) {
    // Integer占用的内存比int多很多,在stream流操作中会自动拆装箱操作
    Integer arr[] = {1,2,3,4,5};
    Stream.of(arr)
      .filter(integer -> integer > 0)
      .forEach(System.out::println);
    // 为了提高程序代码的效率, 我们可以现将流中Integer数据转换为int数据,然后操作
    Integer arr2[] = {1,2,3,4,5,7,9};
    Stream.of(arr2)
      .mapToInt(Integer::intValue)
      .filter(i -> i > 4)
      .forEach(System.out::println); 
  }
}

4.15 concat

如果有两个流希望合并成为一个流,那么可以使用Stream接口的静态方法concat

public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
  Objects.requireNonNull(a);
  Objects.requireNonNull(b);

  @SuppressWarnings("unchecked")
  Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
    (Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
  Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
  return stream.onClose(Streams.composedClose(a, b));
}

使用:

public class StreamTest19tConcat {
  public static void main(String[] args) {
    Stream<String> stream1 = Stream.of("a", "b", "c");

    Stream<String> stream2 = Stream.of("x", "y", "z");
    // 通过concat方法将两个流合并成一个新的流
    Stream.concat(stream1,stream2)
      .forEach(System.out::println);
  }
}

4.16 综合案例

定义两个集合,然后在集合中存储多个用户名称,然后完成如下的操作:

  1. 第一个队伍只保留姓名长度为3的成员
  2. 第一个队伍筛选后只要前三个
  3. 第二个队伍只要姓张的成员
  4. 第二个队伍筛选之后不要前两个人
  5. 将两个队伍合并成一个队伍
  6. 根据姓名创建Person对象
  7. 打印整个队伍的Person信息
public class StreamTest20Demo {
    /*
    1. 第一个队伍只保留姓名长度为3的成员
    2. 第一个队伍筛选后只要前三个
    3. 第二个队伍只要姓张的成员
    4. 第二个队伍筛选之后不要前两个人
    5. 将两个队伍合并成一个队伍
    6. 根据姓名创建Person对象
    7. 打印整个队伍的Person信息
    * */
    public static void main(String[] args) {
        List<String> list1 = Arrays.asList("迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子", "洪七公");
        List<String> list2 = Arrays.asList("古力娜扎", "张无忌", "张三丰", "赵丽颖", "张二狗", "张天爱", "张三");

        // 1. 第一个队伍只保留姓名长度为3的成员
        // 2. 第一个队伍筛选后只要前三个
        list1.stream().filter(s -> s.length() == 3).limit(3)
            .forEach(System.out::println);

        System.out.println("--------------");
        // 3. 第二个队伍只要姓张的成员
        // 4. 第二个队伍筛选之后不要前两个人
        list2.stream().filter(s -> s.contains("张")).skip(2)
            .forEach(System.out::println);
        System.out.println("--------------");
        // 5. 将两个队伍合并成一个队伍
        Stream.concat(list1.stream(), list2.stream()).forEach(System.out::println);

        // 6. 根据姓名创建Person对象
        // 7. 打印整个队伍的Person信息
        System.out.println("-----------------");
        Stream.concat(list1.stream(),list2.stream()).map(Person::new).forEach(System.out::println);
    }
}

5.Stream结果收集

5.1 结果收集到集合中

public static <T>
    Collector<T, ?, List<T>> toList() {
    return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
                               (left, right) -> { left.addAll(right); return left; },
                               CH_ID);
}
public class test01 {
    /*
    * Stream结果收集
    *   收集到集合中
    * */
    @Test
    public void test01(){
        //Stream<String> stream = Stream.of("aa", "bb", "cc");
        List<String> list = Stream.of("aa", "bb", "cc","aa").collect(Collectors.toList());
        System.out.println(list);
        System.out.println("-----------------");
        //收集到set集合中
        Set<String> set=Stream.of("aa", "bb", "cc","aa").collect(Collectors.toSet());
        System.out.println(set);
        System.out.println("------------------");
        //如果需要获取的类型为具体的实现,比如:ArrayList。HashList
        //ArrayList<String> arrayList=Stream.of("aa", "bb", "cc","aa").collect(Collectors.toCollection(()->new ArrayList<>()));
        ArrayList<String> arrayList=Stream.of("aa", "bb", "cc","aa").collect(Collectors.toCollection(ArrayList::new));
        System.out.println(arrayList);
        System.out.println("------------------");
        HashSet<String> hashSet = Stream.of("aa", "bb", "cc", "aa").collect(Collectors.toCollection(HashSet::new));
        System.out.println(hashSet);
        System.out.println("------------------");
    }
}

输出:

[aa, bb, cc, aa]
-----------------
[aa, bb, cc]
------------------
[aa, bb, cc, aa]
------------------
[aa, bb, cc]
------------------

5.2 结果收集到数组中

​ Stream中提供了toArray方法来讲结果放到一个数组中,返回值类型是Object[].如果我们要指定返回的类型,那么可以使用另一个重载的toArray(intFunction)方法

@Test
public void test02() {
    Object[] array = Stream.of("aa", "bb", "cc", "aa").toArray();//返回的数组中的元素是Object类型
    System.out.println(Arrays.toString(array));
    //如果我们需要指定返回的数组中的元素类型
    String[] toArray = Stream.of("aa", "bb", "cc", "aa").toArray(String[]::new);
    System.out.println(Arrays.toString(toArray));
}

5.3 对流中的数据做聚合计算

​ 当我们使用Stream流处理数据后,可以像数据库的聚合函数一样对某个字段进行操作,比如获取最大值,最小值,求和,平均值,统计数量。

@Test
public void test03() {
    //获取年龄的最大值
    Optional<Person> maxAge = Stream.of(
        new Person("张三", 18, "198"),
        new Person("李四", 22, "199"),
        new Person("张三", 18, "167"),
        new Person("赵六", 19, "176"),
        new Person("张三", 33, "180")
    ).collect(Collectors.maxBy((p1, p2) -> p1.getAge() - p2.getAge()));
    System.out.println("最大年龄的人" + maxAge);
    //获取年龄的最小值
    Optional<Person> minAge = Stream.of(
        new Person("张三", 18, "198"),
        new Person("李四", 22, "199"),
        new Person("张三", 18, "167"),
        new Person("赵六", 19, "176"),
        new Person("张三", 33, "180")
    ).collect(Collectors.minBy((p1, p2) -> p1.getAge() - p2.getAge()));
    System.out.println("最小年龄的人" + minAge);

    //求所有人年龄之和
    Integer sumAge = Stream.of(
        new Person("张三", 18, "198"),
        new Person("李四", 22, "199"),
        new Person("张三", 18, "167"),
        new Person("赵六", 19, "176"),
        new Person("张三", 33, "180")
    ).collect(Collectors.summingInt(s -> s.getAge()));
    System.out.println("年龄之和" + sumAge);

    //年龄的平均值
    Double avgAge = Stream.of(
        new Person("张三", 18, "198"),
        new Person("李四", 22, "199"),
        new Person("张三", 18, "167"),
        new Person("赵六", 19, "176"),
        new Person("张三", 33, "180")
    ).collect(Collectors.averagingInt(s -> s.getAge()));
    System.out.println("年龄的平均值" + avgAge);

    //统计数量
    Long count = Stream.of(
        new Person("张三", 18, "198"),
        new Person("李四", 22, "199"),
        new Person("张三", 18, "167"),
        new Person("赵六", 19, "176"),
        new Person("张三", 33, "180")
    ).filter(person -> person.getAge() > 18).collect(Collectors.counting());
    System.out.println("满足条件的记录数" + count);

}

5.4 对流中数据做分组操作

​ 当我们使用Stream流处理数据后,可以根据某个属性将数据分组,

/*
    * 分组计算
    * */
@Test
public void test04() {
    //根据账号对数据进行分组
    Map<String, List<Person>> map = Stream.of(
        new Person("张三", 18, "198"),
        new Person("李四", 22, "199"),
        new Person("张三", 18, "167"),
        new Person("赵六", 19, "176"),
        new Person("张三", 33, "180")
    ).collect(Collectors.groupingBy(Person::getName));
    map.forEach((k, v) -> System.out.println("k=" + k + "v=" + v));
    System.out.println("----------------");
    //根据年龄对数据进行分组,如果大于等于18成年否则未成年
    Map<String, List<Person>> listMap = Stream.of(
        new Person("张三", 18, "198"),
        new Person("李四", 22, "199"),
        new Person("张三", 13, "167"),
        new Person("赵六", 12, "176"),
        new Person("张三", 33, "180")
    ).collect(Collectors.groupingBy(person -> person.getAge() >= 18 ? "成年" : "未成年"));
    listMap.forEach((k, v) -> System.out.println("k=" + k + "\t" + "v=" + v));
}

输出结果:

k=李四v=[Person(name=李四, age=22, heigt=199)]
k=张三v=[Person(name=张三, age=18, heigt=198), Person(name=张三, age=18, heigt=167), Person(name=张三, age=33, heigt=180)]
k=赵六v=[Person(name=赵六, age=19, heigt=176)]
----------------
k=未成年	v=[Person(name=张三, age=13, heigt=167), Person(name=赵六, age=12, heigt=176)]
k=成年	v=[Person(name=张三, age=18, heigt=198), Person(name=李四, age=22, heigt=199), Person(name=张三, age=33, heigt=180)]

多级分组:根据name分组,然后根据age(成年和未成年)对数据进行分组

/*
     * 分组计算--多级分组
     * */
@Test
public void test05() {
   //根据name分组,然后根据age(成年和未成年)对数据进行分组
   Map<String, Map<String, List<Person>>> listMap = Stream.of(
      new Person("张三", 18, "198"),
      new Person("李四", 22, "199"),
      new Person("张三", 13, "167"),
      new Person("赵六", 12, "176"),
      new Person("张三", 33, "180")
   ).collect(Collectors.groupingBy(
      Person::getName
      , Collectors.groupingBy(p -> p.getAge() >= 18 ? "成年" : "未成年")));
   listMap.forEach((k, v) -> {
      System.out.println(k);
      v.forEach((k1, v1) -> System.out.println("\t" + "k1=" + k1 + "\t" + "v1=" + v1));
   }
                  );
}

输出结果:

李四
	k1=成年	v1=[Person(name=李四, age=22, heigt=199)]
张三
	k1=未成年	v1=[Person(name=张三, age=13, heigt=167)]
	k1=成年	v1=[Person(name=张三, age=18, heigt=198), Person(name=张三, age=33, heigt=180)]
赵六
	k1=未成年	v1=[Person(name=赵六, age=12, heigt=176)]

5.5 对流中的数据做分区操作

Collectors.partitioningBy会根据值是否为true,把集合中的数据分割为里那个列表,一个为true列表,一个为false列表

在这里插入图片描述

/*
    * 分区操作
    * */
@Test
public void test06(){
   Map<Boolean, List<Person>> map = Stream.of(
      new Person("张三", 18, "198"),
      new Person("李四", 22, "199"),
      new Person("张三", 13, "167"),
      new Person("赵六", 12, "176"),
      new Person("张三", 33, "180")
   ).collect(Collectors.partitioningBy(p -> p.getAge() > 18));
   map.forEach((k, v) -> System.out.println("k=" + k + "\t" + "v=" + v));
}

输出结果:

k=false	v=[Person(name=张三, age=18, heigt=198), Person(name=张三, age=13, heigt=167), Person(name=赵六, age=12, heigt=176)]
k=true	v=[Person(name=李四, age=22, heigt=199), Person(name=张三, age=33, heigt=180)]

5.6对流中的数据做拼接

Collectors.joining会根据指定的连接符,将所有的元素连接成一个字符串

/*
     * 对流中的数据进行数据拼接操作
     * */
@Test
public void test07(){
    String s = Stream.of(
        new Person("张三", 18, "198"),
        new Person("李四", 22, "199"),
        new Person("张三", 13, "167"),
        new Person("赵六", 12, "176"),
        new Person("张三", 33, "180")
    ).map(person -> person.getName()).collect(Collectors.joining("_"));
    System.out.println(s);
    System.out.println("----------------");
    String s1 = Stream.of(
        new Person("张三", 18, "198"),
        new Person("李四", 22, "199"),
        new Person("张三", 13, "167"),
        new Person("赵六", 12, "176"),
        new Person("张三", 33, "180")
    ).map(person -> person.getName()).collect(Collectors.joining("_"));
    System.out.println(s1);
    System.out.println("-----------------");
    String s2 = Stream.of(
        new Person("张三", 18, "198"),
        new Person("李四", 22, "199"),
        new Person("张三", 13, "167"),
        new Person("赵六", 12, "176"),
        new Person("张三", 33, "180")
    ).map(person -> person.getName()).collect(Collectors.joining("_","###","$$$"));
    System.out.println(s2);
}

输出结果:

张三_李四_张三_赵六_张三
----------------
张三_李四_张三_赵六_张三
-----------------
###张三_李四_张三_赵六_张三$$$## 并行的Stream流

6. 并行的Stream流

6.1 串行的Stream流

我们前面使用的Stream流都是串行的,也就是在一个线程上面执行

/*
* 串行流
* */
public static void main(String[] args) {
  Stream.of(4, 5, 67, 234, 1).filter(s -> {
    System.out.println(Thread.currentThread() + " " + s);
    return s > 3;
  }).count();
}

输出结果:

Thread[main,5,main] 4
Thread[main,5,main] 5
Thread[main,5,main] 67
Thread[main,5,main] 234
Thread[main,5,main] 1

6.2 并行流

parallelStream其实就是一个并行执行的流,他通过默认的ForkjoinPool,可以提高多线程任务的速度

6.2.1 获取并行流

我们可以通过两种方式来获取并行流。

  1. 通过List接口中的parallerStream方法来获取
  2. 通过已有的串行流转换为并行流(parallel)

实现:

@Test
public void test02(){
    List<Integer> list=new ArrayList<>();
    //方式一:直接通过list接口,获取并行流
    Stream<Integer> parallelStream = list.parallelStream();
    //方式二:将已有的串行流转换为并行流
    Stream<Integer> parallel = Stream.of(1, 2, 3).parallel();
}
6.2.2 并行流操作
@Test
public void test03() {
    Stream.of(1, 2, 43, 54, 23, 5634, 12)
        .parallel()//   将流转换为并发流,Stream处理的时候就会通过多线程处理
        .filter(s -> {
            System.out.println(Thread.currentThread() + " s=" + s);
            return s > 2;
        }).count();
}

6.3 并行流和串行流对比

我们通过for循环,串行Stream流,并行Stream流来对5亿个数字求和,来看消耗时间

package com.king.res;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.util.stream.LongStream;

public class test03 {
    private static long times = 500000000;

    private long start;

    /*
    * 如果在Before方法中分配外部资源,则需要在测试运行后释放它们。使用@After注释public void方法会导致该方法在Test方法之后运行*/
    @Before
    public void before() {
        start = System.currentTimeMillis();
    }

    @After
    public void after() {
        long end = System.currentTimeMillis();
        System.out.println("消耗时间=" + (end - start));
    }

    /*
     * 普通的for循环
     * */
    @Test
    public void test01() {
        System.out.println("普通的for循环:");
        long res = 0;
        for (long i = 0; i < times; i++) {
            res += i;
        }
    }

    /*
     * 串行流处理
     * */
    @Test
    public void test02() {
        System.out.println("串行流:serialStream");
        LongStream.rangeClosed(0,times)
            .reduce(0,Long::sum);
    }

    /*
    * 并行流处理,消耗时间156
    * */
    @Test
    public void test03() {
        System.out.println("并行流:parallelStream");
        LongStream.rangeClosed(0,times)
            .parallel()
            .reduce(0,Long::sum);
    }
}

输出结果:

普通的for循环:
消耗时间=188
串行流:serialStream
消耗时间=313
并行流:parallelStream
消耗时间=156

通过案例我们可以看到paralleStreaml的效率数最高的。

Stream并行处理的过程会分而治之,也就是讲一个打的任务切分成了多个小任务,这表示每个任务都是一个小进程操作。

6.4 线程安全问题

在多线程的处理下,肯定会出现数据安全问题。如下:

@Test
public void test01(){
  List<Integer> list = new ArrayList<>();
  for (int i = 0; i < 1000; i++) {
    list.add(i);
  }
  System.out.println(list.size());
  List<Integer> newList = new ArrayList<>();
  //使用并行流来向集合中添加数据
  list.parallelStream()
    .forEach(newList::add);
  System.out.println(newList.size());
}

针对这个问题我们的解决方案有哪些呢?

  1. 加同步锁
  2. 使用线程安全的容器
  3. 将线程不安全发容器转换为你线程安全的容器
  4. 通过Stream中的toArray或者collect操作

实现:

/*
     * 加同步锁
     * */
@Test
public void test02() {
  List<Integer> listNew = new ArrayList<>();
  Object obj = new Object();
  IntStream.rangeClosed(1, 1000)
    .parallel()
    .forEach(
    i -> {
      synchronized (obj) {
        listNew.add(i);
      }
    });
  System.out.println(listNew.size());
}

/*
     * 使用线程安全的容器
     * */
@Test
public void test03() {
  Vector<Integer> vector = new Vector<>();
  Object obj = new Object();
  IntStream.rangeClosed(1, 1000)
    .parallel()
    .forEach(
    i -> {
      vector.add(i);
    });
  System.out.println(vector.size());
}

/*
 * 将线程不安全发容器转换为你线程安全的容器
 * */
@Test
public void test04() {
  List<Integer> listNew = new ArrayList<>();
  List<Integer> synchronizedList = Collections.synchronizedList(listNew);
  //将线程不安全的容器包装成线程安全的容器
  Object obj = new Object();
  IntStream.rangeClosed(1, 1000)
    .parallel()
    .forEach(synchronizedList::add);
  System.out.println(listNew.size());
}
/*
    * 我们还可以通过Stream中的toArray方法或者    collector   方法来操作
    * 就是满足线程安全的要求
    * */
@Test
public void test05() {
  List<Integer> listNew = new ArrayList<>();
  Object obj = new Object();
  List<Integer> list = IntStream.rangeClosed(1, 1000)
    .parallel()
    .boxed().collect(Collectors.toList());
  System.out.println(list.size());
}

7. Fork/Join框架

​ parallelStream 使用的是Fork/Join框架。Fork/Join框架自JDK7引入。Fork/Join框架可以将一个大任务拆分为很多小
任务来异步执行。Fork/Join框架主要包含三个模块:
1.线程池:ForkJoinPool
2.任务对象:ForkJoinTask
3.执行任务的线程:ForkJoinWorkerThread

在这里插入图片描述

7.1 Fork/Join原理-分治法

​ ForkJoinPool主要用来使用分治法Divide-and-Conquer Algorithm)来解决问题。典型的应用比如快速排序算法,

​ ForkJoinPool需要使用相对少的线程来处理大量的任务。比如要对1000万个数据进行排序,那么会将这个任务分割成两个500万的排序任务和一个针对这两组500万数据的合并任务。以此类推,对于500万的数据也会做出同样的分割处理,到最后会设置一个阈值来规定当数据规模到多少时,停止这样的分割处理。比如,当元素的数量小于10时,会停止分割,转而使用插入排序对它们进行排序。那么到最后,所有的任务加起来会有大概2000000+个。问题的关键在于,对于一个任务而言,只有当它所有的子任务完成之后,它才能够被执行。

在这里插入图片描述

7.2 Fork/Join原理-工作窃取算法

​ Fork/Join最核心的地方就是利用了现代硬件设备多核,在一个操作时候会有空闲的cpu,那么如何利用好这个空闲的cpu就成了提高性能的关键,而这里我们要提到的工作窃取(work-stealing)算法就是整个Fork/Join框架的核心理念

​ Fork/Join工作窃取(work-stealing)算法是指某个线程从其他队列里窃取任务来执行。

在这里插入图片描述

​ 那么为什么需要使用工作窃取算法呢?即假如我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不的子任务,为了减少线程间的竞争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一对应。比如A线程负责处理A队列里的任务,但是有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端部队列的尾部拿任务执行。

​ 工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并且消耗了更多的系统资源,比如创建多个线程和多个双端队列。

​ 上文中已经提到了在java8引入了自动并行化的概念。它能够让一部分java代码自动地以并行的方式执行,也就是我们使用了ForkloinPool的ParallelStream.对于ForkJoinPool通用线程池的线程数量,通常使用默认值就可以了,即运行时计算机的处理器数量。可以通过设置系统属性:java.utl.concurrent.ForkJoinPool.common.parallelism=N(N为线程数量),来调整ForkJoinPool的线程数量,可以尝试调整成不同的参数来观察每次的输出结果。

7.3 Fork/Join案例

​ 需求:使用Fork/Join计算1-10000的和,当一个任务的计算数量大于3000的时候拆分任务,数量小3000的时候就计算

在这里插入图片描述

实现:

public class test05 {

  /*需求:使用Fork/Join计算1-10000的和,
    当一个任务的计算数量大于3000的时候拆分任务,
    数量小3000的时候就计算
    * */
  public static void main(String[] args) {
    long start = System.currentTimeMillis();
    ForkJoinPool pool = new ForkJoinPool();
    SumRecursivetask task = new SumRecursivetask(1, 10000);
    Long result = pool.invoke(task);
    System.out.println("result:" + result);
    long end = System.currentTimeMillis();
    System.out.println("总的耗时:" + (end - start));

  }
}

class SumRecursivetask extends RecursiveTask<Long> {

  //定义一个拆分的临界池
  private static final long THRESHOLD = 3000l;

  private final long start;
  private final long end;

  SumRecursivetask(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;
      }
      System.out.println("计算:" + start + "--->" + end + ",结果为:" + sum);
      return sum;
    } else {
      //  数量大于预定的数量,那说明还要继续拆分
      long middle = (start + end) / 2;
      System.out.println("拆分:左边 " + start + "-->" + middle + ",右边" + (middle + 1) + "-->" + end);
      SumRecursivetask left = new SumRecursivetask(start, middle);
      left.fork();
      SumRecursivetask right = new SumRecursivetask(middle + 1, end);
      right.fork();
      return left.join() + right.join();
    }
  }
}

输出结果:

拆分:左边 1-->5000,右边5001-->10000
拆分:左边 1-->2500,右边2501-->5000
计算:1--->2500,结果为:3123750
计算:2501--->5000,结果为:9371250
拆分:左边 5001-->7500,右边7501-->10000
计算:5001--->7500,结果为:15618750
计算:7501--->10000,结果为:21866250
result49980000
总的耗时:5

六、Optional类

这个optional类主要是解决空值针的问题

1. 以前对null的处理

@Test
public void tes01() {
    //String username="张三“
    String username = null;
    if (username != null) {
        System.out.println("字符串长度:" + username.length());
    } else {
        System.out.println("字符串为空");
    }
}

2. Optional类

​ Optional是一个没有子类的工具类,Optional是一个可以为null的容器对象,它的主要作用就是为了避免null检查,防止NullpointException,
在这里插入图片描述

3. Optional的基本使用

Optional对象的创建方式:

/*
    * Optional对象的创建方式
    * */
@Test
public void test02(){
    //第一种方式:通过of方法---of方法不支持null
    Optional<String> op1 = Optional.of("zhangsan");
    //Optional<Object> op2 = Optional.of(null);

    //第二种方式:通过ofNullable方法 支持null;
    Optional<String> op3 = Optional.ofNullable("lisi");
    Optional<Object> op4 = Optional.ofNullable(null);

    //第三种方式:通过empty方法直接创建一个空的Optional对象
    Optional<Object> op5 = Optional.empty();

}

4. Optionam常用的方法

/*
     * Optional的常用方法介绍
     *      get():如果optional有值则返回,否则抛出NoSuchElementException异常
     *              get()通常和isPresent一块使用
     *      isPresent():判断是否包含值,包含值返回true,不包含返回false
     *      orElse(t):如果调用对象包含值,就返回值,否则返回t
     *      orElseGet:如果调用对象包含值,就返回值,否则返回 Lambda表达式的返回值
     * */
@Test
public void test03() {
    Optional<String> op1 = Optional.of("zhangsan");
    Optional<String> op2 = Optional.empty();

    //获取Optional中的值
    if (op1.isPresent()) {
        String s1 = op1.get();
        System.out.println("用户名称:" + s1);
    }
    if (op2.isPresent()) {
        System.out.println(op2.get());
    } else {
        System.out.println("op2是一个空的Optional对象");
    }
    
    String s3 = op1.orElse("李四");
    System.out.println(s3);

    String s4 = op2.orElse("王五");
    System.out.println(s4);

    String s5 = op2.orElseGet(() -> {
        return "hello";
    });
    System.out.println(s5);
}
@Test
public void test04() {
    Optional<String> op1 = Optional.of("zhangsan");
    Optional<String> op2 = Optional.empty();
    //如果存在值,就做什么
    op1.ifPresent(s -> System.out.println("有值:" + s));
}

/*
     * 自定义一个方法,将Person对象中的name转换为大写,并返回
     *      通过Optional方式实现
     * */
@Test
public void test05() {
    Person person = new Person("zhangsan", 12, "123");
    Optional<Person> op = Optional.of(person);
    String name = getNameForOptional(op);
    System.out.println(name);

}

public String getNameForOptional(Optional<Person> op){
    if (op.isPresent()){
        String msg = op.map(Person::getName)
            .map(String::toUpperCase)
            .orElse("null");
        return msg;
    }
    return null;
}

七、新时间日期API

1. 旧版日期时间的问题

​ 在旧版本的中的jdk对于日期和时间这块的实际是非常差的

/*
 *   旧版日期时间设计的问题
 * */
@Test
public void test01() throws ParseException {
    //  1.设计不合理
    Date date = new Date(2022,11,20);
    System.out.println(date);

    //  2.时间格式化和解析操作是线程不安全的
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    for (int i = 0; i < 50; i++) {
        new Thread(()->{
            //System.out.println(sdf.format(date));
            try {
                System.out.println(sdf.parse("2022-11-20"));
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }).start();
    }
}
  1. 设计不合理,在java.util和java.sql的包中都有日期类,java.util.Date同时包含日期和时间的,而java.sql.Date仅仅包含日期,此外用于格式化和解析的类在java.text包下。
  2. 非线程安全,java.util.Date是非线程安全的,所有的日期类都是可变的,这是java日期类最大的问题之一
  3. 时区处理麻烦,日期并不提供国际化,没有时区支持

2. 新日期时间API介绍

JDK8中增加了一套全新的日期时间API,这套API设计合理,是线程安全的。新的日期及时间API位于java.time包

中,下面是一些关键类。

  • LocalDate:表示日期,包含年月日,格式为2019-10-16
  • LocalTime: 表示时间,包含时分秒,格式为16:38:54.158549300
  • LocalDateTime:表示日期时间,包含年月日,时分秒,格式为2018-09-06T15:33:56.750
  • DateTlmeFormatter:日期时间格式化类。
  • Instant:时间戳,表示一个特定的时间瞬间。
  • Duration:用于计算2个时间(LocalTime,时分秒)的距离
  • Period:用于计算2个日期LocalDate,年月日)的距离
  • ZonedDateTime:包含时区的时间

Java中使用的历法是IS0 8601日历系统,它是世界民用历法,也就是我们所说的公历。平年有365天,闰年是366

天。此外java8还提供了4套其他历法,分别是:

  • ThaiBuddhistDate:泰国佛教历
  • MinguoDate:中华民国历
  • JapaneseDate:日本历
  • HijrahDate:伊斯兰历

2.1 日期时间的常见操作

LocalDate,LocalTime以及LocalDateTime的操作

/*
 *   JDk8    日期时间的操作
 * */
@Test
public void test01() {
    //  1.创建指定的日期
    LocalDate date = LocalDate.of(2022, 11, 21);
    System.out.println("date=" + date);

    //  2.得到当前的日期
    LocalDate now = LocalDate.now();
    System.out.println("now=" + now);

    //  3.根据LocalDate对象获取对应的日期信息
    System.out.println("年:" + now.getYear());
    System.out.println("月:" + now.getMonth());
    System.out.println("日:" + now.getDayOfMonth());
    System.out.println("星期:" + now.getDayOfWeek());
}
/*时间操作
**/
@Test
public void test02() {
    //1.得到指定的时间
    LocalTime time = LocalTime.of(5, 26, 33, 23145);
    System.out.println(time);

    //2.获取当前的额时间
    LocalTime now = LocalTime.now();
    System.out.println(now);
    //3.获取时间信息
    System.out.println(now.getHour());
    System.out.println(now.getMinute());
    System.out.println(now.getSecond());
    System.out.println(now.getNano());
}

/*
 *  日期时间类型  LocalDateTime
 * */
@Test
public void test03() {
    //获取指定的日期时间
    LocalDateTime dateTime = LocalDateTime.of(2022, 03, 01, 12, 12, 12, 25);
    System.out.println(dateTime);
    //获取当前的日期时间
    LocalDateTime now = LocalDateTime.now();
    System.out.println(now);
    //获取日期时间信息
    System.out.println(now.getYear());
    System.out.println(now.getMonth().getValue());
    System.out.println(now.getDayOfMonth());
    System.out.println(now.getDayOfWeek().getValue());
    System.out.println(now.getHour());
    System.out.println(now.getMinute());
    System.out.println(now.getSecond());
    System.out.println(now.getNano());
}

2.2 日期时间的修改和比较

/*
     *   日期时间的修改
     * */
@Test
public void test01() {
    LocalDateTime now = LocalDateTime.now();
    System.out.println("now=" + now);

    // 修改日期时间    对日期时间的修改,对已存在的LocalDate对象,创建了它的模板
    //  并不会修改原来的信息
    LocalDateTime localDateTime = now.withYear(1998);
    System.out.println("now:" + now);
    System.out.println("修改后:" + localDateTime);


    System.out.println("月份:" + now.withMonth(10));
    System.out.println("天:" + now.withDayOfMonth(6));
    System.out.println("小时:" + now.withHour(2));
    System.out.println("分钟:" + now.withMinute(14));

    // 在当前日期时间的额基础上,加上或者减去指定的时间
    System.out.println("两天后:" + now.plusDays(2));
    System.out.println("十天后:" + now.plusDays(10));
    System.out.println("6个月后:" + now.plusMonths(6));

    System.out.println("10年前:" + now.minusYears(10));
    System.out.println("半年前:" + now.minusMonths(6));
    System.out.println("一周前:" + now.minusDays(7));
}

/**
     * 日期时间的比较
     */
@Test
public void test02() {
    LocalDate now = LocalDate.now();
    LocalDate date = LocalDate.of(2020, 1, 3);
    //在jdk8中要实现日期的比较    isAfter     isBefore    isEqual通过这几个方法来直接比较
    System.out.println(now.isAfter(date));
    System.out.println(now.isBefore(date));

    System.out.println(now.isEqual(date));
}

​ 注意:在进行日期时间修改的时候,原来的LocalDate对象是不会被修改的,每次操作都是返回了一个新的LocalDate对象,所以在多线程场景下数据是安全的。

2.3 格式化和解析操作

​ 在jdk8中我们可以通过java.time.format.DateTimeFoematter类可以进行日期的解析和格式化操作

/*
     * 日期格式化
     * */
@Test
public void test01() {
    LocalDateTime now = LocalDateTime.now();
    // 指定格式     使用系统默认的格式
    DateTimeFormatter isoLocalDateTime = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
    // 将日期时间转换为字符串
    String format = now.format(isoLocalDateTime);
    System.out.println("format = " + format);


    // 通过 ofPattent 方法来指定特定的格式
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    String format1 = now.format(dateTimeFormatter);
    // 2022-08-08 16:31:57
    System.out.println(format1);

    // 将字符串解析为日期时间类型
    LocalDateTime parse = LocalDateTime.parse("2001-05-03 22:45:45", dateTimeFormatter);
    // parse = 2001-05-03T22:45:45
    System.out.println("parse = " + parse);
}

2.4 Instant类

​ 在jdk8中我们新增了一个instant类(时间戳/时间线),内部保存了1970年11月1日 00:00:00 以来的秒和纳秒

/*
 *   Instant 时间戳
 *       可以用来统计时间的消耗
 * */
@Test
public void test01() throws InterruptedException {
    Instant now = Instant.now();
    System.out.println("now = " + now);

    // 获取从1970年一月一日 00:00:00 到现在的 纳秒
    System.out.println("now.getNano() = " + now.getNano());
    Thread.sleep(5);
    Instant now1 = Instant.now();
    System.out.println("耗时:" + (now1.getNano()-now.etNano()));
}

2.5 计算日期时间差

JDK8中提供了两个工具类Duration/Period:计算日期时间差

  1. Duration:用来计算两个时间差(LocalTime)
  2. Period:用来计算两个日期差(LocalDate)
/*
    *   计算日期时间差
    * */
@Test
public void test01(){
    // 计算时间差
    LocalTime now = LocalTime.now();
    LocalTime time = LocalTime.of(22, 48, 56);
    System.out.println("now = " + now);

    // 通过Duration计算时间差
    Duration duration = Duration.between(now, time);
    System.out.println("duration.toDays() = " + duration.toDays());
    System.out.println("duration.toHours() = " + duration.toHours());
    System.out.println("duration.toMinutes() = " + duration.toMinutes());
    System.out.println("duration.toMillis() = " + duration.toMillis());
    System.out.println("------------------------");

    //计算日期差
    LocalDate nowDate = LocalDate.now();
    LocalDate date = LocalDate.of(1997, 12, 5);
    Period period = Period.between(date, nowDate);
    System.out.println("period.getYears() = " + period.getYears());
    System.out.println("period.getMonths() = " + period.getMonths());
    System.out.println("period.getDays() = " + period.getDays());
}

2.6 时间校正器

有时候我们需要如下调整:将日期调整到下个星期的第一天等操作。这时我们通过时间校正器效果可能会更好

  • TemporalAdjust:时间校正器
  • TemporalAdjusts:通过该类静态方法提供了大量的常用TemporalAdjust的实现
/*
 * 时间校正器
 * */
@Test
public void test02() {
    LocalDateTime now = LocalDateTime.now();
    // 将当前的日期调整到下个月一号
    TemporalAdjuster adjuster = (temporal) -> {
        LocalDateTime dateTime = (LocalDateTime) temporal;
        LocalDateTime nextMonth = dateTime.plusMonths(1).withDayOfMonth(1);
        System.out.println("nextMonth = " + nextMonth);
        return nextMonth;

    };

    // 我们还可以通过 TemparalAdjust 来实现
    //LocalDateTime nextMonth = now.with(adjuster);
    LocalDateTime nextMonth = now.with(TemporalAdjusters.firstDayOfNextMonth());
    System.out.println("nextMonth = " + nextMonth);
}

2.7 日期时间的时区

lava8中加入了对时区的支持,LocalDate、.LocalTime、LocalDateTime是不带时区的,带时区的日期时间类分别为

ZonedDate,ZonedTime,ZonedDateTime。

其中每个时区都对应着ID,ID的格式为"“区域/城市”。例如:Asia/Shanghai等。

/*
 * 时区操作
 * */
@Test
public void test01() {
    // 1.获取所有的时区id
    //ZoneId.getAvailableZoneIds().forEach(System.out::println);
    // 2.获取当前时间 中国使用的 东八区的时区,比标椎时间慢8个小时
    LocalDateTime now = LocalDateTime.now();
    System.out.println("now = " + now);//2022-08-08T22:34:27.153
    //获取标椎时间
    ZonedDateTime zonedDateTime = ZonedDateTime.now(Clock.systemUTC());
    System.out.println("zonedDateTime = " + zonedDateTime);//2022-08-08T14:34:27.155Z

    // 使用计算机默认的时区,创建日期时间
    ZonedDateTime now1 = ZonedDateTime.now();
    System.out.println("now1 = " + now1);//2022-08-08T22:34:27.155+08:00[Asia/Shanghai]

    // 使用指定的时区创建日期时间
    ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("America/Marigot"));
    System.out.println("now2 = " + now2);

}

JDK新的日期和时间API的优势:

  1. 新版日期时间APi中,日期和时间对象是不变的,操作日期不会影响原来的值,而是生成一个新的实例
  2. 提供了不同的两种方式,有效的区分了人和机器的操作
  3. TemporalAdjust可以更精确的操作日期,还可以自定义日期调整期
  4. 线程安全

八、其他新特性

+++

1. 重复注解

自从Java5中引入注解以来,注解开始变得非常流行,并在各个框架和项目中被广泛使用。不过注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。JDK8引入了重复注解的概念,允许在同一个地方多次使用同一个注解。在JDK8中使用@Repeatable注解定义重复注解。

1.1 定义一个重复注解的容器

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
    MyAnnotation[] value();
}

1.2 定义一个可以重复的注释

@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    
    String value();
}

1.3 配置多个重复的注释


@MyAnnotation("test1")
@MyAnnotation("test2")
@MyAnnotation("test3")
public class AnnoTest01 {

    @MyAnnotation("fun1")
    @MyAnnotation("fun2")
    public void test01(){

    }

1.4 解析得到重复的注解

/*
     *  解析重复注解
     * */
public static void main(String[] args) throws NoSuchMethodException {
    // 获取类中标注的重复注解
    MyAnnotation[] annotationsByType = AnnoTest01.class.getAnnotationsByType(MyAnnotation.class);
    for (MyAnnotation myAnnotation : annotationsByType) {
        System.out.println(myAnnotation.value());
    }

    //获取方法上标的重复注解
    MyAnnotation[] test01s = AnnoTest01.class.getMethod("test01")
        .getAnnotationsByType(MyAnnotation.class);
    for (MyAnnotation test01 : test01s) {
        System.out.println(test01.value());
    }
}

2. 类型注解

JDK8为@Target元注解新增了两种类型:TYPE_PARAMETER,TYPE_USE。

  • TYPE PARAMETER:表示该注解能写在类型参数的声明语句中。类型参数声明如:、
  • TYPE_USE:表示注解可以再任何用到类型的地方使用。

TYPE PARAMETER:

@Target(ElementType.TYPE_PARAMETER)
public @interface TypeParam {
}

使用:

public class TypeDemo01<@TypeParam T> {
    public  <@TypeParam k extends Object>  k test01(){
        return null;
    }
}

TYPE_USE

@Target(ElementType.TYPE_USE)
public @interface NotNull {
}

使用:

public class TypeUseDemo01 {
    public Integer age=10;
    public Integer sum(@NotNull Integer a,@NotNull Integer b){
        return a+b;
    }
}

java
/*

  • 时区操作

  • */
    @Test
    public void test01() {
    // 1.获取所有的时区id
    //ZoneId.getAvailableZoneIds().forEach(System.out::println);
    // 2.获取当前时间 中国使用的 东八区的时区,比标椎时间慢8个小时
    LocalDateTime now = LocalDateTime.now();
    System.out.println("now = " + now);//2022-08-08T22:34:27.153
    //获取标椎时间
    ZonedDateTime zonedDateTime = ZonedDateTime.now(Clock.systemUTC());
    System.out.println("zonedDateTime = " + zonedDateTime);//2022-08-08T14:34:27.155Z

    // 使用计算机默认的时区,创建日期时间
    ZonedDateTime now1 = ZonedDateTime.now();
    System.out.println("now1 = " + now1);//2022-08-08T22:34:27.155+08:00[Asia/Shanghai]

    // 使用指定的时区创建日期时间
    ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of(“America/Marigot”));
    System.out.println("now2 = " + now2);

}


JDK新的日期和时间API的优势:

1. 新版日期时间APi中,日期和时间对象是不变的,操作日期不会影响原来的值,而是生成一个新的实例
2. 提供了不同的两种方式,有效的区分了人和机器的操作
3. TemporalAdjust可以更精确的操作日期,还可以自定义日期调整期
4. 线程安全

## 八、其他新特性

+++

### 1. 重复注解

自从Java5中引入注解以来,注解开始变得非常流行,并在各个框架和项目中被广泛使用。不过注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。JDK8引入了重复注解的概念,允许在同一个地方多次使用同一个注解。在JDK8中使用@Repeatable注解定义重复注解。

1.1 定义一个重复注解的容器

```java
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
    MyAnnotation[] value();
}

1.2 定义一个可以重复的注释

@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    
    String value();
}

1.3 配置多个重复的注释


@MyAnnotation("test1")
@MyAnnotation("test2")
@MyAnnotation("test3")
public class AnnoTest01 {

    @MyAnnotation("fun1")
    @MyAnnotation("fun2")
    public void test01(){

    }

1.4 解析得到重复的注解

/*
     *  解析重复注解
     * */
public static void main(String[] args) throws NoSuchMethodException {
    // 获取类中标注的重复注解
    MyAnnotation[] annotationsByType = AnnoTest01.class.getAnnotationsByType(MyAnnotation.class);
    for (MyAnnotation myAnnotation : annotationsByType) {
        System.out.println(myAnnotation.value());
    }

    //获取方法上标的重复注解
    MyAnnotation[] test01s = AnnoTest01.class.getMethod("test01")
        .getAnnotationsByType(MyAnnotation.class);
    for (MyAnnotation test01 : test01s) {
        System.out.println(test01.value());
    }
}

2. 类型注解

JDK8为@Target元注解新增了两种类型:TYPE_PARAMETER,TYPE_USE。

  • TYPE PARAMETER:表示该注解能写在类型参数的声明语句中。类型参数声明如:、
  • TYPE_USE:表示注解可以再任何用到类型的地方使用。

TYPE PARAMETER:

@Target(ElementType.TYPE_PARAMETER)
public @interface TypeParam {
}

使用:

public class TypeDemo01<@TypeParam T> {
    public  <@TypeParam k extends Object>  k test01(){
        return null;
    }
}

TYPE_USE

@Target(ElementType.TYPE_USE)
public @interface NotNull {
}

使用:

public class TypeUseDemo01 {
    public Integer age=10;
    public Integer sum(@NotNull Integer a,@NotNull Integer b){
        return a+b;
    }
}

全网详解(波哥)Java8新特性(Lambda、Stream、LocalDate等)新特性

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到