JDK8新特性

本文详细介绍了JDK8的新特性,包括Lambda表达式的概念、使用规则和优势,接口中的默认方法和静态方法,函数式接口如Supplier、Consumer、Function和Predicate的介绍,方法引用的用途和格式,Stream流的思想、获取方式及其常用方法如forEach、filter、map和reduce等,以及新时间日期API的改进和使用。通过对这些特性的深入探讨,帮助读者理解和掌握JDK8的升级亮点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

总结:

1.写list的高效for循环时.可以list.for 有自动提示
2.写lambda表达式时 Collections.sort(list,(Person o1,Person o2)->{
return o1.getAge() - o2.getAge();
}); 时,鼠标在o2.getAge()后面.return 自动显示

3.数组也可以遍历 数组.for
4.:: 方法引用
5.stream流的reduce方法 .var ----->Integer reduce
6. 计算字符串a出现的次数 map和reduce组合
7.

一 Lambda表达式

创建一个空的maven项目
在这里插入图片描述

1.需求分析:

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

package cn.tedu.jdk.lambda;

public class Demo01Lambda {
    public static void main(String[] args) {
        //开启一个新的线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("新线程中执行的代码:"+Thread.currentThread().getName());
            }
        }).start();
        System.out.println("主线程中的代码:"+Thread.currentThread().getName());
    }
}

代码分析:
1.Thread类需要一个Runnable接口作为参数.其中的run方法用来指定线程任务内容的核心
2.为了指定run方法,不得需要Runnable接口的实现类
3.为了省去定义一个Runnable实现类.,不得不使用匿名内部类
4.必须覆盖抽象run方法,方法名,方法参数,返回值类型,不得不重写一遍,而且不能出错
5.实际上,我们只关注方法体中的代码

2.Lambda表达式初体验

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

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

lambda表达式的优点:简化了匿名内部类的使用,语法更加简单
匿名内部类语法冗余,发现lambda表达式是简化匿名内部类的使用

3.Lambda语法规则

lambda省去了面向对象的条条框框,lambda语法规则有3部分组成

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

无参无返回值的lambda
定义一个接口,

package cn.tedu.jdk.lambda.service;

public interface UserService {
    void show(); //无参无返回值
}

然后创建主方法使用

package cn.tedu.jdk.lambda;

import cn.tedu.jdk.lambda.service.UserService;

public class Demo03Lambda {
    /**
     * (String[] args)->{}
     * @param args
     */
    public static void main(String[] args) {
        goShow(new UserService() {
            @Override
            public void show() {
                System.out.println("show 方法执行了...");
            }
        });
        System.out.println("-----------------");
        goShow(()->{
            System.out.println("lambda show方法 执行了...");
        });
    }
    public static void goShow(UserService userService){
        userService.show();
    }
}

输出:

show 方法执行了...
-----------------
lambda show方法 执行了...

lambda练习2

完成一个有参有返回值的lambda案例
创建一个Person对象

package cn.tedu.jdk.lambda.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

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

然后我们在main方法中,.List集合中保存多个person对象,然后对这些对象做根据age排序操作

package cn.tedu.jdk.lambda;

import cn.tedu.jdk.lambda.domain.Person;

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

public class Demo04Lambda {

    public static void main(String[] args) {
        List<Person> list = new ArrayList<Person>();
        list.add(new Person("周杰伦", 33, 175));
        list.add(new Person("刘德华", 50, 185));
        list.add(new Person("许攸", 46, 165));
        list.add(new Person("曹操", 30, 169));

//        Collections.sort(list, new Comparator<Person>() {
//            public int compare(Person o1, Person o2) {
//                return o1.getAge() - o2.getAge();
//            }
//        });
//        for (Person person : list) {
//            System.out.println(person);
//        }

        System.out.println("------------------");
        Collections.sort(list,(Person o1,Person o2)->{
            return o1.getAge() - o2.getAge();
        });
        for (Person person : list) {
            System.out.println(person);
        }
    }

}

我们发现在sort方法的第二个参数是一个Comparator接口的匿名内部类,且执行的方法有参数和返回值,我们可以改写为lambda表达式
输出结果

------------------
Person(name=曹操, age=30, height=169)
Person(name=周杰伦, age=33, height=175)
Person(name=许攸, age=46, height=165)
Person(name=刘德华, age=50, height=185)

4.@FunctionalInterface注解

使用lambda时,这个接口只能保证有一个抽象方法

package cn.tedu.jdk.lambda.service;

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

5.Lambda表达式的原理

在这里插入图片描述
lambda中的代码是在这个方法里面执行的
在lambda表达式编译的class文件中,cmd–>javap -c -p XXX.class指令
在这里插入图片描述

利用JDK自带工具javap 对字节码进行反汇编操作
javap -c -p XXX.class文件
-c 表示对代码进行反汇编
-p 显示所有的类和成员

在这个反编译的源码中我们看到了一个静态方法 lambda$main$0()

小结:

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

6.Lambda表达式省略写法

在lambda表达式标准写法基础上,可以省略的语法规则为:
1.小括号内的参数类型可以省略
2.如果小括号内有且仅有一个参数,则小括号可以省略
3.如果大括号内有且仅有一条语句,则可以同时省略大括号,return关键字及语句分号

package cn.tedu.jdk.lambda;

import cn.tedu.jdk.lambda.service.OrderService;
import cn.tedu.jdk.lambda.service.StudentService;

public class Demo05Lambda {
    public static void main(String[] args) {
        goStudent((String name, Integer age) -> {
            return name + age + "6666...";
        });
        //省略写法
        goStudent((name,age)->name + age + "6666...");
        System.out.println("--------------");

        goOrder((String name) -> {
            System.out.println("---->"+name);
            return 666;
        });
        //省略写法
        goOrder(name ->666);
    }

    public static void goStudent(StudentService studentService) {
        studentService.show("张三", 22);
    }

    public static void goOrder(OrderService orderService) {
        orderService.show("李四");
    }


}

7.Lambda表达式使用前提

lambda使用时有几个条件特别注意:
1.方法的参数或局部变量的类型必须为接口才能使用lambda
2.接口中有且仅有一个抽象方法 @FunctionalInterface

8.lambda和匿名内部类的对比

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

二 接口中新增的方法

JDK8中针对接口有做新增,在JDK8之前

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

在JDK8后,对接口做了增强,接口中可以有默认方法 和 静态方法

interface 接口名{
	静态常量;
	抽象方法;
	默认方法;
	静态方法;
}
默认方法
为什么要增加默认方法?

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

package cn.tedu.jdk.inter;

public interface Demo01Interface {
    public static void main(String[] args) {
        A b=new B();
        A c=new C();
    }
}
interface A{
    void test1();
    //接口中新增抽象方法,所有实现类都需要重写这个方法,不利于接口的扩展
    void test2();

}
class B implements A{
    @Override
    public void test1() {

    }

