JDK8新特性

Lambda表达式

1. 使用匿名内部类存在的问题

当需要启动一个线程去完成任务时,通常会通过 Runnable 接口来定义任务内容,并使用 Thread 类来启动该线程。
传统写法,代码如下:

public class LambdaIntro {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("新线程任务执行!");
            }
        }).start();
    }
}

由于面向对象的语法要求,首先创建一个 Runnable 接口的匿名内部类对象来指定线程要执行的任务内容,再将其交
给一个线程来启动。
代码分析:
对于 Runnable 的匿名内部类用法,可以分析出几点内容:

  • Thread 类需要 Runnable 接口作为参数,其中的抽象 run 方法是用来指定线程任务内容的核心
  • 为了指定 run 的方法体,不得不需要 Runnable 接口的实现类
  • 为了省去定义一个 Runnable 实现类的麻烦,不得不使用匿名内部类
  • 必须覆盖重写抽象 run 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错
  • 而实际上,似乎只有方法体才是关键所在。

2. Lambda体验

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

new Thread(() -> System.out.println("新线程任务执行!")).start(); // 启动线程

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

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

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

3. Lambda的优点

简化匿名内部类的使用,语法更加简单。
小结:
了解了匿名内部类语法冗余,体验了Lambda表达式的使用,发现Lmabda是简化匿名内部类的简写。

4. Lambda的标准格式

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

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

格式说明:

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

Lambda与方法的对比
匿名内部类

public void run() {
	System.out.println("aa"); 
}

Lambda

() -> System.out.println("bb!")

5. 练习无参数无返回值的Lambda

掌握了Lambda的语法,我们来通过一个案例熟悉Lambda的使用。

interface Swimmable {
	public abstract void swimming(); 
}

/**
 *     Lambda的标准格式:
 *         (int a) -> {
 *             要执行的代码
 *         }
 */
public class LambdaUse {
    public static void main(String[] args) {
        goSwimming(new com.itheima.demo01lambda.Swimmable() {
            @Override
            public void swimming() {
                System.out.println("匿名内部类的游泳");
            }
        });

        // 小结:Lambda表达式相当于是对接口抽象方法的重写
        goSwimming(() -> {
            System.out.println("Lambda表达式的游泳");
        });
    }

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

    public static void test(int a) {

    }
}

6. 练习有参数有返回值的Lambda

下面举例演示 java.util.Comparator 接口的使用场景代码,其中的抽象方法定义为:

  • public abstract int compare(T o1, T o2);

当需要对一个对象集合进行排序时, Collections.sort 方法需要一个 Comparator 接口实例来指定排序的规则。
如果使用传统的代码对 ArrayList 集合进行排序,写法如下:

public class Person {
	private String name;
	private int age;
	private int height;
	// 省略其他 
}
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, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge() - o2.getAge(); // 升序排序
            }
        });*/

        // Lambda表达式进行排序
        Collections.sort(persons, (Person o1, Person o2) -> {
            return o2.getAge() - o1.getAge(); // 降序
        });
        System.out.println("-----------");

        //更简洁的写法
        Collections.sort(persons, (o1,o2) -> o2.getAge() - o1.getAge());

        for (Person person : persons) {
            System.out.println(person);
        }

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


这种做法在面向对象的思想中,似乎也是“理所当然”的。其中 Comparator 接口的实例(使用了匿名内部类)代表
了“按照年龄从小到大”的排序规则。

小结
首先学习了Lambda表达式的标准格式

(参数列表) -> {
	方法体;
}

以后我们调用方法时,看到参数是接口就可以考虑使用Lambda表达式,Lambda表达式相当于是对接口中抽象方法的重

7. 了解Lambda的实现原理

匿名内部类在编译的时候会一个class文件

Lambda在程序运行的时候形成一个类:

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

8. Lambda省略格式

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

  1. 小括号内参数的类型可以省略
  2. 如果小括号内有且仅有一个参数,则小括号可以省略
  3. 如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号
(int a) -> {
	return new Person();
}
a -> new Person()

9. Lambda的前提条件

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

  1. 方法的参数或局部变量类型必须为接口才能使用Lambda
  2. 接口中有且仅有一个抽象方法
public interface Flyable {
	public abstract void flying();
}
    public static void main(String[] args) {
        ArrayList<Person> persons = new ArrayList<>();
        persons.add(new Person("aaaa", 58, 174));
        persons.add(new Person("bbb", 58, 176));
        persons.add(new Person("ccc", 54, 171));
        persons.add(new Person("ddd", 53, 178));
        
        Collections.sort(persons, (o1, o2) -> o2.getAge() - o1.getAge());
        
        persons.forEach(t -> System.out.println(t));
    }

小结
Lambda表达式的前提条件:

  1. 方法的参数或变量的类型是接口
  2. 这个接口中只能有一个抽象方法

10. 函数式接口

函数式接口在Java中是指:有且仅有一个抽象方法的接口
函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以
适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
FunctionalInterface注解
与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注
解可用于一个接口的定义上:

@FunctionalInterface
public interface Operator {
	void myMethod();
}

一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。不过,即
使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。

11. Lambda和匿名内部类对比

了解Lambda和匿名内部类在使用上的区别

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

小结
当接口中只有一个抽象方法时,建议使用Lambda表达式,其他其他情况还是需要使用匿名内部类

常用内置函数式接口

目标

了解内置函数式接口由来

了解常用内置函数式接口

1. 内置函数式接口来由来

我们知道使用Lambda表达式的前提是需要有函数式接口。而Lambda使用时不关心接口名,抽象方法名,只关心抽

象方法的参数列表和返回值类型。因此为了让我们使用Lambda方便,JDK提供了大量常用的函数式接口。

import java.util.List;

public class Demo01UserFunctionalInterface {

    public static void main(String[] args) {

// 调用函数式接口中的方法 

        method((arr) -> {

            int sum = 0;

            for (int n : arr) {

                sum += n;

            }

            return sum;

        });

    }

// 使用自定义的函数式接口作为方法参数 

    public static void method(Operator op) {

        int[] arr = {1, 2, 3, 4};

        int sum = op.getSum(arr);

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

    }

}

@FunctionalInterface

interface Operator {

    public abstract int getSum(int[] arr);

}

2. 常用内置函数式接口介绍

它们主要在 java.util.function 包中。下面是最常用的几个接口。

2.1. Supplier接口

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

型的对象数据。

@FunctionalInterface 
public interface Supplier<T> { 
	public abstract T get(); 
}

供给型接口,通过Supplier接口中的get方法可以得到一个值,无参有返回的接口。

使用Lambda表达式返回数组元素最大值

使用 Supplier 接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值。提示:接口的泛型请使用

java.lang.Integer 类。

代码示例:

import java.util.Arrays;

public class Demo05Supplier {
    public static void main(String[] args) {
        printMax(() -> {
            int[] arr = {10, 20, 100, 30, 40, 50};
            // 先排序,最后就是最大的
            Arrays.sort(arr);
            return arr[arr.length - 1]; // 最后就是最大的
        });
    }

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

2.2. Consumer接口

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

型参数决定。

@FunctionalInterface 
public interface Consumer<T> { 
		public abstract void accept(T t); 
}

使用Lambda表达式将一个字符串转成大写和小写的字符串

Consumer消费型接口,可以拿到accept方法参数传递过来的数据进行处理, 有参无返回的接口。基本使用如:

import java.util.function.Consumer;

public class Demo06Consumer {
    public static void main(String[] args) {
        // Lambda表达式
        test((String s) -> {
            System.out.println(s.toLowerCase());
        });
    }

    public static void test(Consumer<String> consumer) {
        consumer.accept("HelloWorld");
    }
}

默认方法:andThen

如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费一个数据的时候,首先做一个操

作,然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的default方法 andThen 。下面是JDK的源代

码:

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

备注: java.util.Objects 的 requireNonNull 静态方法将会在参数为null时主动抛出

NullPointerException 异常。这省去了重复编写if语句和抛出空指针异常的麻烦。

要想实现组合,需要两个或多个Lambda表达式即可,而 andThen 的语义正是“一步接一步”操作。例如两个步骤组合

的情况:

public class Demo07ConsumerAndThen {
    public static void main(String[] args) {
        // Lambda表达式
        test((String s) -> {
            System.out.println(s.toLowerCase());
        }, (String s) -> {
            System.out.println(s.toUpperCase());
        });
        // Lambda表达式简写
        test(s -> System.out.println(s.toLowerCase()), s ->
                System.out.println(s.toUpperCase()));
    }
    public static void test(Consumer<String> c1, Consumer<String> c2) {
        String str = "Hello World";
        // c1.accept(str); // 转小写
        // c2.accept(str); // 转大写
        // c1.andThen(c2).accept(str);
        c2.andThen(c1).accept(str);
    }
}

运行结果将会首先打印完全大写的HELLO,然后打印完全小写的hello。当然,通过链式写法可以实现更多步骤的组

合。

3.3. Function接口

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

后者称为后置条件。有参数有返回值。

Interface Function<T,R> 可以用作lambda表达式或方法引用的赋值对象。

参数类型
T - 函数输入的类型
R - 函数的结果类型

类型方法说明
default Function<T,V>andThen(Function<? super R,? extends V> after) 返回一个组合函数,首先将该函数应用于其输入,然后将 after函数应用于结果。
Rapply(T t) 将此函数应用于给定的参数。
default Function<V,R>compose(Function<? super V,? extends T> before) 返回一个组合函数,首先将 before函数应用于其输入,然后将此函数应用于结果。
static Function<T,T>identity() 返回一个总是返回其输入参数的函数。
@FunctionalInterface 
public interface Function<T, R> { 
		public abstract R apply(T t); 
}

使用Lambda表达式将字符串转成数字

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

例如:将 String 类型转换为 Integer 类型。

public class Demo08Function {

    public static void main(String[] args) {
        // Lambda表达式
        test((String s) -> {
            return Integer.parseInt(s); // 10
        });
    }

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

默认方法:andThen

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

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {

        Objects.requireNonNull(after);

        return (T t) -> after.apply(apply(t));

        }

该方法同样用于“先做什么,再做什么”的场景,和 Consumer 中的 andThen 差不多:

public class Demo09FunctionAndThen {

    public static void main(String[] args) {
        // Lambda表达式
        test((String s) -> {
            return Integer.parseInt(s);
        }, (Integer i) -> {
            return i * 10;
        });
    }

    public static void test(Function<String, Integer> f1, Function<Integer, Integer> f2) {
// Integer in = f1.apply("66"); // 将字符串解析成为int数字
// Integer in2 = f2.apply(in);// 将上一步的int数字乘以10
        Integer in3 = f1.andThen(f2).apply("66");
        System.out.println("in3: " + in3); // 660
    }
}

第一个操作是将字符串解析成为int数字,第二个操作是乘以10。两个操作通过 andThen 按照前后顺序组合到了一

起。

请注意,Function的前置条件泛型和后置条件泛型可以相同。

以lambda表达式作为传参

manurePriceMonitorStatisticsVO.setRetailPrice(getAvg(v,a -> a.getRetailPrice().compareTo(BigDecimal.ZERO) > 0, ManurePriceMonitorStatisticsVO::getRetailPrice));
manurePriceMonitorStatisticsVO.setTradePrice(getAvg(v,a -> a.getTradePrice().compareTo(BigDecimal.ZERO) > 0, ManurePriceMonitorStatisticsVO::getTradePrice));
manurePriceMonitorStatisticsVO.setLeaveFactoryPrice(getAvg(v,a -> a.getLeaveFactoryPrice().compareTo(BigDecimal.ZERO) > 0, ManurePriceMonitorStatisticsVO::getLeaveFactoryPrice));
// 计算平均值
private BigDecimal getAvg(List<ManurePriceMonitorStatisticsVO> v, Function<ManurePriceMonitorStatisticsVO,Boolean> function,Function<ManurePriceMonitorStatisticsVO,BigDecimal> function2) {
List<BigDecimal> price = v.stream().filter(a->function.apply(a)).map(function2).collect(Collectors.toList());
BigDecimal avg = price.stream().reduce(BigDecimal.ZERO, BigDecimal::add).divide(new BigDecimal(price.size()));
return avg;
}

3.4. Predicate接口

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

java.util.function.Predicate 接口。

@FunctionalInterface 
public interface Predicate<T> { 
	public abstract boolean test(T t); 
}

Predicate接口用于做判断,返回boolean类型的值

使用Lambda判断一个人名如果超过3个字就认为是很长的名字

对test方法的参数T进行判断,返回boolean类型的结果。用于条件判断的场景:

public class Demo10Predicate {

