JDK8新特性

2022-09-14 20:32:13 浏览数 (1)

1、Lambda表达式

1.1、概述

首先,要想明白Lambda表达式就要先明白函数式接口,所以,咱们先来了解一下什么是函数式接口吧!

所谓函数式接口就是有且仅有一个抽象方法的接口

函数式接口就是适用于函数式编程场景的接口,java中的函数式编程的体现就是lambda!所以函数式接口 就是可以适用于Lambda使用的接口。

只有当接口中有且只有一个抽象方法的时候,Java中的lambda表达式才能顺利推导!

也就是说在Java中使用Lambda表达式必须符合函数式接口的规范

所以,使用Lambda接口的前提是:

(1)Lambda关联的接收对象必须是函数式接口。(也就是说方法的形参必须是接口)

(2)这个接口只能有一个抽象方法(函数式接口的规范)

1.2、函数式接口

代码语言:javascript复制
/*
* 函数式接口:有且只有一个抽象方法!
* @FunctionalInterface:用来检测该接口中是否是只有一个抽象方法,如果不止一个就报错!
* */
@FunctionalInterface
public interface FunctionInter {
    public void method();
}

1.3、Lambda表达式和匿名内部类

Lambda表达式“本质上”是一个匿名内部类,只是二者体现形式不一样,但可以把Lambda表达式当做匿名内部类来理解!

1.3.1、匿名内部类

匿名内部类就是某个实现了接口的子类对象

不用匿名内部类

代码语言:javascript复制
//未用匿名内部类
public class MyComparator implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1-o2;
    }
}
代码语言:javascript复制
public static void main(String[] args) {
     Comparator c = new MyComparator();//实现比较器接口,创建对象
     TreeSet<Integer> set = new TreeSet<Integer>(c);
    	
}

使用匿名内部类

代码语言:javascript复制
public static void main(String[] args) {
        Comparator c = new MyComparator();//实现比较器接口,创建对象
        TreeSet<Integer> set = new TreeSet<Integer>(c);

    	//使用匿名内部类
        Comparator c2 = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1-o2;
            }
        };//匿名内部类实现比较器接口
        TreeSet<Integer> set2 = new TreeSet<Integer>(c2);
}

1.3.2、Lambda表达式

代码语言:javascript复制
//超简洁
TreeSet<Integer> set3 = new TreeSet<Integer>((o1,o2) ->o1-o2);

从上面的代码对比中,大家可以发现,lambda表达式真的超简洁!

1.4、Lambda表达式详解

1.4.1、Lambda表达式的标准写法

代码语言:javascript复制
//可以没有形参,如果有多个形参,那么用“,”隔开
//方法体和形参之间用“->”连接
(参数类型 形参1,参数类型 形参2)->{ 
	方法体;
}

代码演示一(无参无返回值)

代码语言:javascript复制
public interface FlyAble {
    public void fly();
}
代码语言:javascript复制
public class Test {
    public static void main(String[] args) {
        //匿名内部类的写法
        rocket(new FlyAble() {
            @Override
            public void fly() {
                System.out.println("i can fly!--匿名内部类");
            }
        });
        //lambda表达式的写法
        rocket(()->{
            System.out.println("i can fly!--Lambda表达式");
        });
    }

    public static void rocket(FlyAble f){
        f.fly();
    }
}

lambda表达式的本质就是重写接口中的方法

代码演示一(有参有返回值)

代码语言:javascript复制
public interface FlyAble {
    public int fly(String demo2);
}
代码语言:javascript复制
	public static void main(String[] args) {
        //匿名内部类的写法
        rocket(new FlyAble() {
            @Override
            public int fly(String name) {

                System.out.println("i can fly!--匿名内部类");

                return 30;//分行高度
            }
        },"小鸟");
        //lambda表达式的写法
        rocket((String name)->{
            System.out.println("i can fly!--Lambda表达式");
            return 10000;
        },"飞机");
    }

    public static void rocket(FlyAble f,String name){
        int height = f.fly(name);
        System.out.println(name "的分行高度是:" height);
    }

1.4.2、Lambda表达式的省略写法

(1)小括号内参数的类型可以省略

(2)如果小括号内有且仅有一个参数,则小括号可以省略

(3)如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号

例如:

代码语言:javascript复制
public interface FlyAble {
    public void fly(String name);
}
代码语言:javascript复制
public class Test {
    public static void main(String[] args) {
        
     //lambda表达式的写法
     rocket(name->System.out.println(name "can fly!--Lambda表达式"));
    }

    public static void rocket(FlyAble f){
        f.fly("bird");
    }
}

2、JDK8接口的方法增强

2.1、概述

JDK1.8之前,接口中允许出现的成员有静态常量、抽象方法

代码语言:javascript复制
//JDK1.8以前
public interface InterA {
    //静态常量
    //抽象方法
}

