jdk8新特性

一、lambda表达式

Lambda表达式相当于是对接口抽象方法的重写

对比匿名内部类与lambda表达式

package com.bz.jdk8.demo01_Lambda;
/**
 * 体验Lambda表达式
 */
public class Demo01LambdaIntro {
    public static void main(String[] args) {
        // 使用匿名内部类存在的问题
        // public Thread(Runnable target)
        // 匿名内部类做了哪些事情
        //  1.定义了一个没有名字的类
        //  2.这个类实现了Runnable接口
        //  3.创建了这个类的对象
        // 使用匿名内部类语法是很冗余的
        // 其实我们最关注的是run方法和里面要执行的代码.
        // Lambda表达式体现的是函数式编程思想,只需要将要执行的代码放到函数中(函数就是类中的方法)
        // Lambda就是一个匿名函数, 我们只需要将要执行的代码放到Lambda表达式中即可
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行一个线程");
            }
        }).start();

        // Lambda表达式的好处: 可以简化匿名内部类,让代码更加精简
        //使用Lambda表达式
        new Thread(()->{
            System.out.println("使用了Lambda表达式");
        }).start();
    }
}

标准格式

()->{}

package com.bz.jdk8.demo01_Lambda;

/**
 * Lambda表达式的标准格式
 * ()->{}
 *
 *  // 小结:Lambda表达式相当于是对接口抽象方法的重写
 */
public class Demo02LambdaUse {
    public static void main(String[] args) {
        goSwimming(new Swimmable() {
            @Override
            public void swimming() {
                System.out.println("练习无参数无返回值的匿名内部类");
            }
        });

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

        goSwimming(()-> System.out.println("练习无参数无返回值的Lambda"));
    }

    // 练习无参数无返回值的Lambda
    public static void goSwimming(Swimmable s) {
        s.swimming();
    }
}

省略写法

在Lambda标准格式的基础上,使用省略写法的规则为:

  1. 小括号内参数的类型可以省略。
  2. 如果小括号内有且仅有一个参数,则小括号可以省略。
  3. 如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号。

Lambda表达式的前提条件

Lambda表达式的前提条件:

  1. 方法的参数或变量的类型是接口。
  2. 这个接口中只能有一个抽象方法。
package com.bz.jdk8.demo01_Lambda;

/**
 * Lambda表达式的前提条件:
 *  1. 方法的参数或变量的类型是接口
 *  2. 这个接口中只能有一个抽象方法
 *
 *  @FunctionalInterface // 检测这个接口是不是只有一个抽象方法
 */
public class Demo04LambdaCondition {
    public static void main(String[] args) {

        test(()-> System.out.println("吃饭了吗?"));

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

        Flyable f = ()->{
            System.out.println("飞呀飞。。。");
        };
    }

    public static void test(Flyable flyable){
        System.out.println("使用了lambda表达式");
    }
}



// 只有一个抽象方法的接口称为函数式接口,我们就能使用Lambda
@FunctionalInterface // 检测这个接口是不是只有一个抽象方法
interface Flyable {
    // 接口中有且仅有一个抽象方法
    public abstract void eat();
    // public abstract void eat2();
}

二、接口增加默认方法和静态方法

接口引入默认方法的背景

如果给接口新增抽象方法,所有实现类都必须重写这个抽象方法。
不利于接口的扩展。

package com.bz.jdk8.demo02interfaceupgrade;

/**
 * 接口引入默认方法的背景
 * 在JDK 8以前接口中只能有抽象方法。存在以下问题:
 *  如果给接口新增抽象方法,所有实现类都必须重写这个抽象方法。
 *  不利于接口的扩展。
 */
public class Demo01InterfaceDefaultIntro{

}

interface A {
    public abstract void test01();
    // 接口新增抽象方法,所有实现类都需要去重写这个方法,非常不利于接口的扩展
    // public abstract void test02();
}

class B implements A {
    @Override
    public void test01() {
        System.out.println("B test01");
    }
}

class C implements A {
    @Override
    public void test01() {
        System.out.println("C test01");
    }
}

默认方法

package com.bz.jdk8.demo02interfaceupgrade;

/**
 * 接口默认方法的使用
 * 方式一:实现类直接调用接口默认方法
 * 方式二:实现类重写接口默认方法
 */
public class Demo02UseDefaultFunction {
    public static void main(String[] args) {

        //方式一:实现类直接调用接口默认方法
        AA aa = new BB();
        aa.test1();

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

        //方式二:实现类重写接口默认方法
        AA ac = new CC();
        ac.test1();
    }
}

interface AA{
    default void test1(){
        System.out.println("jdk8新增的默认的方法");
    }
}

class BB implements AA{
    //不重写方法
}

class CC implements AA{
    @Override
    public void test1() {
        System.out.println("重写了方法");
    }
}

静态方法

package com.bz.jdk8.demo02interfaceupgrade;

/**
 * 接口的静态方法
 * 接口静态方法的使用
 * 直接使用接口名调用即可:接口名.静态方法名();
 *
 * 接口默认方法和静态方法的区别
 *  1. 默认方法通过实例调用,静态方法通过接口名调用。
 *  2. 默认方法可以被继承,实现类可以直接使用接口默认方法,也可以重写接口默认方法。
 *  3. 静态方法不能被继承,实现类不能重写接口静态方法,只能使用接口名调用。
 *
 * 如何选择呢?如果这个方法需要被实现类继承或重写,使用默认方法; 如果接口中的方法不需要被继承就使用静态方法
 */
public class Demo03UseStaticFunction {
    public static void main(String[] args) {
        DD dd = new EE();
        //

        //通过接口名调用
        DD.test();
    }
}

interface DD{
    public static void test(){
        System.out.println("jdk8新增的静态方法");
    }
}

//无法重写静态方法
class EE implements DD{
//    @Override
//    test
}

接口默认方法和静态方法的区别

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

如何选择呢?如果这个方法需要被实现类继承或重写,使用默认方法; 如果接口中的方法不需要被继承就使用静态方法。

三、函数式接口

内置函数式接口的由来

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

Supplier接口

java.util.function.Supplier 接口,它意味着"供给" , 对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。

package com.bz.jdk8.demo03functionalinterface;

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

/**
 * java.util.function.Supplier 接口,它意味着"供给" , 对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。
 * 供给型接口,通过Supplier接口中的get方法可以得到一个值,无参有返回的接口。
 */
public class Demo02Supplier {
    // 使用Lambda表达式返回数组元素最大值
    public static void main(String[] args) {
        printMax(()->{
            int[] arr = new int[]{23,25,11,56,45};
            Arrays.sort(arr);//升序
            return arr[arr.length - 1];
        });
    }

    public static void printMax(Supplier<Integer> supplier){
        System.out.println("supplier函数式接口");
        Integer max = supplier.get();
        System.out.println("max: "+max);
    }
}

Consumer接口

java.util.function.Consumer 接口则正好相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型参数决定。

package com.bz.jdk8.demo03functionalinterface;

import java.util.function.Consumer;

/**
 * java.util.function.Consumer 接口则正好相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型参数决定。
 */
