JDK8 新特性入门

Lambda表达式入门:

Functional Interface(函数式接口)是Lambda表达式的关键所在。

函数式接口定义

  • 只包含唯一一个抽象方法的接口。

  • 对于函数式接口,我们可以通过Lambda表达式来创建该接口的对象。

 

正常调用方式:

public class Demo2 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.eat();
    }
}
​
interface Animal{
    void eat();
}
​
class Dog implements Animal{
    @Override
    public void eat() {
        System.out.println("小狗喜欢吃骨头");
    }
}

静态内部类:

public class Demo2 {
    //静态内部类
    static class Dog implements Animal{
        @Override
        public void eat() {
            System.out.println("小狗喜欢吃骨头");
        }
    }
    
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.eat();
    }
}
​
interface Animal{
    void eat();
}

局部内部类:

public class Demo2 {
    public static void main(String[] args) {
        //局部内部类
        class Dog implements Animal{
            @Override
            public void eat() {
                System.out.println("小狗喜欢吃骨头");
            }
        }
        Dog dog = new Dog();
        dog.eat();
    }
}
​
interface Animal{
    void eat();
}

匿名内部类:

public class Demo2 {
    public static void main(String[] args) {
        //匿名内部类:没有类的名称,必须借助接口或者父类
        ((Animal) new Animal() {
            @Override
            public void eat() {
                System.out.println("小狗爱吃骨头");
            }
        }).eat();
    }
}
​
interface Animal{
    void eat();
}

终极简化版(Lambda表达式):

public class Demo2 {
    public static void main(String[] args) {
        ((Animal) () -> System.out.println("小狗爱吃骨头")).eat();
    }
}
​
interface Animal{
    void eat();
}

简化过程:

外部类==>静态内部类==>局部内部类==>匿名内部类==>Lambda表达式
​
Lambda表达式让我们只用关注参数和方法体,而不用去关注类名和方法名
​
Lambda表达式依赖于函数式接口或者父类!!!
Lambda表达式依赖于函数式接口或者父类!!!
Lambda表达式依赖于函数式接口或者父类!!!

注意:

Lambda表达式只有一行代码的情况下才能简化为一行,如果有多行,就用代码块包裹。
Lambda的前提是函数式接口
多个参数也可以去掉参数类型,要去掉都去掉,必须加上括号

@FunctionalInterface注解

这是一个标志注解,被该注解修饰的接口只能声明一个抽象方法。

小结:

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

Lambda表达式使用的前提:

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

Lambda与匿名内部类对比:

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

接口中新增的方法

在JDK8之前

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

JDK8之后:

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

默认方法

为什么要用默认方法

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

接口默认方法的格式

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

接口中默认方法的使用

接口中默认方法的两种使用方式:
	1.实现类直接调用接口的默认方法
	2.实现类重写接口的默认方法

静态方法

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

语法规则

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

接口中静态方法的使用

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

二者区别介绍

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

函数式接口

函数式接口的由来

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

public class demo4 {
    public static void main(String[] args) {
        func1((arr)->{
            int num=0;
            for (int i : arr) {
                num+=i;
            }
            return num;
        });
    }
    public static void func1(Operator operator){
        int[] num={1,2,3,6,4};
        System.out.println(operator.getNum(num));
    }
}

interface Operator{
    int getNum(int[] num);
}

函数式接口介绍

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

Supplier

无参有返回值接口,对应的Lambda表达式需要提供一个返回值数据的类型。

用来生产数据的 ;

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}
/**
 * Supplier 函数式接口的使用
 */
public class Demo5 {
    public static void main(String[] args) {
        fun1(() -> {
            int[] arr={12,3,43,3,23,45,465,56};
            Arrays.sort(arr);
            return arr[arr.length-1];
        });
    }
    private static void fun1(Supplier<Integer> supplier){
        Integer max = supplier.get();
        System.out.println("max="+max);
    }
}

 

Consumer

有参无返回值接口。

用来消费数据的。使用的时候需要制定泛型,来定义参数类型。

@FunctionalInterface
public interface Consumer<T> {

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

使用:将字符串转换为小写

/**
 * Consumer 函数式接口的使用
 */
public class ConsumerTset {
    public static void main(String[] args) {
        test(a->{
            //转换为小写
            System.out.println(a+"转换为:"+a.toLowerCase());
        });
    }
    private static void test(Consumer<String> consumer){
        consumer.accept("HELLO WORLD");
    }
}

默认方法:andThen

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

/**
 * Consumer 函数式接口的使用
 */
public class ConsumerTset {
    public static void main(String[] args) {
        test2(a->{
            System.out.println(a+"转换为:"+a.toLowerCase());
        },b->{
            System.out.println(b+"转换为:"+b.toUpperCase());
        });
    }
    private static void test(Consumer<String> consumer){
        consumer.accept("HELLO WORLD");
    }
    private static void test2(Consumer<String> a,Consumer<String> b){
        String str="Hello World";
        a.andThen(b).accept(str);
    }
}

 

Function

有参、有返回值接口

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

@FunctionalInterface
public interface Function<T, R> {

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

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

/**
 * Function 函数式接口的使用
 */
public class FunctionTest {
    public static void main(String[] args) {
        test(a -> Integer.parseInt(a));
    }
    private static void test(Function<String,Integer> function){
        Integer apply = function.apply("666");
        System.out.println(apply);
    }
}

默认方法andThen:也是用来进行组合操作

compose:执行顺序与andThen相反

静态方法identity:输入什么参数,就返回什么参数

/**
 * Function 函数式接口的使用
 */
public class FunctionTest {
    public static void main(String[] args) {
        test(a -> Integer.parseInt(a), b->{
            return b*20;
        });
    }
    private static void test(Function<String,Integer> function,Function<Integer,Integer> function2){
        Integer a=function.andThen(function2).apply("5");
        Integer a2=function2.compose(function).apply("5");
        System.out.println(a);
        System.out.println(a2);
    }
}

 

Predicate

有参有返回值(boolean)接口

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

使用:

/**
 * Predicate 函数式接口使用
 */
public class PredicateTest {
    public static void main(String[] args) {
        test(a->{
            return a.length()>3;
        },"rdwq");
    }
    private static void test(Predicate<String> predicate,String msg){
        boolean b = predicate.test(msg);
        System.out.println(b);
    }
}

在Predicate中的默认方法提供了逻辑关系操作的 and or nagate isEquals方法

/**
 * Predicate 函数式接口使用
 */
public class PredicateTest {
    public static void main(String[] args) {
        test(a->{
            return a.contains("H");
        },b->{
            return b.contains("w");
        });
    }
    private static void test(Predicate<String> predicate,Predicate<String> predicate1){
        boolean a = predicate.and(predicate1).test("Hello World");
        boolean a1 = predicate.or(predicate1).test("Hello World");
        boolean a2 = predicate.negate().test("Hello World");//取反:不包含H
        System.out.println(a);
        System.out.println(a1);
        System.out.println(a2);
    }
}

方法引用

为什么要方法引用

Lambda表达式冗余

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

/**
 * 方法引用
 */
public class Demo6 {
    public static void main(String[] args) {
        test(a->{
            int num=0;
            for (int i : a) {
                num+=i;
            }
            System.out.println("数组求和:"+num);
        });
    }
    
