JDK8新特性:Lambda表达式、Stream流、日期时间工具类

重要特性:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • 可选的大括号:如果主体包含了一个语句,就不需要大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器 会自动返回值,大括号需要指明表达式返回了一个数值

一、Lambda表达式

1. 需求分析

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

    @Test
    public void test02(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("新线程执行的代码"+Thread.currentThread().getName());
            }
        }).start();

        System.out.println("主线程"+Thread.currentThread().getName());
    }

代码分析:

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

2. Lambda表达式初体验

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

        new Thread(() -> {
            System.out.println("线程lambda表达式"+Thread.currentThread().getName());
        }).start();

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

3. Lambda的语法规则

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

(参数类型 参数名称)-> {
	方法体;
}

格式说明:

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

3.1.Lambda练习1

无参无返回值的Lambda:
  定义一个接口

public interface UserService {
    void show();
}

  创建方法使用

    @Test
    public void test03(){
        goShow(new UserService() {
            @Override
            public void show() {
                System.out.println("show方法执行了");
            }
        });
        System.out.println("========================");
        goShow(() -> {
            System.out.println("lambda方式执行show方法");
        });
    }

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

3.2.Lambda练习2

有参有返回值的Lambda:
  创建一个对象

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

  使用lambda表达式对集合中的元素进行排序

    @Test
    public void test05(){
        List<Person> list = new ArrayList<>();
        list.add(new Person("周星驰", 44, 440));
        list.add(new Person("周杰伦", 22, 660));
        list.add(new Person("张艺兴", 66, 220));
        list.add(new Person("刘德华", 33, 550));
        list.add(new Person("孙红雷", 55, 330));

        //匿名内部类非lambda使用
        Collections.sort(list, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge()-o2.getAge();
            }
        });
        for (Person person : list){
            System.out.println(person);
        }
        System.out.println("=========================");
        
        //lambda表达式使用
        Collections.sort(list,(Person o1, Person o2) -> {
            return o1.getAddress()- o2.getAddress();
        });
        for (Person person : list){
            System.out.println(person);
        }
    }

4.@FunctionalInterface

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

5.Lambda表达式的原理

  匿名内部类的本质:会在编译时生成一个Class文件,xxxxx$1.class

    @Test
    public void test02(){
        //开启一个新线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("新线程执行的代码"+Thread.currentThread().getName());
            }
        }).start();
        System.out.println("主线程"+Thread.currentThread().getName());
    }

在这里插入图片描述
  可自行通过反编译工具查看
注意:当写有Lambda表达式Java类生成的Class文件使用反编译工具无法打开,这是我们可以通过jdk自带的一个工具,javap对字节码进行反汇编操作

 javap -c -p 文件名.class

-c:表示对代码进行反汇编
-p:显示所有的类和成员

在反编译的源码中可以看到一个静态方法lambda$main$0(), 可在debug的方式进行查看
在这里插入图片描述
  上面的效果可以理解为执行了一个方法,如下

private static void lambda$main$0(){
	 System.out.println("lambda  show方法执行了...");
}

  为了更加直观的理解以上内容,可以通过运行的时候添加 -Djdk.internal.lambda.dumpProcyClasses,加上这个参数会将内部的class码输出到一个文件中

java -Djdk,internal.lambda.dumpProxyClasses 要运行的包名.类名

  执行包含lambda表达式的类之后,会生成一个 xxxxx$$Lambda$1.class 文件
  此时再用反编译工具进行反编译进行查看,可以看到这个匿名内部类实现了UserService接口,并重写了show()方法,在show方法中调用了lambda$main$0(),也就是调用了Lambda中的内容

小结
  匿名内部类在编译的时候会产生一个class文件
  Lambda表达式在程序运行的时候形成一个类

  1. 在类中新增了一个方法,这个方法的方法体就是lambda表达式中的代码
  2. 还会形成一个匿名内部类,实现接口,重写抽象方法
  3. 在接口中重写方法会调用新生成的方法

6.Lambda表达式的省略写法

  在lambda表达式的标准写法的基础上,可以使用省略写法的规则为:

  1. 小括号内的参数类型可以省略
  2. 如果小括号内有且仅有一个参数,则小括号可以省略
  3. 如果大括号内有且仅有一个语句,可以同时省略大括号,return关键字及语句分号
    定义两个接口
public interface StudentService {
    String show(String name, Integer age);
}
public interface OrderService {
    String show(String name);
}
    @Test
    public void test06(){
        goStudent((String name, Integer age) -> {
            return name+age;
        });
        //省略写法
        goStudent((name, age) -> name+age+".....");

        System.out.println("===========================");
        goOrder((String name) -> {
            System.out.println("------>" + name);
            return name;
        });
        //省略写法
        goOrder((name -> {
            System.out.println("name");
            return name;
        }));
        //省略写法
        goOrder(name -> name);
    }
    
    public static void goStudent(StudentService studentService){
        studentService.show("张三", 15);
    }
    public static void goOrder(OrderService orderService){
        orderService.show("李四");
    }

7.Lambda表达式的使用前提

  Lambda表达式的语法是非常简洁的,但是Lambda表达式不是随便使用的,使用时有几个条件需要注意

  1. 方法的参数或局部变量类型必须为接口才能使用
  2. 接口中有且只有一个抽象方法

8.Lambda和匿名内部类对比

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

二、接口中新增的方法

1.jdk8中接口的新增

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

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

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

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

2.默认方法

2.1.为什么增加默认方法

  在jdk8之前的接口中只能有抽象方法和静态常量,会存在以下问题:
  如果在接口中新增抽象方法,那么该接口的实现类中都必须要实现这个抽象方法,非常不利于接口的扩展

2.2.接口默认方法的格式

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

2.3.接口中默认方法的使用

  • 实现类直接调用接口的默认方法
  • 实现类重写接口的默认方法
public class TestInterface {
    public static void main(String[] args) {
        Research a = new A();
        Research b = new B();
        a.test3();
        b.test3();
    }
}
interface Research{
    void test1();

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