public class Demo03Consumer {

    // 使用Lambda表达式将一个字符串转成大写的字符串
    public static void main(String[] args) {
        System.out.println("开始了");
        printHello((s)->{
            System.out.println(s.toUpperCase());
        });
    }

    public static void printHello(Consumer<String> consumer){
        System.out.println("consumer");
        consumer.accept("holle world");
    }
}

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

package com.bz.jdk8.demo03functionalinterface;


import java.util.function.Consumer;

public class Demo04ConsumerAndThen {
    // 使用Lambda表达式先将一个字符串转成小写的字符串,再转成大写
    public static void main(String[] args) {
        System.out.println("开始啦");
        printHello((String str) -> {
            System.out.println(str.toLowerCase());
        }, (String str) -> {
            System.out.println(str.toUpperCase());
        });
    }

    public static void printHello(Consumer<String> c1, Consumer<String> c2) {
        System.out.println("aa");
        String str = "Hello World";
        // c1.accept(str);
        // c2.accept(str);

        c1.andThen(c2).accept(str);

    }
}

Function接口

java.util.function.Function 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。有参数有返回值。

Function转换型接口,对apply方法传入的T类型数据进行处理,返回R类型的结果,有参有返回的接口。使用的场景例如:将 String 类型转换为 Integer

package com.bz.jdk8.demo03functionalinterface;

import java.util.function.Function;

/**
 * Function接口
 * java.util.function.Function 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,
 * 后者称为后置条件。有参数有返回值。
 *
 * Function转换型接口,对apply方法传入的T类型数据进行处理,返回R类型的结果,有参有返回的接口。使用的场景
 * 例如:将 String 类型转换为 Integer 类型。
 */
public class Demo05Function {

//    使用Lambda表达式将字符串转成数字
    public static void main(String[] args) {
        System.out.println("开始了...");
        getNumber(str->{
            return Integer.parseInt(str);
        });
    }

    public static void getNumber(Function<String,Integer> function){
        System.out.println("输入字符串得到int数据");
        Integer apply = function.apply("98");
        System.out.println("num:"+apply);
    }
}

默认方法:andThen
Function 接口中有一个默认的 andThen 方法,用来进行组合操作。

package com.bz.jdk8.demo03functionalinterface;

import java.util.function.Function;

/**
 * 默认方法:andThen
 * Function 接口中有一个默认的 andThen 方法,用来进行组合操作。
 */
public class Demo06FunctionAndThen {

    //第一个操作是将字符串解析成为int数字,第二个操作是乘以10。两个操作通过 andThen 按照前后顺序组合到了一起。
    public static void main(String[] args) {
        getNumber(str->{
            return Integer.parseInt(str);
        },i->{
            return i * 6;
        });
    }

    public static void getNumber(Function<String,Integer> f1,Function<Integer,Integer> f2){
        System.out.println("先转为int类型,如何再乘以6");

/*        Integer apply = f1.apply("5");
        Integer count = f2.apply(apply);*/

        Integer count = f1.andThen(f2).apply("5");

        System.out.println("结果:"+count);
    }

}

Predicate接口

有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用java.util.function.Predicate 接口。

package com.bz.jdk8.demo03functionalinterface;

import java.util.function.Predicate;

public class Demo07Predicate {
    // 使用Lambda判断一个人名如果超过3个字就认为是很长的名字
    public static void main(String[] args) {
        System.out.println("开始啦");
        isLongName((String name) -> {
            return name.length() > 3;
        });
    }

    public static void isLongName(Predicate<String> predicate) {
        System.out.println("aa");
        boolean isLong = predicate.test("迪丽热巴");
        System.out.println("是否是长名字: " + isLong);
    }
}

默认方法:and,使用“与”逻辑实现“并且”的效果。
默认方法:or,实现逻辑关系中的“或”。
默认方法:negate,“非”(取反)。

package com.bz.jdk8.demo03functionalinterface;


import java.util.function.Predicate;

public class Demo08Predicate_And_Or_Negate {
    // 使用Lambda表达式判断一个字符串中即包含W,也包含H
    // 使用Lambda表达式判断一个字符串中包含W或者包含H
    // 使用Lambda表达式判断一个字符串中不包含W
    public static void main(String[] args) {
        test((String str) -> {
            // 判断是否包含W
            return str.contains("W");
        }, (String str) -> {
            // 判断是否包含H
            return str.contains("H");
        });
    }

    public static void test(Predicate<String> p1, Predicate<String> p2) {
        // String str = "Hello orld";
        // boolean b1 = p1.test(str);
        // boolean b2 = p2.test(str);
        // if (b1 && b2) {
        //     System.out.println("即包含W,也包含H");
        // }
        // 使用Lambda表达式判断一个字符串中即包含W,也包含H
        String str = "Hello World";
        boolean b = p1.and(p2).test(str);
        if (b) {
            System.out.println("即包含W,也包含H");
        }

        // 使用Lambda表达式判断一个字符串中包含W或者包含H
        boolean b1 = p1.or(p2).test(str);
        if (b1) {
            System.out.println("包含W或者包含H");
        }
        // 使用Lambda表达式判断一个字符串中不包含W
        boolean b2 = p1.negate().test("Hello W");
        // negate相当于取反 !boolean
        if (b2) {
            System.out.println("不包含W");
        }
    }
}

四、方法引用

方法引用的格式

符号表示 ::

符号说明 : 双冒号为方法引用运算符,而它所在的表达式被称为方法引用。
应用场景 : 如果Lambda所要实现的方案 , 已经有其他方法存在相同方案,那么则可以使用方法引用,

package com.bz.jdk8.demo04methodref;

import java.sql.SQLOutput;
import java.util.function.Consumer;

/**
 * 方法引用的格式
 * 符号表示 : ::
 *  * 符号说明 : 双冒号为方法引用运算符,而它所在的表达式被称为方法引用。
 * 应用场景 : 如果Lambda所要实现的方案 , 已经有其他方法存在相同方案,那么则可以使用方法引用
 */
public class Demo01MethodRefIntro {
    public static void printMax(Consumer<int[]> consumer) {
        int[] arr = {11, 22, 33, 44, 55};
        consumer.accept(arr);
    }

    //求一个数组的和
    public static void getSum(int[] arr){
        int sum = 0;
        for (int i : arr){
            sum += i;
        }

        System.out.println("和:"+sum);
    }

    // 使用方法引用
    // 让这个指定的方法去重写接口的抽象方法,到时候调用接口的抽象方法就是调用传递过去的这个方法
    public static void main(String[] args) {
        // 使用Lambda表达式求一个数组的和
        /*printMax((int[] arr) -> {
            getMax(arr);
        });*/

        printMax(Demo01MethodRefIntro::getSum);
    }
}

方法引用的注意事项:

  • 被引用的方法,参数要和接口中抽象方法的参数一样。

  • 当接口抽象方法有返回值时,被引用的方法也必须有返回值。

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

对象名::引用成员方法