    @Override
    public void test2() {

    }
}
class C implements A{
    @Override
    public void test1() {

    }

    @Override
    public void test2() {

    }
}
接口中默认方法的格式?

接口中默认方法的语法格式

interface 接口名{
	修饰符 default 返回值类型 方法名(){方法体;}
}
package cn.tedu.jdk.inter;

public interface Demo01Interface {
    public static void main(String[] args) {
        A b=new B();
         b.test3();
        A c=new C();
    }
}
interface A{
    void test1();
    //接口中新增抽象方法,所有实现类都需要重写这个方法,不利于接口的扩展
    void test2();

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

    }

    @Override
    public void test2() {

    }

    @Override
    public String test3() {
        System.out.println("B 实现类重写了默认方法");
        return A.super.test3();
    }
}
class C implements A{
    @Override
    public void test1() {

    }

    @Override
    public void test2() {

    }
}
接口中默认方法的使用

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

静态方法

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

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

代码:

package cn.tedu.jdk.inter;

public interface Demo01Interface {
    public static void main(String[] args) {
        A b = new B();
        b.test3();
        A c = new C();
        A.test4();
    }
}

interface A {
    void test1();

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

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

    /**
     * 接口中的静态方法
     * @return
     */
    public static String test4() {
        System.out.println("接口中的静态方法...");
        return "hello";
    }
}

class B implements A {
    @Override
    public void test1() {

    }

    @Override
    public void test2() {

    }


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

class C implements A {
    @Override
    public void test1() {

    }

    @Override
    public void test2() {

    }
}
静态方法的使用

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

如果扩展的内容支持重写用默认方法default,否则用static
在这里插入图片描述

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

三 函数式接口

1.函数式接口的由来?

使用lambda表达式的前提是需要有函数式接口. 而使用lambda表达式不关心接口名,抽象方法名,
只关心抽象抽象方法的参数列表和返回值类型

package cn.tedu.jdk.fun;

public class Demo01Fun {

    public static void main(String[] args) {
        fun1(arr -> {
            int sum=0;
            for (int i : arr) {
                sum+=i;
            }
            return sum;
        });
    }

    public static void fun1(Operator operator) {
        int arr[]={1,2,3,4};
        int sum=operator.getSum(arr);
        System.out.println("sum="+sum);
    }
}

/**
 * 函数式接口
 */
@FunctionalInterface
interface Operator{
    int getSum(int[] arr);
}
2.函数式接口的由介绍

在JDK8后函数式接口.package java.util.function;包下,
Supplier接口, 无参有返回值方法 T get(); //发音 撒破裂
Consumer接口.有参无返回值的方法 void accept(T t);
Predicate接口, 有参有返回值的方法 boolean test(T t); //发音 破瑞dit

Supplier 接口

无参有返回值的接口,对于lambda表达式需要提供一个返回数据类型
Supplier 是用来生产数据的

@FunctionalInterface
public interface Supplier<T> {

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

代码:

package cn.tedu.jdk.fun;

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

/***
 * Supplier 函数式接口的使用
 */
public class SupplierTest {

    public static void main(String[] args) {
        fun1(()-> {
            int[] arr = {22, 66, 10, 5, 4, 77, 30};
            //计算出数组的最大值
            Arrays.sort(arr);
            return arr[arr.length - 1];
        });
    }
    private static void fun1(Supplier<Integer> supplier){
        //get()是一个无参有返回值的抽象方法
        Integer max = supplier.get();
        System.out.println("max="+max);
    }
}

Consumer接口

有参无返回值的接口,是用来消费数据的
Consumer,使用的时候需要指定一个泛型.来定义参数类型

@FunctionalInterface
public interface Consumer<T> {

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

使用: 将使用的数据统一转换为小写,输出

package cn.tedu.jdk;

import java.util.function.Consumer;

public class ConsumerTest {
    public static void main(String[] args) {
        fun2(msg -> {
            System.out.println(msg + "转换为小写:" + msg.toLowerCase());
        });
    }

    public static void fun2(Consumer<String> consumer) {
        consumer.accept("Hello World");
    }
}

默认方法:

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

import java.util.function.Consumer;

public class ConsumerAndThenTest {
    public static void main(String[] args) {
       fun3(msg1->{
           System.out.println(msg1 + "->转换为小写:" + msg1.toLowerCase());
        },msg2->{
           System.out.println(msg2 + "->转换为大写:" + msg2.toUpperCase());
       });
    }

    public static void fun3(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);//大->小
    }
}

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

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

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

@FunctionalInterface
public interface Function<T, R> {

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

使用: 传递一个String,返回一个Integer类型

package cn.tedu.jdk.fun;

import java.util.function.Function;

public class FunctionTest {
    public static void main(String[] args) {
        fun4(msg->{
            return Integer.parseInt(msg);
        });
    }
    public static void fun4(Function<String, Integer> function){
        Integer apply = function.apply("666");
        System.out.println("apply="+apply);
    }
}

andThen 方法

package cn.tedu.jdk.fun;

import java.util.function.Function;

public class FunctionAndThenTest {
    public static void main(String[] args) {
        fun5(msg1->{
            return Integer.parseInt(msg1);
        },msg2->{
            return msg2*10;
        });
    }
    public static void fun5(Function<String, Integer> f1,Function<Integer,Integer> f2){
//        Integer i1 = f1.apply("666");
//        Integer i2=f2.apply(i1);

        Integer i2=f1.andThen(f2).apply("666");
        System.out.println("i2="+i2);

    }
}

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