    /**
     * 接口中定义默认方法
     * @return
     */
    public default String test3(){
        System.out.println("默认方法执行了");
        return "默认方法";
    }
}
class A implements Research{

    @Override
    public void test1() {

    }

    @Override
    public void test2() {

    }
}
class B implements Research{

    @Override
    public void test1() {

    }

    @Override
    public void test2() {

    }

    @Override
    public String test3() {
        System.out.println("B 实现类中重写了默认方法");
        return "ok";
    }
}


3.静态方法

  jdk8中的静态方法也是为了接口的扩展

3.1.语法规则

interface 接口名 {
	修饰符 static 返回值类型 方法名 {
		方法体;
	}
}
public class TestInterface {
    public static void main(String[] args) {
        Research.test4();
    }
}
interface Research{
    void test1();

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

    /**
     * 接口中定义默认方法
     * @return
     */
    public default String test3(){
        System.out.println("默认方法执行了");
        return "默认方法";
    }

    /**
     * 接口中定义静态方法
     * @return
     */
    public static String test4(){
        System.out.println("静态方法执行了");
        return "静态方法";
    }
}

3.2.静态方法的使用

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

4.两者的区别介绍

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

三、函数式接口

1.函数式接口的由来

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

public class FunctionTest {
    public static void main(String[] args) {
        fun((arr) -> {
            int sum = 0;
            for (int i : arr) {
                sum+=i;
            }
            return sum;
        });
    }
    public static void fun(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

  无参有返回值,对于lambda 表达式需要提供一个返回数据的类型

@FunctionalInterface
public interface Supplier<T> {

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

  使用:输出数组中的最大值

public class SupplierTest {
    public static void main(String[] args) {
        test(()->{
            int[] arr = {22,33,44,55,11,};
            Arrays.sort(arr);
            return arr[arr.length-1];
        });
    }
    public static void test(Supplier<Integer> supplier){
        //get方法是一个无参有返回值的抽象方法
        Integer integer = supplier.get();
        System.out.println(integer);
    }
}

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

  使用:转换大小写

public class ConsumerTest {
    public static void main(String[] args) {
        test((msg)->{
            System.out.println("msg = " + msg.toLowerCase() );
        });
    }
    public static void test(Consumer<String> consumer){
        consumer.accept("Hello World");
    }
}

默认方法:andThen
  如果一个方法的参数和返回值全部是Consumer类型,那么就可以实现效果:当操作一个数据的时候,先做一个操作,然后在做一个操作,实现对数据的处理

public class ConsumerAndThenTest {
    public static void main(String[] args) {
        test((msg)->{
            System.out.println("小写:"+msg.toLowerCase());
        },(msg)->{
            System.out.println("大写: "+msg.toUpperCase());
        });
    }
    public static void test(Consumer<String> o1, Consumer<String> o2){
        String s = "Hello World";
        //o1.accept(s);
        //o2.accept(s);
        o2.andThen(o1).accept(s);
    }
}

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

  使用:字符串转成数字

public class FunctionTest {
    public static void main(String[] args) {
        fun((f1) -> {
            return Integer.parseInt(f1);
        });
        fun1((f1)->{
            return Integer.parseInt(f1);
        },(f2)->{
            return f2*10;
        });
    }
    public static void fun(Function<String, Integer> f1){
        Integer apply = f1.apply("666");
        System.out.println("apply=" + apply);
    }
    public static void fun1(Function<String, Integer> o1, Function<Integer, Integer> o2){
/*        Integer a1 = o1.apply("666");
        Integer a2 = o2.apply(a1);
        System.out.println(a2);*/
        Integer apply = o1.andThen(o2).apply("666");
        System.out.println(apply);
    }
}

  默认的compose 方法的作用顺序和andThen方法刚好相反,而静态方法identity 则是输入什么参数就返回什么参数

2.4.Predicate

  有参返回布尔型

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

  使用:

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

        test2((msg)->{
            return msg.contains("H");
        },(date)->{
            return date.contains("W");
        });
    }
    public static void test1(Predicate<String> msg, String data){
        boolean test = msg.test(data);
        System.out.println(test);
    }

    public static void test2(Predicate<String> msg, Predicate<String> data){
        boolean o1 = msg.or(data).test("Hello");
        boolean o2 = msg.and(data).test("Hello");
        boolean o3 = msg.negate().test("Hello");

        System.out.println(o1);
        System.out.println(o2);
        System.out.println(o3);
    }
}

四、方法引用

1、为什么要用方法引用

1.1、lambda表达式冗余

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

public class MethodTest {
    public static void main(String[] args) {
        method1((msg)->{
            int sum = 0;
            for (int i : msg) {
                sum+= i;
            }
            System.out.println(sum);
        });

        int[] arr = {11,22,33,44,55};
        method2(arr);
    }

    public static int method2(int[] arr){
        int sum = 0;
        for (int i : arr) {
            sum+=i;
        }
        System.out.println(sum);
    }

    public static void method1(Consumer<int[]> consumer){
        int[] arr = {11,22,33,44,55};
        consumer.accept(arr);
    }
}

1.2、解决方法

  因为在lambda表达式中要执行的代码和另一个方法中的代码是一样的,这时就没有必要重写一份逻辑了

public class MethodTest {
    public static void main(String[] args) {
    //:: 方法引用  jdk8新语法
        method1(MethodTest::method2);
    }

    public static void method2(int[] arr){
        int sum = 0;
        for (int i : arr) {
            sum+=i;
        }
        System.out.println(sum);;
    }

    public static void method1(Consumer<int[]> consumer){
        int[] arr = {11,22,33,44,55};
        consumer.accept(arr);
    }
}

2、方法引用的格式

  符号表示 ::
  符号说明 双冒号为方法引用运算符,而它所在的表达式被称为方法引用
  应用场景 如果Lambda表达式所要实现的方案,已经有其他方法存在相同的方案
  常用的引用方式:
  instanceName::methodName 对象::方法名
  ClassName::staticMethodName 类名::静态方法
  ClassName::methodName 类名::普通方法
  ClassName::new 类名 ::new 调用的构造器
  TypeName[]::new String[]::new 调用数组的构造器

2.1、对象名::方法名

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