/**
     *对象名::引用成员方法
     *
     *注意:方法引用有两个注意事项
     * 1.被引用的方法,参数要和接口中抽象方法的参数一样
     * 2.当接口抽象方法有返回值时,被引用的方法也必须有返回值
     */
    @Test
    public void test1(){
        Date now = new Date();
        Supplier<Long> supplier = ()->{
            return now.getTime();
        };

        Supplier<Long> sp = now::getTime;

        Long aLong = sp.get();
        System.out.println("时间:"+aLong);
    }

类名::静态方法

/**
     *类名::静态方法
     */
    @Test
    public void test2(){
        Supplier<Long> supplier = ()->{
            return System.currentTimeMillis();
        };

        Supplier<Long> sup = System::currentTimeMillis;

        Long aLong = supplier.get();
        Long aLong1 = sup.get();

        System.out.println(aLong + "---"+aLong1);
    }

类名::实例方法

/**
     *类名::实例方法
     */
    @Test
    public void test3(){
        /*Function f1 = (String str) -> {
            return str.length();
        };*/

        // 类名::实例方法(注意:类名::类名::实例方法实际上会将第一个参数作为方法的调用者)
        Function<String, Integer> f1 = String::length;

        int length = f1.apply("hello");
        System.out.println("length = " + length);

        // BiFunction f2 = String::substring;
        // 相当于这样的Lambda
        BiFunction<String, Integer, String> f2 = (String str, Integer index) -> {
            return str.substring(index);
        };
        String str2 = f2.apply("helloworld", 3);
        System.out.println("str2 = " + str2); // loworld
    }

类名::new引用类的构造器

// 类名::new引用类的构造器
    @Test
    public void test04() {
        /*Supplier su1 = () -> {
            return new Person();
        };*/

        Supplier<Person> su1 = Person::new;

        Person person = su1.get();
        System.out.println("person = " + person);

        /*BiFunction bif = (String name, Integer age) -> {
            return new Person(name, age);
        };*/
        BiFunction<String, Integer, Person> bif = Person::new;
        Person p2 = bif.apply("凤姐", 18);
        System.out.println("p2 = " + p2);
    }

类型[]::new

    // 类型[]::new
    @Test
    public void test05() {
        /*Function f1 = (Integer length) -> {
            return new int[length];
        };*/

        Function<Integer, int[]> f1 = int[]::new;

        int[] arr1 = f1.apply(10);
        System.out.println(Arrays.toString(arr1));
    }

五、Stream流

注意:Stream和IO流(InputStream/OutputStream)没有任何关系,请暂时忘记对传统IO流的固有印象!
Stream是流式思想,相当于工厂的流水线,对集合中的数据进行加工处理

体验集合操作数据的弊端

package com.bz.jdk8.demo05stream;

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

/*
目标:体验集合操作数据的弊端

小结:
    1.集合操作数据的弊端?
        每个需求都要循环一次,还要搞一个新集合来装数据, 麻烦
 */
public class Demo01Intro {
    public static void main(String[] args) {
        // 一个ArrayList集合中存储有以下数据:张无忌,周芷若,赵敏,张强,张三丰
        // 需求:1.拿到所有姓张的 2.拿到名字长度为3个字的 3.打印这些数据
        List<String> arr = new ArrayList<>();
        Collections.addAll(arr,"张无忌","周芷若","张强","张三丰");

        List<String> list = new ArrayList<>();

//        1.拿到所有姓张的
        for (String str : arr){
            if (str.contains("张")){
                list.add(str);
            }
        }

        //拿到名字长度为3个字的
        List<String> three = new ArrayList<>();
        for (String str : arr){
            if (str.length() == 3){
                three.add(str);
            }
        }

        System.out.println(Arrays.toString(list.toArray()));
        System.out.println(Arrays.toString(three.toArray()));

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

        //使用流
        arr.stream().filter(str->{
            return str.startsWith("张");
        }).filter(str->{
            return str.length() == 3;
        }).forEach(s -> System.out.println(s));
    }
}

获取流

Collection 集合通过 stream 默认方法获取流

package com.bz.jdk8.demo05stream;

import java.util.*;
import java.util.stream.Stream;

/**
 * java.util.stream.Stream 是JDK 8新加入的流接口。
 *  获取一个流非常简单,有以下几种常用的方式:
 *   所有的 Collection 集合都可以通过 stream 默认方法获取流;
 *   Stream 接口的静态方法 of 可以获取数组对应的流。
 */
public class Demo02GetStream {
    public static void main(String[] args) {
        // 方式1 : 根据Collection获取流
        // Collection接口中有一个默认的方法: default Stream stream()
        List<String> list = new ArrayList<>();
        Stream<String> stream1 = list.stream();

        Set<String> set = new HashSet<>();
        Stream<String> stream2 = set.stream();

        Map<String, String> map = new HashMap<>();
        Stream<String> stream3 = map.keySet().stream();
        Stream<String> stream4 = map.values().stream();
        Stream<Map.Entry<String, String>> stream5 = map.entrySet().stream();

    }
}

Stream中的静态方法of获取流

// 方式2 : Stream中的静态方法of获取流
      // static Stream of(T... values)
      Stream<String> stream6 = Stream.of("aa", "bb", "cc");

      String[] strs = {"aa", "bb", "cc"};
      Stream<String> stream7 = Stream.of(strs);

      // 基本数据类型的数组行不行?不行的,会将整个数组看做一个元素进行操作.
      int[] arr = {11, 22, 33};
      Stream<int[]> stream8 = Stream.of(arr);

stream方法分为:终结方法和函数拼接方法

package com.bz.jdk8.demo05stream;

import java.util.stream.Stream;

/**
 * 我们学习了Stream的常用方法,我们知道Stream这些常用方法可以分成两类,
 *      终结方法,
 *      函数拼接方法
 *
 * Stream的3个注意事项:
 *  1. Stream只能操作一次
 *  2. Stream方法返回的是新的流
 *  3. Stream不调用终结方法,中间的操作不会执行
 */
public class Demo03StreamNotice {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("aa", "bb", "cc");

        //stream只能操作一次
//        long count = stream.count();
//        long count1 = stream.count();// stream has already been operated upon or closed

//        Stream limit = stream.limit(1);
//
//        System.out.println("流是否相等:"+(stream == limit));//流是否相等:false


        //3. Stream不调用终结方法,中间的操作不会执行
        stream.filter(s->{
            System.out.println(s);
            return true;
        }).count();
    }
}

常用方法

forEach 用来遍历流中的数据

/**
     * forEach 用来遍历流中的数据
     * 该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理。
     */
    @Test
    public void testForEach() {
        List<String> one = new ArrayList<>();
        Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");


        //lambda表达式遍历
//        one.stream().forEach(str-> System.out.println(str));

        //使用方法引用
        one.stream().forEach(System.out::println);
    }

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

    /**
     * Stream流提供 count 方法来统计其中的元素个数
     * 该方法返回一个long值代表元素个数。
     */
    @Test
    public void testCount() {
        List<String> one = new ArrayList<>();
        Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");

        long count = one.stream().count();
        System.out.println("总数:"+count);
    }