    public static void main(String[] args) {
        test(s -> s.length() > 3, "啊啊啊啊");
    }

    private static void test(Predicate<String> predicate, String str) {
        boolean veryLong = predicate.test(str);
        System.out.println("名字很长吗:" + veryLong);
    }
}

条件判断的标准是传入的Lambda表达式逻辑,只要名称长度大于3则认为很长。

默认方法:and

既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个 Predicate 条件使用“与”逻辑连接起来实

现“并且”的效果时,可以使用default方法 and 。其JDK源码为:

default Predicate<T> and(Predicate<? super T> other) { 
	Objects.requireNonNull(other); 
	return (t) -> test(t) && other.test(t); 
}

使用Lambda表达式判断一个字符串中即包含W,也包含H

使用Lambda表达式判断一个字符串中包含W或者包含H

使用Lambda表达式判断一个字符串中即不包含W

如果要判断一个字符串既要包含大写“H”,又要包含大写“W”,那么:

public class Demo10Predicate_And_Or_Negate {

    public static void main(String[] args) {
        // Lambda表达式
        test((String s) -> {
            return s.contains("H");
        }, (String s) -> {
            return s.contains("W");
        });
    }

    public static void test(Predicate<String> p1, Predicate<String> p2) {
        String str = "HelloWorld";
        boolean b1 = p1.test(str); // 判断包含大写“H”
        boolean b2 = p2.test(str); // 判断包含大写“W”
// if (b1 && b2) { 

// System.out.println("即包含W,也包含H"); 

// }
        boolean bb = p1.and(p2).test(str);
        if (bb) {
            System.out.println("即包含W,也包含H");
        }
    }
}

默认方法:or

使用Lambda表达式判断一个字符串中包含W或者包含H

与 and 的“与”类似,默认方法 or 实现逻辑关系中的“”。JDK源码为:

default Predicate<T> or(Predicate<? super T> other) { 
	Objects.requireNonNull(other); 
	return (t) -> test(t) || other.test(t); 
}

如果希望实现逻辑“字符串包含大写H或者包含大写W”,那么代码只需要将“and”修改为“or”名称即可,其他都不变:

public class Demo10Predicate_And_Or_Negate {
    public static void main(String[] args) {
        // Lambda表达式
        test((String s) -> {
            return s.contains("H");
        }, (String s) -> {
            return s.contains("W");
        });
    }

    public static void test(Predicate<String> p1, Predicate<String> p2) {
        String str = "HelloWorld";
        boolean b1 = p1.test(str); // 判断包含大写“H”
        boolean b2 = p2.test(str); // 判断包含大写“W”
        // if (b1 || b2) {
        // System.out.println("有H,或者W");
        // }
        boolean bbb = p1.or(p2).test(str);
        if (bbb) {
            System.out.println("有H,或者W");
        }
    }
}

默认方法:negate

使用Lambda表达式判断一个字符串中即不包含W

“与”、“或”已经了解了,剩下的“非”(取反)也会简单。默认方法 negate 的JDK源代码为:

default Predicate<T> negate() { 
	return (t) -> !test(t); 
}

从实现中很容易看出,它是执行了test方法之后,对结果boolean值进行“!”取反而已。一定要在 test 方法调用之前调

用 negate 方法,正如 and 和 or 方法一样:

import java.util.function.Predicate;

public class Demo10Predicate_And_Or_Negate {
    public static void main(String[] args) {
        // Lambda表达式
        test((String s) -> {
            return s.contains("H");
        }, (String s) -> {
            return s.contains("W");
        });
    }

    public static void test(Predicate<String> p1, Predicate<String> p2) {
        String str = "HelloWorld";
        boolean b1 = p1.test(str); // 判断包含大写“H”
        boolean b2 = p2.test(str); // 判断包含大写“W” 

// 没有H,就打印 

// if (!b1) { 

// System.out.println("没有H"); 

// }
        boolean test = p1.negate().test(str);
        if (test) {
            System.out.println("没有H");
        }
    }
}

介绍方法引用"::"

目标

了解Lambda的冗余场景

掌握方法引用的格式

了解常见的方法引用方式

1. Lambda的冗余场景

使用Lambda表达式求一个数组的和

public class Demo11MethodRefIntro {
    
    public static void getMax(int[] arr) {
        int sum = 0;
        for (int n : arr) {
            sum += n;
        }
        System.out.println(sum);
    }

    public static void main(String[] args) {
        printMax((int[] arr) -> {
            int sum = 0;
            for (int n : arr) {
                sum += n;
            }
            System.out.println(sum);
        });
    }

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

2. 体验方法引用简化Lambda

如果我们在Lambda中所指定的功能,已经有其他方法存在相同方案,那是否还有必要再写重复逻辑?可以直接“引

用”过去就好了:

public class DemoPrintRef {

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

    public static void main(String[] args) {
        printMax(Demo11MethodRefIntro::getMax);
    }

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

请注意其中的双冒号 :: 写法,这被称为“方法引用”,是一种新的语法。

4.方法引用的格式

符号表示: ::

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

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

5. 对象名::引用成员方法

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

码为:

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

方法引用的注意事项

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

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

6. 类名::引用静态方法

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

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

7. 类名::引用实例方法

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

者。

// 类名::实例方法 
@Test 
public void test03() { 
	Function<String, Integer> f1 = (s) -> { 
		return s.length(); 
	};
	System.out.println(f1.apply("abc")); 
	Function<String, Integer> f2 = String::length; 
	System.out.println(f2.apply("abc")); 
	BiFunction<String, Integer, String> bif = String::substring; 
	String hello = bif.apply("hello", 2); 
	System.out.println("hello = " + hello); 
}

8. 类名::new引用构造器

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

public class Person { 
	private String name; 
	public Person(String name) { 
		this.name = name; 
	}
	public String getName() { 
		return name; 
	} 
}

要使用这个函数式接口,可以通过方法引用传递:

// 类名::new 
@Test 
public void test04() { 
	Supplier<Person> sup = () -> { 
		return new Person(); 
	};
	System.out.println(sup.get()); 
	Supplier<Person> sup2 = Person::new; 
	System.out.println(sup2.get()); 
	BiFunction<String, Integer, Person> fun2 = Person::new; 
	System.out.println(fun2.apply("张三", 18)); 
}

9. 数组::new 引用数组构造器

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

// 类型[]::new 
@Test 
public void test05() { 
	Function<Integer, String[]> fun = (len) -> { 
		return new String[len]; 
	};
	String[] arr1 = fun.apply(10); 
	System.out.println(arr1 + ", " + arr1.length); 
	Function<Integer, String[]> fun2 = String[]::new; 
	String[] arr2 = fun.apply(5); 
	System.out.println(arr2 + ", " + arr2.length); 
}

小结

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

JDK8的Stream流

1. 什么是Stream

Stream是JDK8中引入,Stream是一个来自数据源的元素序列并支持聚合操作。可以让你以一种声明的方式处理数据,Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

2. Stream特点

  • 元素:是特定类型的对象,形成一个序列。 Java中的Stream并不会存储元素,而是按需计算。
  • 数据源:流的来源可以是集合,数组,I/O channel等。
  • 过滤、聚合、排序等操作:类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等
  • Pipelining(流水线/管道): 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
  • 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式。
  • 只能遍历一次:数据流的从一头获取数据源,在流水线上依次对元素进行操作,当元素通过流水线,便无法再对其进行操作,也就是执行了结束或者终止操作以后将不能再进行操作,如果再执行操作报错:java.lang.IllegalStateException: stream has already been operated upon or closed

在这里插入图片描述

一个stream是由三部分组成的。数据源,零个或一个或多个中间操作,一个或零个终止操作。
中间操作是对数据的加工,注意:中间操作是lazy操作,并不会立马启动,需要等待终止操作才会执行。
终止操作是stream的启动操作,只有加上终止操作,stream才会真正的开始执行。

小结:
Stream API能让我们快速完成许多复杂的操作,如筛选、切片、映射、查找、去除重复,统计,匹配和归约。
首先我们了解了集合操作数据的弊端,每次都需要循环遍历,还要创建新集合,很麻烦
Stream是流式思想,相当于工厂的流水线,对集合中的数据进行加工处理

3. Stream入门案例

//要求把list1中的空字符串过滤掉,并把结果保存在列表中
public class Test {
    public static void main(String[] args) {
        List<String> list1 = Arrays.asList("ab", "", "cd", "ef", "mm","", "hh");
        System.out.println(list1);//[ab, , cd, ef, mm, , hh]
        List<String> result = list1.stream().filter(s -> !s.isEmpty()).collect(Collectors.toList());
        System.out.println(result);//[ab, cd, ef, mm, hh]
    }
}

上面这个例子可以看出list1是一个字符串的列表,其中有两个空字符串,在stream的操作过程中,我们使用了stream()、filter()、collect()等方法,在filter()过程中,我们引入了Lambda表达式s->!s.isEmpty(),结果是把两个空字符串过滤掉后,形成了一个新的列表result。
上面这个需求如果我们使用传统的代码完成如下:

public class Test {
    public static void main(String[] args) {
        List<String> list1 = Arrays.asList("ab", "", "cd", "ef", "mm","", "hh");
        List<String> result = new ArrayList<>();
        for (String str : list1) {
            if(str.isEmpty()){
                continue;
            }
            result.add(str);
        }
        System.out.println(result);
    }
}

比较两段代码,我们可以发现在第二段代码中我们自己创建了一个字符串对象列表,开启一个for循环遍历字符串对象列表,在for循环中判断是否当前的字符串是空串,如果不是,加到结果列表中。而在第一段程序中,我们并不需要自己开启for循环遍历,stream会在内部做迭代,我们只需要传入我们的过滤条件就可以了,最后这个字符串列表也是代码自动创建出来的,并且把结果放入了列表中,可以看出,第一段代码简洁优雅。

//传统写法
        // 开始时间
        long startTime1 = System.currentTimeMillis();
        List<String> list1 = Arrays.asList("ab", "", "cd", "ef", "mm","", "hh");
        List<String> result1 = new ArrayList<>();
        for (String str : list1) {
            if(str.isEmpty()){
                continue;
            }
            result1.add(str);
        }
        System.out.println(result1);
        // 结束时间
        long endTime1 = System.currentTimeMillis();
        // 计算执行时间
        System.out.println("传统写法执行时长 "+(endTime1 - startTime1)+"毫秒.");

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

        //Stream流写法
        // 开始时间
        long startTime2 = System.currentTimeMillis();
        List<String> list2 = Arrays.asList("ab", "", "cd", "ef", "mm","", "hh");
        //[ab, , cd, ef, mm, , hh]
        List<String> result2 =
                // 创建一个流(stream)
                list1.stream().
                        // 过滤条件
                        filter(s -> !s.isEmpty())
                        .collect(Collectors.toList());
        //[ab, cd, ef, mm, hh]
        System.out.println(result2);
        // 结束时间
        long endTime2 = System.currentTimeMillis();
        // 计算执行时间
        System.out.println("Stream写法执行时长 "+(endTime2 - startTime2)+"毫秒.");

在这里插入图片描述

可以看出作为jdk8新特性竟然比使用传统写法使用的时间还要久,原因:

  • 在少低数据量的处理场景中(size<=1000),stream 的处理效率是不如传统的 iterator 外部迭代器处理速度快的,但是实际上这些处理任务本身运行时间都低于毫秒,这点效率的差距对普通业务几乎没有影响,反而 stream 可以使得代码更加简洁;