JDK1.8之前只允许接口中出现抽象方法,但是在实际的使用过程中,发现这样会影响接口的扩展性。例如:当往一个接口中添加新的抽象方法时,原来实现该接口的类都会报错!这样就显得“牵一发而动全身”!

为了解决这一弊端,JDK在1.8版本中,对接口的功能进行了扩展!

JDK1.8之后,接口中允许出现的成员有静态常量、抽象方法、默认方法、静态方法

代码语言:javascript复制
//JDK1.8以后
public interface InterB {
    //静态常量
    //抽象方法
    //默认方法
    //静态方法
}

2.2、JDK1.8接口新增方法种类

  • 默认方法
  • 静态方法

2.3、默认方法的定义和使用

2.3.1、默认方法的定义

默认方法定义在接口中

他是个有方法体的方法

他定义的关键字是default

default出现的位置在方法返回值类型前面

定义格式如下:

代码语言:javascript复制
interface InterA {
    public void methodA();
    //默认方法
    public default void methodDef(){
        //功能代码
    }
}

2.3.2、默认方法的使用

(1)直接用

(2)重写

代码语言:javascript复制
interface InterA {
    public void methodA();
    public default void methodDef(){
        System.out.println("default ... method");
    }
}

//直接用
class A implements InterA{
    @Override
    public void methodA() {

    }
}
//重写
class B implements InterA{
    @Override
    public void methodA() {

    }

    @Override
    public void methodDef() {
        System.out.println("B ...default ... method");
    }
}

class Test{
    public static void main(String[] args) {
        InterA a = new A();
        a.methodDef();//直接用

        InterA b = new B();
        b.methodDef();//重写
    }
}

运行结果:

2.4、静态方法的定义和使用

2.4.1、静态方法的定义

静态方法定义在接口中

他是个有方法体的静态方法

定义格式如下:

代码语言:javascript复制
interface InterD {
    //定义静态方法
    public static void methodSta(){
        //方法体
    }
    public void methodA();
    public default void methodDef(){
        System.out.println("default ... method");
    }
}

从上面可以看出,接口中静态方法的定义和普通类中的静态方法的定义没啥区别!

2.4.2、静态方法的使用

注意:接口中的静态方法只能通过接口名调用

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

1、默认方法通过实例调用,静态方法通过接口名调用

2、默认方法可以被继承,可以被重写

3、静态方法不能被继承,不能被重写,只能使用接口名调用静态方法

3、JDK提供的常用内置函数式接口

3.1、为什么JDK要提供这些常用内置函数接口?

因为Lambda表达式不关心接口名称,只关心接口的形参列表及返回值,所以为了方便我们使用Lambda表达式进行编程(函数式编程),JDK就提供了大量形参列表不同,返回值不同的函数式接口!

3.2、常用的函数式接口如下

(1)Supplier接口:供给型接口

(2)Consumer接口:消费型接口

(3)Function接口:转换型接口

(4)Predicate接口:判断型接口

3.2.1、Supplier接口:供给型接口
代码语言:javascript复制
@FunctionalInterface
public interface Supplier<T> {

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

Supplier接口,他可以完成供给的功能,对应的lambda表达式需要“对外提供”一个符合泛型类型的对象数据。

供给型接口:通过get方法得到一个返回值,该返回值类型,通过泛型规定,是一个无参有返回值的接口!

案例:使用Lambda表达式返回数组元素的最小值!

代码语言:javascript复制
    public static void main(String[] args) {
        printMin(()->{
            int[] arr = {1,2,3,4,5};
            Arrays.sort(arr);
            return arr[0];
        });
    }

    public static void printMin(Supplier<Integer> min){
        Integer minVlaue = min.get();
        System.out.println(minVlaue);
    }
3.2.2、Consumer接口:消费型接口
代码语言:javascript复制
@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);
}

案例:把一个字符串中的字母全部转换成小写

代码语言:javascript复制
	public static void main(String[] args) {
        toLower((String str)->{
            String lower = str.toLowerCase();
            System.out.println(lower);
        });
    }

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

案例:把一个字符串既转换成大小,又转换成小写

代码语言:javascript复制
	public static void main(String[] args) {
        toLowerAndToUpper((String str)->{
            System.out.println(str.toLowerCase());
        },
        (String str)->{
            System.out.println(str.toUpperCase());
        });
    }

    public static void toLowerAndToUpper(Consumer<String> consumer1,Consumer<String> consumer2){
        consumer1.accept("HelloWorld");
        consumer2.accept("HelloWorld");
    }

另一种写法

代码语言:javascript复制
public static void main(String[] args) {
        toLowerAndToUpper((String str)->{
            System.out.println(str.toLowerCase());
        },
        (String str)->{
            System.out.println(str.toUpperCase());
        });
    }

    public static void toLowerAndToUpper(Consumer<String> consumer1,Consumer<String> consumer2){
        consumer1.andThan(consumer2).accept("HelloWorld");
    }
