Jdk8新特性目录总结

---------------------------------------

Lambda体验
Lambda是一个匿名函数,可以理解为一段可以传递的代码。
Lambda表达式写法,代码如下:
借助Java 8的全新语法,上述 Runnable 接口的匿名内部类写法可以通过更简单的Lambda表达式达到相同的效果

      /**
     * 从匿名类到Lambda表达式的转变
     */
    @Test public void testLambdaHelloWorld() {
        // 匿名类01
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello");
            }
        }).start();

        // lambda01
        new Thread(() -> System.out.println("Hello")).start();
    }

 这段代码和刚才的执行效果是完全一样的,可以在JDK 8或更高的编译级别下通过。从代码的语义中可以看出:我们启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。我们只需要将要执行的代码放到一个Lambda表达式中,不需要定义类,不需要创建对象。
Lambda的优点:简化匿名内部类的使用,语法更加简单。 

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

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

无参数无返回值的Lambda

 
    @Test
    public void test(){
        goSwimming(new Swimmable(){
            @Override
            public void swimming() {
                System.out.println("我要游泳");
            }
        });
        goSwimming( () -> {
            System.out.println("我要游泳");
        });

    }

    // 练习无参数无返回值的Lambda
    public static void goSwimming(Swimmable s) {
        s.swimming();
    }
 
 
/**
 * @author WGR
 * @create 2020/3/23 -- 23:29
 */
public interface Swimmable {
    public abstract void swimming();
}
 

有参数有返回值的Lambda

 
    @Test
    public void test1(){
        goEatting( name ->{
            System.out.println("吃"+name);
            return 0 ;
        });
    }

    public static Integer goEatting(Eatable e){
        return e.eatting("饭");
    }
 
 
/**
 * @author WGR
 * @create 2020/3/23 -- 23:29
 */