  • 在大数据量(szie>10000)时,stream 的处理效率会高于 iterator,特别是使用了并行流,在cpu恰好将线程分配到多个核心的条件下(当然parallel stream 底层使用的是 JVM 的 ForkJoinPool,这东西分配线程本身就很玄学),可以达到一个很高的运行效率,然而实际普通业务一般不会有需要迭代高于10000次的计算;

  • Parallel Stream(并行流) 受引 CPU 环境影响很大,当没分配到多个cpu核心时,加上引用 forkJoinPool 的开销,运行效率可能还不如普通的 Stream;

4. Stream操作分类和常用方法

4.1 Stream操作分类

在这里插入图片描述

  • 无状态:指元素的处理不受之前元素的影响;
  • 有状态:指该操作只有拿到所有元素之后才能继续下去。
  • 非短路操作:指必须处理所有元素才能得到最终结果;
  • 短路操作:指遇到某些符合条件的元素就可以得到最终结果,如 A || B,只要A为true,则无需判断B的结果。

4.2 Stream中间操作常用方法

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

方法名方法作用返回值类型方法种类
count统计个数long终结
forEach逐一处理void终结
filter过滤Stream函数拼接
limit取用前几个Stream函数拼接
skip跳过前几个Stream函数拼接
map映射Stream函数拼接
concat组合Stream函数拼接
sortedStream对stream中所有的元素按照指定规则进行排序
distinctStream对Stream中所有元素进行去重

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

forEach 方法。

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

方法。)

备注:本文章之外的更多方法,请自行参考API文档。

Stream注意事项(重要)

  1. Stream只能操作一次

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

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

小结

我们学习了Stream的常用方法,我们知道Stream这些常用方法可以分成两类,终结方法,函数拼接方法

Stream的3个注意事项:

  1. Stream只能操作一次

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

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

4.3 常用方法的使用

1. Stream流的forEach方法

forEach 用来遍历流中的数据

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

该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理。例如:

@Test
public void testForEach() {
        List<String> one = new ArrayList<>();
        Collections.addAll(one, "萧炎", "美杜莎", "萧薰儿", "药老", "云韵", "海波东");
        /*one.stream().forEach((String s) -> { 
        System.out.println(s); 
        });*/
        // 简写
        // one.stream().forEach(s -> System.out.println(s));
        one.stream().forEach(System.out::println);
}
2. Stream流的count方法

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

long count();

该方法返回一个long值代表元素个数。基本使用:

@Test 
public void testCount() { 
	List<String> one = new ArrayList<>(); 
	Collections.addAll(one, "萧炎", "美杜莎", "萧薰儿", "药老", "云韵", "海波东"); 
	System.out.println(one.stream().count()); 
}
3. Stream流的filter方法

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

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

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

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

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

@Test 
public void testFilter() { 
	List<String> one = new ArrayList<>(); 
	Collections.addAll(one, "萧炎", "美杜莎", "萧薰儿", "药老", "云韵", "海波东"); 
	one.stream().filter(s -> s.length() == 2).forEach(System.out::println); 
}

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

4. Stream流的limit方法

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

Stream<T> limit(long maxSize);

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

@Test 
public void testLimit() { 
	List<String> one = new ArrayList<>(); 
	Collections.addAll(one, "萧炎", "美杜莎", "萧薰儿", "药老", "云韵", "海波东"); 
	one.stream().limit(3).forEach(System.out::println); 
}
5. Stream流的skip方法

如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流:
Stream skip(long n);
如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。基本使用:

@Test 
public void testSkip() { 
	List<String> one = new ArrayList<>(); 
	Collections.addAll(one, "萧炎", "美杜莎", "萧薰儿", "药老", "云韵", "海波东"); 
	one.stream().skip(2).forEach(System.out::println); 
}
6. Stream流的map方法

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

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

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

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

@Test 
public void testMap() { 
	Stream<String> original = Stream.of("11", "22", "33"); 
	Stream<Integer> result = original.map(Integer::parseInt); 
	result.forEach(s -> System.out.println(s + 10)); 
}

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

7. Stream流的sorted方法

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

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

基本使用

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

@Test 
public void testSorted() { 
	// sorted(): 根据元素的自然顺序排序 
	// sorted(Comparator<? super T> comparator): 根据比较器指定的规则排序 
	Stream.of(33, 22, 11, 55) 
	.sorted() 
	.sorted((o1, o2) -> o2 - o1) 
	.forEach(System.out::println); 


        List<Person> one = Arrays.asList(
                new Person("萧炎",18),
                new Person("美杜莎",92),
                new Person("萧薰儿",19),
                new Person("药老",120),
                new Person("云韵",30),
                new Person("海波东",60),
                new Person("纳然嫣然",18));

        sort(one).stream().forEach(System.out::println);
        System.out.println("------------");
        sortDesc(one).stream().forEach(System.out::println);

    }

public static List<Person> sort(List<Person> one) {
    	// 按照年龄自然排序
        List<Person> collect = one.stream().sorted(Comparator.comparing(Person::getAge)).collect(Collectors.toList());
        return collect;
    }
    public static List<Person> sortDesc(List<Person> one) {
    	// 按照年龄逆序排序
        List<Person> collect = one.stream().sorted(Comparator.comparing(Person::getAge).reversed()).collect(Collectors.toList());
        return collect;
    }
}

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

8. Stream流的distinct方法

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

Stream<T> distinct();

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

@Test 
public void testDistinct() { 
	Stream.of(22, 33, 22, 11, 33) 
	.distinct() 
	.forEach(System.out::println); 
}

如果是自定义类型如何是否也能去除重复的数据呢?

@Test 
public void testDistinct2() { 
	Stream.of(
		new Person("萧炎", 58), 
		new Person("美杜莎", 56), 
		new Person("萧薰儿", 56), 
		new Person("云韵", 52)) 
	.distinct() 
	.forEach(System.out::println); 
}
public class Person { 
	private String name; 
	private int age; 
	// 省略其他 
}

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

9. 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 testMatch() { 
	boolean b = Stream.of(5, 3, 6, 1) 
	// .allMatch(e -> e > 0); // allMatch: 元素是否全部满足条件 
	// .anyMatch(e -> e > 5); // anyMatch: 元素是否任意有一个满足条件 
	.noneMatch(e -> e < 0); // noneMatch: 元素是否全部不满足条件 
	System.out.println("b = " + b); 
}
10. 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()); 
	Optional<Integer> any = Stream.of(5, 3, 6, 1).findAny(); 
	System.out.println("any = " + any.get()); 
}
11. 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 testMax_Min() { 
	Optional<Integer> max = Stream.of(5, 3, 6, 1).max((o1, o2) -> o1 - o2); 
	System.out.println("first = " + max.get()); 
	Optional<Integer> min = Stream.of(5, 3, 6, 1).min((o1, o2) -> o1 - o2); 
	System.out.println("any = " + min.get()); 
}
12. Stream流的reduce方法

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

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

基本使用

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

@Test
public void testReduce() {
        int reduce = Stream.of(4, 5, 3, 9)
        .reduce(0, (a, b) -> {
            System.out.println("a = " + a + ", b = " + b);
            return a + b;
        });

        // reduce: 
        // 第一次将默认做赋值给x, 取出第一个元素赋值给y,进行操作 
        // 第二次,将第一次的结果赋值给x, 取出二个元素赋值给y,进行操作 
        // 第三次,将第二次的结果赋值给x, 取出三个元素赋值给y,进行操作 
        // 第四次,将第三次的结果赋值给x, 取出四个元素赋值给y,进行操作 
        System.out.println("reduce = " + reduce);
        int reduce2 = Stream.of(4, 5, 3, 9)
            .reduce(0, (x, y) -> {
            return Integer.sum(x, y);
        });
        int reduce3 = Stream.of(4, 5, 3, 9).reduce(0, Integer::sum);
        int max = Stream.of(4, 5, 3, 9)
            .reduce(0, (x, y) -> {
            return x > y ? x : y;
        });

        System.out.println("max = " + max);
        }
13. Stream流的map和reduce组合使用
@Test 
public void testMapReduce() { 

	// 求出所有年龄的总和
    int totalAge = Stream.of(
                    new Person("刘德华", 58),
                    new Person("张学友", 56),
                    new Person("郭富城", 54),
                    new Person("黎明", 52))
            .map((p) -> p.getAge())
            .reduce(0, (x, y) -> x + y);
        System.out.println("totalAge = "+totalAge);
        
    // 找出最大年龄
    int maxAge = Stream.of(
                    new Person("刘德华", 58),
                    new Person("张学友", 56),
                    new Person("郭富城", 54),
                    new Person("黎明", 52))
            .map((p) -> p.getAge())
            .reduce(0, (x, y) -> x > y ? x : y);
        System.out.println("maxAge = "+maxAge);
        
    // 统计 数字2 出现的次数
    int count = Stream.of(1, 2, 2, 1, 3, 2)
            .map(i -> {
                if (i == 2) {
                    return 1;
                } else {
                    return 0;
                }
            })
            .reduce(0, Integer::sum);
        System.out.println("count = "+count);
}
14. Stream流的mapToInt

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

IntStream mapToInt(ToIntFunction<? super T> mapper);

基本使用

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

@Test 
public void test1() { 
    // Integer占用的内存比int多,在Stream流操作中会自动装箱和拆箱
    Stream<Integer> stream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5});
    // 把大于3的和打印出来
    // Integer result = stream
    // .filter(i -> i.intValue() > 3)
    // .reduce(0, Integer::sum);
    // System.out.println(result);
    // 先将流中的Integer数据转成int,后续都是操作int类型
    IntStream intStream = stream.mapToInt(Integer::intValue);
    int reduce = intStream
            .filter(i -> i > 3)

            .reduce(0, Integer::sum);
    System.out.println(reduce);

    // 将IntStream转化为Stream<Integer> 

    IntStream intStream1 = IntStream.rangeClosed(1, 10);

    Stream<Integer> boxed = intStream1.boxed(); 

    boxed.forEach(s ->System.out.println(s.getClass()+", "+s));
}
15. Stream流的concat方法

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

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

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

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

@Test 
public void testContact() { 
	Stream<String> streamA = Stream.of("萧炎"); 
	Stream<String> streamB = Stream.of("美杜莎"); 
	Stream<String> result = Stream.concat(streamA, streamB); 
	result.forEach(System.out::println); 
}

5. Stream综合案例

现在有两个 ArrayList 集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依次进行以下
若干操作步骤:

  1. 第一个队伍只要名字为3个字的成员姓名;
  2. 第一个队伍筛选之后只要前3个人;
  3. 第二个队伍只要姓张的成员姓名;
  4. 第二个队伍筛选之后不要前2个人;
  5. 将两个队伍合并为一个队伍;
  6. 根据姓名创建 Person 对象;
  7. 打印整个队伍的Person对象信息。

注意:只有java9以上才支持List.of,可修改为Arrays.asList

两个队伍(集合)的代码如下:

public class DemoArrayListNames {
    public static void main(String[] args) {
        List<String> one = List.of("萧炎", "美杜莎", "萧薰儿", "药老", "云韵", "海波东", "纳然嫣然");
        List<String> two = List.of("唐三", "小舞", "戴沐白", "奥斯卡", "马红俊", "宁荣荣", "朱竹清");
        // ....
    }
}

而 Person 类的代码为:

public class Person {
    private String name;
    public Person() {}
    public Person(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Person{name='" + name + "'}";
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
1. 传统方式

使用for循环 , 示例代码:

package com.example.springjpaquery.hand;

import com.example.springjpaquery.pojo.Person;

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


public class DemoArrayListNames {
    public static void main(String[] args) {
        List<String> one = Arrays.asList("萧炎", "美杜莎", "萧薰儿", "药老", "云韵", "海波东", "纳然嫣然");
        List<String> two = Arrays.asList("唐三", "唐舞", "戴沐白", "奥斯卡", "唐红俊", "唐荣荣", "唐竹清");

        // 第一个队伍只要名字为3个字的成员姓名;美杜莎,萧薰儿,海波东
        List<String> oneA = new ArrayList<>();
        for (String name : one) {
            if (name.length() == 3) {
                oneA.add(name);
            }
        }


        // 第一个队伍筛选之后只要前3个人;美杜莎,萧薰儿,海波东
        List<String> oneB = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            oneB.add(oneA.get(i));
        }

        // 第二个队伍只要姓唐的成员姓名;唐三,唐舞,唐红俊,唐荣荣,唐竹清
        List<String> twoA = new ArrayList<>();
        for (String name : two) {
            if (name.startsWith("唐")) {
                twoA.add(name);
            }
        }

        // 第二个队伍筛选之后不要前2个人;唐红俊,唐荣荣,唐竹清
        List<String> twoB = new ArrayList<>();
        for (int i = 2; i < twoA.size(); i++) {
            twoB.add(twoA.get(i));
        }

        // 将两个队伍合并为一个队伍;美杜莎,萧薰儿,海波东,唐红俊,唐荣荣,唐竹清
        List<String> totalNames = new ArrayList<>();
        totalNames.addAll(oneB);
        totalNames.addAll(twoB);

        // 根据姓名创建Person对象;
        List<Person> totalPersonList = new ArrayList<>();
        for (String name : totalNames) {
            totalPersonList.add(new Person(name));
        }

        // 打印整个队伍的Person对象信息。
        for (Person person : totalPersonList) {
            System.out.println(person);
        }

    }

}

在这里插入图片描述

2. Stream方式

等效的Stream流式处理代码为:

package com.example.springjpaquery.hand;

import com.example.springjpaquery.pojo.Person;

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


public class DemoArrayListNames {
    public static void main(String[] args) {
        List<String> one = Arrays.asList("萧炎", "美杜莎", "萧薰儿", "药老", "云韵", "海波东", "纳然嫣然");
        List<String> two = Arrays.asList("唐三", "唐舞", "戴沐白", "奥斯卡", "唐红俊", "唐荣荣", "唐竹清");

        // 第一个队伍只要名字为3个字的成员姓名;
        // 第一个队伍筛选之后只要前3个人;
        Stream<String> streamOne = one.stream().filter(s -> s.length() == 3).limit(3);

        // 第二个队伍只要姓张的成员姓名;
        // 第二个队伍筛选之后不要前2个人;
        Stream<String> streamTwo = two.stream().filter(s -> s.startsWith("唐")).skip(2);

        // 将两个队伍合并为一个队伍;
        // 根据姓名创建Person对象;
        // 打印整个队伍的Person对象信息。
        Stream.concat(streamOne, streamTwo).map(Person::new).forEach(System.out::println);

    }

}

在这里插入图片描述

3. 综合案例2
		//
		List<AuthBO> auth = memberDO.getMemberAuth().getAuth();
        List<String> urlList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(memberUserDO.getUserAuth().getAuth())) {
            List<AuthBO> platformAuthList = memberUserDO.getUserAuth().getAuth().stream().filter(authBO -> 			 authBO.getSource().equals(UserLoginSourceEnum.BUSINESS_MANAGEMENT_PLATFORM.getCode())).collect(Collectors.toList());
            Set<Long> platformAuthParentAuthSet =
                    platformAuthList.stream().map(t -> t.getParentId()).collect(Collectors.toSet());
            Set<String> parentUrlSet = auth.stream().filter(t -> platformAuthParentAuthSet.contains(t.getId())).map(t -> t.getUrl()).collect(Collectors.toSet());
            urlList = platformAuthList.stream().map(AuthBO::getUrl).collect(Collectors.toList());
            urlList.addAll(parentUrlSet);
        }
4. 综合案例3

需求:从给定句子中返回单词长度大于5的单词列表,按长度倒序输出,最多返回3个

/**
 * 【常规方式】
 * 从给定句子中返回单词长度大于5的单词列表,按长度倒序输出,最多返回3个
 *
 * @param sentence 给定的句子,约定非空,且单词之间仅由一个空格分隔
 * @return 倒序输出符合条件的单词列表
 */
public List<String> sortGetTop3LongWords(@NotNull String sentence) {
    // 先切割句子,获取具体的单词信息
    String[] words = sentence.split(" ");
    List<String> wordList = new ArrayList<>();
    // 循环判断单词的长度,先过滤出符合长度要求的单词
    for (String word : words) {
        if (word.length() > 5) {
            wordList.add(word);
        }
    }
    // 对符合条件的列表按照长度进行排序
    wordList.sort((o1, o2) -> o2.length() - o1.length());
    // 判断list结果长度,如果大于3则截取前三个数据的子list返回
    if (wordList.size() > 3) {
        wordList = wordList.subList(0, 3);
    }
    return wordList;
}
    /**
     * 【Stream方式】
     * 从给定句子中返回单词长度大于5的单词列表,按长度倒序输出,最多返回3个
     *
     * @param sentence 给定的句子,约定非空,且单词之间仅由一个空格分隔
     * @return 倒序输出符合条件的单词列表
     */
    private static List<String> sortGetTop3LongWordsByStream(@NotNull String sentence) {
        return Stream.of(sentence.split(" "))
                .filter(s -> s.length() > 5)
                .sorted((o1, o2) -> o2.length() - o1.length())
                .limit(3)
                .collect(Collectors.toList());
    }
5. 综合案例4
6. 比较两个集合是否匹配
        List<String> list = Arrays.asList("business_license","company_name","legal_name","legal_id_card","contact_address","unified_code","registered_capital","establishment_date","company_address","legal_person_phone");
        List<String> strings = Arrays.asList("business_license","company_name","legal_name","legal_id_card","contact_address","unified_code","registered_capital","establishment_date","company_address","legal_person_phone");
        boolean b = strings.containsAll(list);//不匹配则返回false
        System.out.println("b = " + b);
7. 使用Stream流累加BigDecimal
BigDecimal districtSum = reportDOS.stream().map(OrderDistrictCityReportDO::getTotalAmountSum).reduce(BigDecimal.ZERO, BigDecimal::add);
8. 分组,方法引用综合使用
        User sourceUser = new User();
        sourceUser.setName("Tom");
        sourceUser.setAge(25);
        sourceUser.setId(2L);
        sourceUser.setAddress(null);
        User sourceUser1 = new User();
        sourceUser1.setName("Tom1");
        sourceUser1.setAge(25);
        sourceUser1.setId(5L);
        sourceUser1.setAddress(null);
        User sourceUser2 = new User();
        sourceUser2.setName("Tom2");
        sourceUser2.setAge(25);
        sourceUser2.setId(25L);
        sourceUser2.setAddress(null);

        List<User> arrayList = new ArrayList<>();
        arrayList.add(sourceUser);
        arrayList.add(sourceUser1);
        arrayList.add(sourceUser2);
        // 将user.getId()作为map的key分组
        Map<Long, List<User>> assetGroupMap = arrayList.stream().collect(Collectors.groupingBy(User::getId));
        System.out.println("assetGroupMap = " + assetGroupMap);
        List<Integer> aa = new ArrayList<>();
        aa.add(2);
        aa.add(3);
        aa.add(4);
        // 遍历aa通过CommodityStatusEnum的toTypeName(通过code获取message的静态方法)获取message
        List<String> serviceTypeNames = aa.stream().map(CommodityStatusEnum::toTypeName).collect(Collectors.toList());
        System.out.println("serviceTypeNames = " + serviceTypeNames);

        // 获取map中key为25的List<User>,没有则为空集合
        List<User> orDefault = assetGroupMap.getOrDefault(25L, new ArrayList<>());
        System.out.println("orDefault = " + orDefault);

在这里插入图片描述

5. Stream使用案例

5.1. 创建流

5.1.1.使用Collection下的 stream() 和 parallelStream() 方法
public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        Stream<String> stream = list.stream(); //获取一个串行流
        Stream<String> parallelStream = list.parallelStream(); //获取一个并行流
    }
}
5.1.2.使用Arrays 中的 stream() 方法,将数组转成流
public class Test {
    public static void main(String[] args) {
        Integer[] nums = new Integer[10];
        Stream<Integer> stream = Arrays.stream(nums).boxed();    
		//Arrays.stream(array)获取的是一个IntStream对象,boxed 方法用于将目前 Stream 中的基本类型装箱
    }
}
5.1.3.使用Stream中的静态方法:of()、iterate()、generate()
public class Test {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
        stream.forEach(System.out::print);//1 2 3 4 5 6
        System.out.println("==========");
        Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 2).limit(6);
        stream2.forEach(System.out::print); // 0 2 4 6 8 10
        System.out.println("==========");
        Stream<Double> stream3 = Stream.generate(Math::random).limit(2);
        stream3.forEach(System.out::print);//随机产生两个小数
    }
}

5.1.4.使用 BufferedReader.lines() 方法,将每行内容转成流
public class Test {
    public static void main(String[] args) throws FileNotFoundException {
        BufferedReader reader = new BufferedReader(new FileReader("d:\\study\\demo\\test_stream.txt"));
        Stream<String> lineStream = reader.lines();
        lineStream.forEach(System.out::println);
    }
}
5.1.5.使用 Pattern.splitAsStream() 方法,将字符串分隔成流
public class Test {
    public static void main(String[] args) {
        Pattern pattern = Pattern.compile(",");
        Stream<String> stringStream = pattern.splitAsStream("tom,jack,jerry,john");
        stringStream.forEach(System.out::println);
    }
}

5.1.6 直接通过值获取
Stream<String> stream = Stream.of("are","you","ok");
5.1.7 对list分组并排序
// 1. 商品通过店铺id分组,利用TreeMap来自然排序
Map<Long, List<CartCommodityResponse>> cartCommodityMap = collect.stream().collect(Collectors.groupingBy(CartCommodityResponse::getStoreId,TreeMap::new,Collectors.toList()));
// 2. 商品通过店铺id分组,利用TreeMap来反序
Map<Long, List<CartCommodityResponse>> cartCommodityMap = collect.stream().collect(Collectors.groupingBy(CartCommodityResponse::getStoreId,TreeMap::new,Collectors.toList())).descendingMap();
// 3. 前面两个方法不能控制哪个字段来排序,换个思路:分组时使用LinkedHashMap,这样可以保留原来的排序,将排序放在分组前完成
Map<Long, List<CartCommodityResponse>> cartCommodityMap = collect.stream().collect(Collectors.groupingBy(CartCommodityResponse::getStoreId,LinkedHashMap::new,Collectors.toList()));

5.2. 中间操作

5.2.1.筛选与切片
  • filter:过滤流中的某些元素
  • limit(n):获取n个元素
  • skip(n):跳过n元素,配合limit(n)可实现分页
  • distinct:通过流中元素的 hashCode() 和 equals() 去除重复元素
//filter 测试
public class Test {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("aaa", "ff", "dddd","eeeee","hhhhhhh");

        //把字符串长度大于3的过滤掉
        Stream<String> stringStream = list.stream().filter(s -> s.length() <= 3);

        stringStream.forEach(System.out::println);

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


        //验证整个流只遍历一次
        //stream只有遇到终止操作才会触发流启动,中间操作都是lazy
        Stream.of(1, 2, 3, 4, 5)
        .filter(i -> {
            System.out.println("filter1的元素:" + i);
            return i > 0;
        }).filter(i -> {
            System.out.println("filter2的元素:" + i);
            return i == 5;
        }).forEach(i-> System.out.println("最后结果:"+i));


    }
}
//limit 测试
public class Test {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("aaa", "ff", "dddd","eeeee","hhhhhhh");

        //取三个元素
        List<String> result = list.stream().limit(3).collect(Collectors.toList());

        System.out.println(result);

    }
}
//limit 和 skip 测试
public class Test {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("11", "22", "33","44","55","66","77","88","99");

        //演示skip:跳过前三条记录
        list.stream().skip(3).forEach(System.out::println);
        