3.2.3、Function接口:转换型接口
代码语言:javascript复制
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

案例:把字符串转换成一个整数并返回

代码语言:javascript复制
	public static void main(String[] args) {
        parseToInt((String str)->{
            return Integer.parseInt(str);
        });
    }
    public static void parseToInt(Function<String,Integer> fun){
        Integer apply = fun.apply("10");
        System.out.println(apply);
    }

案例:把两个字符串转换成整数,并对这两个整数求和

代码语言:javascript复制
	public static void main(String[] args) {
        sum((String str)->{
            return Integer.parseInt(str);
        },(String str)->{
            return Integer.parseInt(str);
        });
    }
    public static void sum(Function<String,Integer> 					fun1,Function<String,Integer> fun2){
        Integer num1 = fun1.apply("10");
        Integer num2 = fun1.apply("10");
        System.out.println(num1 num2);
    }

案例:传递两个参数,第一个参数是个字符串,要求把这个字符串转成数字;第二个是要求把转换之后的数字乘以5

代码语言:javascript复制
	public static void main(String[] args) {
        mul((String str)->{
            return Integer.parseInt(str);
        },(Integer i)->{
            return i*5;
        });
    }

    public static void mul(Function<String,Integer> fun1,Function<Integer,Integer> fun2){
        Integer num1 = fun1.apply("10");
        Integer num2 = fun2.apply(num1);
        System.out.println(num2);
    }

另外的实现方式

代码语言:javascript复制
public static void main(String[] args) {
        mul((String str)->{
            return Integer.parseInt(str);
        },(Integer i)->{
            return i*5;
        });
    }


    public static void mul(Function<String,Integer> fun1,Function<Integer,Integer> fun2){
        fun1.andThen(fun2).apply("10");
    }
3.2.4、Predicate接口:判断型接口
代码语言:javascript复制
@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

案例:判断字符串的长度是否大于3

代码语言:javascript复制
	public static void main(String[] args) {
        is3Length((String str)->{
            return str.length()>3;
        });
    }
    public static void is3Length(Predicate<String> pre){
        boolean fbb = pre.test("fbbb");
        System.out.println("长度是否是3个长度:" fbb);

    }

案例:判断字符串中是否既包含W,也包含H

代码语言:javascript复制
	public static void main(String[] args) {
        wANDh((String str)->{
            return str.contains("w") && str.contains("h");
        });
    }
    public static void wANDh(Predicate<String> pre){
        boolean rst = pre.test("wwaaah");
        System.out.println("是否既包含W也包含H:" rst);
    }

另外一种写法

代码语言:javascript复制
public static void main(String[] args) {
        wANDh((String str)->{
            return str.contains("w");
        },(String str)->{
            return str.contains("h");
        });
    }
    public static void wANDh(Predicate<String> pre1,Predicate<String> pre2){
        boolean rst1 = pre1.test("hahwahah");
        boolean rst2 = pre2.test("hahwahah");
        boolean rst=rst1&&rst2;
        System.out.println("是否既包含W也包含H:" rst);
    }

另外一种写法

代码语言:javascript复制
	public static void main(String[] args) {
        wANDh((String str)->{
            return str.contains("w");
        },(String str)->{
            return str.contains("h");
        });
    }

    public static void wANDh(Predicate<String> pre1,Predicate<String> pre2){
        boolean rst = pre1.and(pre2).test("aaahhaaawww");
        System.out.println("是否既包含W也包含H:" rst);
    }

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

代码语言:javascript复制
	public static void main(String[] args) {
       wORh((String str)->{
           return str.contains("w");
       },(String str)->{
           return str.contains("h");
       });
    }

    public static void wORh(Predicate<String> pre1,Predicate<String> pre2){
        boolean rst = pre1.or(pre2).test("aaahhaaawww");
        System.out.println("是否包含W或包含H:" rst);
    }

案例:使用Lambda表达式判断一个字符串中是否不包含W

代码语言:javascript复制
	public static void main(String[] args) {
        isNotContain((String str)->{
            return str.contains("a");
        });
    }

    public static void isNotContain(Predicate<String> pre1){
        boolean rst = pre1.negate().test("hahwahah");
        System.out.println("是否不包含:" rst);
    }

negate()的作用就是对后面的test方法的结果取反。

4、Lambda表达式的方法引用

定义:把方法中的代码像变量值一样传递 (int a=10;int b=a)

把方法传递给抽象方法

4.1、Lambda表达式的方法引用的作用是什么?

先来看一下Lambda表达式中代码冗余的场景

代码语言:javascript复制
public class DemoReferenceMethod {
	public static void main(String[] args) {
        int[] arr = {1,2,3};
        printSum(arr);//普通方法调用
       
        //lambda方法调用
        printSumLambda((Integer[] arr2)->{
           	int sum=0;
            for (int i : arr2) {
                sum =i;
            }
            System.out.println(sum);
     	});
    }

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