    @Test
    public void test10(){
        Date date = new Date();
        Supplier<Long> supplier = ()->{
            return date.getTime();
        };
        System.out.println(supplier.get());
        //方法引用
        Supplier<Long> supplier1 = date::getTime;
        System.out.println(supplier1.get());
    }

  方法引用注意事项:

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

2.2、类名::静态方法名

    @Test
    public void test11(){
        Supplier<Long> supplier = ()->{
            return System.currentTimeMillis();
        };
        System.out.println(supplier.get());
        //方法引用
        Supplier<Long> supplier1 = System::currentTimeMillis;
        System.out.println(supplier1.get());
    }

2.3、类名::引用实例方法

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

    @Test
    public void test12(){
        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"));

        BiFunction<String, Integer, String> function2 = String::substring;
        System.out.println(function2.apply("HelloWorld", 3));
    }

2.4、类名::构造器

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

    @Test
    public void test13(){
        Supplier<Person> supplier = ()->{
            return new Person();
        };
        System.out.println(supplier.get());

        Supplier<Person> supplier1 = Person::new;
        System.out.println(supplier1.get());
    }

2.5、数组::构造器

  构造一个数组

    @Test
    public void test14(){
        Function<Integer, String[]> function = (len)->{
            return new String[len];
        };
        System.out.println(function.apply(3).length);

        Function<Integer, String[]> function1 = String[]::new;
        System.out.println(function1.apply(4).length);
    }

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

五、Stream API

1.集合处理数据的弊端

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

    @Test
    public void test15(){
        List<String> list = Arrays.asList("张三","张三丰","成龙","周星驰");

        List<String> list1 = new ArrayList<>();
        for (String s : list) {
            if (s.startsWith("张")){
                list1.add(s);
            }
        }

        List<String> list2 = new ArrayList<>();
        for (String s : list1) {
            if (s.length() == 3){
                list2.add(s);
            }
        }

        for (String s : list2) {
            System.out.println(s);
        }
    }

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

    @Test
    public void test16(){
        List<String> list = Arrays.asList("张三","张三丰","成龙","周星驰");
        list.stream()
                .filter(s -> s.startsWith("张"))
                .filter(s -> s.length() == 3)
                .forEach(System.out::println);
    }

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

2.Stream流式思想概述

  Stream和IO流(InputStream/OutputStream)没有任何关系,Stream流式思想类似工厂车间的“生产流水线”,Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理,Stream可以看做是流水线上的一个工序,在流水线上,通过多个工序让一个原材料加工成一个商品。
在这里插入图片描述
在这里插入图片描述
  Stream API能让我们快速完成许多复杂的操作,如筛选、切片、映射、查找、去除重复、统计、匹配和归约。

3.Stream流的获取方式

3.1根据Collection获取

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

    @Test
    public void test22(){
        List<String> list = new ArrayList<>();
        list.stream();
        Set<String> set = new HashSet<>();
        set.stream();
    }

  Map接口没有实现Collection接口,这时候可以根据Map获取对应的key、value的集合

    @Test
    public void test23(){
        Map<String, Object> map = new HashMap<>();
        Stream<String> stream = map.keySet().stream();
        Stream<Object> stream1 = map.values().stream();
        Stream<Map.Entry<String, Object>> stream2 = map.entrySet().stream();
    }

3.2通过Stream的of方法

  在实际开发中可能会遇到操作数组中的数据,由于数组对象不能添加默认方法,所以Stream接口中提供可静态方法of

    @Test
    public void test24(){
        Stream<String> stringStream = Stream.of("a1", "a2", "a3");
        String[] s = {"aa","bb","cc"};
        Stream<String> s1 = Stream.of(s);
        Integer[] integers = {1,2,3,4};
        Stream<Integer> integers1 = Stream.of(integers);
        integers1.forEach(System.out::println);
        //注意基本数据类型数组不行(可打印查看)
        int[] ints = {1,2,3,4};
        Stream<int[]> ints1 = Stream.of(ints);
        ints1.forEach(System.out::println);
    }

4.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方法不调用终结方法,中间的操作不会执行
    @Test
    public void test25(){
        Stream<String> stringStream = Stream.of("a1", "a2", "a3");
        stringStream.filter(dto->{
            System.out.println("---------");
            return dto.contains("a");
        }).forEach(System.out::println);
        System.out.println("===========");
    }

4.1 forEach

  forEach是用来遍历流中的数据

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

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

    @Test
    public void test26(){
        Stream.of("a1", "a2", "a3").forEach(dto->{
            System.out.println(dto);
        });
    }

4.2 count

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

long count();

  该方法返回一个long类型的值,代表元素的个数

    @Test
    public void test27(){
        long count = Stream.of("a1", "a2", "a3").count();
        System.out.println(count);
    }

4.3 filter

  用来过滤数据的,返回符合条件的数据,可以通过filter方法将一个流转换成一个子集流

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

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

    @Test
    public void test28(){
        Stream.of("a1", "a2", "a3", "bb", "cc")
                .filter(s -> s.contains("a"))
                .forEach(System.out::println);
    }

4.4 limit

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

 Stream<T> limit(long maxSize);

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

    @Test
    public void test29(){
        Stream.of("a1", "a2", "a3", "bb", "cc")
                .limit(2)
                .forEach(System.out::println);
    }

4.5 skip

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

  Stream<T> skip(long n);
    @Test
    public void test30(){
        Stream.of("a1", "a2", "a3", "bb", "cc")
                .skip(2)
                .forEach(System.out::println);
    }

4.6 map

  如果需要将流中的元素映射到另一个流中,可以使用map方法
在这里插入图片描述

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

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

    @Test
    public void test31(){
        Stream.of("1", "2", "3", "4", "5")
                .map(Integer::parseInt)
                .forEach(System.out::println);
    }

4.7 sorted