        //模拟翻页,每页3条记录
        //第一页
        List<String> page1= list.stream().skip(0).limit(3).collect(Collectors.toList());
        System.out.println(page1);
        //第二页
        List<String> page2= list.stream().skip(3).limit(3).collect(Collectors.toList());
        System.out.println(page2);
        //第三页
        List<String> page3= list.stream().skip(6).limit(3).collect(Collectors.toList());
        System.out.println(page3);

        //limit和skip顺序换一下
        //可以看出,最终的结果会收到执行顺序的影响
        List<String> page4= list.stream().limit(3).skip(1).collect(Collectors.toList());
        System.out.println(page4);
    }
}

//distinct去重测试
//注意:当我们自己重写hashcode和equals的方法的时候,要遵循一个原则:
//如果两个对象的hashcode相等,那么用equals比较不一定相等;反之,如果两个对象用equals比较相等,那么他们的hashcode也一定相等
public class Student {
    private Integer id;
    private String name;

    public Student(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return getId().equals(student.getId()) &&
                getName().equals(student.getName());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getId(), getName());
    }

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



//去掉重复的student
//1.Student类的hashcode和equals包含了id和name
//2.Student类的hashcode和equals中只包含name
public class Test {
    public static void main(String[] args) {
        List<Student> studentList = Arrays.asList(
               new Student(1, "zhangsan"),
                new Student(6, "zhangsan"),
                new Student(2, "lisi"),
                new Student(5, "lisi"),
                new Student(3, "wangwu"));
        //1.学生对象去重
        List<Student> result = studentList.stream().distinct().collect(Collectors.toList());
        System.out.println(result);


        //2.普通字符串去重
        Stream<String> stringStream = Stream.of("a", "a", "b", "c", "d");
        List<String> stringList = stringStream.distinct().collect(Collectors.toList());
        System.out.println(stringList);
        
    }
}

	//移除不存在的
    memberRoleDO.getAuth().removeIf(auth -> !updateVO.getMenuIds().contains(auth.getId()));
5.2.2.映射(map和flatMap)
public class Test {
    public static void main(String[] args) {

            //第一个例子对比
            List<String> list = Arrays.asList("a,b,c", "1,2,3");

            //将每个元素转成一个新的且不带逗号的元素
            //注意:这里元素是值在list中的元素,一共有两个,分别是"a,b,c" 和"1,2,3"
            //map函数传入的lambda表达式就是我们的转换逻辑,需要返回一个转换之后的元素
            Stream<String> s1 = list.stream().map(s -> s.replaceAll(",", ""));
            s1.forEach(System.out::println); // abc  123

            System.out.println("===============");
        
            List<Integer> integerList = Arrays.asList(1, 2, 3);
            integerList.stream().map(i->i*2).forEach(System.out::println);

            System.out.println("===============");
        
            //将每个元素转换成一个stream
            //注意:flatMap跟上面的map函数对比
            //两者传入的lambda都是转换逻辑,但是map中的lambda返回的是一个转换后的新元素,
            //flatMap可以把每一个元素进一步处理:例如"a,b,c"进一步分隔成a b c三个元素
            //返回的是这三个元素形成的三个stream,最终把这些单独的stream合并成一个stream返回
            //总结:可以看出,flatMap相比于map,它可以把每一个元素再进一步拆分成更多的元素,
            // 最后,拆分出来的元素个数会多于最初输入的列表中的元素个数
            //就这个例子而言,最初输入两个元素"a,b,c" 和"1,2,3",结果是6个元素  a b c 1 2 3
            Stream<String> s3 = list.stream().flatMap(s -> {

                String[] split = s.split(",");
                Stream<String> s2 = Arrays.stream(split);
                return s2;
            });
            s3.forEach(System.out::println); // a b c 1 2 3

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

            //第二个例子(嵌套的list)[["a","b","c"],["d","e","f"],["h","k"]]
            //输出结果要求是:["A","B","C","D","E","F","G","H"]
            List<List<String>> nestedList = Arrays.asList(
                                                Arrays.asList("a","b","c"),
                                                Arrays.asList("d","e","f"),
                                                Arrays.asList("h","k")
                                            );

            Stream<String> s4 = nestedList.stream()
                .flatMap(Collection::stream)
                .map(s -> s.toUpperCase());


            s4.forEach(System.out::print);
    }
}
5.2.3.排序sorted
  • sorted():自然排序,流中元素需实现Comparable接口
  • sorted(Comparator com):定制排序,自定义Comparator排序器
//字符串排序
public class Test {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("aaa", "ff", "dddd");

        //String 类自身已实现Compareable接口,可以按照字符的自然顺序【升序】排序
        list.stream().sorted().forEach(System.out::println);// aaa dddd ff
 		System.out.println("=====");
        //给sorted函数传入一个lambda表达式
        //1.自定义排序规则,按照字符串的长度【升序】排序,也就是字符串长度最短的排在最前面
        list.stream().sorted((s1,s2)->s1.length()-s2.length()).forEach(System.out::println);//ff aaa dddd
 		System.out.println("=====");
        //2.自定义排序规则,按照字符串的长度【降序】排序,也就是字符串长度最长的排在最前面
        list.stream().sorted((s1,s2)->s2.length()-s1.length()).forEach(System.out::println);//dddd aaa ff

    }
}

//对象排序
public class Employee {
    private String name;
    private Integer salary;
    public Employee(String name,Integer salary) {
        this.name = name;
        this.salary = salary;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getSalary() {
        return salary;
    }
    public void setSalary(Integer salary) {
        this.salary = salary;
    }

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

}

//测试类
public class Test {
    public static void main(String[] args) {
            List<Employee> list = Arrays.asList(
                    new Employee("Tom",1000),
                    new Employee("Jack",900),
                    new Employee("John",1300),
                    new Employee("Jack",2000)
            );
            //自定义排序规则,先按照名称【升序】,如果名称相同,再按照工资【降序】
            list.stream().sorted((e1,e2)->{
                if(e1.getName().equals(e2.getName())){
                    return e2.getSalary()-e1.getSalary();
                }else{
                    return e1.getName().compareTo(e2.getName());
                }
            }).forEach(System.out::println);

            //输出结果:
            //        Employee{name='Jack', salary=2000}
            //        Employee{name='Jack', salary=900}
            //        Employee{name='John', salary=1300}
            //        Employee{name='Tom', salary=1000}

            //打印原始列表,看看是否被改变,注意我们通过stream进行排序操作,原始的列表元素顺序没有变化,也就是说我们没有修改原始的list
            System.out.println(list);

            //Stream排序和集合本身的排序方法对比

            //我们使用List接口本身的sort方法再来排序一下看看
            list.sort((e1,e2)->{
                if(e1.getName().equals(e2.getName())){
                    return e2.getSalary()-e1.getSalary();
                }else{
                    return e1.getName().compareTo(e2.getName());
                }
            });
            //排序后再次打印一下list本身,可以发现,list本身元素的顺序被修改过了
            System.out.println(list);
        }
}

5.2.4.消费peek

peek:如同于map,能得到流中的每一个元素。但map接收的是一个Function表达式,有返回值;而peek接收的是Consumer表达式,没有返回值。

//为Tom增加500工资
public class Test {
    public static void main(String[] args) {
        List<Employee> list = Arrays.asList(
                new Employee("Tom",1000),
                new Employee("John",1300),
                new Employee("Jack",2000)
        );
        //如果是Tom,工资增加500
        list.stream().peek(e->{
            if("Tom".equals(e.getName())){
                e.setSalary(500+e.getSalary());
            }
        }).forEach(System.out::println);
        //输出结果
//        Employee{name='Tom', salary=1500}
//        Employee{name='John', salary=1300}
//        Employee{name='Jack', salary=2000}
    }
}

5.3. 终止操作

5.3.1.匹配
  • allMatch:流中所有的元素都匹配,返回true,否则返回false
  • noneMatch:流中没有任何的元素匹配,返回true,否则返回false
  • anyMatch:流中只要有任何一个元素匹配,返回true,否则返回false
  • findFirst:返回流的第一个元素
  • findAny:返回流中的任意元素
public class Test {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(2, 1, 3, 4, 5);

        //流中所有的元素都匹配,返回true,否则返回false
        boolean allMatch = list.stream().allMatch(e -> {
            System.out.println(e);
            return e > 10;
        }); //false
        System.out.println("allMatch:"+allMatch);
        //流中没有任何的元素匹配,返回true,否则返回false
        boolean noneMatch = list.stream().noneMatch(e -> {
            System.out.println(e);
            return e > 10;
        }); //true
        System.out.println("noneMatch:"+noneMatch);
        //流中只要有任何一个元素匹配,返回true,否则返回false
        boolean anyMatch = list.stream().anyMatch(e -> {
            System.out.println(e);
            return e > 1;
        });  //true
        System.out.println("anyMatch:"+anyMatch);
        //返回流的第一个元素
        Integer findFirst = list.stream().findFirst().get(); //2
        System.out.println("findFirst"+findFirst);
        //返回流中的任意元素,因为是串行流stream()所以返回的都是2
        Integer findAny = list.stream().findAny().get(); //2
        System.out.println("findAny:"+findAny);
        //返回流中的任意元素,这里使用并行流
        Integer findAny2 = list.parallelStream().findAny().get(); 
        System.out.println("findAny2:"+findAny2);

    }
}

5.3.2.聚合
  • count:计算元素总的数量
  • max:找出最大的元素
  • min:找出最小元素
public class Test {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

        //计算元素总的数量
        long count = list.stream().count(); //5
        System.out.println(count);
        //找出最大的元素(需要传入Lambda比较器)
        Integer max = list.stream().max(Integer::compareTo).get(); //5
        System.out.println(max);
        //找出最小元素(需要传入Lambda比较器)
        Integer min = list.stream().min(Integer::compareTo).get(); //1
        System.out.println(min);
    }
}
5.3.3.归约

在java.util.stream.Stream接口中,reduce有下面三个重载的方法

/**
第一次执行时,accumulator函数的第一个参数为流中的第一个元素,第二个参数为流中元素的第二个元素;第二次执行时,第一个参数为第一次函数执行的结果,第二个参数为流中的第三个元素;依次类推。
*/
Optional<T> reduce(BinaryOperator<T> accumulator);

/**
流程跟上面一样,只是第一次执行时,accumulator函数的第一个参数为identity,而第二个参数为流中的第一个元素。
*/
T reduce(T identity, BinaryOperator<T> accumulator);

/**
在串行流(stream)中,该方法跟第二个方法一样,即第三个参数combiner不会起作用。
在并行流(parallelStream)中,我们知道流被fork join创建出多个线程进行执行,此时每个线程的执行流程就跟第二个方法reduce(identity,accumulator)一样,而第三个参数combiner函数,则是将每个线程的执行结果当成一个新的流,然后使用第一个方法reduce(accumulator)流程进行归约。
*/
<U> U reduce(U identity,
             BiFunction<U, ? super T, U> accumulator,
             BinaryOperator<U> combiner);

归约应用举例


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

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        Integer v = list.stream().reduce((a1, a2) -> a1 + a2).get();
        System.out.println("reduce计算v="+v);   // 55



        Integer v1 = list.stream().reduce(10, (a1, a2) -> a1 + a2);
        System.out.println("reduce计算v1="+v1);  //65


        Integer v2 = list.stream().reduce(0,
                (a1, a2) -> {
                    return a1 + a2;
                },
                (a1, a2) -> {
                    return 1000; //第二个表达式在串行流中无效,这里返回1000测试
                });
        System.out.println("reduce计算v2="+v2); 


        //并行流reduce传三个参数
        Integer v3 = list.parallelStream().reduce(0,
                (a1, a2) -> {
                    System.out.println(Thread.currentThread().getName()+":parallelStream accumulator: a1:" + a1 + "  a2:" + a2);
                    return a1 + a2;
                },
                (a1, a2) -> {
                    System.out.println(Thread.currentThread().getName()+":parallelStream combiner: a1:" + a1 + "  a2:" + a2);
                    return a1 + a2;
                });

        System.out.println("并行流reduce计算v3=:"+v3);



    }
}
5.3.4.收集collect

