JDK8部分新特性

文章目录


JDK8新特性

Lambda表达式

1. Lambda表达式的标准格式

(参数类型 参数名称)->{

​ 方法体;

}

格式说明

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

无参Lambda与匿名内部类

package com.lld;
import com.lld.Inte.Smoke;
import com.lld.Inte.Study;

/**
 * @ClassName Lambdademo1
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/6 17:51
 * @Version 1.0
 */
public class Lambdademo1 {
    /*
        Lambda的标准格式:
            (参数列表) -> {

            }
            (参数列表):参数列表
            {}:方法体
            ->:没有实际含义
     */

    public static void main(String[] args) {

        goSmokeing(() -> {
            System.out.println("Lambda在抽烟");
        });
        goSmokeing(new Smoke() {
            @Override
            public void smokeing() {
                System.out.println("匿名内部类在抽烟");
            }
        });

       
        }
    }


    //练习无参数的Lambda
    public static void goSmokeing(Smoke smoke){
        smoke.smokeing();
    }
}
package com.lld.Inte;

/**
 * @ClassName Smoke
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/6 18:00
 * @Version 1.0
 */
public interface Smoke {

    public abstract void smokeing();
}

有参Lambda与匿名内部类

package com.lld;

import com.lld.Inte.Smoke;
import com.lld.Inte.Study;


/**
 * @ClassName Lambdademo1
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/6 17:51
 * @Version 1.0
 */
public class Lambdademo1 {
    /*
        Lambda的标准格式:
            (参数列表) -> {

            }
            (参数列表):参数列表
            {}:方法体
            ->:没有实际含义
     */

    public static void main(String[] args) {

        goStudying(new Study() {
            @Override
            public int Studying(String str) {
                System.out.println(str);
                return 666;
            }
        },"曲秃在学习");

        goStudying((String str) -> {
            System.out.println(str);
            return 666;
        },"张渣在学习");
    }



    //练习有参数的Lambda
    public static void goStudying(Study study,String str){
        int i = study.Studying(str);
        System.out.println("返回值:" + i);
    }

}
package com.lld.Inte;

/**
 * @ClassName Study
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/6 18:39
 * @Version 1.0
 */
public interface Study {

    public int Studying(String str);

}
2. 了解Lambda的实现原理

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

Lambda在程序运行后会生成一个类

1583501672741

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

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

  1. 小括号内的参数可以省略
  2. 如果小括号内有且仅有一个参数,则小括号可以省略
  3. 如果大括号内有且仅有一个语句,可以同时省略return,大括号,分号

省略前

goStudying((String str) -> {
	System.out.println(str);
},"张渣在学习");
  • 运行结果

1583502282387

省略后

goStudying(str -> System.out.println(str),"张渣在学习");
  • 运行结果

1583502291322

4. Lambda表达式的前提条件

Lambda语法虽然简洁,但使用Lambda必须注意以下几点

  1. 方法的参数或者局部变量类型必须为接口才能使用Lambda
  2. 接口中只有一个抽象方法

示范代码

package com.lld;

/**
 * @ClassName demo02
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/6 22:11
 * @Version 1.0
 */
public class demo02 {
    /*
        1. 方法的参数或者局部变量类型必须为接口才能使用Lambda
        2. 接口中只有一个抽象方法
     */

    public static void main(String[] args) {
        //方法参数为接口时
        isFly(str -> {
            System.out.println(str);
            return true;
        });

        //局部变量类型为接口时
        Flyable flyable = str -> true;
    }

    public static void isFly(Flyable flyable){
        boolean b = flyable.isFly("是的");
        System.out.println(b);
    }
}
//只有一个抽象方法的接口称为函数式接口,我们就能使用Lambda表达式
@FunctionalInterface//该注解用于检测该接口内是否只有一个方法,当该接口内有多个方法时,就会报错
interface Flyable{
    public boolean isFly(String str);

}
5. Lambda表达式与内明内部类比较
  1. 所需的类型不一样

    匿名内部类需要的类型可以是类,抽象类,接口

    Lambda表达式需要的必须是接口

  2. 抽象方法的数量不一样

    匿名内部类所需接口中抽象方法数量随意

    Lambda表达式所需接口只能有一个抽象方法

  3. 实现原理不同

    匿名内部类会在编译后形成class

    Lambda表达式在程序运行时动态形成class

6. jdk8中新增的两个方法
介绍

jdk8以前的接口

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

增强后的

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

格式

interface 接口名{
	修饰符 default 返回值类型 方法名(){
		方法体;
	}
}
实例代码
package com.lld;

/**
 * @ClassName demo03
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 10:03
 * @Version 1.0
 */
public class demo03 {
    public static void main(String[] args) {
        BB bb = new BB();
        bb.test1();
        CC cc = new CC();
        cc.test1();
    }
}

interface AA{
    //默认方法是有方法体的
    public default void test1(){
        System.out.println("这是AA接口的默认方法");
    }
}

//默认方法使用方法一:实现类可以直接使用
class BB implements AA{

}

//默认方法使用方法二:实现类可以根据自己需要进行复写
class CC implements AA{
    @Override
    public void test1() {
        System.out.println("这是CC实现类复写的默认方法");
    }
}
静态方法

格式

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

示例代码

package com.lld;

/**
 * @ClassName demo04
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 10:13
 * @Version 1.0
 */
public class demo04 {
    /*
        1. 接口的静态方法不会被实现类继承
        2. 接口的静态方法不会被实现类复写
        3. 接口的的静态方法使用:接口名.静态方法名进行调用
     */
    public static void main(String[] args) {
        AAA.test1();
    }
}


interface AAA{
    //接口的静态方法
    public static void test1(){
        System.out.println("这是接口AAA的静态方法");
    }
}


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

小结

  • 如果方法要被实现类继承或者重写,使用默认方法,如果接口中的方法不需要被继承就使用静态
7. 常用内置函数接口
内置函数接口的由来

Lambda表达式的前提是函数式接口,而Lambda不需要关心接口名,抽象方法名,只关心参数列表及返回值类型

常用的内置函数接口

都在java.util.function;包下

Supplier接口

供给型接口,用于产生一个数据

@FunctionalInterface
public interface Supplier<T> {

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

  • 使用Supplier返回数组元素最大值
package com.lld;

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

/**
 * @ClassName demo05
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 15:08
 * @Version 1.0
 */
public class demo05 {
    /*
        使用Supplier接口的get方法,没有入参,有返回值
     */
    public static void main(String[] args) {
        text(() ->{
            //定义一个数组
            int array[] = {1,99,88,126,66};
            //对数组进行排序,默认升序排序
            Arrays.sort(array);
            //获取最大值
            int i = array[array.length - 1];
            return i;
        });
    }

    public static void text(Supplier<Integer> supplier){
        Integer integer = supplier.get();
        System.out.println("integer:" + integer);
    }
}

Consumer接口

消费型接口,用于消费一个数据,其参数类型由泛型决定

@FunctionalInterface
public interface Consumer<T> {

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