  default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

package cn.tedu.jdk.fun;

import java.util.function.Function;

public class FunctionAndThenTest {
    public static void main(String[] args) {
        fun5(msg1->{
            return Integer.parseInt(msg1);
        },msg2->{
            return msg2*10;
        });
    }
    public static void fun5(Function<String, Integer> f1,Function<Integer,Integer> f2){
//        Integer i1 = f1.apply("666");
//        Integer i2=f2.apply(i1);

       // Integer i2=f1.andThen(f2).apply("666");
        Integer i2 = f2.compose(f1).apply("666");
        System.out.println("i2="+i2);//倒着,相反

    }
}

Predicate接口

有参且有返回值为boolean的接口

@FunctionalInterface
public interface Predicate<T> {

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

代码

package cn.tedu.jdk.fun;

import java.util.function.Predicate;

public class PredicateTest {
    public static void main(String[] args) {
        test(msg->{
            return msg.length() > 3;
        },"hello world");
    }
    public static void test(Predicate<String> predicate,String msg){
        boolean b = predicate.test(msg);
        System.out.println("b:"+b);
    }
}

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

package cn.tedu.jdk.fun;

import java.util.function.Predicate;

public class PredicateDefaultTest {
    public static void main(String[] args) {
        test(msg1->{
            return msg1.contains("H");
        },msg2->{
            return msg2.contains("W");
        });
    }
    public static void test(Predicate<String> p1,Predicate<String> p2){
//        boolean b1 = predicate.test(msg);
//        boolean b2 = predicate.test("Hello");
        //b1包含H  b2包含W
        //b1包含H 同时 b2包含W
        boolean b3=p1.and(p2).test("Hello");
        //b1 包含H 或者 b2包含W
        boolean b4 = p1.or(p2).test("Hello");
        //p1 不包含 H
        boolean b5 = p1.negate().test("Hello");
        System.out.println("b3="+b3);//FALSE
        System.out.println("b4="+b4);//TRUE
        System.out.println("b5="+b5);//FALSE


    }
}

四 方法引用

1.为什么要用方法引用?
lambda表达式冗余

在使用lambda表达式的时候,也会出现代码冗余的时候,比如,用lambda表达式求数组的和

package cn.tedu.jdk.funref;

import java.util.function.Consumer;

public class FunctionRefTest01 {
    public static void main(String[] args) {
        printMax(arr->{
            int sum=0;
            for (int i : arr) {
                sum+=i;
            }
            System.out.println("数组之和:"+sum);
        });
    }
    public static void printMax(Consumer<int[]> consumer){
        int[] arr={10,20,30,40,50};
        consumer.accept(arr);
    }
}

lambda表达式代码冗余

package cn.tedu.jdk.funref;

import java.util.function.Consumer;

public class FunctionRefTest01 {
    public static void main(String[] args) {
        printMax(arr->{
            int sum=0;
            for (int i : arr) {
                sum+=i;
            }
            System.out.println("数组之和:"+sum);
        });
          
        //代码冗余
        int[] arr={10,20,30,40,50};
        getTotal(arr);
    }
    public static void printMax(Consumer<int[]> consumer){
        int[] arr={10,20,30,40,50};
        consumer.accept(arr);
    }

    public static void getTotal(int[] a){
        int sum=0;
        for (int i : a) {
            sum+=i;
        }
        System.out.println("数组之和:"+sum);
    }
}

解决方案:

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

package cn.tedu.jdk.funref;

import java.util.function.Consumer;

public class FunctionRefTest02 {
    public static void main(String[] args) {
        //:: 方法引用,也是JDK8新的语法
        printMax(FunctionRefTest02::getTotal);
    }
    public static void printMax(Consumer<int[]> consumer){
        int[] arr={10,20,30,40,50};
        consumer.accept(arr);
    }

    /***
     * 求数组中所有元素的和
     * @param a
     */
    public static void getTotal(int[] a){
        int sum=0;
        for (int i : a) {
            sum+=i;
        }
        System.out.println("数组之和:"+sum);
    }
}

2.方法引用的格式

:: 方法引用 也是JDK8新的语法
符号说明: 双冒号为方法引用运算符,而它所在的表达式称为方法引用
应用场景: 如果lambda表达式所要实现的方案,已经有其他方法存在相同的方案,那么则可以使用方法引用
在这里插入图片描述

2.1 对象名:: 方法名

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

package cn.tedu.jdk.funref;

import java.util.Date;
import java.util.function.Supplier;

public class FunctionRefTest03 {
    public static void main(String[] args) {
        Date date = new Date();
        Supplier<Long> supplier=()->{
            return date.getTime();
        };
        System.out.println(supplier.get());//获取当前时间的毫秒值
        //通过方法引用的方式来处理
        Supplier<Long> supplier1=date::getTime;
        System.out.println(supplier1.get());
    }
}

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

2.2 类名:: 静态方法名
package cn.tedu.jdk.funref;

import java.util.function.Supplier;

public class FunctionRefTest04 {
    public static void main(String[] args) {
        Supplier<Long> supplier=()->{
            return System.currentTimeMillis();
        };
        System.out.println(supplier.get());
        //通过方法引用来实现
        Supplier<Long> supplier1=System::currentTimeMillis;
        System.out.println(supplier1.get());
    }
}

2.3 类名:: 引用实例方法
package cn.tedu.jdk.funref;

import java.util.function.BiFunction;
import java.util.function.Function;

public class FunctionRefTest05 {
    public static void main(String[] args) {
        Function<String, Integer> function=(s -> {
            return s.length();
        });
        System.out.println(function.apply("hello"));

        //通过方法引用来实现
        Function<String, Integer> function1=String::length;
        System.out.println(function1.apply("hahahaha"));

        //截取字符串
        BiFunction<String,Integer,String> function2=String::substring;
        String msg=function2.apply("HelloWorld",3);
        System.out.println(msg);
    }
}

2.4 类名:: 构造器

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

package cn.tedu.jdk.funref;

import cn.tedu.jdk.lambda.domain.Person;

import java.util.function.BiFunction;
import java.util.function.Supplier;

public class FunctionRefTest06 {
    public static void main(String[] args) {
        Supplier<Person> supplier = () -> {
            return new Person();
        };
        System.out.println(supplier.get());
        //然后通过 方法引用来实现
        Supplier<Person> supplier1=Person::new;
        System.out.println(supplier1.get());
        
        BiFunction<String,Integer,Person> function=Person::new;
        System.out.println(function.apply("张飞",25));
    }
}

2.4 数组:: 构造器

数组是怎么构造出来的呢?

package cn.tedu.jdk.funref;

import java.util.function.Function;

public class FunctionRefTest07 {
    public static void main(String[] args) {
        Function<Integer,String[]> function=(len)->{
            return new String[len];
        };
        String[] a1 = function.apply(5);
        System.out.println("数组的长度是:"+a1.length);

        //方法的引用方式 来调用数组的构造器
        Function<Integer,String[]> function2=String[]::new;
        String[] a2 = function.apply(3);
        System.out.println("数组的长度是:"+a2.length);
    }
}

五 Stream流

1.集合处理的弊端

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

针对不同需求,总是一次次循环,需要更高效的循环,.Stream API处理

package cn.tedu.jdk.stream;

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

public class StreamTest01 {
    public static void main(String[] args) {
        //定义一个list集合
        List<String> list = Arrays.asList("张三", "李四", "王五", "张飞","周星驰","张三丰");
        //1.获取所有姓"张" 的信息
        List<String> list1=new ArrayList<>();
        for (String s : list) {
            if (s.startsWith("张")){
                list1.add(s);
            }
        }

        //2.获取所有长度为3的用户
        List<String> list2=new ArrayList<>();
        for (String s : list1) {
            if (s.length()==3){
                list2.add(s);
            }
        }

        //3.输出所有的用户信息
        List<String> list3=new ArrayList<>();
        for (String s : list2) {
            if (s.length()==3){
                list3.add(s);
            }
        }
        System.out.println(list3);

    }
}

Stream API的含义: 获取流,过滤张,获取长度,逐一打印

package cn.tedu.jdk.stream;

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

public class StreamTest02 {
    public static void main(String[] args) {
        //定义一个list集合
        List<String> list = Arrays.asList("张三", "李四", "王五", "张飞","周星驰","张三丰");
        //1.获取所有姓"张" 的信息
        //2.获取所有长度为3的用户
        //3.输出所有的用户信息
        list.stream()
                .filter(s -> s.startsWith("张"))
                .filter(s->s.length()==3)
                .forEach(s -> {
                    System.out.println(s);
                });
        System.out.println("------------------");
        list.stream()
                .filter(s -> s.startsWith("张"))
                .filter(s->s.length()==3)
                .forEach(System.out::println);

    }
}

2.Stream流式思想

Stream和IO没有任何关系.Stream流不是一种数据结构.不保存数据.而是对数据加工处理

3.Stream流的获取方式
3.1根据Collection获取

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

package cn.tedu.jdk.stream;

import java.util.*;

public class StreamTest03 {
    public static void main(String[] args) {
        //定义一个list集合
        List<String> list = Arrays.asList("张三", "李四", "王五", "张飞","周星驰","张三丰");
        list.stream();
        Set<String> set=new HashSet<>();
        set.stream();
        Vector<String> vector = new Vector<>();
        vector.stream();
        

    }
}

但是Map没有实现Collection接口,那这时怎么办?我们可以根据map获取对应key,value的集合

package cn.tedu.jdk.stream;

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

public class StreamTest04 {
    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<>();
        map.put("a",111);
        map.put("b",122);