filter用于过滤数据,返回符合过滤条件的数据

    /**
     * filter用于过滤数据,返回符合过滤条件的数据
     * 可以通过 filter 方法将一个流转换成另一个子集流。
     *
     * 该接口接收一个 Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。
     */
    @Test
    public void testFilter() {
        List<String> arr = new ArrayList<>();
        Collections.addAll(arr, "宋远桥", "苏星河", "老子", "庄子", "孙子");

        arr.stream().filter(str->str.length() == 3).forEach(System.out::println);//宋远桥,苏星河

    }

limit 方法可以对流进行截取,只取用前n个。

    /**
     *limit 方法可以对流进行截取,只取用前n个。
     * 参数是一个long型,如果集合当前长度大于参数则进行截取。否则不进行操作。
     */
    @Test
    public void testLimit() {
        List<String> one = new ArrayList<>();
        Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");

        one.stream().limit(3).forEach(System.out::println);
    }

skip 方法跳过前几个元素,获取一个截取之后的新流

    /**
     * 如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流:
     *
     * 如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。
     */
    @Test
    public void testSkip() {
        List<String> one = new ArrayList<>();
        Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");

        one.stream().skip(2).forEach(System.out::println);
    }

map 方法将流中的元素映射到另一个流中

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

    /**
     * 如果需要将流中的元素映射到另一个流中,可以使用 map 方法。
     * 该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
     */
    @Test
    public void testMap() {
        Stream<String> original = Stream.of("11", "22", "33");

//        original.map(str->Integer.parseInt(str)).forEach(System.out::println);

        // 将Stream流中的字符串转成Integer
        //方法引用简化
        original.map(Integer::parseInt).forEach(System.out::println);
    }

sorted 方法将数据排序

    /**
     *如果需要将数据排序,可以使用 sorted 方法。
     *sorted 方法根据元素的自然顺序排序,也可以指定比较器排序
     */
    @Test
    public void testSorted() {
        // sorted(): 根据元素的自然顺序排序
        // sorted(Comparator comparator): 根据比较器指定的规则排序
        Stream<Integer> stream = Stream.of(33, 22, 11, 55);

//        stream.sorted().forEach(System.out::println);

        //自定义排序规则
        stream.sorted((o1, o2) -> o2-o1).forEach(System.out::println);

    }

distinct 方法去除重复数据

    /**
     *如果需要去除重复数据,可以使用 distinct 方法。
     *
     * 自定义类型是根据对象的hashCode和equals来去除重复元素的。
     */
    @Test
    public void testDistinct() {
        Stream<Integer> stream = Stream.of(22, 33, 22, 11, 33);

        stream.distinct().forEach(System.out::println);

        Stream<String> streamStr = Stream.of("aa","bb","cc","bb");

        streamStr.distinct().forEach(System.out::println);

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

        //自定义类型是根据对象的hashCode和equals来去除重复元素的。
        Stream<Person> streamPer = Stream.of(
                new Person("貂蝉", 18),
                new Person("杨玉环", 20),
                new Person("杨玉环", 20),
                new Person("西施", 16),
                new Person("西施", 16),
                new Person("王昭君", 25)
        );

        streamPer.distinct().forEach(System.out::println);
    }

Match 相关方法,判断数据是否匹配指定的条件

/**
     *如果需要判断数据是否匹配指定的条件,可以使用 Match 相关方法。
     * allMatch: 元素是否全部满足条件
     * anyMatch: 元素是否任意有一个满足条件
     * noneMatch: 元素是否全部不满足条件
     */
    @Test
    public void testMatch() {
        Stream<Integer> stream = Stream.of(5, 3, 6, 1);

        //allMatch: 匹配所有元素,所有元素都需要满足条件
//        boolean all = stream.allMatch(i -> i > 0);
//        System.out.println("是否都大于0:"+all);

        //anyMatch: 匹配某个元素,只要有其中一个元素满足条件即可
//        boolean any = stream.anyMatch(i -> i > 5);
//        System.out.println("是否存在大于5的元素:"+any);

        //noneMatch: 匹配所有元素,所有元素都不满足条件
        boolean none = stream.noneMatch(i -> i < 0);
        System.out.println("是否都不满足:"+none);

    }

find 方法,要找到某些数据

    /**
     *如果需要找到某些数据,可以使用 find 相关方法。
     */
    @Test
    public void testFind() {
        Stream<Integer> stream = Stream.of(33, 11, 22, 5);
        //获取第一个元素
//        Optional first = stream.findFirst();

        Optional<Integer> any = stream.findAny();
        System.out.println(any.get());
    }

max 和 min 方法,获取最大和最小值

    /**
     * 如果需要获取最大和最小值,可以使用 max 和 min 方法。
     */
    @Test
    public void testMax_Min() {
        Integer max = Stream.of(7, 5, 3, 8, 2, 6).max((o1, o2) -> o1 - o2).get();
        System.out.println("最大值:"+max);//最大值:8

        Integer min = Stream.of(7, 5, 3, 8, 2, 6).min((o1, o2) -> o1 - o2).get();
        System.out.println("最小值:"+min);//最小值:2

    }

reduce 方法将所有数据归纳得到一个数据

/**
     * 如果需要将所有数据归纳得到一个数据,可以使用 reduce 方法。
     */
    @Test
    public void testReduce() {
        // T reduce(T identity, BinaryOperator accumulator);
        // T identity: 默认值
        // BinaryOperator accumulator: 对数据进行处理的方式
        // reduce如何执行?
        // 第一次, 将默认值赋值给x, 取出集合第一元素赋值给y
        // 第二次, 将上一次返回的结果赋值x, 取出集合第二元素赋值给y
        // 第三次, 将上一次返回的结果赋值x, 取出集合第三元素赋值给y
        // 第四次, 将上一次返回的结果赋值x, 取出集合第四元素赋值给y

        Integer reduce = Stream.of(4, 5, 3, 9).reduce(0, (x, y) -> {
            System.out.println("x:" + x + ", y: " + y);
            return x + y;
        });

        System.out.println("reduce:"+reduce);

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

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

        System.out.println("最大值:"+max);

    }

Stream流的map和reduce组合使用

//Stream流的map和reduce组合使用
    @Test
    public void testMapReduce() {
        // 求出所有年龄的总和
        // 1.得到所有的年龄
        // 2.让年龄相加
        Integer totalAge = Stream.of(
                        new Person("刘德华", 58),
                        new Person("张学友", 56),
                        new Person("郭富城", 54),
                        new Person("黎明", 52))
                .map(Person::getAge).reduce(0, Integer::sum);

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


        // 找出最大年龄
        // 1.得到所有的年龄
        // 2.获取最大的年龄
        Integer maxAge = Stream.of(
                        new Person("刘德华", 58),
                        new Person("张学友", 56),
                        new Person("郭富城", 54),
                        new Person("黎明", 52))
                .map(Person::getAge)
                .reduce(0, Math::max);
        System.out.println("maxAge = " + maxAge);

        // 统计 a 出现的次数
        //                          1    0     0    1    0    1
        Integer count = Stream.of("a", "c", "b", "a", "b", "a")
                .map(s -> {
                    if (s == "a") {
                        return 1;
                    } else {
                        return 0;
                    }
                })
                .reduce(0, Integer::sum);
        System.out.println("count = " + count);
    }