    public static void printSumLambda(Consumer<int[]> con){
        int[] arr = {1,2,3};
        con.accept(arr);
    }
}

从上面代码中中可以看出,printSum的方法内容和printSumLambda方法的lambda表达式的形参内容是一样的,所以这里存在了代码冗余。

​ printSum的方法内容=printSumLambda方法的lambda表达式

将上面的内容用方法引用改进

代码语言:javascript复制
		//lambda方法调用
        printSumLambda((Integer[] arr2)->{
           	int sum=0;
            for (int i : arr2) {
                sum =i;
            }
            System.out.println(sum);
     	});
     	//用方法引用改进上面的lambda表达式
     	printSumLambda(DemoReferenceMethod::printSum)

4.2、方法引用的格式

符号表示: ::

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

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

4.3、常见引用方式

对象::方法名

类名::静态方法名

类名::方法名

类名::new (调用的构造器)

数组类型::new (调用数组的构造器)

4.3.1、对象::方法名
代码语言:javascript复制
	public static void main(String[] args) {
        Date d = new Date();
        //不使用方法引用
        printTime(()->{
            return d.getTime();
        });

        //使用方法引用
        printTime(d::getTime);
    }
    public static void printTime(Supplier<Long> supp){
        Long aLong = supp.get();
        System.out.println(aLong);
    }
代码语言:javascript复制
	public void test(){
		Date d = new Date();
		Supplier<Long> aLong= d::getTime;
		Long l=aLong.get();
		System.out.println(l);
	}

方法引用的注意事项:

​ A:被引用的方法,参数要和接口中抽象方法的参数一样

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

4.3.2、类名::静态方法名
代码语言:javascript复制
	public void test(){
		Supplier<Long> aLong= ()->{
            return System.currentTimeMillis();
        };
        System.out.println(aLong.get());
		Supplier<Long> aLong2= System::currentTimeMillis;
        System.out.println(aLong2.get());
	}
4.3.3、类名::方法名
代码语言:javascript复制
	public void test(){
		Function<String,Integer> fun1 = (String str)->{
            return str.length();
        }
        System.out.println(fun1.apply("hello"));
        //方法引用
        Function<String,Integer> fun2 = String::length;
        System.out.println(fun2.apply("helloabc"));
        //方法引用
        BiFunction(String,Integer,String) fun3 = String:substring;
        String sub=fun3.apply("hello",2);
        System.out.println(sub);
	}
4.3.4、类名::new (引用构造器)
代码语言:javascript复制
class Person{
	public Person(){}
	public Person(String name,int age){
		this.name=name;
		this.age=age;
	}
	private String name;
	private int age;
	public String getName(){
		return name;
	}
	public void setName(String name){
		this.name=name;
	}
	public void setAge(int age){
		this.age=age;
	}
	public int getAge(){
		return age;
	}
}
代码语言:javascript复制
	public void test(){
		Supplier<Person> sup = ()->{
            return new Person();
        }
        Person p = sup.get();
        //方法引用
        Supplier<Person> sup2 = Person::new;
        Person p2 = sup2.get();
        
        //方法引用
        BiFunction(String,Integer,Person) fun3 = Person:new;
        Person p3=fun3.apply("zs",2);
	}
4.3.5、数组类型::new
代码语言:javascript复制
public void test{
	Function<String[],Integer> fun = (len)->{
        return new String[len];
    }
    String[] strs1 = fun.apply(10);
    
   	Function<String[],Integer> fun2 = String[]::new
    String[] strs2 = fun2.apply(10);
}

4.4、方法引用总结

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

5、Stream流

5.1、Stream流思想概述

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

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

5.2、Stream流思想的体现案例

代码语言:javascript复制
/*
	假设集合中有如下人物:张学友,周润发,赵薇,张绍忠,张三丰。
	要求:1、拿到所有姓张的人物
		 2、拿到名字长度为3个字的
		 3、打印这些数据
	实现:
		传统做法:遍历三次集合,实现规定的要求
		流式做法:遍历一个,实现规定要求
*/
//1、传统做法
public class StreamDemo{
    public static void main(String[] args){
        ArrayList<String> list = new ArrayList<String>();
        Collections.addAll(list,"张无忌","周芷若","赵薇","张强","张三丰");
        
        //1、拿到所有姓张的人物
        ArrayList<String> zhang = new ArrayList<String>();
        for(String name:list){
            if(name.startsWith("张")){
                zhang.add(name);
            }
        }
        //2、拿到名字长度为3个字的
        ArrayList<String> threeLength = new ArrayList<String>();
        for(String name:zhang){
            if(name.length()==3){
                threeLength.add(name);
            }
        }
        //3、打印这些数据
        for(String name:threeLength){
            System.out.println(name);
        }
    }
}
代码语言:javascript复制
//2、流式做法
public class StreamDemo2{
    public static void main(String[] args){
        ArrayList<String> list = new ArrayList<String>();
        Collections.addAll(list,"张无忌","周芷若","赵薇","张强","张三丰");
        list.stream().filter(s->s.startsWith("张")).
            filter(s->s.length()==3).
            forEach(System.out::println);
    }
}