public interface Swimmable {
    public abstract void swimming();
}
 

 

 
   @Test
    public void test3(){
        ArrayList<Person> persons = new ArrayList<>();
        persons.add(new Person("刘德华", 58, 174));
        persons.add(new Person("张学友", 58, 176));
        persons.add(new Person("刘德华", 54, 171));
        persons.add(new Person("黎明", 53, 178));

        Collections.sort(persons, (o1, o2) -> {
            return o2.getAge() - o1.getAge(); // 降序
        });

        persons.forEach((t) -> {
            System.out.println(t);
       
 

Lambda的实现原理
匿名内部类的class文件

 

 Lambda表达式的断点

 关于这个方法 lambda$test$1 的命名:以lambda开头,因为是在test()函数里使用了lambda表达式,所以带有$test表示,因为是第二个,所以$01
如何调用这个方法呢?其实Lambda在运行的时候会生成一个内部类,为了验证是否生成内部类,可以在运行时加
上 -Djdk.internal.lambda.dumpProxyClasses ,加上这个参数后,运行时会将生成的内部类class码输出到一个文
件中。使用java命令如下:

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

小结 :

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

Lambda省略格式

 
 /**
     * lambda语法: 能省则省
     */
    @SuppressWarnings("unused")
    @Test public void testLambdaSyntax() {
        // 语法
        BinaryOperator<Integer> bo = (Integer x, Integer y) -> {
            return x+y;
        };

        // 简化1: 由于类型推断(编译器javac根据上下文环境推断出类型), 可以省略参数的类型
        bo = (x,y) -> {
            return x+y;
        };

        // 简化2: 当Lambda体只有一条语句的时候可以省略return和大括号{}
        bo = (x,y) -> x + y;

        // 简化3: 当参数只有一个时, 可以省略小括号
        Consumer<String> fun = args -> System.out.println(args);

        // 简化4: 当参数个数为零时, 使用()即可
        Runnable r1 = () -> System.out.println("Hello Lambda");

        // 简化5: 方法引用(下个新特性)
        Consumer<String> fun02 = System.out::println;
    }
 

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

 

 
/**
 * @author WGR
 * @create 2020/3/24 -- 0:11
 */
public class LambdaCondition {
    public static void main(String[] args) {
        // 方法的参数或局部变量类型必须为接口才能使用Lambda
        test(() -> {
        });

        Flyable f = () -> {
            System.out.println("我会飞啦");
        };
    }

    public static void test(Flyable a) {
        new Person() {

        };
    }

}

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

JDK 8接口增强介绍

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

JDK 8对接口的增强,接口还可以有默认方法和静态方法
JDK 8的接口:

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

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

 
interface A {
    public abstract void test1();
    // 接口新增抽象方法,所有实现类都需要去重写这个方法,非常不利于接口的扩展
    public abstract void test2();
}
class B implements A {
    @Override
    public void test1() {
        System.out.println("BB test1");
    }
    // 接口新增抽象方法,所有实现类都需要去重写这个方法
    @Override
    public void test2() {
        System.out.println("BB test2");
    }
}
class C implements A {
    @Override
    public void test1() {
        System.out.println("CC test1");
    }
    // 接口新增抽象方法,所有实现类都需要去重写这个方法
    @Override
    public void test2() {
        System.out.println("CC test2");
    }
}
 

 以前假如一个接口有很多的抽象方法的时候,可以写一个装饰类

例如,JDK 8 时在Map接口中增加了 forEach 方法:

 

接口默认方法的使用
方式一:实现类直接调用接口默认方法
方式二:实现类重写接口默认方法

 
public class Demo02UseDefaultFunction {
    public static void main(String[] args) {
        BB bb = new BB();
        bb.test01();

        CC cc = new CC();
        cc.test01();
    }
}

interface AA {
    public default void test01() {
        System.out.println("我是接口AA默认方法");
    }
}
// 默认方法使用方式一: 实现类可以直接使用
class BB implements AA {
}

// 默认方法使用方式二: 实现类可以重写接口默认方法
class CC implements AA {
    @Override
    public void test01() {
        System.out.println("我是CC类重写的默认方法");
    }
}
 

接口静态方法
为了方便接口扩展,JDK 8为接口新增了静态方法。

 
public class Demo03UseStaticFunction {
    public static void main(String[] args) {
        BBB bbb = new BBB();
        // bbb.test01();
        // 使用接口名.静态方法名();
        AAA.test01();
    }
}

interface AAA {
    public static void test01() {
        System.out.println("我是接口静态方法");
    }
}

class BBB implements AAA {
    // @Override
    // public static void test01() {
    //     System.out.println("我是接口静态方法");
    // }
}
 

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

 如果方法要被继承或者重写就用默认方法,如果不需要被继承就使用静态方法

函数式接口介绍
它们主要在 java.util.function 包中。下面是最常用的几个接口。
1. Supplier接口

 
@FunctionalInterface
public interface Supplier<T> {

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

2. Consumer接口

 
@FunctionalInterface
public interface Consumer<T> {

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

}
 

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

}
 

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

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

 
/**
 * @author WGR
 * @create 2020/3/24 -- 21:46
 */
public class Demo02Supplier {
    // 使用Lambda表达式返回数组元素最大值
    public static void main(String[] args) {
        System.out.println("开始了");
        printMax(() -> {
            int[] arr = {11, 99, 88, 77, 22};
            Arrays.sort(arr); // 升序排序
            return arr[arr.length - 1];
        });
    }

    public static void printMax(Supplier<Integer> supplier) {
        System.out.println("aa");
        int max = supplier.get();
        System.out.println("max = " + max);
    }
}
 

 

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

 
public class Demo03Consumer {

    public static void main(String[] args) {
        printHello( str ->{
            //HELLO JDK8
            System.out.println(str.toUpperCase());
        });
    }

    public static void printHello(Consumer<String> consumer) {
        consumer.accept("hello jdk8");
    }
}
 
 
public class Demo04ConsumerAndThen {

    public static void main(String[] args) {
        printHello( str1 ->{
            System.out.println(str1.toUpperCase());
        },str2 ->{
            System.out.println(str2.toLowerCase());
        });
    }

    public static void printHello(Consumer<String> c1, Consumer<String> c2) {
        String str = "Hello Jdk8";
//        default Consumer<T> andThen(Consumer<? super T> after) {
//            Objects.requireNonNull(after);
//            return (T t) -> { accept(t); after.accept(t); };
//        }
        c2.andThen(c1).accept(str);
    }
}
 

 

 由于c2在前面,所以先操作c2再去操作c1,结果如图所示。

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

 
public class Demo05Function {

    public static void main(String[] args) {
        getNumber( str ->{
            return Integer.parseInt(str);
        });
    }

    public static void getNumber(Function<String, Integer> function) {
        String num = "20";
        Integer apply = function.apply(num);
        System.out.println(apply);
    }
}
 
 
public class Demo06FunctionAndThen {

    public static void main(String[] args) {
        getNumber(str -> {
            return Integer.parseInt(str);
        }, num -> {
            return num * 5;
        });
    }

    public static void getNumber(Function<String, Integer> f1, Function<Integer, Integer> f2) {
        String num = "20";
        Integer integer = f1.andThen(f2).apply(num);
        System.out.println(integer);   //100
    }
}
 

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

 
public class Demo07Predicate {
    public static void main(String[] args) {
        isLongName( str -> {
            return str.length() > 3;
        });
    }

    public static void isLongName(Predicate<String> predicate) {
        boolean test = predicate.test("新年快乐");
        System.out.println(test);
    }
}
 
 
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 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所要实现的方案 , 已经有其他方法存在相同方案,那么则可以使用方法引用。
方法引用在JDK 8中使用方式相当灵活,有以下几种形式:

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

对象名 ::实例方法
这是最常见的一种用法,与上例相同。如果一个类中已经存在了一个成员方法,则可以通过对象名引用成员方法,代码为:

 
// 对象::实例方法
    @Test
    public void test01() {
        Date d = new Date();
        Supplier<Long> getTime = d::getTime;
        Long time = getTime.get();
        System.out.println(time);
    }
 

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

类名 ::引用静态方法
由于在 java.lang.System 类中已经存在了静态方法 currentTimeMillis ,所以当我们需要通过Lambda来调用该
方法时,可以使用方法引用 , 写法是:

 
    // 类名::静态方法
    @Test
    public void test02() {
        //1585110320763
        Supplier<Long> timeMillis = System::currentTimeMillis;
        System.out.println(timeMillis.get());
    }
 

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

 
    // 类名::实例方法
    @Test
    public void test03() {
        //1
        Function<String, Integer> length = String::length;
        System.out.println(length.apply("hello"));   // 5

        Function<String, Integer> length1 = s1 -> {
            return s1.length();
        };

        //2
        BiFunction<String, Integer, String> substring = String::substring;
        String s = substring.apply("hello", 3);  //lo
        System.out.println(s);

        BiFunction<String, Integer, String> substr = (String s1, Integer i1) -> {
            return s1.substring(i1);
        };
        String hello = substr.apply("hello", 3);
        System.out.println(hello);
    }
 

类名 ::new引用构造器
由于构造器的名称与类名完全一样。所以构造器引用使用 类名称 ::new 的格式表示。首先是一个简单的 Person 类:

 
// 类名::new引用类的构造器
    @Test
    public void test04() {
        Supplier<Person> p1 = Person::new;
        Person person = p1.get();
        System.out.println(person);
        BiFunction<String, Integer, Person> p2 = Person::new;
        Person wgr = p2.apply("wgr", 27);
        System.out.println(wgr);
    }
 

数组 ::new 引用数组构造器
数组也是 Object 的子类对象,所以同样具有构造器,只是语法稍有不同。

 
 // 类型[]::new
    @Test
    public void test05() {
        Function<Integer, int[]> f1 = int[]::new;

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

 

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

                                                                 

 

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

小结

Stream是流式思想,相当于工厂的流水线,对集合中的数据进行加工处理

获取 Stream流的两种方式

方式1 : 根据Collection获取流

首先, java.util.Collection 接口中加入了default方法 stream 用来获取流,所以其所有实现类均可获取流。

public interface Collection {

default Stream<E> stream()

}
 
public class Demo04GetStream {
    public static void main(String[] args) {
// 集合获取流
// Collection接口中的方法: default Stream<E> stream() 获取流
        List<String> list = new ArrayList<>();
// ...
        Stream<String> stream1 = list.stream();
        Set<String> set = new HashSet<>();
// ...
        Stream<String> stream2 = set.stream();
        Vector<String> vector = new Vector<>();
// ...
        Stream<String> stream3 = vector.stream();
    }
}
 
 
public class Demo05GetStream {
    public static void main(String[] args) {
// Map获取流
        Map<String, String> map = new HashMap<>();
// ...
        Stream<String> keyStream = map.keySet().stream();
        Stream<String> valueStream = map.values().stream();
        Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
    }
}
 

方式2 : Stream中的静态方法of获取流

由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法 of ,使用很简单:

 
public class Demo06GetStream {
    public static void main(String[] args) {
// Stream中的静态方法: static Stream of(T... values)
        Stream<String> stream6 = Stream.of("aa", "bb", "cc");
        String[] arr = {"aa", "bb", "cc"};
        Stream<String> stream7 = Stream.of(arr);
        Integer[] arr2 = {11, 22, 33};
        Stream<Integer> stream8 = Stream.of(arr2);
// 注意:基本数据类型的数组不行
        int[] arr3 = {11, 22, 33};
        Stream<int[]> stream9 = Stream.of(arr3);
    }
}
 

Stream常用方法

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

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

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

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

Stream的3个注意事项:

1. Stream只能操作一次

2. Stream方法返回的是新的流

3. Stream不调用终结方法,中间的操作不会执行

Stream 流的forEach方法

 
/**
 * @author WGR
 * @create 2020/3/26 -- 21:52
 */
public class StreamTest {

    @Test
    public void test(){
        List<String> strings = Arrays.asList("迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
        strings.stream().forEach(System.out::print);  // 对象 + 实例方法
    }
}
 

Stream 流的count方法

Stream流提供 count 方法来统计其中的元素个数:

 
public class StreamTest {

    @Test
    public void test(){
        List<String> strings = Arrays.asList("迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
        long count = strings.stream().count();
        System.out.println(count);  //6
    }
}
 

Stream 流的filter方法

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

 可以通过 filter 方法将一个流转换成另一个子集流。方法声明:

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

该接口接收一个 Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。

Stream流中的 filter 方法基本使用的代码如:

 
public class StreamTest {

    @Test
    public void test(){
        List<String> strings = Arrays.asList("迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
        strings.stream().filter( s -> s.length() ==2 ).forEach(System.out::println);
    }
}
 

 在这里通过Lambda表达式来指定了筛选的条件:姓名长度为2个字。

Stream 流的limit方法

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

Stream<T> limit(long maxSize);

参数是一个long型,如果集合当前长度大于参数则进行截取。否则不进行操作。基本使用:

 
public class StreamTest {

    @Test
    public void test(){
        List<String> strings = Arrays.asList("迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
        strings.stream().limit(3).forEach(System.out::println);
    }
}
 

 Stream 流的skip方法

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

Stream<T> skip(long n);

如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。基本使用:

 
public class StreamTest {

    @Test
    public void test(){
        List<String> strings = Arrays.asList("迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
        strings.stream().skip(2).forEach(System.out::println);
    }
}
 

Stream 流的map方法

 如果需要将流中的元素映射到另一个流中,可以使用 map 方法。方法签名:

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

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

Stream流中的 map 方法基本使用的代码如:

 
 @Test
    public void test(){
        Stream<String> stringStream = Stream.of("11", "22", "33");
        stringStream.map(Integer::parseInt).forEach(System.out::println);

    }
 

这段代码中, map 方法的参数通过方法引用,将字符串类型转换成为了int类型(并自动装箱为 Integer 类对象)。

Stream 流的sorted方法

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

Stream<T> sorted();

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

Stream流中的 sorted 方法基本使用的代码如:

 
@Test
    public void test(){
        Stream<String> stringStream = Stream.of( "22", "33","11");
        stringStream.map(Integer::parseInt).sorted( (o1,o2) -> o1 -o2 ).forEach(System.out::println);

    }
 

 这段代码中, sorted 方法根据元素的自然顺序排序,也可以指定比较器排序。

Stream 流的distinct方法

 如果需要去除重复数据,可以使用 distinct 方法。方法签名:

Stream<T> distinct();

Stream流中的 distinct 方法基本使用的代码如:

 
@Test
    public void test(){
        Stream<String> stringStream = Stream.of( "11","22", "33","11");
        stringStream.map(Integer::parseInt).distinct().forEach(System.out::println);

    }
 

 
@Test
    public void test(){
        Stream.of(
                new Person("刘德华", 58),
                new Person("张学友", 56),
                new Person("张学友", 56),
                new Person("黎明", 52))
                .distinct()
                .forEach(System.out::println);
    }
 
 
/**
 * @author WGR
 * @create 2020/3/26 -- 22:07
 */
public class Person {
    private String name;
    private int age;

    public Person() {
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

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

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

 自定义类型是根据对象的hashCode和equals来去除重复元素的。

Stream 流的match方法

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

boolean allMatch(Predicate<? super T> predicate);
boolean anyMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);

Stream流中的 Match 相关方法基本使用的代码如:

 
 @Test
    public void test(){
        Stream<String> stringStream = Stream.of( "11","22", "33","11");
        boolean b = stringStream.map(Integer::parseInt).allMatch(i -> i > 10);
        System.out.println(b);   //true 全部大于十
        boolean b1 = stringStream.map(Integer::parseInt).anyMatch(i -> i < 10);
        System.out.println(b1);  // 没有比10小的   //false
        boolean b3 = stringStream.map(Integer::parseInt).noneMatch(i -> i < 20);
        System.out.println(b3);   // 没有比20小的    //false
    }
 

Stream 流的find方法

 如果需要找到某些数据,可以使用 find 相关方法。方法签名:

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

Stream流中的 find 相关方法基本使用的代码如:

 
 @Test
    public void testFind(){
        Optional<Integer> first = Stream.of(5, 3, 6, 1).findFirst();
        System.out.println("first = " + first.get());   //first = 5
        Optional<Integer> any = Stream.of(5, 3, 6, 1).findAny();
        System.out.println("any = " + any.get());      // any = 5
    }
 

Stream 流的max和min方法

 如果需要获取最大和最小值,可以使用 max 和 min 方法。方法签名:

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

Stream流中的 max 和 min 相关方法基本使用的代码如:

 
@Test
    public void testFind(){
        Optional<Integer> max = Stream.of(5, 3, 6, 1).max( (o1,o2) -> o1 - o2); // 取升序的最后一个
        System.out.println("max = " + max.get());   
        Optional<Integer> min = Stream.of(5, 3, 6, 1).min( (o1,o2) -> o1 - o2); // 取升序的第一个
        System.out.println("min = " + min.get());      
    }
 

 Stream 流的reduce方法

 如果需要将所有数据归纳得到一个数据,可以使用 reduce 方法。方法签名:

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

Stream流中的 reduce 相关方法基本使用的代码如:

@Test
    public void testFind(){
        Integer reduce = Stream.of(5, 3, 6, 1).reduce(0, Integer::sum);
        System.out.println(reduce);   //15
    }

 Stream 流的map和reduce组合使用

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

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

 
@Test
    public void testFind(){
        // 统计 数字2 出现的次数
        int count = Stream.of(1, 2, 2, 1, 3, 2)
                .map(i -> i == 2 ? 1 : 0)
                .reduce(0, Integer::sum);
        System.out.println("count = " + count);
    }
 

Stream 流的mapToInt

如果需要将Stream中的Integer类型数据转成int类型,可以使用 mapToInt 方法。方法签名:

IntStream mapToInt(ToIntFunction<? super T> mapper);
 
@Test
    public void testFind(){
        // Integer占用的内存比int多,在Stream流操作中会自动装箱和拆箱
        Stream<Integer> stream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5});
        IntStream intStream = stream.mapToInt(Integer::intValue);
        int reduce = intStream
                .filter(i -> i > 3)
                .reduce(0, Integer::sum);
        System.out.println(reduce);

    }
 

Stream 流的concat方法

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

static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)

备注:这是一个静态方法,与 java.lang.String 当中的 concat 方法是不同的。

该方法的基本使用代码如:

 
 @Test
    public void testFind(){
        Stream<String> streamA = Stream.of("张三");
        Stream<String> streamB = Stream.of("李四");
        Stream<String> result = Stream.concat(streamA, streamB);
        result.forEach(System.out::println);
    }
 

 

Stream流中的结果到集合中
Stream流提供 collect 方法,其参数需要一个 java.util.stream.Collector<T,A, R> 接口对象来指定收集到哪
种集合中。java.util.stream.Collectors 类提供一些方法,可以作为 Collector`接口的实例:

public static <T> Collector<T, ?, List<T>> toList() :转换为 List 集合。
public static <T> Collector<T, ?, Set<T>> toSet() :转换为 Set 集合。

下面是这两个方法的基本使用代码:

 
// 将流中数据收集到集合中
    @Test
    public void testStreamToCollection() {
        Stream<String> stream = Stream.of("aa", "bb", "cc");
        ArrayList<String> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));
        HashSet<String> hashSet = stream.collect(Collectors.toCollection(HashSet::new));
    }
 

Stream流中的结果到数组中
Stream提供 toArray 方法来将结果放到一个数组中,返回值类型是Object[]的:

 
@Test
    public void testStreamToArray() {
        Stream<String> stream = Stream.of("aa", "bb", "cc");
        String[] strings = stream.toArray(String[]::new);
        for (String str : strings) {
            System.out.println(str);
        }
    }
 

对流中数据进行聚合计算

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

 
 @Test
   public void test(){
       Stream<Student> studentStream = Stream.of(
               new Student("赵丽颖", 58, 95),
               new Student("杨颖", 56, 88),
               new Student("迪丽热巴", 56, 99),
               new Student("柳岩", 52, 77));
       //1. 最大值
        Optional<Student> collect = studentStream.collect(Collectors.maxBy(Comparator.comparing(Student::getSocre)));
        //studentStream.collect(Collectors.maxBy((o1,o2) ->( o1.getSocre() - o2.getSocre())));
        System.out.println(collect.get());  //Student{name='迪丽热巴', age=56, socre=99}

    }
 
 
  @Test
   public void test(){
       Stream<Student> studentStream = Stream.of(
               new Student("赵丽颖", 58, 95),
               new Student("杨颖", 56, 88),
               new Student("迪丽热巴", 56, 99),
               new Student("柳岩", 52, 77));
       //最小值
        Optional<Student> collect = studentStream.collect(Collectors.minBy(Comparator.comparing(Student::getSocre)));
        //studentStream.collect(Collectors.minBy((o1,o2) ->( o1.getSocre() - o2.getSocre())));
        System.out.println(collect.get());  //Student{name='柳岩', age=52, socre=77}

    }
 
 
 @Test
   public void test(){
       Stream<Student> studentStream = Stream.of(
               new Student("赵丽颖", 58, 95),
               new Student("杨颖", 56, 88),
               new Student("迪丽热巴", 56, 99),
               new Student("柳岩", 52, 77));
       // studentStream.map(Student::getSocre).reduce(Integer::sum);
        Integer collect = studentStream.collect(Collectors.summingInt(Student::getSocre));
        System.out.println(collect);
    }
 
 
 @Test
   public void test(){
       Stream<Student> studentStream = Stream.of(
               new Student("赵丽颖", 58, 95),
               new Student("杨颖", 56, 88),
               new Student("迪丽热巴", 56, 99),
               new Student("柳岩", 52, 77));
        Double collect = studentStream.collect(Collectors.averagingDouble(Student::getSocre));
        System.out.println(collect);
    }
 

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

 
  @Test
   public void test(){
       Stream<Student> studentStream = Stream.of(
               new Student("赵丽颖", 58, 95),
               new Student("杨颖", 56, 88),
               new Student("迪丽热巴", 56, 99),
               new Student("柳岩", 52, 77));
        Map<Integer, List<Student>> collect = studentStream.collect(Collectors.groupingBy(Student::getAge));
        collect.forEach( (k,v) -> {
            System.out.println(k + "::" + v);
        });
    }
 

 
@Test
   public void test(){
       Stream<Student> studentStream = Stream.of(
               new Student("赵丽颖", 58, 95),
               new Student("杨颖", 56, 58),
               new Student("迪丽热巴", 56, 99),
               new Student("柳岩", 52, 47));
        Map<String, List<Student>> collect = studentStream.collect(Collectors.groupingBy(s -> s.getSocre() > 60 ? "及格" : "不及格"));
        collect.forEach( (k,v) -> {
            System.out.println(k + "::" + v);
        });
    }
 

对流中数据进行多级分组

 
@Test
   public void test(){
        Stream<Student> studentStream = Stream.of(
                new Student("赵丽颖", 52, 95),
                new Student("杨颖", 56, 88),
                new Student("迪丽热巴", 56, 99),
                new Student("柳岩", 52, 77));
        Map<Integer, Map<String, List<Student>>> map =
                studentStream.collect(Collectors.groupingBy(Student::getAge, Collectors.groupingBy(s -> {
                    if (s.getSocre() >= 90) {
                        return "优秀";
                    } else if (s.getSocre() >= 80 && s.getSocre() < 90) {
                        return "良好";
                    } else if (s.getSocre() >= 80 && s.getSocre() < 80) {
                        return "及格";
                    } else {
                        return "不及格";
                    }
                })));

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

对流中数据进行分区
Collectors.partitioningBy 会根据值是否为true,把集合分割为两个列表,一个true列表,一个false列表。

 
 @Test
   public void test(){
        Stream<Student> studentStream = Stream.of(
                new Student("赵丽颖", 52, 95),
                new Student("杨颖", 56, 88),
                new Student("迪丽热巴", 56, 99),
                new Student("柳岩", 52, 77));
// partitioningBy会根据值是否为true,把集合分割为两个列表,一个true列表,一个false列表。
        Map<Boolean, List<Student>> map = studentStream.collect(Collectors.partitioningBy(s ->
                s.getSocre() > 90));
        map.forEach((k, v) -> {
            System.out.println(k + " == " + v);
        });
    }
 

对流中数据进行拼接
Collectors.joining 会根据指定的连接符,将所有元素连接成一个字符串。

 
 @Test
   public void test(){
        Stream<Student> studentStream = Stream.of(
                new Student("赵丽颖", 52, 95),
                new Student("杨颖", 56, 88),
                new Student("迪丽热巴", 56, 99),
                new Student("柳岩", 52, 77));
        String collect = studentStream
                .map(Student::getName)
                .collect(Collectors.joining(">_<", "^_^", "^v^"));
        System.out.println(collect);
    }
 

 
 /**
     * 测试flatMap
     */
    @Test public void testFlatMap() {
        List<String> strs = Arrays.asList("孙悟空", "猪八戒");
        String collect = strs.stream()
                .map(s -> {
                    char[] arr01 = s.toCharArray();
                    Character[] arr02 = new Character[arr01.length];
                    for (int i = 0; i < arr02.length; i++) {
                        arr02[i] = arr01[i];
                    }
                    return arr02;
                })
                .flatMap(Arrays::stream)   // 扁平化
                .map(String::valueOf)
                .collect(Collectors.joining(","));
        System.out.println(collect); // 孙,悟,空,猪,八,戒
    }
 
 
 /**
     * SQL与Stream
     * 
     */
    @Test public void testStreamCompSql() {
        // select name, max(age) from person where name in ('孙悟空','猪八戒') and age is not null group by name order by name
        LinkedHashMap<String, Optional<Integer>> map = 
            plist.stream()
                 .filter(p -> Arrays.asList("孙悟空","猪八戒").contains(p.getName())) // name in ('孙悟空','猪八戒')
                 .filter(p -> nonNull(p.getAge()))                                  // age is not null
                 .sorted(comparing(Person::getName,nullsLast(naturalOrder())))      // order by name, 注意空值问题
               //.collect(groupingBy(Person::getName)                               // Map<String, List<Person>>, 此处搜集到的还是人,但需要的是年龄,继续downstream搜集
                 .collect(groupingBy(Person::getName,LinkedHashMap::new,mapping(Person::getAge, maxBy(Integer::compare))))   // group by name
                 ;
        System.out.println(map);         // {孙悟空=Optional[18], 猪八戒=Optional[28]}
             
        // select * from person where age = (select max(age) from person) limit 1    
        Optional<Person> first = plist.stream().sorted((p1,p2) -> p2.getAge() - p1.getAge()).findFirst();    
        System.out.println(first.get()); // Person [name=沙悟净, age=48, salary=40000.0]
    }

 

对比一下串行流和并行流的效率:

 
/**
 * @author WGR
 * @create 2020/3/31 
 */
public class Demo07Parallel {

    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 : 消耗时间:431
    @Test
    public void testParallelStream() {
        LongStream.rangeClosed(0, times).parallel().reduce(0, Long::sum);
    }

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

我们可以看到parallelStream的效率是最高的。
Stream并行处理的过程会分而治之,也就是将一个大任务切分成多个小任务,这表示每个任务都是一个操作。

下面具体来做一下实验:

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

 并行流:

 
 @Test
    public void test0Parallel() {
        long count = Stream.of(4, 5, 3, 9, 1, 2, 6)
                .parallel() // 将流转成并发流,Stream处理的时候将才去
                .filter(s -> {
                    System.out.println(Thread.currentThread() + ", s = " + s);
                    return true;
                })
                .count();
        System.out.println("count = " + count);
    }
 

获取并行流有两种方式:
直接获取并行流: parallelStream()
将串行流转成并行流: parallel()

并行流的线程安全问题:

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

 
@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);
                    }
                });
        System.out.println("list = " + list.size());
}
 

 

 

 
 // 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<Integer> v = new Vector();
        List<Integer> synchronizedList = Collections.synchronizedList(list);
        IntStream.rangeClosed(1, 1000)
                .parallel()
                .forEach(i -> {
                    synchronizedList.add(i);
                });
        System.out.println("list = " + synchronizedList.size());
}
 

 
 // parallelStream线程安全问题
    @Test
    public void parallelStreamNotice() {

        List<Integer> collect = IntStream.rangeClosed(1, 1000)
                .parallel()
                .boxed()
                .collect(Collectors.toList());
        System.out.println("collect.size = " + collect.size());
    }
 

 

 

旧版日期时间 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 static void main(String[] args) {
        // 旧版日期时间 API 存在的问题
        // 1.设计部合理
        Date now = new Date(1985, 9, 23);
        System.out.println(now);

        // 2.时间格式化和解析是线程不安全的
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 0; i < 50; i++) {
            new Thread(() -> {
                try {
                    Date date = sdf.parse("2019-09-09");
                    System.out.println("date = " + date);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
 

 
/**
     * 线程安全的 DateTimeFormatter
     */
    @Test public void testDateFormatter() throws InterruptedException, ExecutionException {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd");

        // 定义线程池, 任务, 提交100个解析任务并输出任务的执行情况
        ExecutorService pool = Executors.newFixedThreadPool(10);
        Callable<LocalDate> task = () -> LocalDate.parse("20190305",dtf);
        List<Future<LocalDate>> results = new ArrayList<>();
        IntStream.rangeClosed(0, 100).forEach(i -> results.add(pool.submit(task)));
        for (Future<LocalDate> future : results) {
            println(future.get());
        }
    }
 

新日期时间 API介绍

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

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

Java中使用的历法是ISO 8601日历系统,它是世界民用历法,也就是我们所说的公历。平年有365天,闰年是366天。此外Java 8还提供了4套其他历法,分别是:

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

JDK 8 的日期和时间类

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

 
 @Test
    public void testLocalDate() {
        // LocalDate: 表示日期,有年月日
        LocalDate date = LocalDate.of(2020, 4, 1);
        System.out.println("date = " + date);

        LocalDate now = LocalDate.now();
        System.out.println("now = " + now);

        System.out.println(now.getYear());
        System.out.println(now.getMonthValue());
        System.out.println(now.getDayOfMonth());
    }
 
 
 @Test
    public void testLocalTime() {
        // LocalTime: 表示时间,有时分秒
        LocalTime time = LocalTime.of(22, 00, 39);
        System.out.println("time = " + time);

        LocalTime now = LocalTime.now();
        System.out.println("now = " + now);

        System.out.println(now.getHour());
        System.out.println(now.getMinute());
        System.out.println(now.getSecond());
        System.out.println(now.getNano());
    }
 

 
 @Test
    public void testLocalDateTime() {
        // LocalDateTime: LocalDate + LocalTime 有年月日 时分秒
        LocalDateTime dateTime = LocalDateTime.of(2020, 4, 1, 13, 28, 59);
        System.out.println("dateTime = " + dateTime);

        LocalDateTime now = LocalDateTime.now();
        System.out.println("now = " + now);

        System.out.println(now.getYear());
        System.out.println(now.getMonthValue());
        System.out.println(now.getHour());
        System.out.println(now.getSecond());
    }
 

对日期时间的修改,对已存在的LocalDate对象,创建它的修改版,最简单的方式是使用withAttribute方法。
withAttribute方法会创建对象的一个副本,并按照需要修改它的属性。以下所有的方法都返回了一个修改属性的对象,他们不会影响原来的对象。

 
 // LocalDateTime 类: 对日期时间的修改
    @Test
    public void test05() {
        LocalDateTime now = LocalDateTime.now();
        System.out.println("now = " + now);
        // 修改日期时间
        LocalDateTime setYear = now.withYear(2078);
        System.out.println("修改年份: " + setYear);
        System.out.println("now == setYear: " + (now == setYear));
        System.out.println("修改月份: " + now.withMonth(6));
        System.out.println("修改小时: " + now.withHour(9));
        System.out.println("修改分钟: " + now.withMinute(11));
        System.out.println( now.withYear(2021).withMonth(1).withHour(11).withMinute(11));
        // 再当前对象的基础上加上或减去指定的时间
        LocalDateTime localDateTime = now.plusDays(5);
        System.out.println("5天后: " + localDateTime);
        System.out.println("now == localDateTime: " + (now == localDateTime));
        System.out.println("10年后: " + now.plusYears(10));
        System.out.println("20月后: " + now.plusMonths(20));
        System.out.println("20年前: " + now.minusYears(20));
        System.out.println("5月前: " + now.minusMonths(5));
        System.out.println("100天前: " + now.minusDays(100));
    }
 
 
now = 2020-03-31T22:10:02.710
修改年份: 2078-03-31T22:10:02.710
now == setYear: false
修改月份: 2020-06-30T22:10:02.710
修改小时: 2020-03-31T09:10:02.710
修改分钟: 2020-03-31T22:11:02.710
2021-01-31T11:11:02.710
5天后: 2020-04-05T22:10:02.710
now == localDateTime: false
10年后: 2030-03-31T22:10:02.710
20月后: 2021-11-30T22:10:02.710
20年前: 2000-03-31T22:10:02.710
5月前: 2019-10-31T22:10:02.710
100天前: 2019-12-22T22:10:02.710

Process finished with exit code 0
 

日期时间的比较

 
// 日期时间的比较
    @Test
    public void test06() {
        // 在JDK8中,LocalDate类中使用isBefore()、isAfter()、equals()方法来比较两个日期,可直接进行比较。
        LocalDate now = LocalDate.now();
        LocalDate date = LocalDate.of(2020, 4, 1);
        System.out.println(now.isBefore(date)); //true
        System.out.println(now.isAfter(date)); // false
    }
 

JDK 8 的时间格式化与解析
通过 java.time.format.DateTimeFormatter 类可以进行日期时间解析与格式化。

 
@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);  //2020-03-31 22:15:50

        // 将字符串解析为日期时间
        LocalDateTime parse = LocalDateTime.parse("1985-09-23 10:12:22", dtf);
        System.out.println("parse = " + parse);

    }
 

JDK 8 的 Instant 类
Instant 时间戳/时间线,内部保存了从1970年1月1日 00:00:00以来的秒和纳秒。

 
 @Test
    public void test07() {
        Instant now = Instant.now();
        System.out.println("当前时间戳 = " + now);
        // 获取从1970年1月1日 00:00:00的秒
        System.out.println(now.getNano());
        System.out.println(now.getEpochSecond());
        System.out.println(now.toEpochMilli());
        System.out.println(System.currentTimeMillis());
        Instant instant = Instant.ofEpochSecond(5);
        System.out.println(instant);
    }
 

JDK 8 的计算日期时间差类
Duration/Period类: 计算日期时间差。
1. Duration:用于计算2个时间(LocalTime,时分秒)的距离
2. Period:用于计算2个日期(LocalDate,年月日)的距离

 
// Duration/Period 类: 计算日期时间差
    @Test
    public void test08() {
        // Duration计算时间的距离
        LocalTime now = LocalTime.now();
        LocalTime time = LocalTime.of(14, 15, 20);
        Duration duration = Duration.between(time, now);
        System.out.println("相差的天数:" + duration.toDays());
        System.out.println("相差的小时数:" + duration.toHours());
        System.out.println("相差的分钟数:" + duration.toMinutes());
        // Period计算日期的距离
        LocalDate nowDate = LocalDate.now();
        LocalDate date = LocalDate.of(1998, 8, 8);
       // 让后面的时间减去前面的时间
        Period period = Period.between(date, nowDate);
        System.out.println("相差的年:" + period.getYears());
        System.out.println("相差的月:" + period.getMonths());
        System.out.println("相差的天:" + period.getDays());
    }
 
 
相差的天数:0
相差的小时数:8
相差的分钟数:492
相差的年:21
相差的月:7
相差的天:23
 

JDK 8 的时间校正器
有时我们可能需要获取例如:将日期调整到“下一个月的第一天”等操作。可以通过时间校正器来进行。
TemporalAdjuster : 时间校正器。
TemporalAdjusters : 该类通过静态方法提供了大量的常用TemporalAdjuster的实现。

 
 /**
     * TemporalAdjuster
     */
    @Test public void testTemporalAdjuster() {
        LocalDate ld = LocalDate.now();
        println(ld.with(firstDayOfMonth()));      // 2020-03-01 月初
        println(ld.with(firstDayOfNextYear()));   // 2021-01-01 年初
        println(ld.with(lastDayOfMonth()));       // 2020-03-31 月末
        println(ld.with(lastDayOfYear()));        // 2020-12-31 年末
        println(ld.with(next(SUNDAY)));           // 2020-04-05 下周日
        println(ld.with(previousOrSame(MONDAY))); // 2020-03-30 前一个或相同的周一
    }

    private void println(Object obj) {
        System.out.println(obj);
    }
 

JDK 8 设置日期时间的时区

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

 
 // 设置日期时间的时区
    @Test
    public void test10() {
        // 1.获取所有的时区ID
        // ZoneId.getAvailableZoneIds().forEach(System.out::println);
        // 不带时间,获取计算机的当前时间
        LocalDateTime now = LocalDateTime.now(); // 中国使用的东八区的时区.比标准时间早8个小时
        System.out.println("now = " + now);    //now = 2020-03-31T22:43:42.289

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

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

        // 使用指定的时区创建日期时间
        ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("America/Vancouver"));
        System.out.println("now2 = " + now2); // now2 = 2020-03-31T07:43:42.293-07:00[America/Vancouver]

        // 修改时区
        // withZoneSameInstant: 即更改时区,也更改时间
        ZonedDateTime withZoneSameInstant = now2.withZoneSameInstant(ZoneId.of("Asia/Shanghai"));
        System.out.println("withZoneSameInstant = " + withZoneSameInstant); //withZoneSameInstant = 2020-03-31T22:43:42.293+08:00[Asia/Shanghai]

        // withZoneSameLocal: 只更改时区,不更改时间
        ZonedDateTime withZoneSameLocal = now2.withZoneSameLocal(ZoneId.of("Asia/Shanghai"));
        System.out.println("withZoneSameLocal = " + withZoneSameLocal); // withZoneSameLocal = 2020-03-31T07:43:42.293+08:00[Asia/Shanghai]

    }

 

重复注解的使用

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

 
/**
 * @author WGR
 * @create 2020/4/1 -- 19:02
 */
@MyTest("ta")
@MyTest("tb")
@MyTest("tc")
public class Test {
    @org.junit.Test
    @MyTest("ma")
    @MyTest("mb")
    public void test()  {
    }

    public static void main(String[] args) throws NoSuchMethodException {
        // 4.解析重复注解
        // 获取类上的重复注解
        // getAnnotationsByType是新增的API用户获取重复的注解
        MyTest[] annotationsByType = Test.class.getAnnotationsByType(MyTest.class);
        for (MyTest myTest : annotationsByType) {
            System.out.println(myTest);
        }
        MyTests[] annotationsByType1 = Test.class.getAnnotationsByType(MyTests.class);
        for ( MyTests myTests :  annotationsByType1){
            System.out.println(myTests);
        }

        System.out.println("----------");
        // 获取方法上的重复注解
        MyTest[] tests = Test.class.getMethod("test").getAnnotationsByType(MyTest.class);
        for (MyTest test : tests) {
            System.out.println(test);
        }

        MyTests[] test = Test.class.getMethod("test").getAnnotationsByType(MyTests.class);
        for (MyTests t : test) {
            System.out.println(t);
        }
    }
}


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

// 2.定义一个可以重复的注解
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyTests.class)
@interface MyTest {
    String value();
}
 
 
@com.topcheer.annotation.MyTest(value=ta)
@com.topcheer.annotation.MyTest(value=tb)
@com.topcheer.annotation.MyTest(value=tc)
@com.topcheer.annotation.MyTests(value=[@com.topcheer.annotation.MyTest(value=ta), @com.topcheer.annotation.MyTest(value=tb), @com.topcheer.annotation.MyTest(value=tc)])
----------
@com.topcheer.annotation.MyTest(value=ma)
@com.topcheer.annotation.MyTest(value=mb)
@com.topcheer.annotation.MyTests(value=[@com.topcheer.annotation.MyTest(value=ma), @com.topcheer.annotation.MyTest(value=mb)])
 

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

 
 @MyTest("src1")
    public int show01(@MyTest("src")String src) {
        @MyTest("123")int i = 10;
        System.out.println(i + ": " + src);
        return i;
    }

    /**
     * 类型参数读取
     */
    @org.junit.Test public void testTypeParameter() throws NoSuchMethodException, SecurityException {
        Class<?> clazz = this.getClass();
        Method method01 = clazz.getDeclaredMethod("show01", String.class);
        AnnotatedType[] types = method01.getAnnotatedParameterTypes();
        for (AnnotatedType at : types) {
            MyTest anno = at.getAnnotation(MyTest.class);
            System.out.println(anno);
            System.out.println(anno.value());
        }
        
    }
 

 

 

在Java 8中,Base64编码已经成为Java类库的标准。

Java 8 内置了 Base64 编码的编码器和解码器。

Base64工具类提供了一套静态方法获取下面三种BASE64编解码器:

  • 基本:输出被映射到一组字符A-Za-z0-9+/,编码不添加任何行标,输出的解码仅支持A-Za-z0-9+/。
  • URL:输出映射到一组字符A-Za-z0-9+_,输出是URL和文件。
  • MIME:输出隐射到MIME友好格式。输出每行不超过76字符,并且使用'\r'并跟随'\n'作为分割。编码输出最后没有行分割。

内嵌类

序号内嵌类 & 描述
1 static class Base64.Decoder

该类实现一个解码器用于,使用 Base64 编码来解码字节数据。

2 static class Base64.Encoder

该类实现一个编码器,使用 Base64 编码来编码字节数据。

方法

序号方法名 & 描述
1 static Base64.Decoder getDecoder()

返回一个 Base64.Decoder ,解码使用基本型 base64 编码方案。

2 static Base64.Encoder getEncoder()

返回一个 Base64.Encoder ,编码使用基本型 base64 编码方案。

3 static Base64.Decoder getMimeDecoder()

返回一个 Base64.Decoder ,解码使用 MIME 型 base64 编码方案。

4

static Base64.Encoder getMimeEncoder()

返回一个 Base64.Encoder ,编码使用 MIME 型 base64 编码方案。

5 static Base64.Encoder getMimeEncoder(int lineLength, byte[] lineSeparator)

返回一个 Base64.Encoder ,编码使用 MIME 型 base64 编码方案,可以通过参数指定每行的长度及行的分隔符。

6 static Base64.Decoder getUrlDecoder()

返回一个 Base64.Decoder ,解码使用 URL 和文件名安全型 base64 编码方案。

7 static Base64.Encoder getUrlEncoder()

返回一个 Base64.Encoder ,编码使用 URL 和文件名安全型 base64 编码方案。

注意:Base64 类的很多方法从 java.lang.Object 类继承。

 
import java.io.UnsupportedEncodingException;
import java.util.Base64;
import java.util.UUID;

/**
 * @author WGR
 * @create 2020/4/8 -- 20:53
 */
public class Java8Tester {
    public static void main(String args[]){
        try {
            // 使用基本编码
            String base64encodedString = Base64.getEncoder().encodeToString("runoob?java8".getBytes("utf-8"));
            System.out.println("Base64 编码字符串 (基本) :" + base64encodedString);

            // 解码
            byte[] base64decodedBytes = Base64.getDecoder().decode(base64encodedString);

            System.out.println("原始字符串: " + new String(base64decodedBytes, "utf-8"));
            base64encodedString = Base64.getUrlEncoder().encodeToString("runoob?java8".getBytes("utf-8"));
            System.out.println("Base64 编码字符串 (URL) :" + base64encodedString);

            StringBuilder stringBuilder = new StringBuilder();

            for (int i = 0; i < 10; ++i) {
                stringBuilder.append(UUID.randomUUID().toString());
            }

            byte[] mimeBytes = stringBuilder.toString().getBytes("utf-8");
            String mimeEncodedString = Base64.getMimeEncoder().encodeToString(mimeBytes);
            System.out.println("Base64 编码字符串 (MIME) :" + mimeEncodedString);

        }catch(UnsupportedEncodingException e){
            System.out.println("Error :" + e.getMessage());
        }
    }
}
 
 
Base64 编码字符串 (基本) :cnVub29iP2phdmE4
原始字符串: runoob?java8
Base64 编码字符串 (URL) :cnVub29iP2phdmE4
Base64 编码字符串 (MIME) :NWY0ZGRjNzQtYzhlOS00MTY4LWFkNWYtNTlkZGVhMTBhNDNjYmJmNDJhY2ItOTA1Ni00MDNlLThh
YTEtYTExMTFiOTU2Njg5OGQ3NWZmMDUtODBkMy00YjFhLTg3ODktN2I4N2FmMGVmZGRjNTZiZGFh
YjgtMzllMS00OGQ0LTkyMWItNTEwNTVhYzNjNGQwN2IwMTFjYzktOWM0Zi00YzVjLWE4OWMtOGJk
MDE3MGZlOWRkYTg2MDhmYTItOTU5Mi00Yjg4LWIxMDQtMzkxZDJmMGI3NjMwYmJkZWExYjgtOTJk
ZS00OGI2LWE0MmYtNDcwNWMxZDM3MDU4ZTcyNjFkMjgtN2MwOC00ZDI4LTk3MDAtOTcxZWJlMDUx
NmQ2ZjRkNzU0ZjEtMWVjMi00OGJmLTkxODYtN2ZiNzIwMjg4ZWU4MGVjZDAyNjctYzUxNi00YjNh
LTgzZjYtMTViMTI2YjgwMmEw

Process finished with exit code 0

 

 

Comparator提供的方法

 
/**
 * @author WGR
 * @create 2020/4/13 -- 16:12
 */
public class Person {

    private String name;
    private Integer age;
    private Double salary;


    public Person(String name, Integer age, Double salary) {
        super();
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public Double getSalary() {
        return salary;
    }
    public void setSalary(Double salary) {
        this.salary = salary;
    }

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


}
 

 排序实现

 
/**
 * @author WGR
 * @create 2020/4/13 -- 16:14
 */
public class _Comparator {
    List<Person> personList = new ArrayList<>();

    @Before
    public  void before() {
        personList.add(new Person("hepengju", 28, 20000.0));
        personList.add(new Person("lisi", 44, 40000.0));
        personList.add(new Person("wangwu", 55, 50000.0));
        personList.add(new Person("zhaoliu", 66, 60000.0));
        personList.add(new Person("zhangsan", 33, 33333.0));
        personList.add(new Person("zhangsan", 23, 30000.0));
    }

    //按照名字排序
    @Test
    public  void test() {
        personList.stream().sorted(Comparator.comparing(Person::getName)).forEach(System.out::println);
//        Person [name=hepengju, age=28, salary=20000.0]
//        Person [name=lisi, age=44, salary=40000.0]
//        Person [name=wangwu, age=55, salary=50000.0]
//        Person [name=zhangsan, age=33, salary=33333.0]
//        Person [name=zhangsan, age=23, salary=30000.0]
//        Person [name=zhaoliu, age=66, salary=60000.0]
    }

    //按照名字排序降序
    @Test
    public  void test4() {
        personList.stream().sorted(Comparator.comparing(Person::getName).reversed()).forEach(System.out::println);
//        Person [name=zhaoliu, age=66, salary=60000.0]
//        Person [name=zhangsan, age=33, salary=33333.0]
//        Person [name=zhangsan, age=23, salary=30000.0]
//        Person [name=wangwu, age=55, salary=50000.0]
//        Person [name=lisi, age=44, salary=40000.0]
//        Person [name=hepengju, age=28, salary=20000.0]
    }

    // 先按照名字排序, 名字一样再按照年龄排序, 年龄一样再按照薪资排序
    @Test
    public void test2(){
        personList.stream().sorted(Comparator.comparing(Person::getName).thenComparing(Person::getAge).thenComparing(Person::getSalary)).forEach(System.out::println);
//        Person [name=hepengju, age=28, salary=20000.0]
//        Person [name=lisi, age=44, salary=40000.0]
//        Person [name=wangwu, age=55, salary=50000.0]
//        Person [name=zhangsan, age=23, salary=30000.0]
//        Person [name=zhangsan, age=33, salary=33333.0]
//        Person [name=zhaoliu, age=66, salary=60000.0]
    }

    // 4. 处理所有空值问题(null都到最后)
    @Test
    public void test3(){
       // personList.add(null);
        personList.add(new Person(null, 33, 30000.0));
        personList.add(new Person("zhangsan", null, 30000.0));
        personList.add(new Person("zhangsan", 33, null));
        personList.add(new Person(null, null, null));
        personList.stream().sorted(Comparator.comparing(Person::getName,Comparator.nullsLast(Comparator.naturalOrder()))
                .thenComparing(Person::getName,Comparator.nullsLast(Comparator.naturalOrder()))
                .thenComparing(Person::getName,Comparator.nullsLast(Comparator.naturalOrder()))
                ).forEach(System.out::println);
//        Person [name=hepengju, age=28, salary=20000.0]
//        Person [name=lisi, age=44, salary=40000.0]
//        Person [name=wangwu, age=55, salary=50000.0]
//        Person [name=zhangsan, age=33, salary=33333.0]
//        Person [name=zhangsan, age=23, salary=30000.0]
//        Person [name=zhangsan, age=null, salary=30000.0]
//        Person [name=zhangsan, age=33, salary=null]
//        Person [name=zhaoliu, age=66, salary=60000.0]
//        Person [name=null, age=33, salary=30000.0]
//        Person [name=null, age=null, salary=null]

    }
   //jdk8 lambda排序
@Test
public void test5() {
personList.stream().sorted((p1,p2) -> p1.getName().compareTo(p2.getName()) ).forEach(System.out::println);
// Person [name=hepengju, age=28, salary=20000.0]
// Person [name=lisi, age=44, salary=40000.0]
// Person [name=wangwu, age=55, salary=50000.0]
// Person [name=zhangsan, age=33, salary=33333.0]
// Person [name=zhangsan, age=23, salary=30000.0]
// Person [name=zhaoliu, age=66, salary=60000.0]
}

@Test
public void test6() {
personList.stream().sorted((p1,p2) -> p1.getAge() - p2.getAge() ).forEach(System.out::println);
// Person [name=zhangsan, age=23, salary=30000.0]
// Person [name=hepengju, age=28, salary=20000.0]
// Person [name=zhangsan, age=33, salary=33333.0]
// Person [name=lisi, age=44, salary=40000.0]
// Person [name=wangwu, age=55, salary=50000.0]
// Person [name=zhaoliu, age=66, salary=60000.0]
}

}

 

posted @ 2022-02-16 09:52  hanease  阅读(54)  评论(0编辑  收藏  举报