mapToInt 方法,将Stream中的Integer类型数据转成int类型

 /**
     * 如果需要将Stream中的Integer类型数据转成int类型,可以使用 mapToInt 方法。
     */
    @Test
    public void testNumericStream() {
        // Integer占用的内存比int多,在Stream流操作中会自动装箱和拆箱
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
        // 把大于3的打印出来
        stream.filter(i -> i > 3).forEach(System.out::println);

        System.out.println("=========先将流中的Integer数据转成int,后续都是操作int类型=======");

        IntStream intStream = Stream.of(1, 2, 3, 4, 5).mapToInt(Integer::intValue);
        intStream.filter(i -> i > 3).forEach(System.out::println);

    }

concat方法将两个流合并成为一个流

/**
     * 如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat :
     *
     * 备注:这是一个静态方法,与 java.lang.String 当中的 concat 方法是不同的
     */
    @Test
    public void testContact() {
        Stream<String> streamA = Stream.of("张三");
        Stream<String> streamB = Stream.of("李四");

        // 合并成一个流
        Stream<String> newStream = Stream.concat(streamA, streamB);
        // 注意:合并流之后,不能操作之前的流啦.
        // streamA.forEach(System.out::println);

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

综合案例

package com.bz.jdk8.demo05stream;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;

/**
 * Stream综合案例
 * 现在有两个 ArrayList 集合存储队伍当若干集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依
 * 次进行以下操作步骤:
 *  1. 第一个队伍只要名字为3个字的成员
 *  2. 第一个队伍筛选之后只要前3个人
 *  3. 第二个队伍只要姓张的成员姓名;
 *  4. 第二个队伍筛选之后不要前2个人;
 *  5. 将两个队伍合并为一个队伍;
 *  6. 根据姓名创建 Person 对象;
 *  7. 打印整个队伍的Person对象信息。
 */
public class Demo05 {
    public static void main(String[] args) {
        // 第一个队伍
        List<String> one = new ArrayList<>();
        Collections.addAll(one,"迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子", "洪七公");
        // 第二个队伍
        List<String> two = new ArrayList<>();
        Collections.addAll(two,"古力娜扎", "张无忌", "张三丰", "赵丽颖", "张二狗", "张天爱", "张三");

        //1. 第一个队伍只要名字为3个字的成员姓名;
        //2. 第一个队伍筛选之后只要前3个人;
        Stream<String> streamA = one.stream().filter(str -> str.length() == 3).limit(3);
        //3. 第二个队伍只要姓张的成员姓名;
        //4. 第二个队伍筛选之后不要前2个人;
        Stream<String> streamB = two.stream().filter(str -> str.startsWith("张")).skip(2);
        //5. 将两个队伍合并为一个队伍;
        Stream<String> stream = Stream.concat(streamA,streamB);
        //6. 根据姓名创建 Person 对象;
        //7. 打印整个队伍的Person对象信息。
        stream.map(Person2::new).forEach(System.out::println);

    }
}

收集Stream流中的结果

Stream流中的结果到集合中

/**
     *  将流中数据收集到集合中
     * Stream流提供 collect 方法,其参数需要一个 java.util.stream.Collector 接口对象来指定收集到哪
     * 种集合中。java.util.stream.Collectors 类提供一些方法,可以作为 Collector`接口的实例:
     */
    @Test
    public void testStreamToCollection() {
        Stream<String> stream = Stream.of("aa", "bb", "cc", "bb");

        //list集合
//        List list = stream.collect(Collectors.toList());
//        System.out.println("list= "+list);

        //set集合
//        Set set = stream.collect(Collectors.toSet());
//        System.out.println("set="+set);

        //收集到指定的集合中ArrayList
//        ArrayList arrayList = stream.collect(Collectors.toCollection(ArrayList::new));
//        System.out.println("收集到指定的集合中ArrayList="+arrayList);

        //HashSet
        HashSet<String> hashSet = stream.collect(Collectors.toCollection(HashSet::new));
        System.out.println("hashSet="+hashSet);
    }

Stream流中的结果到数组中

    /**
     * Stream流中的结果到数组中
     * Stream提供 toArray 方法来将结果放到一个数组中,返回值类型是Object[]的:
     */
    @Test
    public void testStreamToArray() {
        Stream<String> stream = Stream.of("aa", "bb", "cc");

        // 转成Object数组不方便
        // Object[] objects = stream.toArray();
        // for (Object o : objects) {
        //     System.out.println("o = " + o);
        // }
        // String[]
        String[] strings = stream.toArray(String[]::new);
        for (String string : strings) {
            System.out.println("string = " + string + ", 长度: " + string.length());
        }
    }

对流中数据进行聚合计算

    // 其他收集流中数据的方式(相当于数据库中的聚合函数)
    @Test
    public void testStreamToOther() {
        Stream<Student> studentStream = Stream.of(
                new Student("赵丽颖", 58, 95),
                new Student("杨颖", 56, 88),
                new Student("迪丽热巴", 56, 99),
                new Student("柳岩", 52, 77));

//        Optional max = studentStream.collect(Collectors.maxBy((s1, s2) -> s1.getSocre() - s2.getSocre()));
//        System.out.println("最大值: " + max.get());

//        Optional min = studentStream.collect(Collectors.minBy((o1, o2) -> o1.getSocre() - o2.getSocre()));
//        System.out.println("最小值:"+min);

//        Integer count = studentStream.collect(Collectors.summingInt(Student::getAge));
//        System.out.println("年龄总和:"+count);

        // 平均值
        // Double avg = studentStream.collect(Collectors.averagingInt(s -> s.getSocre()));
        // Double avg = studentStream.collect(Collectors.averagingInt(Student::getSocre));
        // System.out.println("平均值: " + avg);

        // 统计数量
        Long count = studentStream.collect(Collectors.counting());
        System.out.println("统计数量: " + count);

    }

对流中数据进行分组

/**
     * 对流中数据进行分组
     * 当我们使用Stream流处理数据后,可以根据某个属性将数据分组:
      */
    @Test
    public void testGroup() {
        Stream<Student> studentStream = Stream.of(
                new Student("赵丽颖", 52, 95),
                new Student("杨颖", 56, 88),
                new Student("迪丽热巴", 56, 55),
                new Student("柳岩", 52, 33));

        //根据年龄分组
//        Map> ageGroup = studentStream.collect(Collectors.groupingBy(Student::getAge));

        //根据分数分组
        Map<String, List<Student>> socreGroup = studentStream.collect(Collectors.groupingBy(s -> {
            if (s.getSocre() > 60) {
                return "及格";
            } else {
                return "不及格";
            }
        }));
        socreGroup.forEach((k,v)-> System.out.println(k + "::" +v));

    }

对流中数据进行多级分组

// 多级分组
    @Test
    public void testCustomGroup() {
        Stream<Student> studentStream = Stream.of(
                new Student("赵丽颖", 52, 95),
                new Student("杨颖", 56, 88),
                new Student("迪丽热巴", 56, 55),
                new Student("柳岩", 52, 33));

        // 先根据年龄分组,每组中在根据成绩分组
        Map<Integer, Map<String, List<Student>>> map = studentStream.collect(Collectors.groupingBy(Student::getAge, Collectors.groupingBy((s) -> {
            if (s.getSocre() > 60) {
                return "及格";
            } else {
                return "不及格";
            }
        })));

        // 遍历
        map.forEach((k, v) -> {
            System.out.println(k);
            // v还是一个map,再次遍历
            v.forEach((k2, v2) -> {
                System.out.println("\t" + k2 + " == " + v2);
            });
        });
    }

对流中数据进行分区

 // 分区
    @Test
    public void testPartition() {
        Stream<Student> studentStream = Stream.of(
                new Student("赵丽颖", 52, 95),
                new Student("杨颖", 56, 88),
                new Student("迪丽热巴", 56, 55),
                new Student("柳岩", 52, 33));

        Map<Boolean, List<Student>> map = studentStream.collect(Collectors.partitioningBy(s -> {
            return s.getSocre() > 60;
        }));

        map.forEach((k,v)-> System.out.println(k + "=="+v));
    }

对流中数据进行拼接

// 拼接
    @Test
    public void testJoining() {
        Stream<Student> studentStream = Stream.of(
                new Student("赵丽颖", 52, 95),
                new Student("杨颖", 56, 88),
                new Student("迪丽热巴", 56, 99),
                new Student("柳岩", 52, 77));

        //姓名使用_拼接
//        String name = studentStream.map(Student::getName).collect(Collectors.joining("_"));
//        System.out.println(name);   //赵丽颖_杨颖_迪丽热巴_柳岩

//        根据三个字符串拼接
        String name = studentStream.map(Student::getName).collect(Collectors.joining("_", "^_^", "V_V"));
        System.out.println(name);//^_^赵丽颖_杨颖_迪丽热巴_柳岩V_V
    }

并行的Stream流

串行的Stream流

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

获取并行Stream流的两种方式

  1. 直接获取并行的流
  2. 将串行流转成并行流
    @Test
    public void testgetParallelStream() {
        // 掌握获取并行Stream流的两种方式
        // 方式一:直接获取并行的Stream流
        List<String> list = new ArrayList<>();
        Stream<String> stream = list.parallelStream();

        // 方式二:将串行流转成并行流
        Stream<String> parallel = list.stream().parallel();

    }

并行和串行Stream流的效率对比

 private static final int times = 500000000;
    long start;
    @Before
    public void init() {
        start = System.currentTimeMillis();
    }

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

    // 并行的Stream : 消耗时间:137   ---155
    @Test
    public void testParallelStream() {
        LongStream.rangeClosed(0, times).parallel().reduce(0, Long::sum);
    }

    // 串行的Stream : 消耗时间:343   ---210
    @Test
    public void testStream() {
        // 得到5亿个数字,并求和
        LongStream.rangeClosed(0, times).reduce(0, Long::sum);
    }

    // 使用for循环 : 消耗时间:235   ---164
    @Test
    public void testFor() {
        int sum = 0;
        for (int i = 0; i < times; i++) {
            sum += i;
        }
    }

parallelStream线程安全问题

    // parallelStream线程安全问题
    @Test
    public void parallelStreamNotice() {
        ArrayList<Integer> list = new ArrayList<>();
        /*IntStream.rangeClosed(1, 1000)
                .parallel()
                .forEach(i -> {
                    list.add(i);
                });
        System.out.println("list = " + list.size());*/

        // 解决parallelStream线程安全问题方案一: 使用同步代码块
        /*Object obj = new Object();
        IntStream.rangeClosed(1, 1000)
                .parallel()
                .forEach(i -> {
                    synchronized (obj) {
                        list.add(i);
                    }
                });*/

        // 解决parallelStream线程安全问题方案二: 使用线程安全的集合
        // Vector v = new Vector();
        /*List synchronizedList = Collections.synchronizedList(list);
        IntStream.rangeClosed(1, 1000)
                .parallel()
                .forEach(i -> {
                    synchronizedList.add(i);
                });
        System.out.println("list = " + synchronizedList.size());*/

        // 解决parallelStream线程安全问题方案三: 调用Stream流的collect/toArray
        List<Integer> collect = IntStream.rangeClosed(1, 1000)
                .parallel()
                .boxed()
                .collect(Collectors.toList());
        System.out.println("collect.size = " + collect.size());
    }

Fork/Join框架

parallelStream使用的是Fork/Join框架。Fork/Join框架自JDK 7引入。Fork/Join框架可以将一个大任务拆分为很多小任务来异步执行。

Fork/Join框架主要包含三个模块:

  1. 线程池:ForkJoinPool
  2. 任务对象:ForkJoinTask
  3. 执行任务的线程:ForkJoinWorkerThread

Fork/Join原理-分治法

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

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

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

package com.bz.jdk8.demo05stream;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

/**
 * Fork/Join框架介绍
 * parallelStream使用的是Fork/Join框架。
 *
 * Fork/Join框架可以将一个大任务拆分为很多小任务来异步执行。
 * Fork/Join框架主要包含三个模块:
 *      1. 线程池:ForkJoinPool
 *      2. 任务对象:ForkJoinTask
 *      3. 执行任务的线程:ForkJoinWorkerThread
 */
public class Demo08ForkJoin {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        ForkJoinPool pool = new ForkJoinPool();
        SumRecursiveTask task = new SumRecursiveTask(1, 99999999999L);
        Long result = pool.invoke(task);
        System.out.println("result = " + result);
        long end = System.currentTimeMillis();
        System.out.println("消耗时间: " + (end - start));
    }
}