打印结果:

5.3、获取Stream流的两种方式

(1)根据Collection获取流

(2)Stream中的静态方法of获取流

5.3.1、根据Collection的stream()方法获取流
5.3.2、根据Stream类的静态of()方法

直接传入多个字符串

传入一个字符串数组

传入一个整数数组

5.5、Stream的注意事项

​ 1、Stream只能操作一次

​ 2、Stream方法返回的是新的流

​ 3、Stream不调用终结方法,终结的操作不会执行

5.4、Stream常用API

Stream的API分为两类:

​ 1、终结方法—>返回值不是Stream类型—>不支持链式调用

​ 2、非终结方法—>返回值是Stream类型—>支持链式调用

注意:concat是Stream的静态方法

5.6、Stream常用API演示

forEach:逐个遍历

count:逐个遍历

limit:显示前几个

skip:跳过前几个

map:就是把一种类型的流映射成另外一种流

这里的map和集合中的map不是一个意思,这里的map只是把一种类型的值映射成另外一种类型的值,没有键值对!

把字符串转换成整数

sorted:对流中的数据进行排序

distinced:对流中的数据进行去重

math:元素匹配,有三种匹配情况

(1)allMatch():匹配所有

(2)noneMatch():判断是否是无匹配

(3)anyMatch():只要有一个匹配就行

find:元素查找

查找第一个:

方式一:findFirst()

方式二:findAny()

max和min方法,查找最大值和最小值

reduce:对数据进行加工处理

(1)对数据进行求和

(2)找最大值

map和reduce结合的练习

(1)求集合中Person对象的年龄总和

代码语言:javascript复制
 public static void main(String[] args) {
     //求流中的人的年龄和
     ArrayList<Person> list = new ArrayList<Person>();
     Collections.addAll(list,new Person("张学友",18),
     new Person("周杰伦",19),
     new Person("周润发",20),
     new Person("张学友",40));
     Integer totalAge = list.stream().map((p) -> {
     return p.getAge();
     }).reduce(0, (x, y) -> {
     return x   y;
     });
     System.out.println(totalAge);
  }

(2)求Person中年龄最大是多少

代码语言:javascript复制
	public static void main(String[] args) {
        //求流中的人的最大年龄
        ArrayList<Person> list = new ArrayList<Person>();
        Collections.addAll(list,new Person("张学友",18),
                new Person("周杰伦",19),
                new Person("周润发",20),
                new Person("张学友",40));
        Integer max = list.stream().map((p) -> p.getAge()).reduce(0, Integer::max);
        System.out.println(max);
    }

(3)求字符串数组中”a“出现了多少次?

代码语言:javascript复制
	public static void main(String[] args) {
        //求字符串数组中“a”出现了多少次
        String[] str = {"a","b","c","a","b"};
        Integer count = Stream.of(str).map((s) -> {
            if (s == "a") {
                return 1;
            } else {
                return 0;
            }
        }).reduce(0, (x, y) -> {
            return x   y;
        });
        System.out.println(count);
    }

Stream流的mapToInt方法

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

InteStream和Stream的继承体系

mapToInt的基本用法

代码语言:javascript复制
public static void main(String[] args) {
        IntStream intStream = Stream.of(1, 2, 3).mapToInt((i)->i.intValue());
        intStream.forEach(System.out::println);
    }

使用基本数据类型可以节省内存空间

收集Stream流的结果

(1)收集流的结果到集合中去

(2)收集流的结果到数组中去

收集流的结果到集合中去

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

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

public static <T, C extends Collection>Collector<T, ?, C> toCollection(Supplier collectionFactory):转换为指定集合

收集结果到数组中去

对流中的数据进行聚合计算

(1)获取最大值

(2)获取最小值

(3)求总和

(4)求平均值

(5)分组

(6)多级分组

(7)分区

(8)拼接

获取最大值

求四大天王中年龄最大的

求四大天王中年龄最小的

求四大天王的年龄总和

求四大天王的平均年龄

对流中的数据进行分组

四大天王按照年龄进行分组

map的便捷遍历方式

原理:

将年龄19岁以上(包括19)分为一组,19岁以下分为一组。

多级分组

先按性别进行分组,再按年龄进行分组

对流中的数据进行分区,true为一个列表区,false为一个列表区

对流中的数据进行拼接

6、并行的Stream流

串行的Stream流