    public static void getTotal(int[] a){
        int num=0;
        for (int i : a) {
            num+=i;
        }
        System.out.println("数组求和:"+num);
    }//与Lambda表达式中代码冗余
    
    private static void test(Consumer<int[]> consumer){
        int[] a={1,34,234,5,4,6};
        consumer.accept(a);
    }
}

解决方案

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

//::方法引用 JDK8中新语法
public class Demo6 {
    public static void main(String[] args) {

        //::方法引用 JDK8中新语法
        test(Demo6::getTotal);
    }
    public static void getTotal(int[] a){
        int num=0;
        for (int i : a) {
            num+=i;
        }
        System.out.println("数组求和:"+num);
    }
    private static void test(Consumer<int[]> consumer){
        int[] a={1,34,234,5,4,6};
        consumer.accept(a);
    }
}

方法引用

符号表示:

::

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

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

常见的引用方式:

方法引用在JDK8中使用是相当灵活的;有以下几种方式:

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

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

/**
 * 方法引用
 * 对象::方法名
 */
public class TestDemo1 {
    public static void main(String[] args) {
        Date now=new Date();
        Supplier<Long> supplier1=now::getTime;
        System.out.println(supplier1.get());
    }
}

方法引用的注意事项:

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

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

类名::静态方法名
/**
 * 方法引用
 *  public static native long currentTimeMillis();
 *  类名::静态方法名
 */
public class Demo7 {
    public static void main(String[] args) {
        Supplier<Long> supplier=System::currentTimeMillis;
        System.out.println(supplier.get());
    }
}
类名::引用实例方法

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

/**
 * 方法引用
 * 类名::引用实例方法
 *  public int length() {
 *         return value.length;
 *  }
 */
public class Demo8 {
    public static void main(String[] args) {
        Function<String,Integer> function=(a)->{
            return a.length();
        };
        System.out.println(function.apply("hello"));
        //等效于
        Function<String,Integer> function1=String::length;
        System.out.println(function1.apply("helloworld"));
    }
}

 

类名::构造器

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

/**
 * 方法引用
 * 类名::构造器
 */
public class Demo9 {
    public static void main(String[] args) {
        Supplier<Person> supplier= () -> new Person();
        Supplier<Person> supplier1= Person::new;
        System.out.println(supplier.get());
        BiFunction<String,Integer,Person> function=Person::new;
        System.out.println(function.apply("张三",19));
    }
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class Person{
    String name;
    Integer age;
}

 

数组::构造器
/**
 * 方法引用调用数组构造器
 */
public class Demo10 {
    public static void main(String[] args) {
        Function<Integer,String[]> function=(a)->{
            return new String[a];
        };
        String[] str = function.apply(12);
        System.out.println(str.length);

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

小结:

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

Stream API

集合处理的弊端

	当我们需要对集合中的元素进行操作的时候,除了必要的添加,删除,获取外,最典型的操作就是集合遍历。
public static void main(String[] args) {
        List<String> list= Arrays.asList("张三丰","李四","张无忌","张三");
        List<String> list1=new ArrayList<>();
        List<String> list2=new ArrayList<>();
        for (String s : list) {
            if(s.startsWith("张")){
                list1.add(s);
            }
        }

        for (String s : list1) {
            if(s.length()==3){
                list2.add(s);
            }
        }

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

上面的代码随着我们需求的改变总是一遍遍的循环,我们需要更加高效的处理方式,可以通过JDK8中的Stream API来解决这个问题。

Stream的解决方式:

/**
 * Stream api 处理集合
 */
public class Demo2 {
    public static void main(String[] args) {
        List<String> list= Arrays.asList("张三丰","李四","张无忌","张三");
        list.stream()
                .filter(s->s.startsWith("张"))
                .filter(s->s.length()==3)
                .forEach(System.out::println);
    }
}

上面的Stream API 代码的含义:获取流,过滤,过滤,遍历。代码更简洁高效。

Stream流式思想概述

注意:

Stream流和IO流没有任何关系!

Stream流式思想类似于工厂车间的生产流水线,Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理。Stream可以看做是流水线的一个工序。在流水线上,通过多个工序让一个原材料加工成一个商品。

image-20220811143310971

image-20220811143345053

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

Stream流的获取方式

根据Collection获取

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

/**
 * Collect 接口中使用stream方法
 */
public class Demo3 {
    public static void main(String[] args) {
        List list=new ArrayList();
        list.stream();
        Set set=new HashSet();
        set.stream();
        Vector vector=new Vector();
        vector.stream();
    }
}

注意:map接口没有实现Collection接口,根据map获取对应的key value集合来操作。

/**
 * Map 接口中使用stream方法
 */
public class Demo3 {
    public static void main(String[] args) {
        Map map=new HashMap();
        map.keySet().stream();
        map.values().stream();
        map.entrySet().stream();
    }
}

 

根据Stream的of方法

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

/**
 * 数组使用stream方法
 */
public class Demo4 {
    public static void main(String[] args) {
        Stream<Integer> integerStream = Stream.of(1, 2, 43, 67, 5);
        integerStream.forEach(System.out::println);
        //注意:基本数据类型的数据是不行的
        int[] arr={1,45,6,65,677};
        Stream.of(arr).forEach(System.out::println);
    }
}

Stream常用方法

Stream流模型的操作很丰富,常用API,可分为两种。

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

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

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

Stream注意事项:

1.Stream只能操作一次。
2.Stream方法返回的是新的流
3.Stream不调用终结方法,中间的操作不会执行。

forEach

用来遍历流中的数据的。

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

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

/**
 * stream流 forEach 方法
 */
public class Demo5 {
    public static void main(String[] args) {
        Stream.of("张三丰","李四","张无忌","张三")
                .forEach(System.out::println);
    }
}

count

统计流中元素个数

long count();

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

/**
 * stream流 count 方法
 */
public class Demo5 {
    public static void main(String[] args) {
        long count = Stream.of("张三丰", "李四", "张无忌", "张三").count();
        System.out.println(count);
    }
}

filter

用来过滤数据,返回符合条件的数据。

可以通过filter方法可以将一个流转换为另一个子集流

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

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

/**
 * stream流 filter 方法
 */
public class Demo5 {
    public static void main(String[] args) {
        Stream.of("张三丰", "李四", "张无忌", "张三")
                .filter(s -> s.length() == 3)
                .forEach(System.out::println);
    }
}

limit

对流进行截取处理,只截取前n个数据。

image-20220811160249139

Stream<T> limit(long maxSize);

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

/**
 * Stream limit 方法
 */
public class limit {
    public static void main(String[] args) {
        Stream.of(1,2,43,25,4252,52)
                .limit(4)
                .forEach(System.out::println);
    }
}

skip

image-20220811161104079

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

Stream<T> skip(long n);
/**
 * Stream skip 方法
 */
public class Demo6 {
    public static void main(String[] args) {
        Stream.of(1,2,43,25,4252,52)
                .skip(4)
                .forEach(System.out::println);
    }
}

map

将流中的元素映射到另外一个流中。

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

image-20220811161825106

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

/**
 * stream map 方法
 */
public class map {
    public static void main(String[] args) {
        Stream.of("1","32","4","42","67")
                .map(a -> Integer.parseInt(a))
                .forEach(System.out::println);
    }
}

HashMap转为ArrayList

/**
 * stream map 方法
 */
public class map {
    public static void main(String[] args) {
        Map<Integer,String> map=new HashMap();
        map.put(1,"刘德华");
        map.put(2,"张学友");
        map.put(3,"黎明");
        map.values().stream()
                .map(a->{
                    ArrayList<String> strings = new ArrayList<>();
                    strings.add(a);
                    return strings;
                })
                .forEach(System.out::println);
    }
}

sorted

将数据排序

    /**
     * @return the new stream
     */
    Stream<T> sorted();

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

在使用时可以根据自然规则排序,也可以自定义排序规则。

/**
 * stream sorted
 */
public class sorted {
    public static void main(String[] args) {
        Stream.of(1,3,5,9,6,7,8,4,67)
            	//.sorted();正序
                .sorted((a1, a2) -> a2 - a1)//倒序
                .forEach(System.out::println);
    }
}

 

distinct

去重

image-20220811165352467

Stream<T> distinct();

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

/**
 * stream sorted distinct
 */
public class sorted {
    public static void main(String[] args) {
        Stream.of(1,3,5,9,6,7,8,4,67,9,1)
                .sorted((a1, a2) -> a2 - a1)//倒序
                .distinct()
                .forEach(System.out::println);

        Stream.of(new Person("张三",13),
                new Person("李四",13),
                new Person("张三",19))
                .distinct()
                .forEach(System.out::println);
    }
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class Person{
    String name;
    Integer age;
}

match

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

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

 

/**
 * Stream  match
 */
public class match {
    public static void main(String[] args) {
        boolean b = Stream.of("张三", "张无忌", "张三丰")
                .anyMatch(a -> a.contains("张三丰"));//有一个满足条件
        System.out.println(b);

        boolean b1 = Stream.of("张三", "张无忌", "张三丰")
                .allMatch(a -> a.contains("张三丰"));//都满足条件
        System.out.println(b1);

        boolean b2 = Stream.of("张三", "张无忌", "张三丰")
                .noneMatch(a -> a.contains("张三丰"));//都不满足条件
        System.out.println(b2);

    }
}

注意:match是一个终结方法。

find

image-20220811172753864

找到某些数据。

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

 

public class find {
    public static void main(String[] args) {
        Optional<Integer> first = Stream.of(1, 2, 43, 25, 4252, 52).findFirst();
        System.out.println(first.get());

        Optional<Integer> any = Stream.of(1, 2, 43, 25, 4252, 52).findAny();
        System.out.println(any.get());
    }
}

max和min

image-20220811174134429

获取最大值和最小值

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

 

/**
 * Stream max min
 */
public class Demo7 {
    public static void main(String[] args) {
        Optional<Integer> max = Stream.of(1, 2, 43, 25, 4252, 52)
                .max((a1, a2) -> a1 - a2);
        System.out.println(max.get());

        Optional<Integer> min = Stream.of(1, 2, 43, 25, 4252, 52)
                .min((a1, a2) -> a1 - a2);
        System.out.println(min.get());
    }
}

reduce方法

image-20220811174203844

将所有数据归纳为一个数据

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

 

/**
 * Stream reduce
 */
public class reduce {
    public static void main(String[] args) {
        //求和
        Integer reduce = Stream.of(1, 2, 6, 6, 5)
                //identity:默认值
                //第一次会将默认值赋值给X
                //之后每次会将上一次操作的结果赋值给X,y就是每次从数据中获取的元素
                .reduce(0, (x, y) -> {
                    System.out.println("x=" + x + ",y=" + y);
                    return x + y;
                });
        System.out.println(reduce);
        //获取最大值
        Integer reduce1 = Stream.of(1, 2, 6, 6, 5)
                .reduce(0, (a1, a2) -> {
                    return a1 > a2 ? a1 : a2;
                });
        System.out.println(reduce1);
    }
}

map和reduce的组合

在实际开发过程中,经常会将map、reduce组合使用。

/**
 * Stream map reduce 组合使用
 */
public class MapAndReduce {
    public static void main(String[] args) {
        //统计年龄总和
        Integer reduce = Stream.of(new Student("张三", 18),
                        new Student("刘德华", 19),
                        new Student("黎明", 23))
                .map(Student::getAge)
                .reduce(0,Integer::sum);
        System.out.println(reduce);

        //求出所有年龄中的最大值
        Integer reduce1 = Stream.of(new Student("张三", 18),
                        new Student("刘德华", 19),
                        new Student("黎明", 23))
                .map(Student::getAge)
                .reduce(0,Integer::max);
        System.out.println(reduce1);

        //字符出现的次数
        Integer reduce3 = Stream.of("a", "u", "qwe", "a", "p", "a")
                .map(a -> "a".equals(a) ? 1 : 0)
                .reduce(0, Integer::sum);
        System.out.println(reduce3);
    }
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class Student{
    String name;
    Integer age;
}

mapToInt

将Stream中的Integer类型转换为int类型

 

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

concat

两个流合并为一个流。

public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
    Objects.requireNonNull(a);
    Objects.requireNonNull(b);
​
    @SuppressWarnings("unchecked")
    Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
            (Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
    Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
    return stream.onClose(Streams.composedClose(a, b));
}
/**
 * Stream concat
 */
public class Demo {
    public static void main(String[] args) {
        Stream<String> a = Stream.of("a", "b", "c");
        Stream<String> b = Stream.of("x", "y", "z");
        //concat方法将两个流拼接为一个新的流
        Stream.concat(a,b)
                .forEach(System.out::println);
    }
}

综合案例

定义连个集合,在集合中存储多个用户,完成以下操作:

1.第一个队伍只保留姓名长度为3的成员。

2.第一个队伍筛选之后只要前3人

3.第二个队伍只要醒张的成员

4.第二个队员筛选之后不要前两人

5.将两个队伍合并为一个队伍

6.根据姓名创建Person对象

7.打印整个队伍的信息

public class Demo2 {
    public static void main(String[] args) {
        List<String> list= Arrays.asList("迪丽热巴","张三","李四","王五","刘德华","张学友","张三丰");
        List<String> list1= Arrays.asList("李渊","李世民","李元吉","张学友","尉迟敬德","欧阳修","张无忌","张天爱");
        Stream<String> limit = list.stream()
                .filter(s -> s.length() == 3)
                .limit(3);
​
        Stream<String> skip = list1.stream().filter(s -> s.startsWith("张"))
                .skip(2);
​
        Stream.concat(limit,skip).map(s->new Person(s))
                .forEach(System.out::println);
    }
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class Person{
    String name;
    Integer age;
​
    public Person(String name) {
        this.name=name;
    }
}

Stream结果收集

结果收集到集合中

/**
 * Stream 结果收集
 *      收集到集合中
 */
public class Demo3 {
    public static void main(String[] args) {
        //收集到List集合中
        List<String> collect = Stream.of("aa", "bb", "cc").collect(Collectors.toList());
        System.out.println(collect);
        //收集到Set集合
        Set<String> collect1 = Stream.of("aa", "bb", "cc","bb").collect(Collectors.toSet());
        System.out.println(collect1);
​
        //需要获取的类型为具体的实现  比如:ArrayList,HashSet
        ArrayList<String> arrayList = Stream.of("aa", "bb", "cc", "bb")
                .collect(Collectors.toCollection(ArrayList::new));
        System.out.println(arrayList);
        HashSet<String> hashSet = Stream.of("aa", "bb", "cc", "bb")
                .collect(Collectors.toCollection(HashSet::new));
        System.out.println(hashSet);
​
    }
}

结果:

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

 

集合收集到数组中

Stream中提供了toArray()方法来讲结果放到一个数组中,返回值类型是Object[] ,

要返回指定类型,要重载toArray(IntFunction f)方法

/**
 * Stream 结果收集
 *      收集到数组中
 */
public class Demo4 {
    public static void main(String[] args) {
        //返回数组中的元素是Object类型
        Object[] objects = Stream.of("aa", "bb", "cc").toArray();
        System.out.println(Arrays.toString(objects));
​
        //指定返回数组中的元素类型
        String[] strings = Stream.of("aa", "bb", "cc").toArray(String[]::new);
        System.out.println(Arrays.toString(strings));
        Integer[] integers = Stream.of(1,3,7,4,6,7,5).toArray(Integer[]::new);
        System.out.println(Arrays.toString(integers));
    }
}

对流中的数据做聚合计算

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

/**
 * Stream 流中数据做聚合计算
 */
public class Demo5 {
    public static void main(String[] args) {
        //最大值
        Optional<Person> collect = Stream.of(new Person("张三", 16),
                        new Person("李四", 19),
                        new Person("王五", 16),
                        new Person("赵六", 23),
                        new Person("张力", 24))
                .collect(Collectors.maxBy((a1, a2) -> a1.getAge() - a2.getAge()));
        System.out.println(collect);
        //最小值
        Optional<Person> min = Stream.of(new Person("张三", 16),
                        new Person("李四", 19),
                        new Person("王五", 16),
                        new Person("赵六", 23),
                        new Person("张力", 24))
                .collect(Collectors.minBy((a1, a2) -> a1.getAge() - a2.getAge()));
        System.out.println(min);
        //求和
        Integer sum = Stream.of(new Person("张三", 16),
                        new Person("李四", 19),
                        new Person("王五", 16),
                        new Person("赵六", 23),
                        new Person("张力", 24))
                .collect(Collectors.summingInt(s -> s.getAge()));
        System.out.println(sum);
        //平均值
        Double avg = Stream.of(new Person("张三", 16),
                        new Person("李四", 19),
                        new Person("王五", 16),
                        new Person("赵六", 23),
                        new Person("张力", 24))
                .collect(Collectors.averagingInt(Person::getAge));
        System.out.println(avg);
        //count
        Long count = Stream.of(new Person("张三", 16),
                        new Person("李四", 19),
                        new Person("王五", 16),
                        new Person("赵六", 23),
                        new Person("张力", 24))
                .filter(a->a.getAge()>16)
                .collect(Collectors.counting());
        System.out.println(count);
​
    }
}

对流中数据分组

Stream流处理数据之后,根据某个属性将数据分组。

/**
 * 分组计算
 */
public class Demo6 {
    public static void main(String[] args) {
         //根据姓名分组
        Map<String, List<Person>> map1 = Stream.of(
                new Person("张三", 16),
                        new Person("李四", 19),
                        new Person("王五", 16),
                        new Person("王五", 16),
                        new Person("赵六", 23),
                        new Person("张力", 24))
                .collect(Collectors.groupingBy(Person::getName));
        map1.forEach((k,v)-> System.out.println(k+"--->"+v));
​
        //根据年龄分组
        Map<String, List<Person>> map2 = Stream.of(
                        new Person("张三", 16),
                        new Person("李四", 19),
                        new Person("王五", 16),
                        new Person("王五", 16),
                        new Person("赵六", 23),
                        new Person("张力", 24))
                .collect(Collectors.groupingBy(a -> a.getAge() > 18 ? "成年" : "未成年"));
        map2.forEach((k,v)-> System.out.println(k+"--->"+v));
    }
}

多级分组

/**
 * 多级分组
 */
public class Demo7 {
    public static void main(String[] args) {
        Map<String, Map<String, List<Person>>> map = Stream.of(
                        new Person("张三", 16),
                        new Person("李四", 19),
                        new Person("王五", 16),
                        new Person("王五", 23),
                        new Person("赵六", 23),
                        new Person("张力", 24))
                .collect(Collectors.groupingBy(Person::getName, 
                        Collectors.groupingBy(a -> a.getAge() > 18 ? "成年" : "未成年")));
        map.forEach((k,v)->System.out.println(k+"-->"+v));
    }
}

结果:

张力-->{成年=[Person(name=张力, age=24)]}
李四-->{成年=[Person(name=李四, age=19)]}
张三-->{未成年=[Person(name=张三, age=16)]}
王五-->{未成年=[Person(name=王五, age=16)], 成年=[Person(name=王五, age=23)]}
赵六-->{成年=[Person(name=赵六, age=23)]}

对流中的数据做分区操作

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

/**
 * 分区操作
 */
public class Demo8 {
    public static void main(String[] args) {
        Map<Boolean, List<Person>> map = Stream.of(
                        new Person("张三", 16),
                        new Person("李四", 19),
                        new Person("王五", 16),
                        new Person("王五", 23),
                        new Person("赵六", 23),
                        new Person("张力", 24))
                .collect(Collectors.partitioningBy(a -> a.getAge() > 18));
        map.forEach((k,v)-> System.out.println(k+"-->"+v));
    }
}

结果:

false-->[Person(name=张三, age=16), Person(name=王五, age=16)]
true-->[Person(name=李四, age=19), Person(name=王五, age=23), Person(name=赵六, age=23), Person(name=张力, age=24)]

对流中数据做拼接

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

/**
 * 对流中数据做拼接操作
 */
public class Demo9 {
    public static void main(String[] args) {
        String collect = Stream.of(
                        new Person("张三", 16),
                        new Person("李四", 19),
                        new Person("王五", 16),
                        new Person("王五", 23),
                        new Person("赵六", 23),
                        new Person("张力", 24))
                .map(Person::getName)
                .collect(Collectors.joining("--","###","$$$"));
        System.out.println(collect);
    }
}

结果:

###张三--李四--王五--王五--赵六--张力$$$

并行的Stream流

串行的Stream流

在一个线程上执行的Stream流都是串行。

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

结果:

Thread[main,5,main]-->1
Thread[main,5,main]-->1
Thread[main,5,main]-->2
Thread[main,5,main]-->5
Thread[main,5,main]-->6
Thread[main,5,main]-->6
Thread[main,5,main]-->5

并行流

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

获取并行流的两种方式:

1.通过List集合中的parallelStream方法来获取
2.通过已有的串行流转换为并行流(parallel)
/**
 * 并行流
 */
public class Demo11 {
    public static void main(String[] args) {
        List<Integer> list=new ArrayList<>();
        //通过List接口直接获取并行流
        Stream<Integer> integerStream = list.parallelStream();
        //将已有的串行流转换为并行流
        Stream<Integer> parallel = Stream.of(1, 2, 3, 6).parallel();
    }
}

并行流的操作

/**
 * 并行流操作
 */
public class Demo12 {
    public static void main(String[] args) {
        Stream.of(5,12,5,46,5,45,1)//将流转换为并发流,Stream 处理的时候会通过多线程处理
                .parallel()
                .filter(s->{
                    System.out.println(Thread.currentThread()+"-->s="+s);
                    return s>1;
                })
                .count();
    }
}

效果:

Thread[main,5,main]-->s=5
Thread[ForkJoinPool.commonPool-worker-3,5,main]-->s=5
Thread[ForkJoinPool.commonPool-worker-4,5,main]-->s=45
Thread[ForkJoinPool.commonPool-worker-5,5,main]-->s=5
Thread[ForkJoinPool.commonPool-worker-6,5,main]-->s=46
Thread[ForkJoinPool.commonPool-worker-2,5,main]-->s=1
Thread[ForkJoinPool.commonPool-worker-1,5,main]-->s=12

并行流与串行流对比

通过for循环,串行Stream流,并行Stream流来对500000000亿个数字。

public class Demo13 {
    private static long times=500000000;
    private long start;
​
    @Before
    public void befor(){
        start=System.currentTimeMillis();
    }
​
    @After
    public void end(){
        long end =System.currentTimeMillis();
        System.out.println("消耗时间:"+(end-start));
    }
​
    /**
     * 普通for循环
     * 消耗时间:177
     */
    @Test
    public void test1(){
        System.out.println("普通for循环");
        long les=0;
        for (long i = 0; i < times; i++) {
            les+=i;
        }
    }
​
    /**
     * 串行流处理
     * 消耗时间:256
     */
    @Test
    public void test2(){
        System.out.println("串行流:serialStream");
        LongStream.rangeClosed(0,times)
                .reduce(0,Long::sum);
    }
​
    /**
     * 并行流处理
     * 消耗时间:117
     */
    @Test
    public void test3(){
        LongStream.rangeClosed(0,times)
                .parallel()
                .reduce(0,Long::sum);
    }
}

通过parallelStream的效率是最高的。

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

并行线程安全问题

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

public class Demo14 {
    /**
     * 并行流安全问题
     */
    @Test
    public void test(){
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            list.add(i);
        }
        System.out.println(list.size());
        List<Integer> list2=new ArrayList<>();
        list.parallelStream()
                .forEach(list2::add);
        System.out.println(list2.size());
​
    }
}

运行效果:

1000
892
或者直接抛出异常:
java.lang.ArrayIndexOutOfBoundsException: 549

解决方案:

1.加同步锁

2.使用线程安全的容器

3.通过Stream中的toArray/collect操作

/**
 * 加同步锁
 */
@Test
public void test1(){
    List<Integer> list = new ArrayList<>();
    for (int i = 0; i < 1000; i++) {
        list.add(i);
    }
    System.out.println(list.size());
    List<Integer> list2=new ArrayList<>();
    Object o = new Object();
    list.parallelStream()
            .forEach(a->{
                synchronized (o){
                    list2.add(a);
                }
            });
    System.out.println(list2.size());
​
}
​
/**
 * 使用线程安全的容器
 */
@Test
public void test2(){
    Vector<Object> v = new Vector<>();
    IntStream.rangeClosed(1,1000)
                    .parallel()
            .forEach(a->{
                    v.add(a);
            });
    System.out.println(v.size());
​
}
/**
 * 将线程安全的容器转换为线程安全的容器
 */
@Test
public void test3(){
    List<Integer> list = new ArrayList<>();
    //将线程不安全的容器包装为线程安全的容器
    List<Integer> list1 = Collections.synchronizedList(list);
    IntStream.rangeClosed(1,1000)
            .parallel()
            .forEach(a->{
                    list1.add(a);
            });
    System.out.println(list1.size());
​
}
​
/**
 * 还可以通过Stream中的toArray方法或者collect方法来操作
 * 就是满足线程安全的要求
 */
@Test
public void test4(){
    List<Integer> list = new ArrayList<>();
    List<Integer> collect = IntStream.rangeClosed(1, 1000)
            .parallel()
            .boxed()
            .collect(Collectors.toList());
    System.out.println(collect.size());
​
}

Fork/Join框架

parallelStream使用的是Fork/Join框架,Fork/Join框架值JDK1.7引入,它可以将一个大的任务拆分成很多小任务来异步执行。

Fork/Join框架主要包含三个模块:
    1.线程池:ForkJoinPool
    2.任务对象:ForkJoinTask
    3.执行任务的线程:ForkJoinWorkerThread

 

Fork/Join原理-分治法

ForkJoinPool主要使用分治法来解决问题,典型的应用比如快速排序算法,

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

Fork/Join原理-工作窃取法

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

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

 

案例:

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

 

public class Demo15 {
    public static void main(String[] args) {
        long start=System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        SumRecursiveTask sumRecursiveTask = new SumRecursiveTask(1, 10000l);
        Long invoke = forkJoinPool.invoke(sumRecursiveTask);
        System.out.println("result:"+invoke);
        long end =System.currentTimeMillis();
        System.out.println("总耗时:"+(end-start));
​
    }
}
class SumRecursiveTask extends RecursiveTask<Long>{
    //拆分临界值
    private static final long THRESHOLD=3000l;
    private final long start;
    private final long end;
​
    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;
            }
            System.out.println("计算:"+start+"-->"+end+"的结果为:"+sum);
            return sum;
        }else{
            //还要继续拆分
            long middle=(start+end)/2;
            System.out.println("拆分左边:"+start+"--->"+middle+",右边"+(middle+1)+"---->"+end);
            SumRecursiveTask left = new SumRecursiveTask(start, middle);
            left.fork();
            SumRecursiveTask right = new SumRecursiveTask(middle + 1, end);
            right.fork();
            return left.join()+right.join();
        }
    }
}

结果:

拆分左边:1--->5000,右边5001---->10000
拆分左边:5001--->7500,右边7501---->10000
拆分左边:1--->2500,右边2501---->5000
计算:1-->2500的结果为:3126250
计算:5001-->7500的结果为:15626250
计算:2501-->5000的结果为:9376250
计算:7501-->10000的结果为:21876250
result:50005000
总耗时:6

Optional类

解决空指针的问题

以前对空指针的处理:

@Test
public void test(){
    String userName=null;
    if(userName!=null){
        System.out.println("length:"+userName.length());
    }else{
        System.out.println("字符串为空");
    }
    
}

Optional类

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

 

Optional的基本使用

Optional对象的创建方式:

/**
 * Optional对象的创建方式
 */
@Test
public void test1(){
    //通过of方法,of方法不支持null
    Optional<String> s = Optional.of("张三");
    //ofNullable支持null
    Optional<String> o = Optional.ofNullable("lisi");
    Optional<Object> o1 = Optional.ofNullable(null);
    //empty方法直接创建一个空的Optional对象
    Optional<Object> empty = Optional.empty();
}

Optional常用方法

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

  • get()通常和isPresent()一起使用

  • isPresent():判断是否包含值?

  • 包含?true:false

  • orElse(T t):如果调用对象包含值,就返回该值,否则返回t

  • orElseGet(Supplier s):如果调用对象包含值,就返回该值,否则返回Lambda表达式的返回值

/**
 * Optional 常用方法
 *  get():如果Optional有值则返回,否则抛出NoSuchElementException
 *      get()通常和isPresent()一起使用
 *  isPresent():判断是否包含值?
 *      包含?true:false
 *  orElse(T t):如果调用对象包含值,就返回该值,否则返回t
 *  orElseGet(Supplier s):如果调用对象包含值,就返回该值,否则返回Lambda表达式的返回值
 */
@Test
public void test2(){
    Optional<String> s = Optional.of("张三");
    Optional<Object> empty = Optional.empty();
    if(s.isPresent()){
        System.out.println(s.get());
    }
    if(empty.isPresent()){
        System.out.println(empty.get());
    }else{
        System.out.println("empty is null");
    }
    String s1 = s.orElse("lisi");
    System.out.println(s1);
    /*Object a2 = empty.orElse("wangwu");
    System.out.println(a2);*/
    Object o = empty.orElseGet(() -> {
        return "hello";
    });
    System.out.println(o);
}
/**
 * 自定义一个方法将Person中的name 转化为大写
 */
@Test
public void test4(){
    Person s = new Person("zhangsan", 19);
    Optional<Person> s1 = Optional.of(s);
    String s2 = getNameForOptional(s1);
    System.out.println(s2);
}
​
/**
 * 自定义一个方法将Person中的name 转化为大写
 * @param op
 * @return
 */
public String getNameForOptional(Optional<Person> op){
    if(op.isPresent()){
        String s1 = op.map(s -> s.getName())
                .map(s -> s.toUpperCase())
                .orElse(null);
        return s1;
    }
    return null;
}

新时间日期API

旧版日期时间的问题

 

/**
 * 旧版日期时间的问题
 */
@Test
public void test(){
    //设计不合理
    Date date = new Date(2022,05,07);
    System.out.println(date);//Wed Jun 07 00:00:00 CST 3922
​
    //时间格式化和解析操作是线程不安全的
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    for (int i = 0; i < 50; i++) {
        new Thread(()->{
            System.out.println(sdf.format(date));
            try {
                System.out.println(sdf.parse("2020-05-04"));
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

1.设计不合理,在java.util和java.sql的包中都有日期类,java.util.Date同时包含日期和时间的,而java.sql.Date仅仅包含日期,此外,用于格式化和解析的类在java.text包下。

2.非线程安全,java.util.Date是非线程安全的,所有的日期都是可变的,这是java日期类的最大的问题之一。

3.时区处理麻烦,日期类并不提供国际化,没有时区支持,

 

新日期时间API介绍

 

/**
 * JDK 8 日期时间操作
 */
@Test
public void test1(){
    LocalDate date = LocalDate.of(2022, 07, 14);
    System.out.println(date);
​
    LocalDate now = LocalDate.now();
    System.out.println(now);
    System.out.println(now.getYear());
    System.out.println(now.getMonth().getValue());
    System.out.println(now.getDayOfMonth());
    System.out.println(now.getDayOfWeek().getValue());
}
​
​
/**
 * JDK 8 时间操作
 */
@Test
public void test2(){
    LocalTime of = LocalTime.of(5, 26, 33, 354615);
    System.out.println(of);
​
    LocalTime now = LocalTime.now();
    System.out.println(now);
    System.out.println(now.getHour());
    System.out.println(now.getMinute());
    System.out.println(now.getSecond());
    System.out.println(now.getNano());
}
​
/**
 * 日期时间类型 LocalDateTime
 */
@Test
public void test3(){
    LocalDateTime time = LocalDateTime.of(2020,
            06,
            07,
            12,
            12,
            12,
            8465);
    System.out.println(time);
​
    LocalDateTime now = LocalDateTime.now();
    System.out.println(now);
}

日期时间的修改与比较

/**
 * 日期时间的修改
 *      不会修改原来的信息,会创建一个模板,
 */
@Test
public void test4(){
    LocalDateTime now = LocalDateTime.now();
    System.out.println(now);
    LocalDateTime localDateTime = now.withYear(1998);
    System.out.println(localDateTime);
    System.out.println(now.withMonth(9));
    System.out.println(now.withDayOfMonth(31));
    System.out.println(now.withHour(12));
    System.out.println(now.withMinute(12));
​
    //在当前日期的基础上加减时间
    System.out.println(now.plusDays(2));
    System.out.println(now.plusYears(2));
    System.out.println(now.minusYears(10));
    System.out.println(now.minusMonths(6));
    System.out.println(now.minusDays(7));
​
}
​
/**
 * 日期时间的比较
 */
@Test
public void test5(){
    LocalDate now = LocalDate.now();
    LocalDate date = LocalDate.of(2020, 1, 3);
    System.out.println(now.isAfter(date));
    System.out.println(now.isBefore(date));
    System.out.println(now.isEqual(date));
​
}

注意:

在进行日期时间修改的 时候,原来的LocalDate对象不会被修改的,每次操作都返回了新的LocalDate对象,所以线程安全。

格式化和解析

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

/**
 * 日期格式化
 */
@Test
public void test6(){
    LocalDateTime now = LocalDateTime.now();
    //指定格式  使用系统默认的格式
    DateTimeFormatter isoLocalDateTime = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
    String format = now.format(isoLocalDateTime);
    System.out.println(format);
​
    //通过ofPattern方法来指定特定格式
    DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    String s = now.format(pattern);
    System.out.println(s);
​
    //将字符串解析为一个日期时间类型
    LocalDateTime time = LocalDateTime.parse("1998-05-06 12:12:21", pattern);
    System.out.println(time);
​
}

Instant类

时间戳或者时间线。

内部保存了1970年1月1日00:00:00以来的秒和纳秒

/**
 * Instant 时间戳
 *      用来统计时间消耗
 */
@Test
public void test7(){
    Instant now = Instant.now();
    System.out.println(now);
    System.out.println(now.getNano());
    try {
        Thread.sleep(5);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    Instant now1 = Instant.now();
    System.out.println("耗时:"+(now1.getNano() - now.getNano()));
}

计算日期时间差

Duriatoin/Period:计算日期时间差

1.Duriation:用来计算两个时间差(LocalTime)

2.Period:用来计算两个日期差(LocalDate)

/**
 * 计算日期时间差
 *
 */
@Test
public void test8(){
    LocalTime now = LocalTime.now();
    LocalTime date = LocalTime.of(22, 22, 22);
    Duration duration = Duration.between(now, date);
    System.out.println(duration.toDays());
    System.out.println(duration.toMinutes());
    System.out.println(duration.toHours());
​
    LocalDate now1 = LocalDate.now();
    LocalDate date1 = LocalDate.of(1998, 05, 05);
    Period period = Period.between(now1, date1);
    System.out.println(period.getYears());
    System.out.println(period.getMonths());
    System.out.println(period.getDays());
}

时间校正器

有时候我们需要如下调整:

将日期调整到下个月的第一天等操作,需要时间校正器效果更好

TemporalAdjuster:时间校正器

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

/**
 * 时间校正器
 *
 */
@Test
public void test9(){
    LocalDateTime now = LocalDateTime.now();
    //将当前日期调整到下个月一号
    TemporalAdjuster  adjuster=(temporal -> {
        LocalDateTime date=(LocalDateTime) temporal;
        LocalDateTime localDateTime = date.plusMonths(1).withDayOfMonth(1);
        System.out.println(localDateTime);
        return localDateTime;
    });
    /*LocalDateTime with = now.with(adjuster);
    System.out.println(with);*/
​
    //TemporalAdjusters:
    LocalDateTime with = now.with(TemporalAdjusters.firstDayOfNextMonth());
    System.out.println(with);
}

日期时间的时区

JDK8中加入了对时区的支持,LocalDate,LocalTime,LocalDateTime是不带时区的,

带时区的日期时间类分别是:ZonedDate,ZonedTime,ZonedDateTime

每个时区都对应这ID,ID格式为:“区域/城市”,例如:“Asia/Shanghai”

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

/**
 * 时区
 *
 */
@Test
public void test10(){
    /*ZoneId.getAvailableZoneIds().forEach(System.out::println);*/
    //获取当前时间,中国使用的是东8区的时区,比标准时间早8个小时
    LocalDateTime now = LocalDateTime.now();
    System.out.println(now);
​
    ZonedDateTime now1 = ZonedDateTime.now(Clock.systemUTC());//标准时间
    System.out.println(now1);
​
    //使用计算机默认的时区,创建日期时间;
    ZonedDateTime now2 = ZonedDateTime.now();
    System.out.println(now2);
​
    //使用指定时区创建时间
    ZonedDateTime now3 = ZonedDateTime.now(ZoneId.of("America/Marigot"));
    System.out.println(now3);
}

JDK新版的优势:

1.新版的API中,日期和时间对象时不可变的,操作日期不会影响原来的值,而是生成一个新的实例。

2.提供不同的两种方式。有效区分人和机器的两种操作。

3.TemporalAdjuster可以更精确的操作日期,还可以自定义日期调整期

4.线程安全

其他新特性

1.重复注解

自JDK5以来,注解变得非常流行,在各个框架和项目中使用,注解有一个很大的限制:在一个地方不能多次使用同一个注解。

JDK8引用了重复注解的概念,允许在同一个 地方多次使用同一个注解。在JDK8中使用@Repeatable注解定义重复注解。

1.定义一个重复注解的容器

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

2.定义一个可以重复的注解

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

3.配置多个重复的注解

@MyAnnotation("test")
@MyAnnotation("test1")
@MyAnnotation("test2")
@MyAnnotation("test3")
public class Test {
​
    @MyAnnotation("fun1")
    @MyAnnotation("fun2")
    public void test1(){
​
    }
}

4.解析得到指定的注解

    /**
     * 解析重复注解
     * @param args
     */
    public static void main(String[] args) {
        //获取类中标注的重复注解
        MyAnnotation[] annotationsByType = Test.class.getAnnotationsByType(MyAnnotation.class);
        for (MyAnnotation myAnnotation : annotationsByType) {
            System.out.println(myAnnotation);
        }
        //获取方法上标注的重复注解
        try {
            MyAnnotation[] test1s = Test.class.getMethod("test1").getAnnotationsByType(MyAnnotation.class);
            for (MyAnnotation test1 : test1s) {
                System.out.println(test1);
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
​

 

 

2.类型注解

posted @   kevinBilly  阅读(76)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
点击右上角即可分享
微信分享提示