  可以对数据进行排序

Stream<T> sorted(Comparator<? super T> comparator);

  在使用的时候可以根据自然规则排序,也可以指定对应的排序规则

    @Test
    public void test32(){
        Stream.of("5", "2", "3", "1", "6")
                .map(Integer::parseInt)
                .sorted((o1, o2) -> o2-o1)
                .forEach(System.out::println);
    }

4.8 distinct

  去除重复数据

Stream<T> distinct();

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

    @Test
    public void test33(){
        Stream.of("5", "2", "3", "1", "6", "3")
                .map(Integer::parseInt)
                .sorted((o1, o2) -> o2-o1)
                .distinct()
                .forEach(System.out::println);
        System.out.println("=================");
        Stream.of(new Person("张三", 13, 13)
                ,new Person("李四", 13, 13)
                ,new Person("张三", 13, 13)
                ).distinct()
                .forEach(System.out::println);
    }

4.9 1

  判断数据是否匹配指定的条件

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

  match也是一个终结方法

    @Test
    public void test34(){
        boolean b = Stream.of("5", "2", "3", "1", "6", "3")
                .map(Integer::parseInt)
                //.allMatch(s -> s > 0);
                //.anyMatch(s->s>3);
                .noneMatch(s->s>3);
        System.out.println(b);
    }

4.10 find

  如果需要找到某些数据,可以使用find方法来实现

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

  

    @Test
    public void test35(){
        
        Optional<String> first = Stream.of("5", "2", "3", "1", "6", "3").findFirst();
        System.out.println(first.get());

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

4.11 max和min

  获取最大值和最小值

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

  使用

    @Test
    public void test36(){

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

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

4.12 reduce

  将所有数据归纳得到一个数据
在这里插入图片描述

T reduce(T identity, BinaryOperator<T> accumulator);
    @Test
    public void test37(){
        Integer reduce = Stream.of("5", "2", "3", "1", "6", "3")
                .map(Integer::parseInt)
                //参数identity是默认值
                //第一次的时候会将默认值赋给x
                //之后会将每一次的操作赋值给x, y就是从数据中获取的元素
                .reduce(0, (x, y) -> {
                    System.out.println("x="+x+"   y="+y);
                    return x + y;
                });
        System.out.println(reduce);

        //获取最大值
        Integer reduce1 = Stream.of("5", "2", "3", "1", "6", "3")
                .map(Integer::parseInt)
                .reduce(0, (x, y) -> {
                    return x > y ? x : y;
                });
        System.out.println(reduce1);
    }

4.13 map和reduce的组合

  在实际开发中经常会将map和reduce一块使用

    @Test
    public void test38(){
        //求出所有年龄总和
        Integer reduce = Stream.of(
                new Person("张三", 10, 10),
                new Person("李四", 20, 20),
                new Person("王五", 30, 30),
                new Person("张三", 40, 40),
                new Person("赵六", 50, 50)
        ).map(Person::getAge) //数据类型转换
                .reduce(0, Integer::sum);
        System.out.println(reduce);

        //求出所有年龄最大值
        Integer reduce1 = Stream.of(
                new Person("张三", 10, 10),
                new Person("李四", 20, 20),
                new Person("王五", 30, 30),
                new Person("张三", 40, 40),
                new Person("赵六", 50, 50)
        ).map(Person::getAge) //数据类型转换
                .reduce(0, Integer::max);
        System.out.println(reduce1);

        //统计字符a出现的次数
        Integer reduce2 = Stream.of("a", "b", "c", "a", "a", "f", "h")
                .map(x -> {
                    return "a".equals(x) ? 1 : 0;
                }).reduce(0, Integer::sum);
        System.out.println(reduce2);
    }

4.14 mapToInt

  将Stream流中Integer类型转换成int类型

IntStream mapToInt(ToIntFunction<? super T> mapper);
LongStream mapToLong(ToLongFunction<? super T> mapper);
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);

在这里插入图片描述

    @Test
    public void test39(){
        //Integer类型比int类型占用的内存多,在Stream流中会自动装箱和拆箱
        Integer[] integers = {1,2,3,4,5};
        Stream<Integer> integerStream = Stream.of(integers);
        integerStream.forEach(System.out::println);
        //为了提高代码效率,先把流中的Integer类型转成int类型
        IntStream intStream = Stream.of(integers).mapToInt(Integer::intValue);
        intStream.forEach(System.out::println);
    }

4.15 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));
    }
    @Test
    public void test40(){
        Stream<String> stream1 = Stream.of("a", "b", "c");
        Stream<String> stream2 = Stream.of("x", "y", "z");
        Stream.concat(stream1, stream2)
                .forEach(System.out::println);
    }

4.16 综合案例

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

  1. 第一个队伍只保留姓名长度为3的成员
  2. 第一个队伍筛选之后只要前3个人
  3. 第二个队伍只要姓张的成员
  4. 第二个队伍筛选之后不要前两个人
  5. 将两个队伍合并成一个队伍
  6. 根据姓名创建Person对象
  7. 打印整个队伍的Person信息
    @Test
    public void test41(){
        List<String> list1 = Arrays.asList("迪丽热巴", "罗志祥", "周杰伦", "张家辉", "黄渤", "赵今麦", "张三", "孙红雷");
        List<String> list2 = Arrays.asList("古力娜扎", "张天爱", "张三丰", "张无忌", "王迅", "张艺兴", "张纪中", "郑凯");

        Stream<String> stream1 = list1.stream()
                .filter(s -> s.length() == 3)
                .limit(3);

        Stream<String> stream2 = list2.stream()
                .filter(s -> s.startsWith("张"))
                .skip(2);
        
        Stream.concat(stream1, stream2)
                .map(s -> new Person(s))
                .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);
    }
    @Test
    public void test42(){
    	//收集到List中
        List<String> list = Stream.of("aa", "bb", "cc", "aa")
                .collect(Collectors.toList());
        System.out.println(list);
		//收集到set中
        Set<String> set = Stream.of("aa", "bb", "cc", "aa")
                .collect(Collectors.toSet());
        System.out.println(set);

        //如需要获取的类型为ArrayList  HashSet
        ArrayList<String> arrayList = Stream.of("aa", "bb", "cc", "aa")
                .collect(Collectors.toCollection(ArrayList::new));
        System.out.println(arrayList);

        HashSet<String> hashSet = Stream.of("aa", "bb", "cc", "aa")
                .collect(Collectors.toCollection(HashSet::new));
        System.out.println(hashSet);
    }