// 1.创建一个求和的任务
// RecursiveTask: 一个任务
class SumRecursiveTask extends RecursiveTask<Long> {
    // 是否要拆分的临界值
    private static final long THRESHOLD = 3000L;
    // 起始值
    private final long start;
    // 结束值
    private final long end;

    public 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;
            }
            return sum;
        } else {
            // 拆分
            long middle = (start + end) / 2;
            SumRecursiveTask left = new SumRecursiveTask(start, middle);
            left.fork();
            SumRecursiveTask right = new SumRecursiveTask(middle + 1, end);
            right.fork();

            return left.join() + right.join();
        }
    }
}

总结:

  1. parallelStream是线程不安全的。
  2. parallelStream适用的场景是CPU密集型的,只是做到别浪费CPU,假如本身电脑CPU的负载很大,那还到处用并行流,那并不能起到作用。
  3. I/O密集型 磁盘I/O、网络I/O都属于I/O操作,这部分操作是较少消耗CPU资源,一般并行流中不适用于I/O密集型的操作,就比如使用并流行进行大批量的消息推送,涉及到了大量I/O,使用并行流反而慢了很多。
  4. 在使用并行流的时候是无法保证元素的顺序的,也就是即使你用了同步集合也只能保证元素都正确但无法保证其中的顺序。