    /**
     * Returns a composed {@code Consumer} that performs, in sequence, this
     * operation followed by the {@code after} operation. If performing either
     * operation throws an exception, it is relayed to the caller of the
     * composed operation.  If performing this operation throws an exception,
     * the {@code after} operation will not be performed.
     *
     * @param after the operation to perform after this operation
     * @return a composed {@code Consumer} that performs in sequence this
     * operation followed by the {@code after} operation
     * @throws NullPointerException if {@code after} is null
     */
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}
  • 使用Consumer将一个字符串先转成大写,在转成小写
package com.lld;

import java.util.function.Consumer;

/**
 * @ClassName demo06
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 15:39
 * @Version 1.0
 */
public class demo06 {

    /*
        使用Consumer将一个字符串转成小写,再转成大写
     */
    public static void main(String[] args) {
                   //转成小写再打印,s是入参                 转成大写再打印,s是入参
        printHello(s -> System.out.println(s.toLowerCase()),s -> System.out.println(s.toUpperCase()));
    }

    public static void printHello(Consumer<String> c1,Consumer<String> c2){
        String str = "Hello World";
        //表示执行完c1的accept再执行c2的accept
        c1.andThen(c2).accept(str);
        //c1.accept(str);
        // c2.accept(str);
    }
}

Function接口

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

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}
  • 示例代码
package com.lld;

import java.util.function.Function;

/**
 * @ClassName demo07
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 16:00
 * @Version 1.0
 */
public class demo07 {

    public static void main(String[] args) {
        number(s -> Integer.parseInt(s), integer -> integer * 5);
    }

    //将字符串转成整型,再将整型数据乘以五
    public static void number(Function<String,Integer> f1,Function<Integer,Integer> f2){
        String str = "10";
        /*Integer num1 = f1.apply(str);
        System.out.println(num1);
        Integer num2 = f2.apply(num1);
        System.out.println(num2);*/
        //将str转成Integer,再将Integer乘以5
        System.out.println(f1.andThen(f2).apply(str));
    }
}

Predicate接口

判断某个值,得到一个Boolean类型的数据,可以用java.util.function.Predicate接口

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}
  • 判断一个人的名字是否很长,大于三个字返回true,小于三个字返回false
package com.lld;

import java.util.function.Predicate;

/**
 * @ClassName demo08
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 16:40
 * @Version 1.0
 */
public class demo08 {

    public static void main(String[] args) {

        //如果名字长度大于三,返回true,否则返回false
        isLongName(s -> s.length()>3,"曲秃");
    }

    public static void isLongName(Predicate<String> predicate,String string){
        boolean b = predicate.test(string);
        System.out.println("名字很长吗?   "+b);
    }
}
  • 使用Lambda表达式判断一个字符串中既包含W也包含H
  • 使用Lambda表达式判断一个字符串中既包含W或者包含H
  • 使用Lambda表达式判断一个字符串中既不包含W
package com.lld;

import java.util.function.Predicate;

/**
 * @ClassName demo08
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 16:40
 * @Version 1.0
 */
public class demo08 {
    public static void main(String[] args) {

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

        test(s -> s.contains("W"),s -> s.contains("H"));
    }

    public static void test(Predicate<String> p1,Predicate<String> p2){

        // * 使用Lambda表达式判断一个字符串中既包含W也包含H
        String str = "Hello World";
        boolean b = p1.and(p2).test(str);
        if (b) {
            System.out.println("既包含W也包含H");
        }

        // * 使用Lambda表达式判断一个字符串中既包含W或者包含H
        boolean b1 = p1.or(p2).test(str);
        if (b1) {
            System.out.println("既包含W或者包含H");
        }

        // * 使用Lambda表达式判断一个字符串中不包含W
        boolean b2 = p1.negate().test("Hello");
        if (b2) {
            System.out.println("不包含W");
        }
    }
}

1583571659965

8.介绍方法引用
Lambda的冗余场景

使用方法引用前

package com.lld;

import java.util.function.Consumer;

/**
 * @ClassName demo09
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 17:10
 * @Version 1.0
 */
public class demo09 {

    //将数组所有元素相加输出
    public static void getSum(int [] arr){
        int sum = 0;
        for (int i : arr) {
            sum += i;
        }
        System.out.println(sum);
    }

    public static void main(String[] args) {
        //不使用方法引用
        //int[] arr是入参
        //getSum(arr);是调用了上面的静态方法
        pringMax((int[] arr) -> {
            getSum(arr);
        });
       
    }

    //使用Consumer获得数组的总和
    public static void pringMax(Consumer<int []> consumer){
        int [] arr = {1,2,3,4,5,6,7,8,9,10};
        consumer.accept(arr);
    }

}

使用方法引用后

package com.lld;

import java.util.function.Consumer;

/**
 * @ClassName demo09
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 17:10
 * @Version 1.0
 */
public class demo09 {

    //将数组所有元素相加输出
    public static void getSum(int [] arr){
        int sum = 0;
        for (int i : arr) {
            sum += i;
        }
        System.out.println(sum);
    }

    public static void main(String[] args) {
        /*//不使用方法引用
        //int[] arr是入参
        //getSum(arr);是调用了上面的静态方法
        pringMax((int[] arr) -> {
            getSum(arr);
        });*/
        // 使用方法引用
        pringMax(demo09::getSum);
    }

    //使用Consumer获得数组的总和
    public static void pringMax(Consumer<int []> consumer){
        int [] arr = {1,2,3,4,5,6,7,8,9,10};
        consumer.accept(arr);
    }

}

1583573193817

方法引用的格式

符号表示:::(一对冒号)

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

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

常见的方法引用方式
  1. 对象::方法名
  2. 类名::静态方法
  3. 类名::普通方法
  4. 类名::new 调用构造器
  5. String[]::new 调用数组构造器
9. 对象::方法名
  • 示例代码
//对象::方法名
@Test
public void test01(){
    //创建一个对象
    Date date = new Date();
    //不使用方法引用
    //Supplier<Long> supplier = () -> date.getTime();
    //使用方法引用
    Supplier<Long> supplier1 = date::getTime;
    System.out.println(supplier1.get());
    /*
            使用方法引用的两个注意事项
                1. 被引用的方法,参数要和接口中的抽象方法参数一致
                2. 当接口抽象方法有返回值时,被引用的方法必须有返回值
         */
}

注意事项

  1. 被引用的方法,参数要和接口中的抽象方法参数一致
  2. 当接口抽象方法有返回值时,被引用的方法必须有返回值
10.类名::静态方法
  • 实例代码
//类名::静态方法
@Test
public void test02(){

    //不使用方法引用
    //Supplier<Long> su = () -> System.currentTimeMillis();

    //使用方法引用
    Supplier<Long> su = System::currentTimeMillis;
    Long time = su.get();
    System.out.println("time = " + time);
}
  • System.currentTimeMillis()方法解释(因为之前不知道)
//获取系统时间:new Date().getTime()底层也是调用的System.currentTimeMillis();方法
@Test
public void test03(){
    //获得系统的时间,单位为毫秒,转换为妙
    long totalMilliSeconds = System.currentTimeMillis();
    long totalSeconds = totalMilliSeconds / 1000;

    //求出现在的秒
    long currentSecond = totalSeconds % 60;

    //求出现在的分
    long totalMinutes = totalSeconds / 60;
    long currentMinute = totalMinutes % 60;

    //求出现在的小时
    long totalHour = totalMinutes / 60;
    long currentHour = totalHour % 24;

    //显示时间
    System.out.println("总毫秒为: " + totalMilliSeconds);
    System.out.println(currentHour + ":" + currentMinute + ":" + currentSecond + " GMT");
}
11. 类名::实例方法

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

  • 示例代码