        Stream<String> stream = map.keySet().stream();//key
        Stream<Integer> stream1 = map.values().stream();//value
        Stream<Map.Entry<String, Integer>> stream2 = map.entrySet().stream();

    }
}

3.2 通过Stream的of方法

在实际开发中.我们不可避免的是数组中的数据,由于数组对象不可能添加默认方法,所以stream流提供了静态方法of

package cn.tedu.jdk.stream;

import java.util.stream.Stream;

public class StreamTest05 {
    public static void main(String[] args) {
        Stream<String> a1 = Stream.of("a1", "a2", "a3", "a4");
        String[] arr1={"aa","bb","cc"};
        Stream<String> arr11 = Stream.of(arr1);
        Integer[] arr2={1,2,3,4,5};
        Stream<Integer> arr21 = Stream.of(arr2);
        arr21.forEach(System.out::print);

		//注意:基本数据类型的数组是不行的
		int[] arr3={1,2,3,4,5};
		Stream.of(arr3).forEach(System.out::println);
		
    }
}

4 Stream的常用方法

在这里插入图片描述
在这里插入图片描述

4.1forEach方法

用来遍历流中的数据,

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

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

package cn.tedu.jdk.stream;

import java.util.stream.Stream;

public class StreamTest06 {
    public static void main(String[] args) {
        Stream<String> a1 = Stream.of("a1", "a2", "a3");
        a1.filter(s -> {
            System.out.println("-----------");
            return s.contains("a");
        }).forEach(System.out::println);
        System.out.println("---------->");
    }
}

package cn.tedu.jdk.stream;

import java.util.stream.Stream;

public class StreamTest07ForEach {
    public static void main(String[] args) {
      Stream.of("a1", "a2", "a3").forEach(System.out::println);
    }
}

4.2count方法

stream流中的count方法用来统计其中的元素个数,返回一个long值,统计个数

  long count();
 public static void main(String[] args) {
        long count=Stream.of("a1", "a2", "a3").count();
        System.out.println(count);
    }
4.3 filter方法

filter方法用来过滤数据的,返回符合条件的数据
在这里插入图片描述
可以通过filter方法将一个流转换为另一个子集流

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

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

 public static void main(String[] args) {
        Stream.of("a1", "a2", "a3","b1").filter(s -> s.contains("a")).forEach(System.out::println);
    }
4.4 limit方法

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

  Stream<T> limit(long maxSize);

代码

package cn.tedu.jdk.stream;

import java.util.stream.Stream;

public class StreamTest10Limit {
    public static void main(String[] args) {
        Stream.of("a1", "a2", "a3","b1").limit(5).forEach(System.out::println);
    }
}
4.5 skip方法

如果需要跳过前面几个元素.,则可以使用skip方法获取一个截取之后的新流
skip方法跳过前面几个long数值

  Stream<T> skip(long n);

在这里插入图片描述

  public static void main(String[] args) {
        Stream.of("a1", "a2", "a3","b1","cc").skip(1).forEach(System.out::println);
    }
4.6 map方法

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

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

在这里插入图片描述
该接口需要一个Function函数式接口,将T类型转换为R类型的数据

package cn.tedu.jdk.stream;

import java.util.stream.Stream;

public class StreamTest12Map {
    public static void main(String[] args) {
        Stream.of("1", "2", "3","11")
//                .map(msg->Integer.parseInt(msg))
                .map(Integer::parseInt)
                .forEach(System.out::println);
    }
}

4.7 sorted方法

如果需要将数据排序.可以使用sorted方法

Stream<T> sorted(Comparator<? super T> comparator);
package cn.tedu.jdk.stream;

import java.util.stream.Stream;

public class StreamTest13Sorted {
    public static void main(String[] args) {
        Stream.of("1", "2", "3","0","5","11")
//                .map(msg->Integer.parseInt(msg))
                .map(Integer::parseInt)
                //.sorted()//根据数据的自然排序  升序排序
                .sorted((o1, o2) -> o2-o1)//根据比较器Comparator指定排序规则 降序排序
                .forEach(System.out::println);
    }
}


   public static void main(String[] args) {
        Stream.of("1","5","0","120","3")
                .map(Integer::parseInt)
                .sorted(((o1, o2) -> o2-o1))
                .forEach(System.out::println);

//                .map(msg->Integer.parseInt(msg))
//                .sorted()
//                .forEach(msg-> System.out.println(msg));

    }
4.8 distinct方法

如果去掉重复数据,可以使用distinct
在这里插入图片描述

Stream<T> distinct();

stream流中distinct方法可以对基本数据类型过滤,
但是对于自定义类型,我们需要重写equals和hashCode方法来移出重复元素

package cn.tedu.jdk.stream;

import cn.tedu.jdk.lambda.domain.Person;

import java.util.stream.Stream;

public class StreamTest14Distinct {
    public static void main(String[] args) {
        Stream.of("1", "2", "1","0","5","1")
//                .map(msg->Integer.parseInt(msg))
                .map(Integer::parseInt)
                //.sorted()//根据数据的自然排序
                .sorted((o1, o2) -> o2-o1)//根据比较器Comparator指定排序规则
                .distinct()//去掉重复记录
                .forEach(System.out::println);
        System.out.println("-----------");

        Stream.of(
                new Person("张三",18),
                new Person("李四",22),
                new Person("张三",18)
        ).distinct().forEach(System.out::println);//对age进行过滤
        //Preson类的equals和hashCode方法根据名称和年龄进行判断
    }
}

Person类

package cn.tedu.jdk.lambda.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Objects;

//@Data
//@AllArgsConstructor
//@NoArgsConstructor
public class Person {
    private String name;
    private Integer age;
    private Integer height;

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