5.2 结果收集到数组中

Object[] toArray();
<A> A[] toArray(IntFunction<A[]> generator);
    @Test
    public void test43(){
        //返回数组的元素是object类型
        Object[] objects = Stream.of("aa", "bb", "cc", "aa")
                .toArray();
        System.out.println(Arrays.toString(objects));

        //指定返回数组的元素类型
        String[] strings = Stream.of("aa", "bb", "cc", "aa")
                .toArray(String[]::new);
        System.out.println(Arrays.toString(strings));
    }

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

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

    @Test
    public void test44(){
        //最大年龄
        Optional<Person> collect = Stream.of(new Person("张三", 13),
                new Person("李四", 14),
                new Person("王五", 15),
                new Person("赵六", 16),
                new Person("周七", 17)
        ).collect(Collectors.maxBy((o1, o2) -> o1.getAge() - o2.getAge()));
        System.out.println(collect.get());
        //最小年龄
        Optional<Person> collect1 = Stream.of(new Person("张三", 13),
                new Person("李四", 14),
                new Person("王五", 15),
                new Person("赵六", 16),
                new Person("周七", 17)
        ).collect(Collectors.minBy((o1, o2) -> o1.getAge() - o2.getAge()));
        System.out.println(collect1.get());
        //年龄求和
        Integer collect2 = Stream.of(new Person("张三", 13),
                new Person("李四", 14),
                new Person("王五", 15),
                new Person("赵六", 16),
                new Person("周七", 17)
        ).collect(Collectors.summingInt(Person::getAge));
        System.out.println(collect2);
        //年龄平均值
        Double collect3 = Stream.of(new Person("张三", 13),
                new Person("李四", 14),
                new Person("王五", 15),
                new Person("赵六", 16),
                new Person("周七", 17)
        ).collect(Collectors.averagingDouble(Person::getAge));
        System.out.println(collect3);
        //统计数量
        Long collect4 = Stream.of(new Person("张三", 13),
                new Person("李四", 14),
                new Person("王五", 15),
                new Person("赵六", 16),
                new Person("周七", 17)
        ).collect(Collectors.counting());
        System.out.println(collect4);
    }

5.4 对流中的数据做分组操作

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

    @Test
    public void test45(){
        //根据姓名进行分组
        Map<String, List<Person>> collect = Stream.of(
                new Person("张三", 13),
                new Person("李四", 13),
                new Person("张三", 15),
                new Person("李四", 16),
                new Person("张三", 17)
        ).collect(Collectors.groupingBy(Person::getName));
        collect.forEach((x, y) -> System.out.println("x=" + x + "\t" + "y=" +y));
        System.out.println("====================");
        
        //根据年龄进行分组,大于15为成年否则为未成年
        Map<String, List<Person>> collect1 = Stream.of(
                new Person("张三", 13),
                new Person("李四", 13),
                new Person("张三", 15),
                new Person("李四", 16),
                new Person("张三", 17)
        ).collect(Collectors.groupingBy(s -> s.getAge() > 15? "成年" : "未成年"));
        collect1.forEach((k, v) -> System.out.println("k=" + k + "\t" + "v=" + v));
        System.out.println("====================");
        
        //多个字段分组计算
        Map<String, Map<String, List<Person>>> collect2 = Stream.of(
                new Person("张三", 13),
                new Person("李四", 13),
                new Person("张三", 15),
                new Person("李四", 16),
                new Person("张三", 17)
        ).collect(Collectors.groupingBy(Person::getName, Collectors.groupingBy((s -> s.getAge() > 15? "成年" : "未成年"))));
        collect2.forEach((k, v) -> {
            System.out.println(k);
            v.forEach((m, n) -> {
                System.out.println("m=" + m + "\t" + "n=" + n);
            });
        });
    }

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

  Collectors.partitioningBy会根据值是否为true,把集合中的数据分割为两个列表,一个为true列表,一个为false列表
在这里插入图片描述

    @Test
    public void test46(){
        Map<Boolean, List<Person>> collect = Stream.of(
                new Person("张三", 13),
                new Person("李四", 13),
                new Person("张三", 15),
                new Person("李四", 16),
                new Person("张三", 17)
        ).collect(Collectors.partitioningBy(s -> s.getAge() > 15));
        collect.forEach((k, v) -> System.out.println("k=" + k + "\t" + "v=" + v));
    }

5.6 对流中的数据做拼接

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

    @Test
    public void test47(){
        String collect = Stream.of(
                new Person("张三", 13),
                new Person("李四", 13),
                new Person("张三", 15),
                new Person("李四", 16),
                new Person("张三", 17)
        ).map(Person::getName)
                .collect(Collectors.joining());
        System.out.println(collect);
        System.out.println("=====================");
        String collect1 = Stream.of(
                new Person("张三", 13),
                new Person("李四", 13),
                new Person("张三", 15),
                new Person("李四", 16),
                new Person("张三", 17)
        ).map(Person::getName)
                .collect(Collectors.joining(","));
        System.out.println(collect1);
        System.out.println("=====================");
        String collect2 = Stream.of(
                new Person("张三", 13),
                new Person("李四", 13),
                new Person("张三", 15),
                new Person("李四", 16),
                new Person("张三", 17)
        ).map(Person::getName)
                .collect(Collectors.joining(",", "###", "$$$"));
        System.out.println(collect2);
    }

6. 并行的Stream流

6.1 串行的Stream流

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

    @Test
    public void test48(){
        Stream.of(1,2,3,8,7,6,5)
                .filter(s -> {
                    System.out.println(Thread.currentThread()+ "" + s);
                    return s > 3;
                }).count();
    }