这样单线程处理,如果数量大,cpu核心多,势必会造成效率低下、资源浪费的情况!

6.1、并行的Stream流的获取方式

(1)通过集合直接获取并行流

(2)通过Stream对象的parallel()方法将串行流转变成并行流

方式一:通过集合直接获取并行流

方式二:通过Stream对象的parallel()方法将串行流转变成并行流

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

需求:计算8亿的累加和

(1)for循环

(2)串行流

(3)并行流

6.2、并行流的线程安全问题

线程安全问题现场

需求:把1000个数字用并行流存到集合中去。

线程安全问题的解决方案:

(1)加锁

(2)使用线程安全的集合

(3)使用串行流

把并行流转成串行流

(4)使用Collectors的toList方法/Stream的toArray方法

7、Fork/Join框架

Fork/Join框架是并行流parallelStream底层使用的技术,Fork/Join框架是JDK1.7底层使用的技术。Fork/Join框架可以将一个大任务拆分成很多个小任务来异步执行。

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

(1):线程池,ForkJoinPool

(2):任务对象,ForkJoinTask

(3):执行任务的线程,ForkJoinWorkerThread

7.1、Fork/Join框架原理-分治法

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

7.2、Fork/Join原理-工作窃取法

当执行新的任务时Fork/Join框架会将任务拆分分成更小的任务执行,并将小任务加到线程队列中,当多个线程同时执行时,就总会有线程先执行完毕,有线程后执行完毕。先执行完毕的线程会从其它线程队列的末尾窃取任务来执行。为什么会从其它线程的末尾窃取了,因为如果从头部位置开始窃取,可能会遇到线程安全的问题。

7.3、ForkJoin案例

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

代码语言:javascript复制
package Test;

import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Demo10 {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        ForkJoinPool pool = new ForkJoinPool();
        SumRecursiveTask task = new SumRecursiveTask(1L,10000L);
        Long rst = pool.invoke(task);
        long endTime = System.currentTimeMillis();
        System.out.println("结果是:" rst);
        System.out.println("计算时间是:" (endTime-startTime));

    }
}

class SumRecursiveTask extends RecursiveTask<Long>{
    private Long start;
    private Long end;
    private static final int THRESHOLD=3000;

    public SumRecursiveTask(Long start, Long end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        Long length = end-start;//计算任务长度
        if(length<=THRESHOLD){//如果在阈值范围内就进行计算
            long sum=0;
            for(long i=start;i<=end;i  ){
                sum =i;
            }
            System.out.println("计算:start:" start "->" end "之间的值是:" sum);
            return sum;
        }else{//如果不在阈值范围内就继续拆分
            long middle = (start end)/2;
            System.out.println("拆分:左边" start "->" middle "  右边:" (middle 1) "->" end);
            //递归调用
            SumRecursiveTask left = new SumRecursiveTask(start,middle);
            left.fork();
            SumRecursiveTask right = new SumRecursiveTask(middle 1,end);
            right.fork();
            return left.join() right.join();
        }
    }
}

8、Optional的使用

我们之前写代码,要经常进行空值的判断,避免出现空指针异常。JDK8针对这一情况推出了Optional来改进这一情况!

首先来看一下之前对null值的处理情况吧!

代码语言:javascript复制
	public static void main(String[] args) {
        Person p= new Person("张三",18);
        p=null;
        if(p!=null){
            System.out.println(p);
        }else{
            System.out.println("查无此人");
        }
    }

8.1、Optional类介绍

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

8.2、Optional类的创建方式:

代码语言:javascript复制
Optional.of(T t):创建一个Optional实例
Optional.empty():创建一个空的Optional实例
Optional.ofNullable(T t):若t不为null,创建Optional实例,否则创建空实例
代码语言:javascript复制
    //Optional.of(T t):创建一个Optional实例
	public static void main(String[] args) {
        Person p= new Person("张三",18);
        Optional<Person> p1 = Optional.of(p);//不能放空值
        Person person = p1.get();//获取对象
        System.out.println(person);
    }
代码语言:javascript复制
    public static void main(String[] args) {
        Person p= new Person("张三",18);
        //Optional.ofNullable(T t):若t不为null,创建Optional实例,否则创建空实例
        Optional<Person> p1 = Optional.ofNullable(p);
        Person person = p1.get();
        System.out.println(person);
    }

8.3、Optional类的常用方法:

代码语言:javascript复制
isPresent():判断是否包含值,包含值返回true,不包含值返回false
get():如果Optional有值则将其返回,否则抛出NoSuchElementException
orElse(T t):如果调用对象包含值,返回该值,否则返回参数t
orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回s获取的值
map(Function f):如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()

9、JDK8的时间和日期

9.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类,但他们同样存在上述所有的问题。

Date类的缺陷

代码语言:javascript复制
    public static void main(String[] args) throws Exception {
        Date d = new Date(1990,1,1);
        System.out.println(d);
    }