//类名::实例方法
@Test
public void test04(){

    //根据字符串获取长度

   /*
    //不使用方法引用
    Function<String,Integer> fn = (String s) -> {
        return s.length();
    };*/
    //使用方法引用
    Function<String,Integer> fn = String::length;

    Integer integer = fn.apply("Hello World");
    System.out.println("integer = " + integer);

    //截取字符串
    //使用方法引用:BiFunction泛型有三个参数,参数一:方法调用者,参数二:方法参数,参数三:返回值类型
    BiFunction<String,Integer,String> bfn = (s, integer1) -> s.substring(integer1);
    //上面方法其实就是
    BiFunction<String,Integer,String> bfn1 = (String s,Integer i) ->{
        return s.substring(i);
    };
    String s = bfn.apply("Hello world", 3);

    System.out.println("s = " + s);
}

1583579961507

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

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

  • person
package com.lld;

/**
 * @ClassName Person
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 19:27
 * @Version 1.0
 */
public class Person {

    private String name ;
    private Integer age;

    public Person() {
        System.out.println("调用了无参数的构造方法");
    }

    public Person(String name, Integer age) {
        System.out.println("调用了有参数的构造方法");
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  • 示例代码
//类名::new
@Test
public void test05() {

    //不使用方法引用
    /*Supplier<Person> s = () -> {
      return new Person();
    };*/

    //使用方法引用
    Supplier<Person> s = Person::new;
    Person person = s.get();
    System.out.println("person = " + person);

    //不使用方法引用
    /*BiFunction<String,Integer,Person> bfn = (s1, integer) -> {
        return new Person(s1, integer);
    };*/
    //使用方法引用
    BiFunction<String,Integer,Person> bfn = Person::new;
    Person p1 = bfn.apply("曲秃", 22);
    System.out.println("p1 = " + p1);
}

1583581080297

13. 数组::new引用数组构造器
//数组::new
@Test
public void test06() {
    //不使用方法引用
    /*Function<Integer,int[]> function = (Integer integer) -> {
        return new int[integer];
    };*/
    //使用方法引用
    Function<Integer,int[]> function = int[]::new;
    int[] ints = function.apply(10);
    System.out.println("Arrays.toString(ints) = " + Arrays.toString(ints));
}
14.方法引用小结

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

集合之Stream流式操作

1. Stream流介绍
集合处理数据的弊端
  • 每一个需求都要循环一次集合,并创建一个集合进行数据的存储
package com.lld;

import org.junit.Test;

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

/**
 * @ClassName demo01
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 20:09
 * @Version 1.0
 */
public class demo01 {
    // 1.拿到集合中所有姓张的
    // 2.拿到名字为三个字的
    // 3.进行输出

    @Test
    public void test01(){

        //准备工作:将数据添加到list集合中
        List<String> list = new ArrayList<>();
        Collections.addAll(list,"张无忌","张三丰","周芷若","张渣","曲秃","段黑");

        //不使用Stream流操作
        // 1.拿到集合中所有姓张的
        List<String> zhangList = new ArrayList<>();
        for (String s : list) {
            //判断第一个字符是不是张
            if (s.startsWith("张")){
                zhangList.add(s);
            }
        }
        // 2.拿到名字为三个字的
        List<String> twoList = new ArrayList<String>();
        for (String s : zhangList) {
            if (s.length() == 3){
                twoList.add(s);
            }
        }
        // 3.进行输出
        System.out.println(twoList.toString());


    }

}

Stream流的思想和作用

注意:Stream流和IO流没有任何关系

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

package com.lld;

import org.junit.Test;

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

/**
 * @ClassName demo01
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 20:09
 * @Version 1.0
 */
public class demo01 {
    // 1.拿到集合中所有姓张的
    // 2.拿到名字为三个字的
    // 3.进行输出

    @Test
    public void test01(){

        //准备工作:将数据添加到list集合中
        List<String> list = new ArrayList<>();
        Collections.addAll(list,"张无忌","张三丰","周芷若","张渣","曲秃","段黑");

        //不使用Stream流操作
        // 1.拿到集合中所有姓张的
        List<String> zhangList = new ArrayList<>();
        for (String s : list) {
            //判断第一个字符是不是张
            if (s.startsWith("张")){
                zhangList.add(s);
            }
        }
        // 2.拿到名字为三个字的
        List<String> twoList = new ArrayList<String>();
        for (String s : zhangList) {
            if (s.length() == 3){
                twoList.add(s);
            }
        }
        // 3.进行输出
        System.out.println(twoList.toString());

        //分割线
        System.out.println("------------------------------------------");

        //使用Stream流操作
        // 1.拿到集合中所有姓张的
        // 2.拿到名字为三个字的
        // 3.进行输出
        list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(s -> System.out.println(s));
    }

}

1583584622235

2. 获取Stream的两种方法
  1. 所有的Collection集合都可以通过Stream默认方法获取流
  2. Stream接口的静态方法of可以获取数组对应的流
方式1:根据Collection获取流
@Test
//方法一:通过Collection的默认静态方法:default Stream<E> stream()
public void test01(){
    //方法一:通过Collection的默认静态方法:default Stream<E> stream()
    //list集合
    List<String> list = new ArrayList<>();
    Stream<String> stream1 = list.stream();

    //set集合
    Set<String> set = new HashSet<>();
    Stream<String> stream2 = set.stream();

    //Map集合:map集合和collection没有关系,那么他怎么获取Stream流呢
    Map<String,String> map = new HashMap<>();
    //他的keyset方法时返回一个set集合,里面有所有的key
    Set<String> keySet = map.keySet();
    Stream<String> stream3 = keySet.stream();
    //他的entrySet方法时返回一个set集合,里面有所有的key和value的关系
    Set<Map.Entry<String, String>> entrySet = map.entrySet();
    Stream<Map.Entry<String, String>> stream4 = entrySet.stream();
    //他的values方法时返回一个sCollection集合,里面有所有的value
    Collection<String> values = map.values();
    Stream<String> stream5 = values.stream();
}
方式二:根据Stream的of方法获取流
@Test
//通过Stream的静态方法of获取Stream流:public static<T> Stream<T> of(T... values)参数是可变长度的
public void test02() {
    Stream<String> stream1 = Stream.of("张渣", "段黑", "曲秃", "莽夫", "奥特曼");

    //String数组
    String[] str = {"张渣", "段黑", "曲秃", "莽夫", "奥特曼"};
    Stream<String> stream2 = Stream.of(str);

    //基本数据类型的数组是不行的,因为他会将数组当做一个整体处理
    int[] ints = {11,22,33,44,55,66};
    Stream<int[]> Sstream3 = Stream.of(ints);//返回的泛型类型是int数组,而不是int
}
  • 注意:基本数据类型的数组是不支持的
3. Stream流的常用方法和注意事项
常用方法
方法名方法作用返回值类型方法种类
count统计个数long终结
forEach逐一处理void终结
filter过滤Stream函数拼接
limit取用前几个Stream函数拼接
skip跳过前几个Stream函数拼接
map映射Stream函数拼接
concat组合Stream函数拼接
  • 终结方法:返回值类型不再是Stream类型的方法,不支持链式调用
  • 非终结方法:返回值类型依然是Stream方法,支持链式调用(除终结方法外,其余均是非终结方法,也称为函数拼接)

其余更多方法:参考api文档

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

遍历

forEach用来输出

void forEach(Consumer<? super T> action);//接收一个Consumer接口

//使用forEach方法
@Test
public void test03() {
    List<String> list = new ArrayList<>();
    Collections.addAll(list,"张无忌","张三丰","周芷若","张渣","曲秃","段黑");

    //使用Lambda表达式
    list.stream().forEach(s -> {
        System.out.println(s);
    });

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

    //使用Lambda表达式简写
    list.stream().forEach(s -> System.out.println(s));

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

    //使用方法引用
    list.stream().forEach(System.out::println);
}
5. Stream流中的count方法

计算个数

long count();

//Stream流中Count计数
@Test
public void test04() {
    List<String> list = new ArrayList<>();
    Collections.addAll(list,"张无忌","张三丰","周芷若","张渣","曲秃","段黑");

    long count = list.stream().count();
    System.out.println(count);
}
6. Stream流的Filter方法

过滤

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

//Stream流中Filter方法
@Test
public void test05() {
    List<String> list = new ArrayList<>();
    Collections.addAll(list,"张无忌","张三丰","周芷若","张渣","曲秃","段黑");
    //过滤出姓名长度为二的并输出
    list.stream().filter(s -> s.length() == 2).forEach(System.out::println);
}
7. Stream流的limit方法

选择前几个

Stream limit(long maxSize);

//Stream流的limit方法
@Test
public void test06() {
    List<String> list = new ArrayList<>();
    Collections.addAll(list,"张无忌","张三丰","周芷若","张渣","曲秃","段黑");

    //取数组前三个进行输出
    list.stream().limit(3).forEach(System.out::println);
}
8. Stream流的skip方法

跳过前几个

Stream skip(long n);

//Stream流的skip方法
@Test
public void testSkip() {
    List<String> list = new ArrayList<>();
    Collections.addAll(list,"张无忌","张三丰","周芷若","张渣","曲秃","段黑");
    //跳过集合前三个进行输出
    list.stream().skip(3).forEach(System.out::println);
}
9.Stream流的map操作

将流中一种类型的数据转换成另一种类型的数据

//Stream流的map方法:将一种类型的流转换成另一种类型的流
@Test
public void testMap() {

    Stream<String> stream = Stream.of("11", "22", "33", "44");

    //使用Lambda表达式
    /*stream.map((String s)->{
        return Integer.parseInt(s);
    }).forEach(System.out::println);*/

    //使用Lambda表达式简写
    /*stream.map(s -> Integer.parseInt(s)).forEach(System.out::println);*/
    //使用方法引用
    stream.map(Integer::parseInt).forEach(System.out::println);
}
10.Stream流中的sorted方法

如果要将数据进行排序,可以使用sorted方法

Stream<T> sorted();进行自然排序Stream<T> 
Stream<T> sorted(Comparator<? super T> comparator);使用传入的比较器进行排序
//Stream流中的sorted方法:进行排序
@Test
public void testSorted() {
    // Stream<T> sorted();进行自然排序
    // Stream<T> sorted(Comparator<? super T> comparator);使用传入的比较器进行排序
    Stream<Integer> stream = Stream.of(11, 22, 33, 66, 55, 982);
    // 使用sorted的无参方法进行自然排序
    //stream.sorted().forEach(System.out::println);

    // 使用sorted的有参方法传入比较器进行降序排序
   /* stream.sorted((Integer o1, Integer o2) -> {
        return o2 - o1;
    }).forEach(System.out::println);*/

    // 使用sorted的有参方法传入比较器进行降序排序:使用简写
    stream.sorted((o1, o2) -> o2-o1).forEach(System.out::println);
}
11.Stream流中的distinct方法

去重

  • String和Integer类型数组去重
//Stream流中distinct方法:String和Integer等类型去重
@Test
public void testDistinct() {
    //字符串去重
    Stream<String> stream = Stream.of("aa", "bb", "cc", "aa", "cc");
    stream.distinct().forEach(System.out::println);

    //Integer去重
    Stream<Integer> stream1 = Stream.of(11, 22, 33, 44, 55, 66, 66, 55, 44);
    stream1.distinct().forEach(System.out::println);
}
  • 自定义类型去重(一定要重写自定义类型中的equals方法和hashCode方法)
//Stream流中distinct方法:自定义类型去重(自定义类中一定要复写)
@Test
public void testDistinct1() {
    Stream<Person> personStream = Stream.of(
            new Person("段黑", 22),
            new Person("张渣", 22),
            new Person("曲秃", 22),
            new Person("曲秃", 22),
            new Person("段黑", 22),
            new Person("张渣", 22)
    );
    personStream.distinct().forEach(System.out::println);
}
12.Stream流中的Math方法
  1. allMatch:当stream流中所有元素都满足条件时,返回true,否则返回false
  2. anyMatch:当stream流中任意元素满足条件时,返回true,否则返回false
  3. noneMatch:当stream流中所有元素都不满足条件时,返回true,否则返回false
@Test
public void testMatch() {
    Stream<Integer> stream = Stream.of(1, 2, 5, 9, 3);

    //boolean b = stream.allMatch(integer -> integer > 0);//allMatch:当stream流中所有元素都满足大于0这个条件时,返回true,否则返回false
    //boolean b = stream.anyMatch(integer -> integer > 7);//anyMatch:当stream流中任意元素满足大于7这个条件时,返回true,否则返回false
    boolean b = stream.noneMatch(integer -> integer > 10);//noneMatch:当stream流中所有元素都不满足大于10这个条件时,返回true,否则返回false

    System.out.println(b);
}
13.Stream流中的Find

找出Stream流中的第一个元素

Optional<T> findAny();
Optional<T> findFirst();
@Test
public void testFind() {
    Stream<Integer> integerStream = Stream.of(11, 22, 33, 44, 55);
    //查找Stream中的第一个元素
    //Optional<Integer> integer = integerStream.findAny();
    //查找Stream中的第一个元素,与findAny效果一致
    Optional<Integer> first = integerStream.findFirst();
    System.out.println(first.get());
}
14.Stream流中的Max_Min
  • max获取最大值:需要传入一个比较器
  • min获取最小值:需要传入一个比较器
@Test
public void testMax_Min() {
    //获取最大值
    Optional<Integer> max = Stream.of(11, 22, 1, 5, 9).max((o1, o2) -> o1 - o2);
    System.out.println("最大值:" + max.get());

    //获取最小值
    Optional<Integer> min = Stream.of(11, 22, 1, 5, 9).min((o1, o2) -> o1 - o2);
    System.out.println("最小值:" + min.get());
}
15.Stream流中的reduce方法

将所有的数据归纳总结,得到一个

@Test
public void testReduce() {
    Stream<Integer> stream = Stream.of(4, 5, 9, 3);
    // T reduce(T identity, BinaryOperator<T> accumulator);
    // 获得总和
    // reduce是怎么执行的呢?
    // 第一次,将默认值(参数一)赋给x,将Stream流第一个元素赋给y
    // 第二次,将上一次返回的值赋给x,将Stream流中的第二个元素赋给y
    // 第三次,将上一次返回的值赋给x,将Stream流中的第三个元素赋给y
    // 第四次,将上一次返回的值赋给x,将Stream流中的第四个元素赋给y
    //Integer reduce = stream.reduce(0, (x, y) -> x + y);

    //获得最大值
    Integer reduce = stream.reduce(0, (x, y) -> x > y ? x : y);
    System.out.println("reduce = " + reduce);
}
16. map方法和reduce方法结和使用
//map方法和reduce方法一同使用
@Test
public void testMap_Reduce() {
    // 1.求出年龄的和
    // 使用map方法处理,其返回的流是年龄
    // 使用reduce方法,设置默认值为0,使用integer的sum方法求和
    Integer reduce = Stream.of(
            new Person("段黑", 22),
            new Person("张渣", 50),
            new Person("曲秃", 28)).map(person -> person.getAge()).reduce(0, Integer::sum);
    System.out.println("年龄总数 = " + reduce);
    // 2.求出年龄最大的
    // 使用map方法处理,其返回的流是年龄
    // 使用reduce方法,设置默认值为0,使用integer的max方法获取最大值
    Integer reduce1 = Stream.of(
            new Person("段黑", 22),
            new Person("张渣", 50),
            new Person("曲秃", 28)).map(person -> person.getAge()).reduce(0, Integer::max);
    System.out.println("年龄最大的 = " + reduce1);
    // 3.计算a出现的次数
    // 使用map方法进行判断,如果是a,返回1,不是,返回0
    // 处理后的数据               1   0     1    0     1
    // 使用reduce方法,设置默认值为零,使用Integer的sum方法求出a的个数
    Integer reduce2 = Stream.of("a", "b", "a", "c", "a").map(s -> {
        if (s.equals("a")) {
            return 1;
        } else {
            return 0;
        }
    }).reduce(0, Integer::sum);
    System.out.println("a出现的次数 = " + reduce2);
}
17.Stream流的mapToInt

如果需要将Stream类型的数据转换为int

@Test
public void testmapToInt() {
    // Integer占用内存比int多,在Stream六中操作会进行自动装箱和拆箱
    Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
    // 把大于三的打印出类
    // stream.filter(integer -> integer > 3).forEach(System.out::println);

    // IntStream:内部操作的是int类型的数据,可以节省内存空间
    /*IntStream intStream = Stream.of(1, 2, 3, 4, 5).mapToInt(value -> {
        return value.intValue();
    });
    intStream.forEach(System.out::println);*/
    // 简写
    IntStream intStream = Stream.of(1, 2, 3, 4, 5).mapToInt(Integer::intValue);
    intStream.filter(value -> value > 3).forEach(System.out::println);
}
18.Stream流的Concat

将两个Stream流合并成一个流

  • 注意:流合并后只能操作新的流,不能操作之前的流
@Test
public void testConcat() {
    // 定义两个流
    Stream<Person> 曲秃 = Stream.of(new Person("曲秃", 22));
    Stream<Person> 张渣 = Stream.of(new Person("张渣", 21));

    // 将两个流合并成为一个流
    Stream<Person> newStream = Stream.concat(曲秃, 张渣);

    newStream.forEach(System.out::println);
}
19. Stream流综合操作
package com.lld;

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

/**
 * @ClassName demo03
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/8 20:26
 * @Version 1.0
 */
public class demo03 {

    public static void main(String[] args) {
        // 第一个队伍
        List<String> one = new ArrayList<>();
        Collections.addAll(one,"迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子", "洪七公");
        // 第二个队伍
        List<String> two = new ArrayList<>();
        Collections.addAll(two,"古力娜扎", "张无忌", "张三丰", "赵丽颖", "张二狗", "张天爱", "张三");

        // 1.第一个队伍只要名字为三个字的成员名称
        // one.stream().filter(s -> s.length() == 3).forEach(System.out::println);

        // 2.第一个队伍筛选之后只要前三人
        Stream<String> stream1 = one.stream().filter(s -> s.length() == 3).limit(3);

        System.out.println("------------分割线---------------");

        // 3.第二个队伍只要姓张的成员名称
        //two.stream().filter(s -> s.startsWith("张")).forEach(System.out::println);
        // 4.第二个队伍筛选之后不要前两人
        Stream<String> stream2 = two.stream().filter(s -> s.startsWith("张")).skip(2);
        // 5.将两个队伍合并成为一个队伍
        Stream<String> newStream = Stream.concat(stream1,stream2);
        // 6.根据姓名创建Person对象
        Stream<Person> stream = newStream.map(Person::new);
        // 7.打印整个队伍的Person对象的信息
        stream.forEach(System.out::println);
        
    }
}
20. 收集流中的结果
1. Stream流中数据放入集合

1583722905470

@Test
public void testToCollection(){
    // 一个Stream流
    Stream<String> stream = Stream.of("ss", "xx", "cc");
    //转成List
    // List<String> collect = stream.collect(Collectors.toList());
    // System.out.println("collect = " + collect);
    
    // 转成set
    // Set<String> collect = stream.collect(Collectors.toSet());
    // System.out.println("collect = " + collect);

    // 转成ArrayList
    // ArrayList<String> collect = stream.collect(Collectors.toCollection(ArrayList::new));
    // System.out.println("collect = " + collect);

    // 转成HashSet
    // HashSet<String> collect = stream.collect(Collectors.toCollection(HashSet::new));
    // System.out.println("collect = " + collect);
}
2. Stream流中数据放入数组

1583722924845

@Test
public void testToArray() {
    // 一个Stream流
    Stream<String> stream = Stream.of("ss", "xx", "cc");

    // toArray无参构造方法,转成object
    // Object[] objects = stream.toArray();
    //  for (Object object : objects) {
    //    System.out.println("object = " + object);
    // }

    // toArray有参构造方法,参数:数组构造器
    String[] strings = stream.toArray(String[]::new);
    for (String string : strings) {
        System.out.println("string = " + string);
    }
}
3. Stream流中结果进行聚合计算

1583722947730

// 其他流中数据聚合的方式:相当于数据库中的聚合函数
@Test
public void testStreamToOther() {
    Stream<Student> studentStream = Stream.of(
            new Student("张渣", 20, 90),
            new Student("曲秃", 22, 92),
            new Student("段黑", 21, 80),
            new Student("奥特曼", 28, 85),
            new Student("高莽夫", 24, 96)
    );

    // 1.获取成绩最大值
    /*Optional<Student> student = studentStream.collect(Collectors.maxBy((o1, o2) -> o1.getGrade() - o2.getGrade()));
    if (student.get() != null){
        System.out.println(student.get());
    }*/
    // 2.获取成绩最小值
    /*Optional<Student> student = studentStream.collect(Collectors.minBy((o1, o2) -> o1.getGrade() - o2.getGrade()));
    if (student.get() != null){
        System.out.println(student.get());
    }*/
    // 3.求总和
    /*Integer sum = studentStream.collect(Collectors.summingInt(value -> value.getGrade()));
    System.out.println("sum = " + sum);*/

    // 4.平均值
    /*Double avg = studentStream.collect(Collectors.averagingInt(value -> value.getGrade()));
    System.out.println("avg = " + avg);*/
    // 5.统计数量
    Long count = studentStream.collect(Collectors.counting());
    System.out.println("count = " + count);
}
4. Stream流中结果进行分组
@Test
public void testGroupBy() {
    Stream<Student> studentStream = Stream.of(
            new Student("张渣", 20, 30),
            new Student("曲秃", 20, 40),
            new Student("段黑", 22, 50),
            new Student("奥特曼", 22, 85),
            new Student("高莽夫", 22, 96)
    );
    // 1.根据年龄进行分组
    // Map<Integer, List<Student>> map = studentStream.collect(Collectors.groupingBy(Student::getAge));
    // map.forEach((integer, students) -> System.out.println(integer + "::" + students));

    // 2.根据是否及格进行分组
    Map<String, List<Student>> map = studentStream.collect(Collectors.groupingBy(o -> o.getGrade() > 60 ? "及格" : "不及格"));
    map.forEach((s, students) -> System.out.println(s + "::" + students));
}

结果

不及格::[Student{name='张渣', age=20, grade=30}, Student{name='曲秃', age=20, grade=40}, Student{name='段黑', age=22, grade=50}]
及格::[Student{name='奥特曼', age=22, grade=85}, Student{name='高莽夫', age=22, grade=96}]
5. Stream流中结果进行多级分组
@Test
public void testCustomGroupBy() {
    Stream<Student> studentStream = Stream.of(
            new Student("张渣", 20, 30),
            new Student("曲秃", 20, 80),
            new Student("段黑", 22, 50),
            new Student("奥特曼", 22, 30),
            new Student("高莽夫", 22, 96)
    );

    // 先按照年龄分组,在按照成绩分组
    Map<Integer, Map<String, List<Student>>> map = studentStream.collect(Collectors.groupingBy(Student::getAge, Collectors.groupingBy(o -> o.getGrade() > 60 ? "及格" : "不及格")));

    // 遍历map集合
    map.forEach((integer, stringListMap) -> {
        System.out.println(integer);
        // 遍历map中的map
        stringListMap.forEach((s, students) -> System.out.println("\t" + s + " == " + students));
    });

结果

20
	不及格 == [Student{name='张渣', age=20, grade=30}]
	及格 == [Student{name='曲秃', age=20, grade=80}]
22
	不及格 == [Student{name='段黑', age=22, grade=50}, Student{name='奥特曼', age=22, grade=30}]
	及格 == [Student{name='高莽夫', age=22, grade=96}]

6. Stream流中数据进行分区

Collectors.partitioningBy 会根据值是否为true,把集合分割为两个列表,一个true列表,一个false列表。

@Test
public void testPartition() {
    Stream<Student> studentStream = Stream.of(
            new Student("张渣", 20, 30),
            new Student("曲秃", 20, 80),
            new Student("段黑", 22, 50),
            new Student("奥特曼", 22, 30),
            new Student("高莽夫", 22, 96)
    );
    Map<Boolean, List<Student>> map = studentStream.collect(Collectors.partitioningBy(o -> o.getGrade() > 60));

    map.forEach((aBoolean, students) -> System.out.println(aBoolean + " :: " +students));
}

结果

false :: [Student{name='张渣', age=20, grade=30}, Student{name='段黑', age=22, grade=50}, Student{name='奥特曼', age=22, grade=30}]
true :: [Student{name='曲秃', age=20, grade=80}, Student{name='高莽夫', age=22, grade=96}]
7. Stream流中数据进行拼接

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

@Test
public void testJoining() {
    Stream<Student> studentStream = Stream.of(
            new Student("张渣", 20, 30),
            new Student("曲秃", 20, 80),
            new Student("段黑", 22, 50),
            new Student("奥特曼", 22, 30),
            new Student("高莽夫", 22, 96)
    );
    // 根据一个拼接:张渣__曲秃__段黑__奥特曼__高莽夫
    // String s = studentStream.map(Student::getName).collect(Collectors.joining("__"));

    // 根据三个参数拼接:^___^张渣__曲秃__段黑__奥特曼__高莽夫V___V
    String s = studentStream.map(Student::getName).collect(Collectors.joining("__", "^___^", "V___V"));
    System.out.println("s = " + s);
}
8. 小结

收集Stream流中的结果
到集合中: Collectors.toList()/Collectors.toSet()/Collectors.toCollection()
到数组中: toArray()/toArray(int[]::new)
聚合计算:
Collectors.maxBy/Collectors.minBy/Collectors.counting/Collectors.summingInt/Collectors.averagingInt
分组: Collectors.groupingBy
分区: Collectors.partitionBy
拼接: Collectors.joinging

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

1583725234732

2. 获取并行Stream流的两种方式
  1. 直接获取并行流
  2. 将串行流转换为并行流
@Test
public void testgetParallelStream() {
    ArrayList<Integer> list = new ArrayList<>();
    // 直接获取并行的流
    // Stream<Integer> stream = list.parallelStream();
    // 将串行流转成并行流
    Stream<Integer> stream = list.stream().parallel();
}

操作代码

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

1583725376390

3. 并行流串行流及for循环速度比较

private static Long times = 500000000L;
private Long start;

@Before
public void init(){
    start = System.currentTimeMillis();
}

@After
public void destory(){
    System.out.println(System.currentTimeMillis() - start);
}

//时间:2688毫秒
@Test
public void testFor() {
    Long sum = 0L;
    for (Long i = 0L; i < times; i++) {
        sum += sum;
    }
}

//时间:831毫秒
@Test
public void serialStream() {
    LongStream.rangeClosed(0,times).reduce(0,Long::sum);
}

//时间:110毫秒
@Test
public void parallelStream() {
    LongStream.rangeClosed(0,times).parallel().reduce(0,Long::sum);
}

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

4. parallelStream线程问题
//parallelStream流的线程安全问题
@Test
public void parallelStreamNotice() {
    List<Integer> list = new ArrayList<>();
    // 测试线程是否安全:792个
    /*IntStream.rangeClosed(0,1000).parallel().forEach(value -> list.add(value));
    System.out.println(list.size());*/

    // 解决方法一:使用同步代码块
    /*Object obj = new Object();
    IntStream.rangeClosed(0,999).parallel().forEach(value -> {
        synchronized (obj){
            list.add(value);
        }
    });
    System.out.println(list.size());*/

    // 解决方法二:使用线程安全的集合
    // 获得一个线程安全的list集合
    /*List<Integer> list1 = Collections.synchronizedList(list);
    IntStream.rangeClosed(0,999).parallel().forEach(value -> list1.add(value));
    System.out.println(list1.size());*/

    // 解决方法三:调用Stream的 toArray() / collect()
    /*List<Integer> collect = IntStream.rangeClosed(0, 999).parallel().boxed().collect(Collectors.toList());
    System.out.println("collect = " + collect.size());*/
    Integer[] array = IntStream.rangeClosed(0, 999).boxed().toArray(Integer[]::new);
    System.out.println("array = " + array.length);
}
5. parallelStream 背后的技术
Fork/Join框架介绍

parallelStream使用的是Fork/Join框架。Fork/Join框架自JDK 7引入。Fork/Join框架可以将一个大任务拆分为很多小任务来异步执行。 Fork/Join框架主要包含三个模块:

  1. 线程池:ForkJoinPool
  2. 任务对象:ForkJoinTask
  3. 执行任务的线程:ForkJoinWorkerThread

1583735287097

Fork/Join原理-分治法

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

1583735321981

Fork/Join原理-工作窃取算法

Fork/Join最核心的地方就是利用了现代硬件设备多核,在一个操作时候会有空闲的cpu,那么如何利用好这个空闲的cpu就成了提高性能的关键,而这里我们要提到的工作窃取(work-stealing)算法就是整个Fork/Join框架的核心理念Fork/Join工作窃取(work-stealing)算法是指某个线程从其他队列里窃取任务来执行。

1583735392308

那么为什么需要使用工作窃取算法呢?假如我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不依赖的子任务,为了减少线程间的竞争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应,比如A线程负责处理A队列里的任务。但是有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并且消耗了更多的系统资源,比如创建多个线程和多个双端队列。上文中已经提到了在Java 8引入了自动并行化的概念。它能够让一部分Java代码自动地以并行的方式执行,也就是我们使用了ForkJoinPool的ParallelStream。
对于ForkJoinPool通用线程池的线程数量,通常使用默认值就可以了,即运行时计算机的处理器数量。可以通过设置系统属性:java.util.concurrent.ForkJoinPool.common.parallelism=N (N为线程数量),来调整ForkJoinPool的线程数量,可以尝试调整成不同的参数来观察每次的输出结果。

Fork/Join 案例

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

1583735439145

package com.lld;

/**
 * @ClassName demo05ForkJoin
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/9 14:31
 * @Version 1.0
 */
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class demo05ForkJoin {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        ForkJoinPool pool = new ForkJoinPool();
        SumRecursiveTask task = new SumRecursiveTask(1, 10000L);
        Long result = pool.invoke(task);
        System.out.println("result = " + result);
        long end = System.currentTimeMillis();
        System.out.println("消耗的时间为: " + (end - start));
    }
}
class SumRecursiveTask extends RecursiveTask<Long> {
    private static final long THRESHOLD = 3000L;
    private final long start;
    private final long end;
    public SumRecursiveTask(long start, long end) {
        this.start = start;
        this.end = end;
    }
    @Override
    protected Long compute() {
        long length = end - start;
        if (length <= THRESHOLD) {
// 任务不用再拆分了.可以计算了
            long sum = 0;
            for (long i = start; i <= end; i++) {
                sum += i;
            }
            System.out.println("计算: " + start + " -> " + end + ",结果为: " + sum);
            return sum;
        } else {
// 数量大于预定的数量,任务还需要再拆分
            long middle = (start + end) / 2;
            System.out.println("拆分: 左边 " + start + " -> " + middle + ", 右边 " + (middle +
                    1) + " -> " + end);
            SumRecursiveTask left = new SumRecursiveTask(start, middle);
            left.fork();
            SumRecursiveTask right = new SumRecursiveTask(middle + 1, end);
            right.fork();
            return left.join() + right.join();
        }
    }
}

Optional类

以前对null的处理方式
// 以前对null的处理
@Test
public void test1(){

    String str = "曲秃";
    if (str == null){
        System.out.println("str为null");
    }else {
        System.out.println(str);
    }
}
Optional类介绍

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

1583736965917

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()
// 现在对null的处理
@Test
public void test02() {
    // of:不可以传入null
    // ofNullable:可以传入值,也可以传入null
    // empty:默认null,不可传值

    // 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("用户名不存在");
    }
}
Optional 类的高级使用:
@Test
public void test3() {
    User hello_world = new User(null);
    String s = getUpperCase(hello_world);
    System.out.println(s);
    Optional<User> hello_world1 = Optional.of(hello_world);
    System.out.println(getUpperCase1(hello_world1));

}

// 现在的方法
public String getUpperCase1(Optional<User> uO){
    String s1 = uO.map(user -> user.getUsername()).map(s -> s.toUpperCase()).orElse("null");
    return s1;
}

// 以前的方法
public String getUpperCase(User user){
    if (user != null){
        if (user.getUsername() != null){
            return user.getUsername().toUpperCase();
        }else {
            return null;
        }
    }else {
        return null;
    }
}

@Test
public void test4() {
    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 test5() {
    // 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);
}

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

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类,但他们同样存在上述所有的问题。
@Test
public void test01(){
    /*
        问题一:设计不规范
            1. java.util.Date:包含日期和时间
            2. java.sql.Date:包含日期
            3. java.text.SimpleDateFormat:用于格式化和解析
    */
    Date date = new Date(1999,1,12);
    System.out.println(date);

    // 问题二:线程不安全
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    for (int i = 0; i < 50; i++) {
        new Thread(() -> {
            try {
                Date parse = dateFormat.parse("1999-09-28");
                System.out.println("parse = " + parse);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }).start();
    }
}
2. 新日期时间 API介绍

JDK 8中增加了一套全新的日期时间API,这套API设计合理,是线程安全的。新的日期及时间API位于 java.time 包
中,下面是一些关键类。
LocalDate :表示日期,包含年月日,格式为 2019-10-16
LocalTime :表示时间,包含时分秒,格式为 16:38:54.158549300
LocalDateTime :表示日期时间,包含年月日,时分秒,格式为 2018-09-06T15:33:56.750
DateTimeFormatter :日期时间格式化类。
Instant:时间戳,表示一个特定的时间瞬间。
Duration:用于计算2个时间(LocalTime,时分秒)的距离
Period:用于计算2个日期(LocalDate,年月日)的距离
ZonedDateTime :包含时区的时间
Java中使用的历法是ISO 8601日历系统,它是世界民用历法,也就是我们所说的公历。平年有365天,闰年是366天。

此外Java 8还提供了4套其他历法,分别是:

  • ThaiBuddhistDate :泰国佛教历

  • MinguoDate :中华民国历

  • JapaneseDate :日本历

  • HijrahDate :伊斯兰历

3. JDK 8 的日期和时间类

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

@Test
public void testLocalDate() {
    // 获取当前时间
    LocalDate now = LocalDate.now();
    System.out.println("now = " + now);
    // 获取指定时间
    LocalDate date = LocalDate.of(1999, 9, 28);
    System.out.println("date = " + date);

    // 获得年
    System.out.println(now.getYear());
    // 获得月
    System.out.println(now.getMonthValue());
    // 获得日
    System.out.println(now.getDayOfMonth());
}

@Test
public void testDateTime() {
    // 获得当前的时:分:秒:纳秒
    LocalTime now = LocalTime.now();
    System.out.println("now = " + now);

    // 指定时:分:秒
    LocalTime of = LocalTime.of(9, 19, 19);
    System.out.println("of = " + of);

    // 获得时
    System.out.println(now.getHour());
    // 获得分
    System.out.println(now.getMinute());
    // 获得秒
    System.out.println(now.getSecond());
}

@Test
public void testLocalDateTime() {
    // 获取年-月-日  时:分:秒:纳秒
    LocalDateTime now = LocalDateTime.now();
    System.out.println("now = " + now);

    // 指定年-月-日  时:分:秒
    LocalDateTime time = LocalDateTime.of(1999, 9, 28, 19, 19, 19);
    System.out.println("time = " + time);

    // 获取年
    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());
}

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

// 对日期时间修改
@Test
public void test() {
    // 获取年-月-日  时:分:秒:纳秒
    LocalDateTime now = LocalDateTime.now();

    // 修改时间:with···
    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));