    public Person() {
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public Integer getHeight() {
        return height;
    }

    public void setHeight(Integer height) {
        this.height = height;
    }

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

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

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


4.9 match方法
4.10 find方法
4.11 max和min 方法
4.12 reduce方法

如果需要将所有数据归纳得到一个数据,可以使用reduce方法
在这里插入图片描述

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

BinaryOperator继承了BiFunction的

 R apply(T t, U u);

代码

package cn.tedu.jdk.stream;

import java.util.stream.Stream;

public class StreamTest17Reduce {
    public static void main(String[] args) {
        Integer sum=Stream.of(4,5,9,10)
                //identity默认值
                //第一次计算的时候会将默认值赋值给x,
                //之后会将 上一次的操作结果赋值给x y就是每次从数据中获取的元素
                .reduce(0,(x,y)->{
                    System.out.println("x="+x+",y="+y);
                    return x + y;
                });
        System.out.println(sum);
        //获取最大值
        Integer max = Stream.of(4, 5, 9, 10)
                .reduce(0, (x, y) -> {
                    return x > y ? x : y;
                });
        System.out.println(max);
    }
}

运行结果

x=0,y=4
x=4,y=5
x=9,y=9
x=18,y=10
28
10

4.13 map和reduce组合方法
package cn.tedu.jdk.stream;

import cn.tedu.jdk.lambda.domain.Person;

import java.util.stream.Stream;

public class StreamTest18MapReduce {
    public static void main(String[] args) {
        //1.求出所有的年龄总和
        Integer sum = Stream.of(
                new Person("张三", 18),
                new Person("李四", 22),
                new Person("张三", 13),
                new Person("王五", 19),
                new Person("张三", 26)
//        ).map(msg -> msg.getAge())//实现数组类型的转换
//                .reduce(0, (x, y)->x+y);
        ).map(Person::getAge)
                .reduce(0,Integer::sum);
        System.out.println(sum);


        //2.求出所有年龄的最大值
        Integer max = Stream.of(
                new Person("张三", 18),
                new Person("李四", 22),
                new Person("张三", 13),
                new Person("王五", 19),
                new Person("张三", 26)
        ).map(Person::getAge) //实现数据类型的转换,符合reduce对数据的要求
                .reduce(0,Math::max);//reduce实现数据的处理
        System.out.println(max);

        //3.统计字符串 a 出现的次数
        Integer count = Stream.of("a", "", "b", "d", "a", "d")
                .map(ch -> "a".equals(ch) ? 1 : 0)
                .reduce(0, Integer::sum);
        System.out.println(count);//2次
    }
}

4.14 mapToint方法

如果需要将Stream流中的Integer类型转换为int类型, 可以使用maptoint实现
在这里插入图片描述

package cn.tedu.jdk.stream;

import java.util.stream.IntStream;
import java.util.stream.Stream;

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

    }
}

4.15 concat方法

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

import java.util.stream.Stream;

public class StreamTest20Concat {
    public static void main(String[] args) {
        Stream<String> stream1 = Stream.of("a", "b", "c");
        Stream<String> stream2 = Stream.of("x", "y", "z");

        //通过concat方法将两个合并为一个新的流
        Stream.concat(stream1,stream2).forEach(System.out::print);
    }
}

4.16 综合案例

定义两个集合,然后向集合中存储多个用户名称,然后完成如下操作
1.第一个队伍只保留姓名为3的用户
2.第一个队伍筛选完之后,只保留前3个人
3.第二个队伍只要姓张的成员
4.第二个队伍筛选之后不要前两个人
5.将两个队伍合并为一个队伍
6.根据姓名创建Person对象
7.打印整个队伍的Person信岛

package cn.tedu.jdk.stream;

import cn.tedu.jdk.lambda.domain.Person;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class StreamTest21Demo {
    /**
     * 定义两个集合,然后向集合中存储多个用户名称,然后完成如下操作
     * 1.第一个队伍只保留姓名长度为3的用户
     * 2.第一个队伍筛选完之后,只保留前3个人
     * 3.第二个队伍只要姓张的成员
     * 4.第二个队伍筛选之后不要前两个人
     * 5.将两个队伍合并为一个队伍
     * 6.根据姓名创建Person对象
     * 7.打印整个队伍的Person信岛
     */
    public static void main(String[] args) {
        List<String> list1= Arrays.asList("迪丽热","宋远桥","周星驰","庄子","孔子","字孟德");
        List<String> list2= Arrays.asList("张三丰","张无忌","周星驰","张飞","关云长");
        //1.第一个队伍只保留姓名长度为3的用户
        //2.第一个队伍筛选完之后,只保留前3个人
        Stream<String> stream1= list1.stream().filter(s -> s.length() == 3).limit(3);

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

        // 5.将两个队伍合并为一个队伍
        //6.根据姓名创建Person对象
        //7.打印整个队伍的Person信岛
        Stream.concat(stream1, stream2)
//                .map(n -> new Person(n))
                .map(Person::new)
                .forEach(System.out::println);
    }
}

输出

Person{name='迪丽热', age=null, height=null}
Person{name='宋远桥', age=null, height=null}
Person{name='周星驰', age=null, height=null}
Person{name='张飞', age=null, height=null}

在这里插入图片描述

Stream结果收集
 /**
     * Stream结果收集
     * 收集到集合中
     */
    @Test
    public void test01(){
        List<String> list = Stream.of("aa", "bb", "cc")
                .collect(Collectors.toList());
        System.out.println(list);
        //收集到set集合中
        Set<String> set = Stream.of("aa", "bb", "cc","aa").collect(Collectors.toSet());
        System.out.println(set);

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

        //转换为HashSet
        HashSet<String> hashSet = Stream.of("aa", "bb", "cc", "aa")
                .collect(Collectors.toCollection(HashSet::new));
        System.out.println(hashSet);
    }
结果收集到数组中

Stream中提供了toArray()方法将结果放到一个数组中,返回值类型是Object[],如果我们要指定返回的类型,那么可以使用另一个重载的toArray(IntFunction)方法