日期解析和格式化缺陷

线程不安全

代码语言:javascript复制
	public static void main(String[] args) throws Exception {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        for (int i=0;i<100;i  ){
            new Thread(()->{
                Date d = null;
                try {
                    d = sdf.parse("2020-1-1");
                } catch (ParseException e) {
                    e.printStackTrace();
                }
                System.out.println(d.toLocaleString());
            }).start();

        }
    }

9.2、新版本日期时间API介绍

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

LocalDate:表示日期,包含年月日,格式为2019-10-16

LocalTime:表示时间,包含时分秒,格式为 16:38:54 158549300

LocalDateTime:表示日期时间,包含年月日,时分秒,格式为:2018-09-06T15:33:56.750

DateTimeFormatter:日期时间格式化类。

Instant:时间戳,表示一个特定的时间瞬间

Duration:用于计算两个时间(LocalTime,时分秒)的距离

Period:用于计算两个日期(LocalDate,年月日)的距离

ZonedDateTime:包含时区的时间

9.3、Java中的历法

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

​ ThaiBuddhistDate:泰国佛教历

​ MinguoDate:中华民国历

​ JapaneseDate:日本历

​ HijrahDate:伊斯兰历

9.4、JDK8的日期和时间类

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

代码语言:javascript复制
 	public static void main(String[] args) throws Exception {
        //LocalDate:表示日期,年、月、日
        //创建指定日期
        LocalDate fj = LocalDate.of(2020,1,1);
        System.out.println(fj);//2020-01-01
        //创建当前日期
        LocalDate now = LocalDate.now();
        System.out.println(now);
        //获取日期信息
        System.out.println(now.getYear());//获取年
        System.out.println(now.getMonthValue());//获取月
        System.out.println(now.getDayOfMonth());//日
        System.out.println(now.getDayOfWeek());//周几
    }
代码语言:javascript复制
	public static void main(String[] args) throws Exception {
        //LocalTime:表示十分秒
        //得到当前时间对象
        LocalTime lt = LocalTime.now();
        System.out.println(lt);
        int hour = lt.getHour();//得到小时
        int minute = lt.getMinute();//得到分钟
        int second = lt.getSecond();//得到秒
        int nano = lt.getNano();//得到纳秒
        System.out.println(hour);
        System.out.println(minute);
        System.out.println(second);
        System.out.println(nano);
    }
代码语言:javascript复制
	public static void main(String[] args) throws Exception {
        //获取当前日期和时间
        LocalDateTime ldt = LocalDateTime.now();
        //以下分别得到:年、月、日、时、分、秒、纳秒
        int year = ldt.getYear();
        int month = ldt.getMonthValue();
        int day = ldt.getDayOfMonth();
        int hour = ldt.getHour();
        int minute = ldt.getMinute();
        int second = ldt.getSecond();
        int nano = ldt.getNano();
        System.out.println(year);
        System.out.println(month);
        System.out.println(day);
        System.out.println(hour);
        System.out.println(minute);
        System.out.println(second);
        System.out.println(nano);
    }

对日期的修改,使用withAttribute方法,该方法会返回修改之后的日期时间对象,原来的日期时间对象不会发生改变。

修改日期

代码语言:javascript复制
	public static void main(String[] args) throws Exception {
        LocalDateTime ldt = LocalDateTime.now();
        System.out.println(ldt);
        //修改年份
        LocalDateTime ldt2 = ldt.withYear(2022);
        System.out.println(ldt2);
    }

增加和减少年份

代码语言:javascript复制
	public static void main(String[] args) throws Exception {
        LocalDateTime ldt = LocalDateTime.now();
        System.out.println(ldt);
        //增加和减少年份
        LocalDateTime localDateTime = ldt.plusYears(10);//增加年份
        System.out.println(localDateTime);
        //减少年份
        LocalDateTime localDateTime1 = localDateTime.minusYears(10);
        System.out.println(localDateTime1);
    }

年份的判断

代码语言:javascript复制
	public static void main(String[] args) throws Exception {
        LocalDateTime ldt = LocalDateTime.now();
        LocalDateTime ldt2 = LocalDateTime.of(2019, 10, 10, 10, 10, 10);
        boolean after = ldt.isAfter(ldt2); //是否在ldt2之后
        System.out.println(after);
        boolean before = ldt.isBefore(ldt2);//是否在ldt2之前
        System.out.println(before);
        boolean equal = ldt.isEqual(ldt2);//是否和ldt2相等
        System.out.println(equal);
    }

JDK 8的时间格式化与解析

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

JDK自带的时间、日期格式化模板

代码语言:javascript复制
	public static void main(String[] args) throws Exception {
        LocalDateTime ldt = LocalDateTime.now();
        DateTimeFormatter isoDateTime = DateTimeFormatter.ISO_DATE_TIME;//jdk自带的时间日期格式化模板
        String format = isoDateTime.format(ldt);
        System.out.println(format);
    }