    // 再当前对象的基础上加上或减去指定的时间  plus···增加/minus···减少
    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));
}

@Test
public void testEquals() {
    // 在JDK8中,LocalDate类中使用isBefore()、isAfter()、equals()方法来比较两个日期,可直接进行比较。
    LocalDate now = LocalDate.now();
    LocalDate date = LocalDate.of(2018, 8, 8);
    // 判断两个日期谁在谁之前
    System.out.println(now.isBefore(date)); // false
    // 判断两个日期谁在谁之后
    System.out.println(now.isAfter(date)); // true
    // 判断两个日期是否相等
    System.out.println(now.equals(date));  //
}
4. JDK 8 的时间格式化与解析

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

// 日期格式化
@Test
public void test02() {

    // 获取当前日期
    LocalDateTime now = LocalDateTime.now();

    // JDK自带的日期格式化
    DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE_TIME;
    String format = now.format(dtf);
    System.out.println("format = " + format);

    // 自己定义格式化
    DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyy年MM月dd日 HH时mm分ss秒");
    String s = now.format(pattern);
    System.out.println("s = " + s);

    // 解析及测试多线程是否安全

    for (int i = 0; i < 50; i++) {
        new Thread(() -> {
            LocalDateTime time = LocalDateTime.parse("1999年03月09日 20时54分03秒", pattern);
            System.out.println("time = " + time);
        }).start();
    }
}
5. JDK 8 的 Instant 类

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