collect:接收一个Collector实例,将流中元素收集成另外一个数据结构

Collectors提供的收集器

方法含义说明
toList将流中的元素收集到一个List中
toSet将流中的元素收集到一个Set中
toCollection将流中的元素收集到一个Collection中
toMap将流中的元素映射收集到一个Map中
counting统计流中的元素个数
summingInt计算流中指定int字段的累加总和。针对不同类型的数字类型,有不同的方法,比如summingDouble等
averagingInt计算流中指定int字段的平均值。针对不同类型的数字类型,有不同的方法,比如averagingLong等
joining将流中所有元素(或者元素的指定字段)字符串值进行拼接,可以指定拼接连接符,或者首尾拼接字符
maxBy根据给定的比较器,选择出值最大的元素
minBy根据给定的比较器,选择出值最小的元素
groupingBy根据给定的分组函数的值进行分组,输出一个Map对象
partitioningBy根据给定的分区函数的值进行分区,输出一个Map对象,且key始终为布尔值类型
collectingAndThen包裹另一个收集器,对其结果进行二次加工转换
reducing从给定的初始值开始,将元素进行逐个的处理,最终将所有元素计算为最终的1个值输出
<R, A> R collect(Collector<? super T, A, R> collector);

应用举例:

//创建一个Person类
public class Person {
    private String name;
    private String sex;
    private Integer age;
    public Person(String name, String sex, Integer age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
    }

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

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
}
public class Test {


    public static void main(String[] args) {
        //1.collect(Collectors.toList()) 把流转换成一个列表(允许重复值)
        Stream<String> stringStream = Stream.of("aa","bb","dd","ee","bb");
        List<String> listResult = stringStream.collect(Collectors.toList());
        System.out.println(listResult);//[aa, bb, dd, ee, bb]

        //2.collect(Collectors.toSet()) 把流转换成一个集合(去重)
        Stream<String> stringStream1 = Stream.of("aa","bb","dd","ee","bb");
        Set<String> setResult = stringStream1.collect(Collectors.toSet());
        System.out.println(setResult);//[aa, bb, dd, ee]

        //3.collect(Collectors.toCollection(LinkedList::new)) 把流转换成一个指定的集合类型(LinkedList)
        Stream<String> stringStream2 = Stream.of("aa","bb","dd","ee","bb");
        LinkedList<String> linkedListResult = stringStream2.collect(Collectors.toCollection(LinkedList::new));
        System.out.println(linkedListResult);//[aa, bb, dd, ee, bb]

        //4.collect(Collectors.toCollection(ArrayList::new)) 把流转换成一个指定的集合类型(ArrayList)
        Stream<String> stringStream3 = Stream.of("aa","bb","dd","ee","bb");
        ArrayList<String> arrayListResult = stringStream3.collect(Collectors.toCollection(ArrayList::new));
        System.out.println(arrayListResult);//[aa, bb, dd, ee, bb]

        //5.collect(Collectors.toCollection(TreeSet::new)) 把流转换成一个指定的集合类型(TreeSet)
        Stream<String> stringStream4 = Stream.of("aa","bb","dd","ee","bb");
        TreeSet<String> treeSetResult = stringStream4.collect(Collectors.toCollection(TreeSet::new));
        System.out.println(treeSetResult);//[aa, bb, dd, ee]

        //6.collect(Collectors.joining()) 使用joining拼接流中的元素
        Stream<String> stringStream5 = Stream.of("A","B","C","D","E");
        String result5 = stringStream5.collect(Collectors.joining());
        System.out.println(result5);//ABCDE

        //7.collect(Collectors.joining("-")) 使用joining拼接流中的元素并指定分隔符
        Stream<String> stringStream6 = Stream.of("A","B","C","D","E");
        String result6 = stringStream6.collect(Collectors.joining("-"));
        System.out.println(result6);//A-B-C-D-E

        //7.collect(Collectors.joining("-","<",">")) 使用joining拼接流中的元素并指定分隔符
        Stream<String> stringStream7 = Stream.of("A","B","C","D","E");
        String result7 = stringStream7.collect(Collectors.joining("-","<",">"));
        System.out.println(result7);//<A-B-C-D-E>



        //8.collect(Collectors.groupingBy(Person::getSex) 对person流按照性别进行分组
        Stream<Person> stringStream8 = Stream.of(
                new Person("zhangsan", "男", 10),
                new Person("lisi", "女", 11),
                new Person("wangwu", "男", 15),
                new Person("zhaoliu", "男", 12),
                new Person("xiaoming", "女", 13)
        );

        Map<String, List<Person>> resultMap1 = stringStream8.collect(Collectors.groupingBy(Person::getSex));
        System.out.println(resultMap1.toString());//{女=[Person{name='lisi', sex='女', age=11}, Person{name='xiaoming', sex='女', age=13}], 男=[Person{name='zhangsan', sex='男', age=10}, Person{name='wangwu', sex='男', age=15}, Person{name='zhaoliu', sex='男', age=12}]}

        //9.collect(Collectors.groupingBy(Person::getSex, Collectors.mapping(Person::getName, Collectors.toList())))
        // 对person流按照性别进行分组,并且把每一组对象流中人员的姓名转成列表
        Stream<Person> stringStream9 = Stream.of(
                new Person("zhangsan", "男", 10),
                new Person("lisi", "女", 11),
                new Person("wangwu", "男", 15),
                new Person("zhaoliu", "男", 12),
                new Person("xiaoming", "女", 13)
        );
        Map<String, List<String>> listMap = stringStream9.collect(
                Collectors.groupingBy(Person::getSex, Collectors.mapping(Person::getName, Collectors.toList()))
        );
        System.out.println(listMap.toString());//{女=[lisi, xiaoming], 男=[zhangsan, wangwu, zhaoliu]}

        //10.collect(Collectors.groupingBy(Person::getSex, Collectors.mapping(Person::getAge, Collectors.maxBy(Integer::compareTo))))
        // 对person流按照性别进行分组,并统计每一组中年龄最大的人的年龄
        Stream<Person> stringStream10 = Stream.of(
                new Person("zhangsan", "男", 10),
                new Person("lisi", "女", 11),
                new Person("wangwu", "男", 15),
                new Person("zhaoliu", "男", 12),
                new Person("xiaoming", "女", 13)
        );
        Map<String, Optional<Integer>> listMap1 = stringStream10.collect(
                Collectors.groupingBy(Person::getSex, Collectors.mapping(Person::getAge, Collectors.maxBy(Integer::compareTo)))
        );
        System.out.println(listMap1.toString());//{女=Optional[13], 男=Optional[15]}


        //11.collect(
        //           Collectors.groupingBy(Person::getName,
        //                                 Collectors.reducing(BinaryOperator.maxBy(Comparator.comparingInt(Person::getAge)))
        //   )
        //对person流按照性别进行分组,并统计每一组中年龄最大的人
        //这个案例使用了groupingBy和reducing组合
        Stream<Person> stringStream111 = Stream.of(
                new Person("zhangsan", "男", 10),
                new Person("lisi", "女", 11),
                new Person("zhangsan", "男", 15),
                new Person("zhaoliu", "男", 12),
                new Person("lisi", "女", 13)
        );
        Map<String, Optional<Person>> resultMap111 = stringStream111.collect(
                Collectors.groupingBy(Person::getSex,
                        Collectors.reducing(BinaryOperator.maxBy(Comparator.comparingInt(Person::getAge)))
                )
        );
        System.out.println(resultMap111.toString());//{女=Optional[Person{name='lisi', sex='女', age=13}], 男=Optional[Person{name='zhangsan', sex='男', age=15}]}


        //12.collect(Collectors.groupingBy(Person::getSex,
        //                 Collectors.reducing(0,Person::getAge,(x,y)->x+y)
        //          )
        //        )
        //对person流按照性别进行分组,并统计每一组人员年龄和
        Stream<Person> stringStream121 = Stream.of(
                new Person("zhangsan", "男", 10),
                new Person("lisi", "女", 11),
                new Person("zhangsan", "男", 15),
                new Person("zhaoliu", "男", 12),
                new Person("lisi", "女", 13)
        );
        Map<String, Integer> resultMap121 = stringStream121.collect(
                Collectors.groupingBy(Person::getSex,
                        Collectors.reducing(0,Person::getAge,(x,y)->x+y)
                )
        );
        /*上面这段如果不使用reducing,还可以用下面这中方式完成
        Map<String, Integer> resultMap121 = stringStream121.collect(
                             Collectors.groupingBy(Person::getSex, Collectors.summingInt(Person::getAge))
        );*/
        System.out.println(resultMap121.toString());//{女=24, 男=37}



        //12.collect(Collectors.groupingBy(Person::getName,  TreeMap::new, Collectors.toList()))
        // 对person流按照name进行分组,结果转成TreeMap,key是name,value是这个组的对象列表
        //groupingBy的第一个参数就是获取分组的属性,第二个参数指定返回类型,第三个是把每个分组里面的对象元素转成一个列表
        Stream<Person> stringStream11 = Stream.of(
                new Person("zhangsan", "男", 10),
                new Person("lisi", "女", 11),
                new Person("zhangsan", "男", 15),
                new Person("lisi", "男", 12),
                new Person("xiaoming", "女", 13)
        );
        TreeMap<String, List<Person>> listMap2 = stringStream11.collect(
                Collectors.groupingBy(Person::getName,  TreeMap::new, Collectors.toList())
        );
        System.out.println(listMap2.toString());//{lisi=[Person{name='lisi', sex='女', age=11}, Person{name='lisi', sex='男', age=12}], xiaoming=[Person{name='xiaoming', sex='女', age=13}], zhangsan=[Person{name='zhangsan', sex='男', age=10}, Person{name='zhangsan', sex='男', age=15}]}


        //13.collect(Collectors.collectingAndThen(
        //                Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Person::getName))),
        //                ArrayList::new))
        //对Person流先通过TreeSet去重,去重的比较属性是name,然后在把这个TreeSet中的元素转换成ArrayList
        Stream<Person> stringStream12 = Stream.of(
                new Person("lisi", "女", 11),
                new Person("lisi", "女", 11),
                new Person("zhangsan", "男", 15),
                new Person("zhangsan", "男", 15),
                new Person("xiaoming", "女", 13)
        );

        List<Person> list = stringStream12.collect(Collectors.collectingAndThen(
                Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Person::getName))),
                ArrayList::new));//这里的ArrayList::new等同于pset->new ArrayList(pset),是把前面生成的TreeSet赋值给ArrayList构造函数
        System.out.println(list);//[Person{name='lisi', sex='女', age=11}, Person{name='xiaoming', sex='女', age=13}, Person{name='zhangsan', sex='男', age=15}]


        //14.collect(Collectors.groupingBy(Person::getName, Collectors.summingInt(Person::getAge)))
        // 对person流按照姓名进行分组,并对每一个组内的人员的年龄求和
        Stream<Person> stringStream13 = Stream.of(
                new Person("zhangsan", "男", 10),
                new Person("zhangsan", "女", 11),
                new Person("lisi", "男", 15),
                new Person("zhaoliu", "男", 12),
                new Person("lisi", "女", 13)
        );

        Map<String, Integer> resultMap2 = stringStream13.collect(Collectors.groupingBy(Person::getName, Collectors.summingInt(Person::getAge)));
        System.out.println(resultMap2.toString());//{lisi=28, zhaoliu=12, zhangsan=21}

        //15.collect(Collectors.groupingBy(Person::getName, Collectors.averagingInt(Person::getAge)))
        // 对person流按照姓名进行分组,并对每一个组内的人员的年龄求平均值
        Stream<Person> stringStream14 = Stream.of(
                new Person("zhangsan", "男", 10),
                new Person("zhangsan", "女", 11),
                new Person("lisi", "男", 15),
                new Person("zhaoliu", "男", 12),
                new Person("lisi", "女", 13)
        );

        Map<String, Double> resultMap3 = stringStream14.collect(Collectors.groupingBy(Person::getName, Collectors.averagingInt(Person::getAge)));
        System.out.println(resultMap3.toString());//{lisi=14.0, zhaoliu=12.0, zhangsan=10.5}

        //16.parallel().collect(
        //                Collectors.groupingByConcurrent(Person::getSex, Collectors.summingInt(Person::getAge))
        //        )
        //使用并行流,把人员按照性别分组,计算每一组中的年龄和,返回的类型是ConcurrentMap,保证线程安全
        Stream<Person> stringStream16 =  Stream.of(
                new Person("zhangsan", "男", 10),
                new Person("zhangsan", "女", 11),
                new Person("lisi", "男", 15),
                new Person("zhaoliu", "男", 12),
                new Person("zhaoliu", "男", 16),
                new Person("zhaoliu", "男", 17),
                new Person("lisi", "女", 13));
        ConcurrentMap<String, Integer> resultMap4 = stringStream16.parallel().collect(
                Collectors.groupingByConcurrent(Person::getSex, Collectors.summingInt(Person::getAge))
        );
        System.out.println(resultMap4.toString());//{女=24, 男=70}

        //17.collect(Collectors.partitioningBy(p -> p.getAge() > 12))
        //把流中元素根据年龄是否大于12分成两组,保存在Map中,key是true或者false,value是对象列表
        Stream<Person> stringStream17 =  Stream.of(
                new Person("zhangsan", "女", 11),
                new Person("wangwu", "男", 10),
                new Person("lisi", "男", 15),
                new Person("zhaoliu", "女", 13));
        Map<Boolean, List<Person>> resultMap5 = stringStream17.collect(Collectors.partitioningBy(p -> p.getAge() > 12));
        System.out.println(resultMap5.toString());//{false=[Person{name='zhangsan', sex='女', age=11}], true=[Person{name='lisi', sex='男', age=15}, Person{name='lisi', sex='女', age=13}]}

        //18.collect(Collectors.partitioningBy(p -> p.getAge() > 12,Collectors.summingInt(Person::getAge)))
        //把流中元素根据年龄是否大于12分成两组,保存在Map中,key是true或者false,每一组的年龄的和
        Stream<Person> stringStream18 =  Stream.of(
                new Person("zhangsan", "女", 11),
                new Person("wangwu", "男", 10),
                new Person("lisi", "男", 15),
                new Person("zhaoliu", "女", 13));
        Map<Boolean, Integer> resultMap6 = stringStream18.collect(Collectors.partitioningBy(p -> p.getAge() > 12,Collectors.summingInt(Person::getAge)));
        System.out.println(resultMap6.toString());//{false=21, true=28}

        //19.Collectors.toMap:有两个参数的toMap方法,流中对象的key是不允许存在相同的,否则报错
        //toMap的第二个参数需要创建一个列表,并且key对应的元素对象放入列表
        Stream<Person> stringStream19 =  Stream.of(
                new Person("zhangsan", "女", 11),
                new Person("zhaoliu", "女", 13));
        Map<String, List<Person>> resultMap7 = stringStream19.collect(Collectors.toMap(Person::getName, p -> {
            List<Person> personList = new ArrayList<>();
            personList.add(p);
            return personList;
        }));
        System.out.println(resultMap7.toString());//{zhaoliu=[Person{name='zhaoliu', sex='女', age=13}], zhangsan=[Person{name='zhangsan', sex='女', age=11}]}

        //20.Collectors.toMap:有两个参数的toMap方法,流中对象的key是不允许存在相同的,否则报错
        //toMap的第二个参数直接使用流中的对象作为key所对应的value
        Stream<Person> stringStream20 = Stream.of(
                new Person("zhangsan", "女", 11),
                new Person("zhaoliu", "女", 13));
        Map<String, Person> resultMap8 = stringStream20.collect(Collectors.toMap(Person::getName, p -> p));
        System.out.println(resultMap8.toString());//{zhaoliu=Person{name='zhaoliu', sex='女', age=13}, zhangsan=Person{name='zhangsan', sex='女', age=11}}


        //21.Collectors.toMap:有三个参数的toMap方法,流中对象的key是允许存在相同的,
        // 第三个参数表示key重复的处理方式(这里是把重复的key对应的value用新的替换老的)
        //toMap的第二个参数直接使用流中的对象作为key所对应的value
        Stream<Person> stringStream21 = Stream.of(
                new Person("zhangsan", "女", 11),
                new Person("zhangsan", "男", 12),
                new Person("zhaoliu", "男", 13)
        );

        Map<String, Person> resultMap9 = stringStream21.collect(
                Collectors.toMap(Person::getName,
                        p -> p,
                        (oldPerson,newPerson)->newPerson
                )
        );
        System.out.println(resultMap9.toString());//{zhaoliu=[Person{name='zhaoliu', sex='男', age=13}], zhangsan=[Person{name='zhangsan', sex='女', age=11}, Person{name='zhangsan', sex='女', age=11}]}


        //22.Collectors.toMap:有三个参数的toMap方法,流中对象的key是允许存在相同的,第三个参数表示key重复的处理方式(这里是把重复的key对应的value放入列表)
        //toMap的第二个参数直接使用流中的对象作为key所对应的value
        Stream<Person> stringStream22 = Stream.of(
                new Person("zhangsan", "女", 11),
                new Person("zhangsan", "女", 11),
                new Person("zhaoliu", "男", 13)
        );

        Map<String, List<Person>> resultMap10 = stringStream22.collect(
                Collectors.toMap(Person::getName,
                        p -> {
                            List<Person> personList = new ArrayList<>();
                            personList.add(p);
                            return personList;
                        },
                        (oldList,newList)->{
                            oldList.addAll(newList);
                            return oldList;
                        })
        );
        System.out.println(resultMap10.toString());//{zhaoliu=[Person{name='zhaoliu', sex='男', age=13}], zhangsan=[Person{name='zhangsan', sex='女', age=11}, Person{name='zhangsan', sex='女', age=11}]}


        //23.Collectors.toMap:有四个参数的toMap方法,流中对象的key是允许存在相同的,
        //toMap的第二个参数直接使用流中的对象作为key所对应的value
        //第三个参数表示key重复的处理方式(这里是把重复的key对应的value放入列表)
        //第四个参数可以指定一个返回的Map具体类型
        Stream<Person> stringStream23 = Stream.of(
                new Person("zhangsan", "女", 11),
                new Person("zhangsan", "女", 11),
                new Person("zhaoliu", "男", 13)
        );

        Map<String, List<Person>> resultMap11 = stringStream23.collect(
                Collectors.toMap(Person::getName,
                        p -> {
                            List<Person> personList = new ArrayList<>();
                            personList.add(p);
                            return personList;
                        },
                        (oldList,newList)->{
                            oldList.addAll(newList);
                            return oldList;
                        },
                        LinkedHashMap::new
                )

        );
        System.out.println(resultMap11.toString());//{zhangsan=[Person{name='zhangsan', sex='女', age=11}, Person{name='zhangsan', sex='女', age=11}], zhaoliu=[Person{name='zhaoliu', sex='男', age=13}]}


        //24.Collectors.summarizingInt((a -> a.getAge()))
        //针对Integer类型的元素进行汇总计算
        //得到1、元素数量 2、元素的和 3、元素的最大值 4、元素的最小值 5、平均值
        Stream<Person> personStream24=Stream.of(
                        new Person("zhangsan", "女", 11),
                        new Person("zhangsan", "女", 25),
                        new Person("zhaoliu", "男", 13)
        );
        IntSummaryStatistics intSummaryStatistics = personStream24.collect(Collectors.summarizingInt((a -> a.getAge())));
        System.out.println(intSummaryStatistics);//IntSummaryStatistics{count=3, sum=49, min=11, average=16.333333, max=25}
    }
}