六、Optional类

Optional是一个没有子类的工具类,Optional是一个可以为null的容器对象。它的作用主要就是为了解决避免Null检查,防止NullPointerException。

package com.bz.jdk8.demo06optional;

import org.junit.Test;

import java.util.Optional;

/**
 * Optional是一个没有子类的工具类,Optional是一个可以为null的容器对象。
 * 它的作用主要就是为了解决避免Null检查,防止NullPointerException。
 */
public class Demo01 {
    public static void main(String[] args) {

    }

    @Test
    public void test05() {
        User u = new User("Hello", 18);
        // getUpperUserName1(u);
        Optional<User> op = Optional.of(u);
        System.out.println(getUpperUserName2(op));
    }

    // 定义一个方法将User中的用户名转成大写并返回
    // 使用Optional方式
    public String getUpperUserName2(Optional<User> op) {
        /*String upperName = op.map(u -> u.getUserName())
                .map(s -> s.toUpperCase())
                .orElse("null");*/
        String upperName = op.map(User::getUserName)
                .map(String::toUpperCase)
                .orElse("null");
        return upperName;
    }

    // 定义一个方法将User中的用户名转成大写并返回
    // 使用传统方式
    public String getUpperUserName1(User u) {
        if (u != null) {
            String userName = u.getUserName();
            if (userName != null) {
                return userName.toUpperCase();
            } else {
                return null;
            }
        } else {
            return null;
        }
    }


    // Optional类的基本使用
    @Test
    public void test02() {
        // 1.创建Optional对象
        // of:只能传入一个具体值,不能传入null
        // ofNullable: 既可以传入具体值,也可以传入null
        // empty: 存入的是null
        Optional<String> op1 = Optional.of("凤姐");

        // 2.isPresent: 判断Optional中是否有具体值, 有值返回true,没有值返回false
        // boolean present = op1.isPresent();
        // System.out.println("present = " + present);

        // 3.get: 获取Optional中的值,如果有值就返回值具体值,没有值就报错
        // System.out.println(op3.get());

        if (op1.isPresent()) {
            System.out.println(op1.get());
        } else {
            System.out.println("没有值");
        }
    }


    // 以前对null的处理方式
    @Test
    public void test01() {
        String userName = "凤姐";

        if (userName != null) {
            System.out.println("姓名为: " + userName);
        } else {
            System.out.println("姓名不存在");
        }
    }
}

七、新的日期和时间 API

旧版日期时间 API 存在的问题

  1. 设计很差: 在java.util和java.sql的包中都有日期类,java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期。此外用于格式化和解析的类在java.text包中定义。
  2. 非线程安全:java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
  3. 时区处理麻烦:日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
package com.bz.jdk8.demo07newdatetimeapi;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 旧版日期时间 API 存在的问题
 *  1. 设计很差: 在java.util和java.sql的包中都有日期类,java.util.Date同时包含日期和时间,而java.sql.Date仅包
 *      含日期。此外用于格式化和解析的类在java.text包中定义。
 *  2. 非线程安全:java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
 *  3. 时区处理麻烦:日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和
 *       java.util.TimeZone类,但他们同样存在上述所有的问题。
 */
public class Demo01 {
    public static void main(String[] args) {
        //设计不合理
        Date date = new Date(2022,9,23);
        System.out.println(date);//Mon Oct 23 00:00:00 GMT+08:00 3922

        // 2.时间格式化和解析是线程不安全的
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

        for (int i = 0;i < 50;i++){
            new Thread(()->{
                Date now = null;
                try {
                    now = sdf.parse("2022-12-10");
                } catch (ParseException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(now);
            }).start();
        }
    }
}

JDK 8的日期和时间类

    @Test
    public void testLocalDate() {
        //日期格式,年月日
        LocalDate localDate = LocalDate.of(2022, 12, 20);
        System.out.println(localDate);//2022-12-20

        //获取当前的年,月,日
        LocalDate now = LocalDate.now();
        System.out.println(now.getYear());
        System.out.println(now.getMonthValue());
        System.out.println(now.getDayOfMonth());

    }

    /**
     * LocalTime: 表示时间,有时分秒
     */
    @Test
    public void testLocalTime() {
        LocalTime localTime = LocalTime.of(15, 43, 56);
        System.out.println(localTime);//15:43:56

        LocalTime now = LocalTime.now();
        System.out.println("当前时间:"+now);//当前时间:15:46:47.207
        System.out.println("时:"+now.getHour()+"分:"+now.getMinute()+"秒:"+now.getSecond()+"纳秒:"+now.getNano());//时:15分:46秒:47纳秒:207000000

    }


    /**
     * 日期时间
     */
    @Test
    public void testLocalDateTime() {
        LocalDateTime localDateTime = LocalDateTime.of(2022, 12, 20, 15, 48, 30);
        System.out.println(localDateTime);//2022-12-20T15:48:30

        LocalDateTime now = LocalDateTime.now();
        System.out.println("当前日期:"+now);//2022-12-20T15:50:30.465
        System.out.println("当前年:"+now.getYear());//当前年:2022
        System.out.println("当前时:"+now.getHour());//当前时:15
    }

    /**
     * 修改时间
     */
    @Test
    public void testLocalDateTime2() {
        LocalDateTime now = LocalDateTime.now();

        //修改时间
        LocalDateTime withYear = now.withYear(9102);
        System.out.println("修改后的时间:"+withYear);//修改后的时间:9102-12-20T15:54:55.656
        System.out.println(now == withYear);//false

        //增加或减去指定的时间,plus:增加     minus:减少
        System.out.println("增加了2年后:"+now.plusYears(2));//增加了2年后:2024-12-20T15:54:55.656
        System.out.println("减少了5个月:"+now.minusMonths(5));//减少了5个月:2022-07-20T15:54:55.656
    }

    /**
     * 比较时间
     */
    @Test
    public void testEquals() {
        LocalDateTime dateTime = LocalDateTime.of(2022, 12, 20, 16, 00, 00);

        LocalDateTime now = LocalDateTime.now();

        System.out.println("当前时间是否在指定时间之前:"+now.isBefore(dateTime));//当前时间是否在指定时间之前:true
        System.out.println("当前时间是否在指定时间之后:"+now.isAfter(dateTime));//当前时间是否在指定时间之后:false
        System.out.println("当前时间是否等于指定时间:"+now.isEqual(dateTime));//当前时间是否等于指定时间:false
    }

JDK 8的时间格式化与解析