// 时间戳
@Test
public void test03() {
    // Instant:内部保存了秒和纳秒,不是给用户用的,是用于给程序做一些统计的
    Instant now = Instant.now();
    System.out.println("now = " + now);

    // 减少时间操作
    Instant time = now.minusSeconds(20);
    System.out.println("instant = " + time);
    // 增加时间操作
    Instant seconds = now.plusSeconds(20);
    System.out.println("time = " + seconds);

    // 得到秒
    long epochSecond = now.getEpochSecond();
    System.out.println("epochSecond = " + epochSecond);
    // 得到纳秒
    int nano = now.getNano();
    System.out.println("nano = " + nano);
}
6. JDK 8 的计算日期时间差类

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

  1. Duration:用于计算2个时间(LocalTime,时分秒)的距离
  2. Period:用于计算2个日期(LocalDate,年月日)的距离
// Duration/Period 类: 计算时间/日期差
@Test
public void test04() {
    // 获取当前时间
    LocalTime nowTime = LocalTime.now();
    // 指定一个时间
    LocalTime time = LocalTime.of(10, 8, 27);
    // 使用Duration比较:后面时间减去前面时间
    Duration duration = Duration.between(time, nowTime);
    System.out.println("计算相差的天数 " + duration.toDays());
    System.out.println("计算相差的小时数 " + duration.toHours());
    System.out.println("计算相差的分钟数 " + duration.toMinutes());

    // 获取当前日期
    LocalDate nowDate = LocalDate.now();
    // 指定一个日期
    LocalDate date = LocalDate.of(1999, 9, 28);
    // 使用Period比较:后面的日期减去前面的日期
    Period period = Period.between(date, nowDate);
    System.out.println("计算相差年 " + period.getYears());
    System.out.println("计算相差月 " + period.getMonths());
    System.out.println("计算相差日 " + period.getDays());
}
7. JDK 8 的时间校正器

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

  • TemporalAdjuster : 时间校正器。

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