6.2 并行流

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

  1. 通过List接口中的parallelStream方法来获取
  2. 通过已有的串行流转换为并行流(parallel)
    @Test
    public void test49(){
        List<Integer> list = new ArrayList<>();
        //通过List接口中的parallelStream方法来获取
        Stream<Integer> integerStream = list.parallelStream();
        //通过已有的串行流转换为并行流(parallel)
        Stream<Integer> stream = Stream.of(1, 3, 4).parallel();
    }
    @Test
    public void test50(){
        Stream.of(1, 3, 4, 7, 5)
                .parallel()
                .filter(s->{
                    System.out.println(Thread.currentThread() + "" + s);
                    return s > 3;
                }).count();
    }

6.3 并行流和串行流对比

public class TimeTest {
    private final long times = 500000000L;
    private long start = 0L;
    private long sum = 0L;

    @Before
    public void before(){
        start = System.currentTimeMillis();
    }

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

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

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

    /**
     * 并行流
     */
    @Test
    public void test03(){
        System.out.println("并行流");
        LongStream.rangeClosed(0, times)
                .parallel()
                .reduce(0, Long::sum);
    }
}

  可以看到并行流的效率最高,Stream并行流将一个大任务分成了多个小任务,然后每个任务都是一个线程操作。

6.4 并行流线程安全问题

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

    @Test
    public void test51(){
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            list.add(i);
        }
        //非线程安全下
        System.out.println(list.size());

        List<Integer> listNew = new ArrayList<>();
        list.stream().parallel().forEach(listNew::add);
        //线程安全
        System.out.println(listNew.size());
    }

  上述代码可能报错