自定义时间、日期格式化模板

代码语言:javascript复制
	public static void main(String[] args) throws Exception {
        LocalDateTime ldt = LocalDateTime.now();
        DateTimeFormatter zdy = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");//自定义解析模板
        String format = zdy.format(ldt);
        System.out.println(format);
    }

时间日期的解析

代码语言:javascript复制
	public static void main(String[] args) throws Exception {
        LocalDateTime ldt = LocalDateTime.now();
        DateTimeFormatter zdy = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");//自定义解析模板
        LocalDateTime parse = LocalDateTime.parse("2020年01月04日 10时30分05秒", zdy);
        System.out.println(parse);
    }
代码语言:javascript复制
	public static void main(String[] args) throws Exception {
        LocalDateTime ldt = LocalDateTime.now();
        DateTimeFormatter zdy = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");//自定义解析模板
        for (int i=0;i<100;i  ){
            new Thread(
                    new Runnable() {
                        @Override
                        public void run() {
                            LocalDateTime parse = LocalDateTime.parse("2020年01月04日 10时30分05秒", zdy);
                            System.out.println(parse);
                        }
                    }
            ).start();
        }
    }

Instant

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

代码语言:javascript复制
	public static void main(String[] args) throws Exception {
        Instant now = Instant.now();
        System.out.println("当前时间戳=" now);
        System.out.println(now.getNano());//获取时间戳的纳秒值
        System.out.println(now.getEpochSecond());
        System.out.println(now.toEpochMilli());
        System.out.println(System.currentTimeMillis());
    }

Instant的加操作

计算时间和日期差

1、Duration:用于计算2个时间(LocalTime,时分秒)的距离

2、Period:用于计算2个日期(LocalDate,年月日)的距离

JDK8的时间矫正器

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

TemporalAdjuster:时间校正器

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

TemporalAdjuster:时间校正器

代码语言:javascript复制
//TemporaAdjuster:时间矫正器
//TemporalAdjusters:该类通过静态方法提供了大量的常用TemporalAdjuster的实现
public class Demo36 {

    public static void main(String[] args) throws Exception {
        LocalDateTime dateTime = LocalDateTime.now();
        //得到下个月的第一天
        TemporalAdjuster firsWeekDayOfNextMonth = (Temporal temporal) -> {
            LocalDateTime ldt = (LocalDateTime) temporal;//把temporal转换成LocalDateTime类型
            LocalDateTime nextMonth = ldt.plusMonths(1).withDayOfMonth(1);//调整时间,加一个月,加一个月后的第一天
            System.out.println(nextMonth);
            return nextMonth;//返回调整之后的时间
        };
    }
}
代码语言:javascript复制
public static void main(String[] args) throws Exception {
        LocalDateTime dateTime = LocalDateTime.now();
        //得到下个月的第一天
        TemporalAdjuster firsWeekDayOfNextMonth = (Temporal temporal) -> {
            LocalDateTime ldt = (LocalDateTime) temporal;//把temporal转换成LocalDateTime类型
            LocalDateTime nextMonth = ldt.plusMonths(1).withDayOfMonth(1);//调整时间,加一个月,加一个月后的第一天
            System.out.println(nextMonth);
            return nextMonth;//返回调整之后的时间
        };

        LocalDateTime now = dateTime.with(firsWeekDayOfNextMonth);
//        LocalDateTime now2 = (LocalDateTime)firsWeekDayOfNextMonth;
        System.out.println(now);

    }

结果:

JDK8设置时间和日期的时区

Java8 中加入了对时区的支持,LocalDate、LocalTime、LocalDateTime是不带时区的,带时区的日期时间类分别为:ZonedDate、ZonedTime、ZonedDateTime。

其中每个时区都对应着ID,ID的格式为“区域/城市”。例如:Asia/Shanghai 等。

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

代码语言:javascript复制
public static void main(String[] args) throws Exception {
        //获得所有的时区ID
//        ZoneId.getAvailableZoneIds().forEach(System.out::println);
        //创建世界标准时间
        final ZonedDateTime zonedDateTimeFromClock = ZonedDateTime.now(Clock.systemUTC());
        System.out.println(zonedDateTimeFromClock);//2020-01-09T01:44:36.951697400Z
        //创建一个当前时间
        LocalDateTime now = LocalDateTime.now();
        System.out.println(now);//2020-01-09T09:48:21.130519700
        //创建一个带时区的ZonedDateTime
        final ZonedDateTime zonedDateTimeFromZone = ZonedDateTime.now(ZoneId.of("Africa/Nairobi"));
        System.out.println(zonedDateTimeFromZone);//2020-01-09T04:48:21.131519700 03:00[Africa/Nairobi]
    }

0 人点赞