// TemporalAdjuster时间调整器
// TemporalAdjusters提供了大量TemporalAdjuster的实现类
@Test
public void test05() {
    LocalDate now = LocalDate.now();

    // 自定义一个时间调整器:将时间调到下个月第一天
    TemporalAdjuster firstDateOfNextMonth = temporal -> {
        // 将传进来的参数强转成时间(因为传进来的就是时间)
        LocalDate date = (LocalDate) temporal;
        return date.plusMonths(1).withDayOfMonth(1);
    };

    // 时间调用with方法,将时间调整器传入
    LocalDate newDate = now.with(firstDateOfNextMonth);
    System.out.println("newDate = " + newDate);

    // TemporalAdjusters提供了大量TemporalAdjuster的实现类
    LocalDate localDate = now.with(TemporalAdjusters.firstDayOfMonth());
    System.out.println("localDate = " + localDate);
}
  • TemporalAdjusters 的一些方法

1583760745978

8. 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]

    // 修改时区
    // withZoneSameInstant: 即更改时区,也更改时间
    ZonedDateTime withZoneSameInstant = now2.withZoneSameInstant(ZoneId.of("Asia/Shanghai"));
    System.out.println("withZoneSameInstant = " + withZoneSameInstant); // 2019-10-19T16:53:41.225898600+08:00[Asia/Shanghai]

    // withZoneSameLocal: 只更改时区,不更改时间
    ZonedDateTime withZoneSameLocal = now2.withZoneSameLocal(ZoneId.of("Asia/Shanghai"));
    System.out.println("withZoneSameLocal = " + withZoneSameLocal); // 2019-10-19T01:54:52.058871300+08:00[Asia/Shanghai]
}
9. 小结
  • 详细学习了新的日期是时间相关类,LocalDate表示日期,包含年月日,LocalTime表示时间,包含时分秒,LocalDateTime = LocalDate + LocalTime,
  • 时间的格式化和解析,通过DateTimeFormatter类型进行.
  • 学习了Instant类,方便操作秒和纳秒,一般是给程序使用的.
  • 学习Duration/Period计算日期或时间的距离,还使用时间调
    整器方便的调整时间,学习了带时区的3个类ZoneDate/ZoneTime/ZoneDateTime