java.lang.ArrayIndexOutOfBoundsException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	...
Caused by: java.lang.ArrayIndexOutOfBoundsException: 549
	at java.util.ArrayList.add(ArrayList.java:459)

  解决方案:

  1. 加同步锁
  2. 使用线程安全的容器
  3. 通过Stream中的toArray/collect操作
       @Test
    public void test52(){
        //加同步锁
        Object obj = new Object();
        List<Integer> listNew = new ArrayList<>();
        IntStream.rangeClosed(1, 1000)
                .parallel()
                .forEach(s->{
                    synchronized (obj){
                        listNew.add(s);
                    }
        });
        System.out.println(listNew.size());

        //使用线程安全的的容器Vector
        Vector vector = new Vector();
        IntStream.rangeClosed(1, 1000)
                .parallel()
                .forEach(vector::add);
        System.out.println(vector.size());

        //把容器转成线程安全的
        List<Integer> list1 = new ArrayList<>();
        List<Integer> list = Collections.synchronizedList(list1);
        IntStream.rangeClosed(1, 1000)
                .parallel()
                .forEach(list::add);
        System.out.println(list.size());

        //toArray、collect
        List<Integer> toList =
        IntStream.rangeClosed(1, 1000)
                .parallel()
                //int流转成Integer流
                .boxed()
                .collect(Collectors.toList());
        System.out.println(toList.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工作窃取算法就是指某个线程从其他队列里窃取任务来执行。
在这里插入图片描述
  

1比较大的任务,我们可以把这个任务分割为若干个互不依赖的子任务,为了减少线程间的竞争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应,比如A线程负责处理A队列里的任务,但是有的线程会把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮助其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时他们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。
  工作窃取算法的优点是充分利用线程进行并行计算,并减少线程间的竞争,其缺点是在某些情况下还是存在竞争,比如双端队列只有一个任务时,并且消耗了更多的系统资源,比如创建多个线程和多个双端队列。
  上文中已经提到了在java8引入了自动并行化的概念,它能够让一部分java代码自动地以并行的方式执行,也就是我们使用了ForkJoinPool的ParallelStream.
  对于ForkJoinPool通过线程池的线程数量,通常使用默认值就可以了,即运行时计算机的处理器数量,可以通过设置系统属性:java.util.concurrent.ForkJoinPool.common.parallelism=N (N为线程数量),来调整ForkJoinPool的线程数量,可以尝试调整成不同的参数来观察每次的输出结果。

7.3 Fork/Join案例

  需求:使用Fork/Join计算1-10000的和,当一个任务的计算数量大于3000的时候拆分任务,数量小于3000的时候就计算
在这里插入图片描述

    @Test
    public void test53(){
        long start = System.currentTimeMillis();
        NewTask newTask = new NewTask(1, 10000);
        Long compute = newTask.compute();
        System.out.println(compute);
        long end = System.currentTimeMillis();
        System.out.println(end-start);
    }
public class NewTask extends RecursiveTask<Long>{
    private final long number = 3000;
    private long start;
    private long end;

    public NewTask(long start, long end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        long length = end - start;
        if (length < number){
            long sum = 0L;
            for (int i = 0; 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+1L) + "--->" + end);
            NewTask startTest = new NewTask(start, middle);
            NewTask endTask = new NewTask((middle + 1L), end);
            startTest.fork();
            endTask.fork();
            return startTest.join()+endTask.join();
        }
    }
}

七、Optional类

  Optional类主要是解决空指针的问题,是一个没有子类的工具类,Optional是一个可以为Null的容器对象,主要作用就是为了避免null检查,防止空指针异常

1. Optional的基本使用

  Optional对象的创建:

  1. 通过of方法,of方法是不支持null
  2. 通过ofNullable方法,支持null
  3. 通过empty方法直接创建一个空的Optional对象
    @Test
    public void test54(){
        //of方法
        Optional<String> op1 = Optional.of("s");
        //Optional<Object> op2 = Optional.of(null);

        //ofNullable方法
        Optional<String> op3 = Optional.ofNullable("s");
        Optional<Object> op4 = Optional.ofNullable(null);
        
        //empty方法
        Optional<Object> op5 = Optional.empty();
    }

2. Optional中常用方法

2.1 get()方法

  如果Optional有值则返回,否则抛出NoSuchElementException异常,get()通常和isPresent()方法一块使用

2.2 isPresent()方法

  判断是否包含值,包含值返回true,不包含返回false

    @Test
    public void test02(){
        Optional<String> op1 = Optional.of("张三");
        Optional<String> op2 = Optional.empty();

        System.out.println(op1.get());
        //System.out.println(op2.get());
        
        System.out.println(op1.isPresent());
        if (op1.isPresent()){
            System.out.println(op1.get());
        }

        System.out.println(op2.isPresent());
        if (op2.isPresent()){
            System.out.println(op2.get());
        }
    }

2.3 orElse()方法

  如果调用对象包含值,就返回值,否则返回括号中的内容

2.4 orElseGet(Supplier s)方法

  如果调用对象包含值,就返回值,否则返回括号lambda表达式的值

   @Test
    public void test03(){
        Optional<String> op1 = Optional.of("张三");
        Optional<String> op2 = Optional.empty();

        System.out.println(op1.orElse("lisi"));
        System.out.println(op2.orElse("wangwu"));

        String s1 = op1.orElseGet(() -> {
            return "======";
        });
        System.out.println(s1);

        String s2 = op2.orElseGet(() -> {
            return "++++++";
        });
        System.out.println(s2);
    }

2.5 ifPresent(Consumer<? super T> consumer)方法

  括号中lambda表达式如果存在就执行什么

    @Test
    public void test04(){
        Optional<String> op1 = Optional.of("张三");
        Optional<String> op2 = Optional.empty();

        op1.ifPresent(s->{
            System.out.println("有值");
        });

        op2.ifPresent(s->{
            System.out.println("无值");
        });
    }

  其它用法:

    @Test
    public void test05(){
        Person person = new Person("zhangsan", 15);
        Optional<String> s = Optional.ofNullable(person)
                .map(Person::getName)
                .map(String::toUpperCase);
        System.out.println(s.get());

        Person person1 = new Person(null, 16);
        String b = Optional.ofNullable(person1)
                .map(Person::getName)
                .map(String::toUpperCase)
                .orElse("lisi");
        System.out.println(b);
    }

八、新时间日期Api

1. 旧版本日期的问题

   @Test
    public void test06() throws Exception{
        //设计不合理
        Date date = new Date(2008, 8, 8);
        System.out.println(date);

        //时间格式化和解析是线程不安全的
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        System.out.println(simpleDateFormat.format(new Date()));
        System.out.println(simpleDateFormat.parse("2008-08-08"));

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

2. 新日期时间Api介绍

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

  • LocalDate:表示日期,包含年月日,格式为2008-08-08
  • LocalTime:表示时间,包含时分秒,格式为08:08:08.158549300
  • LocalDateTime:表示日期时间,包含年月日,时分秒,格式为2008-08-08T15:33:56.750
  • DateTimeFormatter:日期时间格式化类
  • Instant:时间戳,表示一个特定的时间瞬间
  • Duration:用于计算2个时间(LocalTime 时分秒)的距离
  • Period:用于计算2个时间(LocalDate 年月日)的距离
  • ZonedDateTime:包含时区的时间。
      java中使用的历法是ISO 8601日历系统,它是世界民用历法,也就是我们说的公历, 平年幼365天,闰年366天,此外java8还提供了其它历法
  • ThaiBuddhistDate:泰国佛教历
  • MinguoDate:中华民国历
  • JapaneseDate:日本历
  • HijrahDate:伊斯兰历

2.1 日期时间的常见操作

2.1.1 LocalDate
    @Test
    public void test55(){
        //获取指定日期
        LocalDate date1 = LocalDate.of(2008, 8, 8);
        System.out.println("date1:" + date1);
        //获取当前日期
        LocalDate date2 = LocalDate.now();
        System.out.println("date2:" + date2);
        //根据LocalDate对象获取对应的日期信息
        System.out.println("年:" + date1.getYear());
        System.out.println("月:" + date1.getMonth().getValue());
        System.out.println("月:" + date1.getMonthValue());
        System.out.println("日:" + date1.getDayOfMonth());
        System.out.println("星期:" + date1.getDayOfWeek());
        System.out.println("星期:" + date1.getDayOfWeek().getValue());
    }
2.1.2 LocalTime
    @Test
    public void test56(){
        //获取指定时间
        LocalTime time1 = LocalTime.of(8, 8, 8,8);
        System.out.println("time1:" + time1);
        //获取当前时间
        LocalTime time2 = LocalTime.now();
        System.out.println("time2:" + time2);
        //获取对应的时间信息
        System.out.println(time1.getHour());
        System.out.println(time1.getMinute());
        System.out.println(time1.getSecond());
        System.out.println(time1.getNano());
    }
2.1.3 LocalDateTime
    @Test
    public void test57(){
        //获取指定日期时间
        LocalDateTime dateTime1 = LocalDateTime.of(2008, 8, 8, 8, 8, 8, 888);
        System.out.println("dateTime1:" + dateTime1);
        //获取当前日期时间
        LocalDateTime dateTime2 = LocalDateTime.now();
        System.out.println("dateTime2:" + dateTime2);
        //获取对应的日期时间信息
        System.out.println("年:" + dateTime2.getYear());
        System.out.println("月:" + dateTime2.getMonthValue());
        System.out.println("日:" + dateTime2.getDayOfMonth());
        System.out.println("时:" + dateTime2.getHour());
        System.out.println("分:" + dateTime2.getMinute());
        System.out.println("秒:" + dateTime2.getSecond());
        System.out.println("周:" + dateTime2.getDayOfWeek().getValue());
    }

2.2 日期时间的修改比较

    @Test
    public void test58(){
        //修改日期时间
        LocalDateTime dateTime1 = LocalDateTime.of(2008, 8, 8, 8, 8, 8, 888);
        System.out.println(dateTime1);
        System.out.println(dateTime1.withYear(2009));
        System.out.println(dateTime1.withMonth(9));
        System.out.println(dateTime1.withDayOfMonth(9));
        System.out.println(dateTime1.withHour(9));
        System.out.println(dateTime1.withMinute(9));
        System.out.println(dateTime1.withSecond(9));
        //对当前日期加减
        LocalDateTime dateTime2 = LocalDateTime.now();
        System.out.println(dateTime2);
        System.out.println(dateTime2.minusYears(1));
        System.out.println(dateTime2.minusMonths(1));
        System.out.println(dateTime2.minusDays(1));
        System.out.println(dateTime2.plusHours(1));
        System.out.println(dateTime2.plusMinutes(1));
        System.out.println(dateTime2.plusSeconds(1));
        //日期时间的比较
        System.out.println(dateTime1.isAfter(dateTime2));
        System.out.println(dateTime1.isBefore(dateTime2));
        System.out.println(dateTime1.isEqual(dateTime2));
    }

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

2.3 格式化和解析操作

  在JDK8中通过java.time.format.DateTimeFormatter类可以进行日期的解析和格式化操作。

    @Test
    public void test59(){
        //将日期转换成字符串
        LocalDateTime dateTime = LocalDateTime.now();
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        System.out.println(dateTime.format(dateTimeFormatter));
        //将字符串解析成日期
        LocalDateTime dateTime1 = LocalDateTime.parse("2008-08-08 08:08:08", dateTimeFormatter);
        System.out.println(dateTime1);
    }

2.4 Instant类

  在JDK8新增了一个Instant类(时间戳)

    @Test
    public void test60() throws Exception{
        Instant instant1 = Instant.now();
        System.out.println("instant1 = " + instant1);
        System.out.println("纳秒 = " + instant1.getNano());
        Thread.sleep(5);
        Instant instant2 = Instant.now();
        System.out.println("instant2 = " + instant2);
        System.out.println("时间差:" + (instant2.getNano() - instant1.getNano()));
    }

2.5 计算日期时间差

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

  1. Duration:用来计算两个时间差(LocalTime)
  2. Period:用来计算两个日期差(LocalDate)
    @Test
    public void test61(){
        LocalTime time = LocalTime.now();
        LocalTime localTime = LocalTime.of(22, 22, 22);
        Duration between = Duration.between(time, localTime);
        System.out.println(between.toDays());
        System.out.println(between.toHours());
        System.out.println(between.toMinutes());
        System.out.println(between.toMillis());
    }
    @Test
    public void test62(){
        LocalDate date = LocalDate.now();
        LocalDate localDate = LocalDate.of(2008, 8, 8);
        Period between = Period.between(localDate, date);
        System.out.println(between.getDays());
        System.out.println(between.getMonths());
        System.out.println(between.getYears());
    }

2.6 时间矫正器

  如需要调整:将日期调整到下个月的第一天

  1. TemporalAdjuster:时间矫正器
  2. TemporalAdjusters:通过该类静态方法提供了大量的常用TemporalAdjuster的实现
    @Test
    public void test63(){
        LocalDateTime dateTime = LocalDateTime.now();
        TemporalAdjuster adjuster = (temporal -> {
            LocalDateTime localDateTime = (LocalDateTime)temporal;
            LocalDateTime time = localDateTime.plusMonths(1).withDayOfMonth(1);
            return time;
        });
        //LocalDateTime with = dateTime.with(adjuster);
        LocalDateTime with = dateTime.with(TemporalAdjusters.firstDayOfNextMonth());
        System.out.println("with = " + with);
    }

2.7 日期时间的时区

  java8中加入了对时区的支持,LocalDate、LocalTime、LocalDateTime是不带时区的,带时区的日期时间类分别为:ZonedDate、ZonedTime、ZonedDateTime,其中每个时区都对应着ID,ID的格式为“区域/城市”。列如:Asia/Shanghai等,ZoneId:该类中包含了所有的时区信息。

    @Test
    public void test64(){
        //获取所有时区id
        //ZoneId.getAvailableZoneIds().forEach(System.out::println);
        //获取当前时间,中国使用东八区
        LocalDateTime dateTime = LocalDateTime.now();
        System.out.println(dateTime);
        //获取标准时间
        ZonedDateTime zonedDateTime = ZonedDateTime.now(Clock.systemUTC());
        System.out.println(zonedDateTime);
        //使用计算机默认时间
        ZonedDateTime zonedDateTime1 = ZonedDateTime.now();
        System.out.println(zonedDateTime1);
        //使用指定时区创建时间  America/Marigot
        ZonedDateTime now = ZonedDateTime.now(ZoneId.of("America/Marigot"));
        System.out.println(now);
    }

  JDK新的日期时间API优势:

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

九、其他新特性

1. 重复注解

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

  1. 定义一个重复注解的容器
	@Retention(RetentionPolicy.RUNTIME)
	public @interface MyAnnotations {
		MyAnnotation[] value();
	}
  1. 定义一个可以重复的注解
	@Repeatable(MyAnnotations.class)
	@Retention(RetentionPolicy.RUNTIME)
	public @interface MyAnnotation {
		String value();
	}
  1. 配置多个重复的注解
	@MyAnnotation("test1")
	@MyAnnotation("test2")
	@MyAnnotation("test3")
	public class AnnoTest {
		@MyAnnotation("fun1")
		@MyAnnotation("fun2")
		public void test(){
		
		}
	}
  1. 解析得到指定的注解
	public static void main(String[] args) throws NoSuchMethodException {
		MyAnnotation[] byType = AnnoTest.class.getAnnotationsByType(MyAnnotation.class);
		for (MyAnnotation myAnnotation : byType){
			System.out.println(myAnnotation.value());
		}
		
		MyAnnotation[] test01s = AnnoTest.class.getMethod("test01").getAnnotationsByType(MyAnnotation.class);
		for (MyAnnotation test01: test01s){
			System.out.println(test01.value());
		}
	}

2. 类型注解

  JDK8为@Target元注解新增了两种类型,TYPE_PAPAMETER、TYPE_USE

  • TYPE_PAPAMETER:表示该注解能写在类型参数的声明语句中,类型声明参数如:
  • TYPE_USE:表示注解可以在任何用到类型的地方使用。
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值