1. 累加summingLong的用法
		// 采购数量
        Long totalNum = orderDO.getProducts().stream().map(s -> s.getQuantity()).collect(Collectors.summingLong(i->i.longValue()));

6. 补充方法:

在这里插入图片描述

JDK 8新增的Optional类

1. 以前对null的处理方式

@Test 
public void test01() { 
	String userName = "凤姐"; 
	// String userName = null; 
	if (userName != null) { 
		System.out.println("用户名为:" + userName); 
	} else { 
		System.out.println("用户名不存在"); 
	}
}

2. Optional类介绍

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

查,防止NullPointerException。

3. Optional的基本使用

Optional类的创建方式:

Optional.of(T t) : 创建一个 Optional 实例 
Optional.empty() : 创建一个空的 Optional 实例 
Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例

Optional类的常用方法:

isPresent() : 判断是否包含值,包含值返回true,不包含值返回false 
get() : 如果Optional有值则将其返回,否则抛出NoSuchElementException 
orElse(T t) : 如果调用对象包含值,返回该值,否则返回参数t 
orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值 
map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
@Test 
public void test02() { 
	// Optional<String> userNameO = Optional.of("凤姐"); 
	// Optional<String> userNameO = Optional.of(null); 
	// Optional<String> userNameO = Optional.ofNullable(null); 
	Optional<String> userNameO = Optional.empty(); 
	// isPresent() : 判断是否包含值,包含值返回true,不包含值返回false。 
	if (userNameO.isPresent()) { 
		// get() : 如果Optional有值则将其返回,否则抛出NoSuchElementException。 
		String userName = userNameO.get(); 
		System.out.println("用户名为:" + userName); 
	} else { 
		System.out.println("用户名不存在"); 
	} 
}

4. Optional的高级使用

@Test 

public void test03() { 
	Optional<String> userNameO = Optional.of("凤姐"); 
	// Optional<String> userNameO = Optional.empty(); 
	// 存在做的什么 
	// userNameO.ifPresent(s -> System.out.println("用户名为" + s)); 
	// 存在做的什么,不存在做点什么 
	userNameO.ifPresentOrElse(s -> System.out.println("用户名为" + s), () -> System.out.println("用户名不存在")); 
}

@Test 

public void test04() { 
	// Optional<String> userNameO = Optional.of("凤姐"); 
	Optional<String> userNameO = Optional.empty(); 
	// 如果调用对象包含值,返回该值,否则返回参数t 
	System.out.println("用户名为" + userNameO.orElse("null"));
	// 如果调用对象包含值,返回该值,否则返回参数Supplier得到的值 
	String s1 = userNameO.orElseGet(() -> {return "未知用户名";}); 
	System.out.println("s1 = " + s1); 
}
@Test 
public void test05() { 
	// User u = new User("凤姐", 18); 
	// User u = new User(null, 18); 
	// User u = null; 
	// System.out.println(getUpperCaseUserName1(u)); 
	// 我们将可能会为null的变量构造成Optional类型 
	// User u = new User("凤姐", 18); 
	User u = new User(null, 18); 
	Optional<User> uO = Optional.of(u); 
	System.out.println(getUpperCaseUserName2(uO)); 
}

public String getUpperCaseUserName2(Optional<User> uO) { 
	return uO.map(u -> u.getUserName()) 
				.map(name -> name.toUpperCase()) 
				.orElse("null"); 
}

/*public String getUpperCaseUserName1(User u) { 
if (u != null) { 
String userName = u.getUserName(); 
if (userName != null) { 
return userName; 
} else { 
return null; 
} 
} else { 
return null; 
} 
}*/

小结
Optional是一个可以为null的容器对象。orElse,ifPresent,ifPresentOrElse,map等方法避免对null的判断,写出更加优雅的代码。

复习一下java中的时间

Data

Date类的两个常用方法(getTime和toString)

Date(long date),即Date类的形参是long类型的date日期参数

(1)getTime():返回自 1970 年 1 月 1 日 00:00:00 GMT 以此 Date 对象表示的毫秒数。

(2)toString():把Date 对象转换为以下形式的 String: dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat),zzz是时间标准。