JDK 8新的日期和时间 API的优势:

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

重复注解与类型注解

1. 重复注解

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

  1. 定义重复的注解容器注解
  2. 定义一个可以重复的注解
  3. 配置多个重复的注解
  4. 解析得到指定注解
package com.lld;

/**
 * @ClassName demo02
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/9 21:59
 * @Version 1.0
 */
import org.junit.Test;

import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
// 3.配置重复注解
@MyTest("ta")
@MyTest("tb")
@MyTest("tc")
public class demo02 {
    @Test
    @MyTest("ma")
    @MyTest("mb")
    public void test()  {
    }

    public static void main(String[] args) throws NoSuchMethodException {
        // 4.解析重复注解
        // 获取类上的重复注解
        // getAnnotationsByType是新增的API用户获取重复的注解
        MyTest[] annotationsByType = demo02.class.getAnnotationsByType(MyTest.class);
        for (MyTest myTest : annotationsByType) {
            System.out.println(myTest);
        }
        System.out.println("----------");
        // 获取方法上的重复注解
        MyTest[] tests = demo02.class.getMethod("test").getAnnotationsByType(MyTest.class);
        for (MyTest test : tests) {
            System.out.println(test);
        }
    }
}

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

// 2.定义一个可以重复的注解
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyTests.class)
@interface MyTest {
    String value();
}
2. 类型注解

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

package com.lld;

/**
 * @ClassName demo03
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/9 22:00
 * @Version 1.0
 */
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.ArrayList;

public class demo03 <@TypeParam T> {
    private @NotNull int a = 10;

    public void test(@NotNull String str, @NotNull int a) {
        @NotNull double d = 10.1;
    }

    public <@TypeParam E extends Integer> void test01() {
    }
}

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

@Target(ElementType.TYPE_PARAMETER)
@interface TypeParam {
}
3. 小结

通过@Repeatable元注解可以定义可重复注解, TYPE_PARAMETER 可以让注解放在泛型上, TYPE_USE 可以让注解放在类型的前面

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值