    // 日期格式化
    @Test
    public void test04() {
        LocalDateTime now = LocalDateTime.now();

        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分SS秒");
        String format = now.format(dtf);
        System.out.println("格式化后的日期:"+format);//格式化后的日期:2022年12月20日 16时04分12秒

        //解析
        for (int i = 0; i < 50; i++) {
            new Thread(()->{
                LocalDateTime parse = LocalDateTime.parse("2022年09月20 15时16分16秒", dtf);
                System.out.println("parse:"+parse);
            }).start();
        }
    }

这里解析错误:

Exception in thread “Thread-1” Exception in thread “Thread-13” Exception in thread “Thread-8” Exception in thread “Thread-6” Exception in thread “Thread-2” Exception in thread “Thread-15” Exception in thread “Thread-18” Exception in thread “Thread-11” Exception in thread “Thread-12” java.time.format.DateTimeParseException: Text ‘2022年09月20 15时16分16秒’ could not be parsed at index 10
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
at java.time.LocalDateTime.parse(LocalDateTime.java:492)

Instant 类,时间戳

// 时间戳
    @Test
    public void test07() {
        // Instant内部保存了秒和纳秒,一般不是给用户使用的,而是方便我们程序做一些统计的.
        Instant now = Instant.now();
        System.out.println("Instant:"+now);//Instant:2022-12-20T08:13:24.426Z

        Instant plus = now.plusSeconds(20);
        System.out.println("plus:"+plus);//plus:2022-12-20T08:13:44.426Z

        Instant minusSeconds = now.minusSeconds(20);
        System.out.println("minus:"+minusSeconds);//minus:2022-12-20T08:13:04.426Z

        //得到纳秒
        long epochSecond = now.getEpochSecond();
        System.out.println("epoch:"+epochSecond);//epoch:1671524004

    }

JDK 8的计算日期时间差类

// Duration/Period类: 计算日期时间差
    @Test
    public void test08() {
        LocalTime now = LocalTime.now();
        LocalTime localTime = LocalTime.of(16, 17, 20);

        Duration duration = Duration.between(now, localTime);
        System.out.println("相差的天数:"+duration.toDays());//相差的天数:0
        System.out.println("相差的小时数:"+duration.toHours());//相差的小时数:0
        System.out.println("相差的分钟数:"+duration.toMinutes());//相差的分钟数:-3
        System.out.println("相差的秒数:"+duration.toMillis());//相差的秒数:-218907

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

        // Period计算日期的距离
        LocalDate nowDate = LocalDate.now();
        LocalDate localDate = LocalDate.of(1998, 11, 14);

        // 让后面的时间减去前面的时间
        Period period = Period.between(localDate,nowDate);

        System.out.println("相差的年:" + period.getYears());//相差的年:24
        System.out.println("相差的月:" + period.getMonths());//相差的月:1
        System.out.println("相差的天:" + period.getDays());//相差的天:6
    }

JDK 8的时间校正器

    // TemporalAdjuster类:自定义调整时间
    @Test
    public void test09() {
        LocalDateTime now = LocalDateTime.now();

        // 将日期调整到“下一个月的第一天”操作。
        TemporalAdjuster firstDayOfNextMonth = temporal -> {
            // temporal要调整的时间
            LocalDateTime dateTime = (LocalDateTime)temporal;
            return dateTime.plusMonths(1).withDayOfMonth(1); // 下一个月的第一天
        };

        // JDK中自带了很多时间调整器
        // LocalDateTime newDateTime = now.with(firstDayOfNextMonth);
        LocalDateTime newDateTime = now.with(TemporalAdjusters.firstDayOfNextYear());
        System.out.println("newDateTime = " + newDateTime);//newDateTime = 2023-01-01T18:33:24.152
    }

JDK 8设置日期时间的时区

// 设置日期时间的时区
    @Test
    public void test10() {
        // 1.获取所有的时区ID
        // ZoneId.getAvailableZoneIds().forEach(System.out::println);

        // 不带时间,获取计算机的当前时间
        LocalDateTime now = LocalDateTime.now(); // 中国使用的东八区的时区.比标准时间早8个小时
        System.out.println("now = " + now);

        // 2.操作带时区的类
        // now(Clock.systemUTC()): 创建世界标准时间
        ZonedDateTime bz = ZonedDateTime.now(Clock.systemUTC());
        System.out.println("bz = " + bz);

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

        // 使用指定的时区创建日期时间
        ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("America/Vancouver"));
        System.out.println("now2 = " + now2); // 2019-10-19T01:53:41.225898600-07:00[America/Vancouver]

        // 修改时区
        // withZoneSameInstant: 即更改时区,也更改时间
        ZonedDateTime withZoneSameInstant = now2.withZoneSameInstant(ZoneId.of("Asia/Shanghai"));
        System.out.println("withZoneSameInstant = " + withZoneSameInstant); // 2019-10-19T16:53:41.225898600+08:00[Asia/Shanghai]

        // withZoneSameLocal: 只更改时区,不更改时间
        ZonedDateTime withZoneSameLocal = now2.withZoneSameLocal(ZoneId.of("Asia/Shanghai"));
        System.out.println("withZoneSameLocal = " + withZoneSameLocal); // 2019-10-19T01:54:52.058871300+08:00[Asia/Shanghai]
    }

八、JDK 8重复注解与类型注解

重复注解的使用

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

package com.bz.jdk8.demo08annotation;

import org.junit.Test;

import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * 配置重复注解
 * 自从Java 5中引入 注解 以来,注解开始变得非常流行,并在各个框架和项目中被广泛使用。不过注解有一个很大的限
 * 制是:在同一个地方不能多次使用同一个注解。JDK 8引入了重复注解的概念,允许在同一个地方多次使用同一个注
 * 解。在JDK 8中使用@Repeatable注解定义重复注解。
 */
@MyTest("ta")
@MyTest("tb")
@MyTest("tc")
public class Demo01 {
    @Test
    @MyTest("ma")
    @MyTest("mb")
    public void test()  {
    }

    public static void main(String[] args) throws NoSuchMethodException {
        // 4.解析重复注解
        // 获取类上的重复注解
        // getAnnotationsByType是新增的API用户获取重复的注解
        MyTest[] annotationsByType = Demo01.class.getAnnotationsByType(MyTest.class);
        for (MyTest myTest : annotationsByType) {
            System.out.println(myTest);
        }
        System.out.println("----------");
        // 获取方法上的重复注解
        MyTest[] tests = Demo01.class.getMethod("test").getAnnotationsByType(MyTest.class);
        for (MyTest test : tests) {
            System.out.println(test);
        }
    }
}

// 1.定义重复的注解容器注解
@Retention(RetentionPolicy.RUNTIME)
@interface MyTests { // 这是重复注解的容器
    MyTest[] value();
}

// 2.定义一个可以重复的注解
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyTests.class)
@interface MyTest {
    String value();
}

类型注解的使用

JDK 8为@Target元注解新增了两种类型: TYPE_PARAMETER , TYPE_USE 。
TYPE_PARAMETER :表示该注解能写在类型参数的声明语句中。
TYPE_USE :表示注解可以再任何用到类型的地方使用。

package com.bz.jdk8.demo08annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.ArrayList;

public class Demo02 <@TypeParam T> {
    private @NotNull int a = 10;

    public void test(@NotNull String str, @NotNull int a) {
        @NotNull double d = 10.1;
    }

    public <@TypeParam E extends Integer> void test01() {
    }
}

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

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

介绍!!


					要散布阳光到别人心里,先得自己心里有阳光。  --罗曼罗兰

你可能感兴趣的:(Java基础,java,开发语言,jdk,jdk8)