  /**
     * Stream结果收集到数组中
     *
     */
    @Test
    public void test02(){
        Object[] objects = Stream.of("aa", "bb", "cc", "aa")
                .toArray();//返回的数组中的元素是Object类型
        System.out.println(Arrays.toString(objects));

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

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

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

package cn.tedu.jdk.res;

import cn.tedu.jdk.lambda.domain.Person;
import org.junit.Test;

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

public class test01 {
    /**
     * Stream结果收集
     * 收集到集合中
     */
    @Test
    public void test01(){
        List<String> list = Stream.of("aa", "bb", "cc")
                .collect(Collectors.toList());
        System.out.println(list);
        //收集到set集合中
        Set<String> set = Stream.of("aa", "bb", "cc","aa").collect(Collectors.toSet());
        System.out.println(set);

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

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

    /**
     * Stream结果收集到数组中
     *
     */
    @Test
    public void test02(){
        Object[] objects = Stream.of("aa", "bb", "cc", "aa")
                .toArray();//返回的数组中的元素是Object类型
        System.out.println(Arrays.toString(objects));

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

    /**
     * Stream流中的聚合计算
     */
    @Test
    public void test03(){
        //获取年龄的最大值
        Optional<Person> maxAge = Stream.of(
                new Person("张三", 18),
                new Person("李四", 22),
                new Person("张三", 13),
                new Person("王五", 19),
                new Person("张三", 26) //Comparator 比较器
        ).collect(Collectors.maxBy((p1, p2) -> p1.getAge() - p2.getAge()));
        System.out.println("最大年龄是:"+maxAge.get());

        //获取年龄的最小值
        Optional<Person> minAge = Stream.of(
                new Person("张三", 18),
                new Person("李四", 22),
                new Person("张三", 13),
                new Person("王五", 19),
                new Person("张三", 26) //Comparator 比较器
        ).collect(Collectors.minBy((p1, p2) -> p1.getAge() - p2.getAge()));
        System.out.println("最小年龄是:"+minAge.get());

        //求所有人的年龄总和
        Integer sumAge = Stream.of(
                new Person("张三", 18),
                new Person("李四", 22),
                new Person("张三", 13),
                new Person("王五", 19),
                new Person("张三", 26) //Comparator 比较器
        ).collect(Collectors.summingInt(Person::getAge));
        System.out.println("年龄总和为:"+sumAge);

        //求所有人的年龄平均值
        Double avgAge = Stream.of(
                new Person("张三", 18),
                new Person("李四", 22),
                new Person("张三", 13),
                new Person("王五", 19),
                new Person("张三", 26) //Comparator 比较器
        ).collect(Collectors.averagingInt(Person::getAge));
        System.out.println("年龄平均值为:"+avgAge);

        //统计总的数量
        Long countAge = Stream.of(
                new Person("张三", 18),
                new Person("李四", 22),
                new Person("张三", 13),
                new Person("王五", 19),
                new Person("张三", 26) //Comparator 比较器
        ).filter(s -> s.getAge() > 15)
                .collect(Collectors.counting());
        System.out.println("满足条件的记录数为:"+countAge);
    }
}

对流中的数据做分组操作

当我们使用stream流做处理数据时,可以根据某个属性将数据分组

 /**
     * 分组计算
     *
     */
    @Test
    public void test04(){
        //根据名字对数据分组
        Map<String, List<Person>> map = Stream.of(
                new Person("张三", 18, 175),
                new Person("李四", 12, 180),
                new Person("张三", 13, 175),
                new Person("王五", 19, 166),
                new Person("张三", 26, 173) //Comparator 比较器
        ).collect(Collectors.groupingBy(s -> s.getName()));
        map.forEach((k,v)-> System.out.println("k="+k+"\t"+"v="+v));

        System.out.println("----------------");
        //根据年龄进行分组,如果年龄大于18成年.否则未成年
        Map<String, List<Person>> map2 = Stream.of(
                new Person("张三", 18, 175),
                new Person("李四", 12, 180),
                new Person("张三", 13, 175),
                new Person("王五", 19, 166),
                new Person("张三", 26, 173) //Comparator 比较器
        ).collect(Collectors.groupingBy(s -> s.getAge() >= 18 ? "成年" : "未成年"));
        map2.forEach((k,v)-> System.out.println("k="+k+"\t"+"v="+v));
    }

输出

k=李四	v=[Person{name='李四', age=12, height=180}]
k=张三	v=[Person{name='张三', age=18, height=175}, Person{name='张三', age=13, height=175}, Person{name='张三', age=26, height=173}]
k=王五	v=[Person{name='王五', age=19, height=166}]
----------------
k=未成年	v=[Person{name='李四', age=12, height=180}, Person{name='张三', age=13, height=175}]
k=成年	v=[Person{name='张三', age=18, height=175}, Person{name='王五', age=19, height=166}, Person{name='张三', age=26, height=173}]

多级分组:
先根据name分组,再根据age分组

/**
     * 多级分组
     *
     */
    @Test
    public void test05(){
        //先根据name分组,然后根据age(成年和未成年分组)
        Map<String, Map<String, List<Person>>> map3 = Stream.of(
                new Person("张三", 18, 175),
                new Person("李四", 12, 180),
                new Person("张三", 13, 175),
                new Person("王五", 19, 166),
                new Person("张三", 26, 173) //Comparator 比较器
        ).collect(Collectors.groupingBy(Person::getName, Collectors.groupingBy(p -> p.getAge() >= 18 ? "成年" : "未成年")));
        map3.forEach((k,v)-> System.out.println("k="+k+"\t"+"v="+v));
    }

输出

k=李四	v={未成年=[Person{name='李四', age=12, height=180}]}
k=张三	v={未成年=[Person{name='张三', age=13, height=175}], 成年=[Person{name='张三', age=18, height=175}, Person{name='张三', age=26, height=173}]}
k=王五	v={成年=[Person{name='王五', age=19, height=166}]}
对流中的数据做分区操作

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

  /**
     * 分区操作
     */
    @Test
    public void test06(){
        Map<Boolean, List<Person>> map = Stream.of(
                new Person("张三", 18, 175),
                new Person("李四", 12, 180),
                new Person("张三", 13, 175),
                new Person("王五", 19, 166),
                new Person("张三", 26, 173) //Comparator 比较器
        ).collect(Collectors.partitioningBy(p -> p.getAge() > 18));
        map.forEach((k,v)-> System.out.println(k+"\t"+v));
    }

输出

false	[Person{name='张三', age=18, height=175}, Person{name='李四', age=12, height=180}, Person{name='张三', age=13, height=175}]
true	[Person{name='王五', age=19, height=166}, Person{name='张三', age=26, height=173}]

对流中的数据做拼接

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

 /**
     * 对流中的数据做拼接操作
     */
    @Test
    public void test07(){
        String s1 = Stream.of(
                new Person("张三", 18, 175),
                new Person("李四", 12, 180),
                new Person("张三", 13, 175),
                new Person("王五", 19, 166),
                new Person("张三", 26, 173) //Comparator 比较器
        ).map(Person::getName)
                .collect(Collectors.joining());
        System.out.println(s1);//张三李四张三王五张三

        String s2 = Stream.of(
                new Person("张三", 18, 175),
                new Person("李四", 12, 180),
                new Person("张三", 13, 175),
                new Person("王五", 19, 166),
                new Person("张三", 26, 173) //Comparator 比较器
        ).map(Person::getName)
                .collect(Collectors.joining("_"));
        System.out.println(s2);//张三_李四_张三_王五_张三

        String s3 = Stream.of(
                new Person("张三", 18, 175),
                new Person("李四", 12, 180),
                new Person("张三", 13, 175),
                new Person("王五", 19, 166),
                new Person("张三", 26, 173) //Comparator 比较器
        ).map(Person::getName)
                .collect(Collectors.joining("_","###","$$$"));
        System.out.println(s3);//###张三_李四_张三_王五_张三$$$
    }

输出

张三李四张三王五张三
张三_李四_张三_王五_张三
###张三_李四_张三_王五_张三$$$
并行的stream流

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

  /**
     * 串行流
     */
    @Test
    public void test02(){
        Stream.of(5,6,8,9,12)
                .filter(s->{
                    System.out.println(Thread.currentThread()+"-----"+s);
                    return s > 6;
                }).count();
    }

输出:
在同一线程中

Thread[main,5,main]-----5
Thread[main,5,main]-----6
Thread[main,5,main]-----8
Thread[main,5,main]-----9
Thread[main,5,main]-----12

并行流
parallelStream,其实就是一个并行执行的流,它通过默认的ForkjoinPool,可以提高多线程任务的速度
我们可以通过2种方式来获取并行流
1.通过list接口的parallelStream方法来获取
2.通过已有的串行流转换为并行流parallel

  /**
     * 获取并行流的两种方式
     */
    @Test
    public void test02(){
        List<Integer> list = new ArrayList<>();
        //通过list接口直接获取并行流
        Stream<Integer> stringStream = list.parallelStream();

        //将已有的串行流转换为并行流
        Stream<Integer> parallel = Stream.of(5, 6, 8, 9, 12).parallel();
    }

并行流操作

 /**
     * 并行流操作
     */
    @Test
    public void test03(){
        //将已有的串行流转换为并行流
       Stream.of(5, 6, 8, 9, 12)
               .parallel() //将流转换为并行流,Stream处理的时候就会通过多线程处理
               .filter(s-> {
                   System.out.println(Thread.currentThread()+" s="+s);
                   return s > 6;
               })
       .count();

    }

输出

Thread[ForkJoinPool.commonPool-worker-2,5,main] s=12
Thread[ForkJoinPool.commonPool-worker-4,5,main] s=9
Thread[ForkJoinPool.commonPool-worker-11,5,main] s=5
Thread[ForkJoinPool.commonPool-worker-9,5,main] s=6
Thread[main,5,main] s=8

39stream

六 新时间日期API

旧版日期时间的问题是非常差的

 /**
     * 旧版日期设计的格式
     */
    @Test
    public void test01() throws ParseException {
        //1.设计不合理
        Date date = new Date(2021,11,13);
        System.out.println(date);//Tue Dec 13 00:00:00 CST 3921

        //2.时间格式化
        SimpleDateFormat sdf=new SimpleDateFormat("YYYY-MM-dd");

        for (int i = 0; i < 50; i++) {
            new Thread(()->{
                try {
                    System.out.println(sdf.parse("2021-11-13"));
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }).start();
        }
//        System.out.println(sdf.format(date));

    }

设计不合理,在java.util和java.sql的包中都有日期类,.java.util.Date同时包含日期和时间的,而java.sql.Date仅仅包含日期,此外用于格式化和解析的类在java.text包下
非线程安全,java.util.Date 是非线程安全的,所有的日期类都是可变的,
时区处理麻烦,日期类并不提供国际化,没有时区支持

新日期时间API介绍

线程安全,位于java.time包下
在这里插入图片描述

日期操作
    /**
     * JDK8 日期时间操作
     */
    @Test
    public void test01(){
        //1.创建指定的日期
        LocalDate date1 = LocalDate.of(2021, 11, 13);
        System.out.println("date1="+date1);

        //2.获取当前的日期
        LocalDate now = LocalDate.now();
        System.out.println("now="+now);

        //3.根据LocalDate对象获取对应的日期信息
        System.out.println("年:"+now.getYear());
        System.out.println("月:"+now.getMonth().getValue());
        System.out.println("日:"+now.getDayOfMonth());
        System.out.println("星期:"+now.getDayOfWeek().getValue());
    }

    /**
     * 时间操作
     */
    @Test
    public void test02(){
        //1.得到指定的日期
        LocalTime time=LocalTime.of(12,25,33,522);
        System.out.println(time);

        //2.获取当前时间
        LocalTime now = LocalTime.now();
        System.out.println(now);
        //3.获取时间信息
        System.out.println(now.getHour());
        System.out.println(now.getMinute());
        System.out.println(now.getSecond());
        System.out.println(now.getNano());//纳秒
    }

    /**
     * 日期时间类型
     */
    @Test
    public void test03(){
        //获取指定的日期时间
        LocalDateTime localDateTime=LocalDateTime.of(2021,11,13,16,18,50,522);
        System.out.println(localDateTime);
        //获取当前的日期
        LocalDateTime dateTime
                = localDateTime.now();
        System.out.println(dateTime);
        System.out.println(dateTime.getYear());
        System.out.println(dateTime.getMonth().getValue());
        System.out.println(dateTime.getDayOfMonth());
        System.out.println(dateTime.getHour());
        System.out.println(dateTime.getMinute());
        System.out.println(dateTime.getSecond());
        System.out.println(dateTime.getNano());
    }
日期时间的修改和操作

    /**
     * 日期时间的修改
     */
    @Test
    public void test01(){
        LocalDateTime now = LocalDateTime.now();
        System.out.println("now=" + now);
        //修改日期时间   对日期时间的修改,对已存在的localDateTime对象,创建了它的模板,
        //并不会修改原原来的模板信息
        LocalDateTime localDateTime = now.withYear(1998);
        System.out.println("now:" + now);
        System.out.println("修改后的:"+localDateTime);

        System.out.println("月份:"+now.withMonth(10));
        System.out.println("天:"+now.withDayOfMonth(6));
        System.out.println("小时:"+now.withHour(15));
        System.out.println("分钟:"+now.withMinute(55));
        System.out.println("秒:"+now.withSecond(10));
        System.out.println("纳秒:"+now.withNano(310));

        //在当前日期的时间的基础上,加上或减去指定的时间
        System.out.println("2天后:"+now.plusDays(2));
        System.out.println("10年后:"+now.plusYears(10));
        System.out.println("6个月后:"+now.plusMonths(6));

        System.out.println("10年前:"+now.minusYears(10));
        System.out.println("半年前:"+now.minusMonths(6));
        System.out.println("1周前:"+now.minusDays(7));
    }

    /**
     * 日期时间的比较
     */
    @Test
    public void test02(){
        LocalDate now = LocalDate.now();
        LocalDate date = LocalDate.of(2020, 10, 15);
        //在JDK8中要实现日期的比较 isAfter  isBefore  isEqual 通过这几个方法来直接比较
        System.out.println(now.isAfter(date));//true  now在指定日期之后
        System.out.println(now.isBefore(date));//false
        System.out.println(now.isEqual(date));//false
    }

线程安全的对象,原来的日期对象不会被修改,每次操作都会返回一个新的localdate对象,所以在多线程场景下是线程安全的.

格式化和解析操作
  /**
     * 日期格式化
     */
    @Test
    public void test01(){
        LocalDateTime now = LocalDateTime.now();
        //指定格式
        DateTimeFormatter isoLocalDateTime = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
        //将日期时间转换为字符串
        String format = now.format(isoLocalDateTime);
        System.out.println("format=" + format);

        //通过 ofPattern 方法来指定特殊的格式
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String format1 = now.format(dateTimeFormatter);
        //format1=2021-11-13 16:58:34
        System.out.println("format1=" + format1);

        //将字符串解析为一个日期时间类型
        LocalDateTime parse = LocalDateTime.parse("2021-11-13 16:58:34",dateTimeFormatter);
        System.out.println("parse=" + parse);


    }
Instant类

在JDK8中,我们新增了一个Instant(时间戳/时间线),内部保存了从1970年1月1日00:00:00以来的秒和纳秒


    /**
     * Instant 时间戳
     * 可以用来统计时间消耗
     */
    @Test
    public void test01() throws Exception{
        Instant now = Instant.now();
        System.out.println(now);
        //获取从1970年一月一日 00:00:00  到现在的纳秒
        System.out.println(now.getNano());

        Thread.sleep(5);
        Instant now1 = Instant.now();
        System.out.println("耗时:" + (now1.getNano() - now.getNano()));

    }
计算日期时间差

JDK8中提供了两个工具类Duration和Period,计算日期时间差
Duration 用来计算两个时间差 localTime
Period 用来计算两个日期差 localDate

 /**
     * 计算日期时间差
     */
    @Test
    public void test01(){
        //计算时间差
        LocalTime time = LocalTime.of(17, 24, 00);
        LocalTime now = LocalTime.now();
        //通过Duration来计算时间差
        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.toMillis());

        System.out.println("------------------");
        //计算日期差
        LocalDate of = LocalDate.of(1997, 06, 05);
        LocalDate now1 = LocalDate.now();
        Period period = Period.between(of, now1);
        System.out.println(period.getYears());
        System.out.println(period.getMonths());
        System.out.println(period.getDays());

    }
时间校正器

有时候我们可以需要如下调整,将日期调整到下个月的第一天等操作,这时候我们通过时间校正器效果更好
TemporalAdjuster 时间校正器
TemporalAdjusters 通过该类静态方法提供了大量常用的TemporalAdjuster实现

  /**
     * 时间校正器
     */
    @Test
    public void test02(){
        LocalDateTime now = LocalDateTime.now();
        System.out.println("now = " + now);
        //將时间调整为下个月的一号
        TemporalAdjuster adjuster=(temporal)->{
            LocalDateTime dateTime= (LocalDateTime) temporal;
            LocalDateTime nextMonth = dateTime.plusMonths(1).withDayOfMonth(1);
            return nextMonth;
        };

        //我们可以通过TemporalAdjusters 来实现
        //LocalDateTime nextMonth = now.with(adjuster);
        LocalDateTime nextMonth = now.with(TemporalAdjusters.firstDayOfNextMonth());
        System.out.println("nextMonth = " + nextMonth);

    }
日期时间的时区

在这里插入图片描述

 /**
     * 时区操作
     *
     */
    @Test
    public void test01(){
        //1.获取所有的时区 Id
//        ZoneId.getAvailableZoneIds().forEach(System.out::println);
        //2.获取当前时间  中国使用的 东八区时区,比标准时间早8个小时
        LocalDateTime now = LocalDateTime.now();
        System.out.println("now = " + now);//2021-11-13T18:20:40.983
        //获取标准时间
        ZonedDateTime zdt = ZonedDateTime.now(Clock.systemUTC());
        System.out.println("zdt = " + zdt);//2021-11-13T10:20:40.983Z

        //使用计算机默认的时区,创建日期时间
        ZonedDateTime now1 = ZonedDateTime.now();
        System.out.println("now1 = " + now1);//2021-11-13T18:20:40.983+08:00[Asia/Shanghai]

        //使用指定的时区创建日期格式
        ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("America/Marigot"));
        System.out.println("now2 = " + now2);
    }
其他特性
1.重复注解

java5引入注解,限制性,Java8后,引入重复注解,.允许在同一个地方多次使用同一注解,

定义一个重复注解的容器

package cn.tedu.jdk.anno;

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

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

定义一个可以重复的注解

package cn.tedu.jdk.anno;

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

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

配置多个重复的注解,解析重复的注解

package cn.tedu.jdk.anno;

import java.util.Arrays;

@MyAnnotation("test01")
@MyAnnotation("test02")
@MyAnnotation("test03")
public class AnnoTest01 {

    @MyAnnotation("fun1")
    @MyAnnotation("fun2")
    public void test01(){}

    /**
     *
     * 解析重复注解
     * @param args
     */
    public static void main(String[] args) throws NoSuchMethodException {
        //1.获取类中标注的重复注解
        MyAnnotation[] annotationsByType = AnnoTest01.class.getAnnotationsByType(MyAnnotation.class);
        for (MyAnnotation myAnnotation : annotationsByType) {
            System.out.println(myAnnotation.value());
        }
        //2.获取方法上标注的重复注解
        MyAnnotation[] test01s = AnnoTest01.class.getMethod("test01")
                .getAnnotationsByType(MyAnnotation.class);
        Arrays.stream(test01s).forEach((annotation)->System.out.println(annotation.value()));
    }
}

2.类型注解

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

TYPE_PARAMETER

package cn.tedu.jdk.type;

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

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

}

使用

package cn.tedu.jdk.type;

public class TypeDemo01 <@TypeParam T>{

    public <@TypeParam T extends Object> T test01(){
        return null;
    }
}

TYPE_USE

package cn.tedu.jdk.type;

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

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

使用

package cn.tedu.jdk.type;

public class TypeUseDemo01 {
    public @NotNull Integer age=10;

    public Integer sum(@NotNull Integer a,@NotNull Integer b){
        return a+b;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值