pattern格式的写法总结:
yyyy:年
MM:月
dd:日
hh:1~12小时制(1-12)
HH24小时制(0-23)
mm:分
ss:秒
S:毫秒
E:星期几
D:一年中的第几天
F:一月中的第几个星期(会把这个月总共过的天数除以7)
w:一年中的第几个星期
W:一月中的第几星期(会根据实际情况来算)
a:上下午标识
k:和HH差不多,表示一天24小时制(1-24)K:和hh差不多,表示一天12小时制(0-11)。
z:表示时区

SimpleDateFormat

SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm");
String createTime = document.getElementsByClass("article-meta-time").eq(0).text().trim();
Long createTimeM = sdf.parse(createTime).getTime();
String time = sdf.format(date);
LocalDateTime beginDate = LocalDateTime.now();

//        beginDate = beginDate.minus(1, ChronoUnit.MONTHS);
        beginDate = beginDate.withDayOfMonth(1);

        BigDecimal monthPriceIndex = orderRepository.getMonthSellIndexs();
        BigDecimal priceIndex = orderRepository.getSellIndexs(beginDate);
        BigDecimal subtract = priceIndex.subtract(monthPriceIndex);
        BigDecimal divide = subtract.divide(priceIndex,BigDecimal.ROUND_CEILING);

// 判断已完成的订单创建是否已经超过30天,超过则不显示售后按钮
            if ((order.getOuterStatus().equals(OrderOuterStatusEnum.ACCOMPLISHED.getCode()) && order.getBuyerInnerStatus().equals(BuyerInnerStatusEnum.ACCOMPLISHED.getCode()))) {
                LocalDateTime startTime = LocalDateTime.now();
                Duration duration = Duration.between(order.getSubmitTime(), startTime);
                Long days = duration.toDays();
                Long toDay = 30L;
                if (days > toDay) {
                    queryVO.setShowAfterSales(false);
                }
            }
  private static final ZoneId ZONE_ID = ZoneOffset.systemDefault();
    /**
     * 8小时的秒数
     */
    private static final int OFFSET = 8 * 60 * 60;
 
 
    /**
     * LocalDateTime -> 秒
     *
     * @param localDateTime localDateTime
     * @return 秒
     */
    public static long toSeconds(LocalDateTime localDateTime) {
        return localDateTime.atZone(ZONE_ID).toEpochSecond();
    }
 
    /**
     * LocalDateTime -> 毫秒
     *  竟然加了8小时
     *
     * @param localDateTime localDateTime
     * @return 毫秒
     */
    public static long toMilliSecond(LocalDateTime localDateTime) {
        // 比标准实际慢8小时,就是当前的时间了。
        return localDateTime.toInstant(ZoneOffset.ofTotalSeconds(OFFSET)).toEpochMilli();
    }
 
    /**
     * LocalDateTime 转秒和毫秒
     */
    @Test
    public void toSecondsAndMilliSecond() {
        LocalDateTime now = LocalDateTime.now();
        System.out.println(TimeUtils.toSeconds(now));
        System.out.println(TimeUtils.toMilliSecond(now));
    }

判断json对象是Map还是List

JSONObject object = JSONObject.parseObject(res);
        if (object instanceof List) {
            System.out.println("是list集合");
        }
        if (object instanceof Map) {
            System.out.println("是Map集合");
        }

hutool

各种工具DateUtil,strUtil等挺好用

BeanUtil.copyProperties(c, commodityDetailResponse);//复制bean对象属性(源对象,目标对象)

JDK 8新的日期和时间 API

1. 了解旧版日期时间 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类,但他们同样存在上述所有的问题。

2. 新日期时间 API介绍

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

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

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

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

3. JDK 8的日期和时间类

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

3.1 LocalDate转换为LocalDatetime

LocalDate localDate = LocalDate.now();
LocalDateTime localDateTime = localDate.atStartOfDay();

这样就可以将当前的LocalDate对象转换为对应的LocalDateTime对象了。在转换过程中,会将LocalDate的日期部分置为时间的0点,即00:00:00。

3.2 LocalDateTime转LocalDate

// LocalDateTime转LocalDate
 LocalDate data = LocalDateTime.now().toLocalDate()
// LocalDate:获取日期时间的信息。格式为 2022-09-29
    @Test
    public void test01() {
        // 创建指定日期
        LocalDate fj = LocalDate.of(1985, 9, 23);
        System.out.println("fj = " + fj); // 1985-09-23
        // 得到当前日期
        LocalDate nowDate = LocalDate.now();
        System.out.println("nowDate = " + nowDate); // 2022-09-29
        // 获取日期信息
        System.out.println("年: " + nowDate.getYear());
        System.out.println("月: " + nowDate.getMonthValue());
        System.out.println("日: " + nowDate.getDayOfMonth());
        System.out.println("星期: " + nowDate.getDayOfWeek());
    }

    // LocalTime类: 获取时间信息。格式为 18:03:50.492
    @Test
    public void test02() {
        // 得到指定的时间
        LocalTime time = LocalTime.of(12, 15, 28, 129_900_000);
        System.out.println("time = " + time);
        // 得到当前时间
        LocalTime nowTime = LocalTime.now();
        System.out.println("nowTime = " + nowTime);
        // 获取时间信息
        System.out.println("小时: " + nowTime.getHour());
        System.out.println("分钟: " + nowTime.getMinute());
        System.out.println("秒: " + nowTime.getSecond());
        System.out.println("纳秒: " + nowTime.getNano());
    }

    // LocalDateTime类: 获取日期时间信息。格式为 2022-09-29T18:05:19.360
    @Test
    public void test03() {
        LocalDateTime fj = LocalDateTime.of(1985, 9, 23, 9, 10, 20);
        System.out.println("fj = " + fj); // 1985-09-23T09:10:20
        // 得到当前日期时间
        LocalDateTime now = LocalDateTime.now();
        System.out.println("now = " + now); // 2022-09-29T18:05:19.360
        System.out.println(now.getYear());
        System.out.println(now.getMonthValue());
        System.out.println(now.getDayOfMonth());
        System.out.println(now.getHour());
        System.out.println(now.getMinute());
        System.out.println(now.getSecond());
        System.out.println(now.getNano());
    }

对日期时间的修改,对已存在的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));

        // 再当前对象的基础上加上或减去指定的时间
        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));
    }
    // LocalDateTime类: 对日期时间的判断,isBefore()和isAfter()
    @Test
    public void test06() {
        LocalDateTime dt1 = LocalDateTime.parse("2025-12-05T12:45:30"); 
  
        System.out.println(dt1);
  
        LocalDateTime dt2 = LocalDateTime.parse("2022-12-05T12:45:30"); 
  
        System.out.println(dt2); 
  
        System.out.println(dt1.isAfter(dt2));//true
        System.out.println(dt1.isBefore(dt2));//false
	}

3.3 LocalDate转为时间戳

普通方法

LocalDate localDate = LocalDate.now();
long timestamp = localDate.atStartOfDay(ZoneOffset.ofHours(8)).toInstant().toEpochMilli();

4. JDK 8的时间格式化与解析

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

// 日期格式化
    @Test
    public void test04() {
        // 得到当前日期时间
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

        // 将日期时间格式化为字符串
        String format = now.format(formatter);
        System.out.println("format = " + format);
        String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
        System.out.println("keySuffix = " + keySuffix );

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

    }

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

6. JDK 8的计算日期时间差类

Duration/Period类: 计算日期时间差。

  1. Duration:用于计算2个时间(LocalTime,时分秒)的距离
  2. Period:用于计算2个日期(LocalDate,年月日)的距离
  3. Temporal :可用于计算两个时间差(可精准到到任何时间单位,推荐使用)
    // 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());
//        System.out.println("相差的秒数:" + duration.toSeconds());
        System.out.println("相差的毫秒秒数:" + duration.toMillis());
        // 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());

    }

        Temporal start = LocalDate.of(2022, 3, 9);;
        Temporal end = LocalDate.now();
        // 方法返回为相差月份
        long l = ChronoUnit.MONTHS.between(start, end);
        System.out.println(l);
        
        Temporal start1 = LocalDateTime.of(2022, 3, 9,5,12);
        Temporal end2 = LocalDateTime.now();
        // 方法返回为相差小时
        long l1 = ChronoUnit.HOURS.between(start1, end2);
        System.out.println(l1);

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

        // 2.操作带时区的类
        // now(Clock.systemUTC()): 创建世界标准时间
        ZonedDateTime bz = ZonedDateTime.now(Clock.systemUTC());
        System.out.println("bz = " + bz);
        // now(): 使用计算机的默认的时区,创建日期时间
        ZonedDateTime now1 = ZonedDateTime.now();
        System.out.println("now1 = " + now1); // 2019-10-19T16:19:44.007153500+08:00[Asia/Shanghai]

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

    }

8. JDK 8的时间校正器

有时我们可能需要获取例如:将日期调整到“下一个月的第一天”等操作。可以通过时间校正器来进行。

  • TemporalAdjuster : 时间校正器。
  • TemporalAdjusters : 该类通过静态方法提供了大量的常用TemporalAdjuster的实现。
// TemporalAdjuster类:自定义调整时间
    @Test
    public void test09() {
        LocalDateTime now = LocalDateTime.now();
        // 得到下一个月的第一天
        TemporalAdjuster firsWeekDayOfNextMonth = temporal -> {
            LocalDateTime dateTime = (LocalDateTime) temporal;
            LocalDateTime nextMonth = dateTime.plusMonths(1).withDayOfMonth(1);
            System.out.println("nextMonth = " + nextMonth);
            return nextMonth;
        };
        LocalDateTime nextMonth = now.with(firsWeekDayOfNextMonth);
        System.out.println("nextMonth = " + nextMonth);
    }

9. 小结

详细学习了新的日期是时间相关类,LocalDate表示日期,包含年月日,LocalTime表示时间,包含时分秒,LocalDateTime = LocalDate + LocalTime,时间的格式化和解析,通过DateTimeFormatter类型进行.

学习了Instant类,方便操作秒和纳秒,一般是给程序使用的.学习Duration/Period计算日期或时间的距离,还使用时间调整器方便的调整时间,学习了带时区的3个类ZoneDate/ZoneTime/ZoneDateTime
JDK 8新的日期和时间 API的优势:

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

复习正则表达式(非新特性)

过滤特殊字符

	String regEx = "\\pP|\\pS|\\s+";
    title = Pattern.compile(regEx).matcher(title).replaceAll("").trim();
    System.out.println("title = " + title);

Pattern类用于创建一个正则表达式,也可以说创建一个匹配模式,它的构造方法是私有的,不可以直接创建,但可以通过Pattern.complie(String regex)简单工厂方法创建一个正则表达式

Pattern p=Pattern.compile("\\w+");
p.pattern();// 返回 \w+
// pattern() 返回正则表达式的字符串形式,其实就是返回Pattern.complile(String regex)的regex参数

注意事项:
\s+是空格一个或者多个,不管在那个位置都能匹配

\pP 其中的小写 p 是 property 的意思,表示 Unicode 属性,用于 Unicode 正表达式的前缀。

大写 P 表示 Unicode 字符集七个字符属性之一:标点字符。

其他六个是

L:字母;

M:标记符号(一般不会单独出现);

Z:分隔符(比如空格、换行等);

S:符号(比如数学符号、货币符号等);

N:数字(比如阿拉伯数字、罗马数字等);

C:其他字符

上面这七个是属性,七个属性下还有若干个子属性,用于更进一步地进行细分。

Java 中用于 Unicode 的正则表达式数据都是由 Unicode 组织提供的。

Unicode 正则表达式标准(可以找到所有的子属性):http://www.unicode.org/reports/tr18/

各 Unicode 字符属性的定义,可以用一看看某个字符具有什么属性:http://www.unicode.org/Public/UNIDATA/UnicodeData.txt

这个文本文档一行是一个字符,第一列是 Unicode 编码,第二列是字符名,第三列是 Unicode 属性, 以及其他一些字符信息。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值