JDK1.8新特性

JDK1.8新特性

1. Lambda表达式

1.1 lambda初识

  • Lambda表达式是Java SE 8中一个重要的新特性。lambda表达式允许通过表达式来代替功能接口。 lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。
  • 示例:
package com.jdk.lambda;
public class Code01 {
    public static void main(String[] args) {
        // 开启一个新的线程,指定线程要执行的任务
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("新线程中执行的代码:" + Thread.currentThread().getName());//新线程中执行的代码:Thread-0
            }
        }).start();
        System.out.println("主线程中的代码:" + Thread.currentThread().getName());//主线程中的代码:main
        /**
         * 代码分析:
         * 1.Thread类需要一个Runnable接口作为参数,其中的抽象方法run方法是用来指定线程任务内容的核心
         * 2.为了指定run方法体,不得不需要Runnable的实现类
         * 3.为了省去定义一个Runnable的实现类,不得不使用匿名内部类
         * 4.必须覆盖重新抽象的run方法,所有的方法名称,方法参数,方法返回值不得不重写一遍,而且不能出错
         * 5.实际上,我们只在乎方法体中的代码
         */
        new Thread(()->{
            System.out.println("新线程lambda表达式。。。"+Thread.currentThread().getName());
        }).start();//新线程lambda表达式。。。Thread-1
        /**
         * 1.lambda表达式是一个匿名函数,可以理解为一段可以传递的代码
         * 2.优点:简化了匿名内部类的使用,语法更加简单
         * 3.匿名内部类语法冗余,体验lambda表达式后,发现lamdbda表达式就是简化匿名内部类的一种方式
         */
    }
}

1.2 lambda语法规则

  • lambda标准格式由以下三个部分组成
(参数类型 参数名) -> {
	代码体;
}
  • 格式说明:
    • (参数类型 参数名) : 参数列表
    • { 代码体 } : 方法体
    • -> : 分割参数列表和方法体
  • 示例1:无返回值
@FunctionalInterface//被该注解修饰的接口只能声明一个抽象方法
public interface UserService {
    void show();
}

public class Code02 {

    public static void goShow(UserService userService) {
        userService.show();
    }

    public static void main(String[] args) {
    	//正常执行
        goShow(new UserService() {
            @Override
            public void show() {
                System.out.println("show方法执行了");
            }//show方法执行了
        });
		//lambda表达式执行
        goShow(() -> {
            System.out.println("lambda show方法执行了");
        });//lambda show方法执行了

        goShow(() -> System.out.println("简化版lambda show方法执行了"));//简化版lambda show方法执行了
    }

}
  • 示例2:有返回值
// 以person类为例
public class Person {
    private String name;
    private Integer age;
    private Double height;

    public Person() {
    }

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

    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 Double getHeight() {
        return height;
    }

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", height=" + height +
                '}';
    }
}
public class Code03 {
    public static void main(String[] args) {
        List<Person> list = new ArrayList<>();
        list.add(new Person("楚君归",33,175.5));
        list.add(new Person("李悠然",53,185.5));
        list.add(new Person("赵日天",93,195.5));
        list.add(new Person("叶良辰",23,165.5));
//        Collections.sort(list, new Comparator<Person>() {
//            @Override
//            public int compare(Person o1, Person o2) {
//                return o1.getAge()-o2.getAge();
//            }
//        });
//        list.forEach(System.out::println);
        Collections.sort(list,(Person o1, Person o2)-> {
            return o1.getAge()-o2.getAge();
        });
        list.forEach(System.out::println);
        /**
         * 执行结果:
         * Person{name='叶良辰', age=23, height=165.5}
         * Person{name='楚君归', age=33, height=175.5}
         * Person{name='李悠然', age=53, height=185.5}
         * Person{name='赵日天', age=93, height=195.5}
         */
    }
}

1.3 lambda表达式原理

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

1.4 lambda表达式简写规则

  • 小括号内的参数类型可以省略
  • 如果小括号内有且仅有一个参数,则小括号可以省略
  • 如果大括号内有且仅有一个语句,可以同时省略大括号,return关键字及语句分号
  • 示例:
public interface StudentService {
    String show(String name,Integer age);
}
public interface OrderService {
    Integer show(String name);
}

public class Code06 {
    public static void goStudent(StudentService studentService) {
        studentService.show("林夕", 22);
    }

    public static void goOrder(OrderService orderService) {
        orderService.show("心仪");
    }

    public static void main(String[] args) {
        //标准写法
        goStudent((String name, Integer age) -> {
            return name + age + "666..." ;
        });
        goOrder((String name) -> {
            System.out.println("goOrder invoked!order name :" + name);
            return 666;
        });//goOrder invoked!order name :心仪
        //省略写法
        goStudent((name,age)->name + age + "666...");
        goOrder(name ->{System.out.println("goOrder invoked!order name :" + name);return 666;});//goOrder invoked!order name :心仪
        // 如果goOrder没有sout语句,可以继续简写
        goOrder(name -> 666);
    }
}

1.5 lambda表达式的使用前提

  • 方法的参数或者局部变量类型笔触为接口才能使用lambda表达式
  • 接口中有且仅有一个抽象方法(@FunctionalInterface)

1.6 lambda和匿名内部类的对比

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

2.接口新增的方法

2.1 JDK8接口增强

  • 在JDK8中针对接口做了增强,在此之前:
interface 接口名{
	静态常量;
	抽象方法;
}
  • JDK8之后对接口做了增加,接口总可以有默认方法和静态方法
interface 接口名{
	静态常量;
	抽象方法;
	默认方法;
	静态方法;
}

2.2 默认方法

2.2.1 为什么要增加默认方法?

  • 在JDK8之前,如果新增抽象方法,那么实现类都必须重写盖方法,不利于接口扩展
public class Demo01Imterface {
    public static void main(String[] args) {
        A ab = new B();
        A ac = new C();
    }
}
interface A{
    void test1();
    // 新增test2方法,classB和classC都必须重写该方法
    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() {}
}

2.2.2 默认方法语法格式

  • 接口中默认方法的语法格式:
interface 接口名{
	修饰符 default 返回值类型 方法名{
		方法体;
	}
}
  • 示例:
public class Demo01Imterface {
    public static void main(String[] args) {
        A ab = new B();
        A ac = new C();
        ab.test3();//class B 重写了默认方法!
        ac.test3();//接口中的默认方法执行了。。。
    }
}
interface A{
    void test1();
    // 新增test2方法,classB和classC都必须重写该方法
    void test2();
	// 接口中定义的默认方法
    public default String test3(){
        System.out.println("接口中的默认方法执行了。。。");
        return "test3 invoked!";
    }
}
class B implements A{
    @Override
    public void test1() {}
    @Override
    public void test2() {}
    @Override
    public String test3() {
        System.out.println("class B 重写了默认方法!");
        return "test3 invoked!";
    }
}
class C implements A{
    @Override
    public void test1() {}
    @Override
    public void test2() {}
}

2.2.3 默认方法的使用

  • 实现类直接调用接口的默认方法
  • 实现类重写接口的默认方法

2.3 静态方法

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

2.3.1 静态方法语法规则

  • 接口中静态方法的语法格式:
interface 接口名{
	修饰符 static 返回值类型 方法名{
		方法体;
	}
}
  • 示例:
public class Demo01Imterface {
    public static void main(String[] args) {
        A ab = new B();
        A ac = new C();
        ab.test3();//class B 重写了默认方法!
        ac.test3();//接口中的默认方法执行了。。。
        A.test4();//接口中的静态方法执行了。。。
    }
}
interface A{
    void test1();
    // 新增test2方法,classB和classC都必须重写该方法
    void test2();
	// 接口中定义的默认方法
    public default String test3(){
        System.out.println("接口中的默认方法执行了。。。");
        return "test3 invoked!";
    }
    // 接口中定义的静态方法
    public static String test4(){
        System.out.println("接口中的静态方法执行了。。。");
        return "test4 invoked!";
    }
}
class B implements A{
    @Override
    public void test1() {}
    @Override
    public void test2() { }
    @Override
    public String test3() {
        System.out.println("class B 重写了默认方法!");
        return "test3 invoked!";
    }
}
class C implements A{
    @Override
    public void test1() { }
    @Override
    public void test2() {}
}

2.3.2 静态方法的使用

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

2.4 默认方法和静态方法的区别

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

3. 函数式接口

3.1 函数式接口的由来

  • 使用lambda表达式的前提是需要又函数式接口,而lambda表达式使用时不关心接口名,抽象方法名,只关心抽象方法的参数列表和返回值类型,因此,为了更加方便使用lambda表达式,JDK提供了大量常用的函数式接口。
public class Code07 {
    public static void fun1(Operator operator) {
        int[] arr = {1, 2, 3, 4, 56};
        int sum = operator.getSum(arr);
        System.out.println("sum = " + sum);
    }
    public static void main(String[] args) {
        fun1((arr) -> {
            int sum = 0;
            for (int i : arr) {
                sum += i;
            }
            return sum;
        });
        //执行结果:sum = 66
    }
}
interface Operator {
    int getSum(int[] arr);
}

3.2 常用函数式接口

  • JDK中帮我们提供的函数式接口,主要是在Java.util.function包中

3.2.1 Supplier

  • 无参有返回值
@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     * 获取一个结果。
     * @return a result
     */
    T get();
}
/**
 * Supplier 函数式接口的使用
 */
public class SupplierTest {
    private static void fun1(Supplier<Integer> supplier) {
        Integer max = supplier.get();
        System.out.println("max = " + max);
    }
    public static void main(String[] args) {
        fun1(() -> {
            int arr[] = {11, 22, 44, 33, 88, 66};
            Arrays.sort(arr);
            return arr[arr.length - 1];
        });
        // 执行结果:max = 88
    }
}

3.2.2 Consumer

  • 有参数无返回值,前面介绍的Supplier接口是用来生产数据的,而Consumer是用来消费数据的,使用时需要指定泛型
@FunctionalInterface
public interface Consumer<T> {
    /**
     * Performs this operation on the given argument.
     *	对给定的参数执行此操作。
     * @param t the input argument
     */
    void accept(T t);
  • 默认方法:andThen
  • 如果一个方法的参数和返回值全是Consumer类型,消费一个数据时,连续做2次或以上操作,就可以用andThen方法实现组合
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
  • 示例:
public class ConsumerTest {
    public static void test(Consumer<String> consumer) {
        consumer.accept("Hello World");
    }
    public static void test2(Consumer<String> c1, Consumer<String> c2) {
        String str = "Hello Word";
        c1.accept(str);//将执行小写
        c2.accept(str);//将执行大写
    }
    public static void main(String[] args) {
        test(msg -> {
            System.out.println(msg + " 转化为小写:" + msg.toLowerCase());
        });
        // 执行结果--->Hello World 转化为小写:hello world
        System.out.println("andThen方法示例:");
        test2(msg1 -> {
            System.out.println(msg1 + " 转化为小写:" + msg1.toLowerCase());
        }, msg2 -> {
            System.out.println(msg2 + " 转化为大写:" + msg2.toUpperCase());
        });
        //andThen方法示例:
        //Hello Word 转化为小写:hello word
        //Hello Word 转化为大写:HELLO WORD
    }
}

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

    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;
    }
  • 默认方法:andThen,也是用来进行组合操作,先执行前置再执行后置
  • 默认方法:compose,也是用来进行组合操作,作用顺序与andThen相反,先执行后置再执行前置
  • 静态方法:identity,输入什么结果就返回什么
  • 示例:
public class FunctionTest {
    public static void test1(Function<String, Integer> function) {
        Integer apply = function.apply("666");
        System.out.println("apply的结果:" + apply);
    }
    public static void test2(Function<String, Integer> f1,Function<Integer, Integer> f2) {
//        Integer apply1 = f1.apply("666");
//        Integer apply2= f2.apply(apply1);
        // 以上注释代码可用andThen替代,执行结果不变
        Integer apply2= f1.andThen(f2).apply("666");
        System.out.println("apply2的结果:" + apply2);
    }
    public static void test3(Function<String, Integer> f1,Function<Integer, Integer> f2) {
        // compose替代andThen,f1、f2交换位置,执行结果不变
        Integer apply2= f2.compose(f1).apply("666");
        System.out.println("apply2的结果:" + apply2);
    }
    public static void main(String[] args) {
        test1(msg -> {
            return Integer.parseInt(msg);
        });
        //test1 ---> apply的结果:666
        System.out.print("Function andThen方法测试:");
        test2(msg1->{
            return Integer.parseInt(msg1);
        },msg2->{
            return msg2*10;
        });
        // test2 --->Function andThen方法测试:apply2的结果:6660
        System.out.print("Function compose方法测试:");
        test3(msg1->{
            return Integer.parseInt(msg1);
        },msg2->{
            return msg2*10;
        });
        // test3 --->Function compose方法测试:apply2的结果:6660
    }
}

3.2.4 Predicate

  • 有参数,返回值为Boolean的接口
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);

    /**
     * Returns a composed predicate that represents a short-circuiting logical
     * AND of this predicate and another.  When evaluating the composed
     * predicate, if this predicate is {@code false}, then the {@code other}
     * predicate is not evaluated.
     * 逻辑与
     */
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    /**
     * Returns a predicate that represents the logical negation of this
     * predicate.
     *
     * @return a predicate that represents the logical negation of this
     * predicate
     * 逻辑非
     */
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    /**
     * Returns a composed predicate that represents a short-circuiting logical
     * OR of this predicate and another.  When evaluating the composed
     * predicate, if this predicate is {@code true}, then the {@code other}
     * predicate is not evaluated.
     * 逻辑或
     */
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    /**
     * Returns a predicate that tests if two arguments are equal according
     */
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}
  • 在Predicate中的默认方法提供了逻辑关系的操作and or negate方法
public class PredicateTest {
    private static void test1(Predicate<String> predicate, String msg) {
        boolean b = predicate.test(msg);
        System.out.println("b:" + b);
    }
    private static void test2(Predicate<String> p1, Predicate<String> p2) {
        //逻辑与and方法的使用
        //p1包含H且p2包含W
        boolean b1 = p1.and(p2).test("Hello");
        System.out.println("逻辑与and方法的使用,验证‘p1包含H且p2包含W’:" + b1);
        //逻辑或or方法的使用
        //p1包含H或者p2包含W
        boolean b2 = p1.or(p2).test("Hello");
        System.out.println("逻辑或or方法的使用,验证‘p1包含H或者p2包含W’:" + b2);
        //逻辑非negate方法的使用
        //p1不包含H
        boolean b3 = p1.negate().test("Hello");
        System.out.println("逻辑非negate方法的使用,验证‘p1不包含H’:" + b3);
    }
    public static void main(String[] args) {
        test1(msg -> {
            return msg.length() > 3;
        },"HelloWorld");
        //b:true
        test2(msg1->{
            return msg1.contains("H");
        },msg2->{
            return msg2.contains("W");
        });
        //逻辑与and方法的使用,验证‘p1包含H且p2包含W’:false
        //逻辑或or方法的使用,验证‘p1包含H或者p2包含W’:true
        //逻辑非negate方法的使用,验证‘p1不包含H’:false
    }
}

4. 方法引用

4.1 为什么要用方法引用?

  • lambda表达式冗余,或者已经有方法实现了需求
  • 示例:
public class ConsumerTest2 {
    public static void test(Consumer<int[]> consumer) {
        int[] arr = {10, 20, 30, 40, 50};
        consumer.accept(arr);
    }
    /**
     * 求数组中所有元素的和
     * @param arr
     */
    public static void getTotal(int[] arr){
        int sum = 0;
        for (int i : arr) {
            sum += i;
        }
        System.out.println("数组之和:" + sum);
    }
    public static void main(String[] args) {
        test(arr -> {
            int sum = 0;
            for (int i : arr) {
                sum += i;
            }
            System.out.println("数组之和:" + sum);
            //数组之和:150
        });
    }
}
  • 这种情况就可以使用方法引用
public class ConsumerTest2 {
    public static void test(Consumer<int[]> consumer) {
        int[] arr = {10, 20, 30, 40, 50};
        consumer.accept(arr);
    }
    /**
     * 求数组中所有元素的和
     * @param arr
     */
    public static void getTotal(int[] arr){
        int sum = 0;
        for (int i : arr) {
            sum += i;
        }
        System.out.println("数组之和:" + sum);
    }
    public static void main(String[] args) {
    	// 使用方法引用
        test(ConsumerTest2::getTotal);
        // 执行结果 ---> 数组之和:150
    }
}

4.2 方法引用语法格式

  • 符号表示:::
  • 符号说明:双冒号为方法引用运算符,而它所在的表达式被称为方法引用
  • 应用场景:如果lambda表达式所要实现的方案已经有其他方法实现,那么就可以使用方法引用。
  • 方法引用在JDK8中使用是相当灵活的,主要有以下几种方式:
    • instanceName::methodName 对象名 :: 方法名
    • ClassName::StaticMethodName 类名::静态方法名
    • ClassName::methodName 类名::方法名
    • ClassName::new 类名::new 调用的构造器
    • TypeName[]::new String[]::new 调用数组的构造器

4.2.1 对象名 :: 方法名

  • 这是常见的一种用法,如果一个类中已经存在了一个成员方法,则可以通过对象名引用成员方法
  • 注意事项:
    1. 被引用的方法:参数要和接口中的抽象方法的参数一样
    2. 当接口抽象方法有返回值时,被引用的方法也必须有返回值
  • 示例:
public class FunctionRefTest1 {
    public static void main(String[] args) {
        Date now = new Date();
        Supplier<Long> supplier1 = () -> {
            return now.getTime();
        };
        System.out.println("supplier1:"+supplier1.get());
        //简写
        Supplier<Long> supplier2 = () -> now.getTime();
        System.out.println("supplier2:"+supplier2.get());
        //方法引用 ---> 对象名 :: 方法名
        Supplier<Long> supplier3 = now::getTime;
        System.out.println("supplier3:"+supplier3.get());
        //执行结果:
        //supplier1:1658806711987
        //supplier2:1658806711987
        //supplier3:1658806711987
    }
}

4.2.2 类名::静态方法名

  • 这也是比较常见的一种用法,如果一个类中已经存在了一个静态方法,则可以通过类名引用静态方法
  • 示例:
public class FunctionRefTest2 {
    public static void main(String[] args) {
        Supplier<Long> supplier1 = () -> {
            return System.currentTimeMillis();
        };
        System.out.println("supplier1:"+supplier1.get());
        //简写
        Supplier<Long> supplier2 = () -> System.currentTimeMillis();
        System.out.println("supplier2:"+supplier2.get());
        //方法引用 ---> 类名 :: 静态方法名
        Supplier<Long> supplier3 = System::currentTimeMillis;
        System.out.println("supplier3:"+supplier3.get());
        //执行结果:
        //supplier1:1658806971135
        //supplier2:1658806971135
        //supplier3:1658806971151
    }
}

4.2.3 类名::引用实例方法

  • java面向对象中,类名只能调用静态方法,类名引用实例方法是有前提的,实际是拿第一个参数作为方法的调用者
  • 示例:
public class FunctionRefTest3 {
    public static void main(String[] args) {
        Function<String, Integer> function1 = (s) -> {
            return s.length();
        };
        System.out.println(function1.apply("hello"));
        // 简写
        Function<String, Integer> function2 = (s) -> s.length();
        System.out.println(function2.apply("hello"));
        // 通过方法引用来实现
        Function<String, Integer> function3 = String::length;
        System.out.println(function3.apply("hello"));// 5

        BiFunction<String,Integer,String> biFunction = String::substring;
        String msg = biFunction.apply("helloworld", 3);
        System.out.println(msg);//loworld
    }
}

4.2.4 类名::构造器

  • 由于构造器的名称和类名完全一直,所以构造器引用使用::new的格式
  • 示例:
public class FunctionRefTest4 {
    public static void main(String[] args) {
        //lambda表达式
        Supplier<Person> supplier1 = ()->{return new Person();};
        System.out.println(supplier1.get());
        //简写
        Supplier<Person> supplier2 = ()-> new Person("楚君归",22);
        System.out.println(supplier2.get());
        //方法引用 1
        Supplier<Person> supplier3 = Person::new;
        System.out.println(supplier3.get());
        //方法引用 2
        BiFunction<String,Integer,Person> function = Person::new;
        System.out.println(function.apply("李悠然",66));
        //Person{name='null', age=null}
        //Person{name='楚君归', age=22}
        //Person{name='null', age=null}
        //Person{name='李悠然', age=66}
    }
}

4.2.5 数组::构造器

  • 数组构造示例:
public class FunctionRefTest5 {
    public static void main(String[] args) {
        //lambda表达式
        Function<Integer,String[]> fun1 = (len)->{
            return new String[len];
        };
        String[] arr1 = fun1.apply(3);
        System.out.println("数组的长度是:"+arr1.length);//数组的长度是:3
        //简写
        Function<Integer,String[]> fun2 = (len)-> new String[len];
        String[] arr2 = fun2.apply(3);
        System.out.println("数组的长度是:"+arr2.length);//数组的长度是:3
        //方法引用 1
        Function<Integer,String[]> fun3 = String[]::new;
        String[] arr3 = fun3.apply(6);
        System.out.println("数组的长度是:"+arr3.length);//数组的长度是:6
    }
}

4.3 方法引用总结

  • 方法引用是对lambda表达式符合特定情况下的一种缩写方式,不过要注意方法引用只能引用已经存在的方法。

5. Stream API

5.1 集合处理数据的弊端

  • 日常我们除了对集合的元素进行添加、删除、获取操作外,最典型的操作就是集合的遍历
  • 示例:
public class StreamTest1 {
    public static void main(String[] args) {
        // 定义一个集合
        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.输出筛选后所有用户的信息
        for (String s : list2) {
            System.out.println(s);
        }
        //张三丰
    }
}
  • 上面的代码,针对于我们不同的需求总是一次次的循环,这时我们希望有更加高效的处理方式,所以JDK8引入Stream API来解决此类问题
public class StreamTest2 {
    public static void main(String[] args) {
        // 定义一个集合
        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));
       //简写
        list.stream()
                .filter(s->s.startsWith("张"))
                .filter(s->s.length() == 3)
                .forEach(System.out::println);
        // 执行结果:张三丰
    }
}
  • 上面的Stream API代码的含义:获取流,过滤“张”,过滤长度,逐一打印,相较于以前的写法更加简洁直观

5.2 Stream流式思想

  • Stream和IO流(InputStream/OutputStream)没有任何关系
  • Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构,它并不保存数据,而是对数据进行加工处理,Stream可以看做是流水线上的一个工序,在流水线上,通过多个工序让一个原材料加工成一个商品。
  • Stream API能让我们快速完成许多复杂的操作,如筛选,切片,映射,查找,去重,统计,匹配和归约等操作。

5.3 Stream流获取方式

5.3.1 根据Collection获取

  • 首先,Java.util.Collection接口加入了default方法,也就是说Collection接口下的所有实现都可以通过Stream方法来获取Stream流。
public class T {
    public static void main(String[] args) {
        List<System> list = new ArrayList<>();
        list.stream();
        Set<String> set = new HashSet<>();
        set.stream();
        Vector vector = new Vector();
        vector.stream();
    }
}
  • 虽然Map接口并没有实现Collection接口,但是我们可以根据Map获取对于key value的集合
public class T {
    public static void main(String[] args) {
        Map<String,Object> map = new HashMap<>();
        Stream<String> stream1 = map.keySet().stream();
        Stream<Object> stream2 = map.values().stream();
        Stream<Map.Entry<String, Object>> stream3 = map.entrySet().stream();
    }
}

5.3.2 通过Stream的of方法获取

  • 在实际开发中我们不可避免的还是会操作到数组中的数据,由于数组对象不可能添加默认方法,所以Stream接口中提供了静态方法of
  • 示例:
public class StreamTest3 {
    public static void main(String[] args) {
        Stream<String> a1 = Stream.of("a1", "a2", "a3");
        String[] arr1 = {"aa", "bb", "cc"};
        Stream<String> arr11 = Stream.of(arr1);
        Integer[] arr2 = {1, 2, 3, 4};
        Stream<Integer> arr21 = Stream.of(arr2);
        arr21.forEach(System.out::println);//1 2 3 4
        //注意:基本数据类型的数组是不行的,会看做一个整体
        int[] arr3 = {1,2,3,4};
        Stream.of(arr3).forEach(System.out::println);//[I@6d311334
    }
}

5.4 Stream的常用方法

方法名方法作用返回值类型方法种类
count统计个数long终结
forEach逐一处理void终结
filter过滤Stream函数拼接
limit取前几个Stream函数拼接
skip跳过前几个Stream函数拼接
map映射Stream函数拼接
concat组合Stream函数拼接
  • 终结方法:返回值类型不再是Stream类型的方法,不再支持链式调用
  • 非终结方法:即函数拼接,返回值类型依然是Stream类型的方法,支持链式调用(除了终结方法外,其余方法均为非终结方法)
  • Stream注意事项:
    • Stream只能操作一次,且不可逆
    • Stream方法返回的是新的流
    • Stream不调用终结方法,中间的操作不会执行

5.4.1 forEach

  • forEach用来遍历流中的数据的
 void forEach(Consumer<? super T> action);
  • 该方法接受一个Consumer接口,会将每一个流元素交给函数处理
public class StreamTest6ForEach {
    public static void main(String[] args) {
        Stream.of("aa","ab","ac").filter(s->s.contains("a"))
                .forEach(System.out::println);
        //执行结果:aa ab ac
    }
}

5.4.2 count

  • Stream流中的count方法用来统计其中的元素个数,该方法返回一个long值,代表元素个数。
long count();
  • 示例:
public class StreamTest6Count {
    public static void main(String[] args) {
        long count = Stream.of("aa", "ab", "ac").count();
        System.out.println(count);// 3
    }
}

5.4.3 filter

  • filter方法的作用是用来过滤数据的,返回符合条件的数据,通过filter方法将一个流转换成另一个子集流
Stream<T> filter(Predicate<? super T> predicate);
  • 该接口接收一个Predicate函数式接口参数作为筛选条件
  • 示例:
public class StreamTest7Filter {
    public static void main(String[] args) {
        Stream.of("aa","ab","ac","bb","cc","dd")
                .filter(s->s.contains("a"))
                .forEach(System.out::println);//aa ab ac
    }
}

5.4.4 limit

  • limit方法可以对流进行截取处理,只取前几个数据
Stream<T> limit(long maxSize);
  • 注意:maxSize>实际元素个数时,结果为实际元素,maxSize为负数时报错,maxSize为0时结果集为空
  • 示例:
public class StreamTest8Limit {
    public static void main(String[] args) {
        Stream.of("aa","ab","ac","bb","cc","dd")
                .limit(5)
                .forEach(System.out::print);//aa ab ac bb cc
    }
}

5.4.5 skip

  • 如果希望跳过前面几个元素,就可以好似一个skip方法获取一个截取之后的新流。
Stream<T> skip(long n);
  • 参数需要传入一个long类型的数,用法参考limit方法

  • 示例:

public class StreamTest9Skip {
    public static void main(String[] args) {
        Stream.of("aa","ab","ac","bb","cc","dd")
                .skip(3)
                .forEach(System.out::print);//bb cc dd
    }
}

5.4.6 map

  • 如果我们需要将流中的元素映射到另一个流中,可以使用map方法
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
  • 该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的数据
  • 示例:
public class StreamTest10Map {
    public static void main(String[] args) {
        Stream.of("1","2","3","4","5","6","7")
                .map(msg->Integer.parseInt(msg))
                .forEach(System.out::print);//1234567
        Stream.of("1","2","3","4","5","6","7")
                .map(Integer::parseInt)
                .forEach(System.out::print);//1234567
    }
}

5.4.7 sorted

  • 如果需要排序,可以使用sorted方法
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
  • 源码中有2个方法,默认是自然顺序排序,也可以重写Compartor方法自定义排序
  • 示例:
public class StreamTest11Sorted {
    public static void main(String[] args) {
        Stream.of("7","2","8","0","9","6","1")
                .map(Integer::parseInt)
                .sorted()
                .forEach(System.out::print);//0126789
        Stream.of("7","2","8","0","9","6","1")
                .map(Integer::parseInt)
                .sorted((o1,o2)->o2-o1)
                .forEach(System.out::print);//9876210
    }
}

5.4.8 distinct

  • 该方法用于去除重复元素
Stream<T> distinct();
  • 注意:该方法对于基本数据类型是可以直接去重的,对于引用数据类型需要重写equals和hashCode方法才可以移除重复数据。
  • 示例:
public class StreamTest12Distinct {
    public static void main(String[] args) {
        Stream.of("2","2","8","9","9","6","1")
                .map(Integer::parseInt)
                .distinct()
                .forEach(System.out::print);//28961
        Stream.of(
                new Person("楚君归",22)
                ,new Person("李心怡",18)
                ,new Person("楚君归",22)
        ).distinct()
                .forEach(System.out::println);
        //执行结果:
        //Person{name='楚君归', age=22}
        //Person{name='李心怡', age=18}
    }
}

5.4.9 match

  • 该方法是一个终结方法,用于判断元素是否匹配指定的条件
 // 元素是否有任意一个满足条件
 boolean anyMatch(Predicate<? super T> predicate);
 // 元素是否都满足条件
 boolean allMatch(Predicate<? super T> predicate);
 // 元素是否都不满足条件
 boolean noneMatch(Predicate<? super T> predicate);
  • 示例:
public class StreamTest13Match {
    public static void main(String[] args) {
        boolean b1 = Stream.of("1", "3", "9", "7", "5", "6")
                .map(Integer::parseInt)
                .anyMatch(s -> s > 4);
        System.out.println(b1);// true
        boolean b2 = Stream.of("1", "3", "9", "7", "5", "6")
                .map(Integer::parseInt)
                .allMatch(s -> s > 4);
        System.out.println(b2);// false
        boolean b3 = Stream.of("1", "3", "9", "7", "5", "6")
                .map(Integer::parseInt)
                .noneMatch(s -> s > 4);
        System.out.println(b3);//false
    }
}

5.4.10 find

  • 该方法用于查找某些数据
// 查询第一个
Optional<T> findFirst();
// 查询全部
Optional<T> findAny();
public class StreamTest13Find {
    public static void main(String[] args) {
        Optional<String> first = Stream.of("1", "3", "3", "4", "5", "1", "7").findFirst();
        System.out.println(first.get());

        Optional<String> any = Stream.of("1", "3", "3", "4", "5", "1", "7").findAny();
        System.out.println(any.get());
    }
}

5.4.11 max和min

  • 该方法用于获取最大值或者最小值
    Optional<T> max(Comparator<? super T> comparator);
    Optional<T> min(Comparator<? super T> comparator);
  • 示例:
public class StreamTest14MaxMin {
    public static void main(String[] args) {
        Optional<Integer> max = Stream.of("1", "2", "3", "4", "5", "6", "7")
                .map(Integer::parseInt)
                .max((o1, o2) -> o1 - o2);
        System.out.println(max.get());//7
        Optional<Integer> min = Stream.of("1", "2", "3", "4", "5", "6", "7")
                .map(Integer::parseInt)
                .min((o1, o2) -> o1 - o2);
        System.out.println(min.get());//1
    }
}

5.4.12 reduce

  • 该方法可以将所有数据归纳得到一个数据
T reduce(T identity, BinaryOperator<T> accumulator);
  • 示例:
public class StreamTest15Reduce {
    public static void main(String[] args) {
        Integer sum = Stream.of(4, 5, 3, 9)
                //identity默认值
                //第一次会将默认值赋值给x
                //y就是每次从数据中获取的元素
                //后续每次会将上一次的操作结果赋值给x
                .reduce(0, (x, y) -> x + y);
        System.out.println(sum);//求和:21
        
        Integer max = Stream.of(4, 5, 3, 9)
                .reduce(0, (x, y) -> x > y ? x : y);
        System.out.println(max);//求最大:9
    }
}

5.4.13 map和reduce组合

  • 实际开发中,map和reduce经常组合使用
public class StreamTest16Reduce {
    public static void main(String[] args) {
    	// 求年龄和
        Integer sumAge = Stream.of(
                new Person("楚君归", 22)
                , new Person("李心怡", 18)
                , new Person("楚君归", 22)
                , new Person("林兮", 23)
                , new Person("楚君归", 22)
        ).map(Person::getAge)//实现数据类型的转换
                .reduce(0, Integer::sum);
        System.out.println(sumAge);//107
        // 求最大年龄
        Integer maxAge = Stream.of(
                new Person("楚君归", 22)
                , new Person("李心怡", 18)
                , new Person("楚君归", 22)
                , new Person("林兮", 23)
                , new Person("楚君归", 22)
        ).map(Person::getAge)//实现数据类型的转换,符合reduce数据的要求
                .reduce(0, (x, y) -> x > y ? x : y);//reduce实现数据处理
        System.out.println(maxAge);//23
        // 统计字符a出现的次数
        Integer count = Stream.of("a", "b", "c", "d", "a", "c", "a")
                .map(ch -> "a".equals(ch) ? 1 : 0)
                .reduce(0, Integer::sum);
        System.out.println(count);//3
    }
}

5.4.14 mapToInt

  • 该方法可以将Stream中的引用数据类型转换为对应基本类型,减少自动拆箱和装箱以提升运行效率。
public class StreamTest17MapToInt {
    public static void main(String[] args) {
        //Integer占用内存比int多很多,,在Stream流中会自动装箱和拆箱操作
        Integer[] arr = {1, 2, 3, 5, 6, 8};
        Stream.of(arr)
                .filter(i -> i > 0)
                .forEach(System.out::println);
        //为了提高程序的效率,可以先将流中的Integer转换为int数据再操作
        IntStream intStream = Stream.of(arr)
                .mapToInt(Integer::intValue);
        intStream.filter(i -> i > 3)
                .forEach(System.out::print);//568
    }
}

5.4.15 concat

  • 该方法用于将两个Stream流合成一个Stream流
    public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
        Objects.requireNonNull(a);
        Objects.requireNonNull(b);

        @SuppressWarnings("unchecked")
        Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
                (Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
        Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
        return stream.onClose(Streams.composedClose(a, b));
    }
  • 示例:
public class StreamTest18Concat {
    public static void main(String[] args) {
        Stream<String> stream1 = Stream.of("a","b","c");
        Stream<String> stream2 = Stream.of("x","y");
        Stream.concat(stream1,stream2).forEach(System.out::print);//abcxy
    }
}

5.4.16 综合练习

  • 学了这么久,简单做个小练习吧!
public class StreamTest19 {
    public static void main(String[] args) {
        List<String> list1 = Arrays.asList("迪丽热巴","宋远桥","苏星河","老子","庄子","孙子","洪七 公");
        List<String> list2 = Arrays.asList("古力娜扎","张无忌","张三丰","赵丽颖  ","张二狗","张天爱","张三");
        //	- 第一个队伍只保留姓名长度为3的成员
        //	- 第一个队伍筛选后只要前三个人
        Stream<String> stream1 = list1.stream()
                .filter(s -> s.length() == 3)
                .limit(3);
        //	- 第二个队伍只要姓张的成员第二个队伍赛选之后不要前两个人
        Stream<String> stream2 = list2.stream()
                .filter(s -> s.startsWith("张"))
                .skip(2);
        //	- 将两个队伍合并成一个队伍
        //	- 根据姓名创建Person对象
        //	- 打印整个队伍的Person信息
        Stream.concat(stream1,stream2)
                //.map(n->new Person(n))
                .map(Person::new)
                .forEach(System.out::println);
        //执行结果:
        // Person{name='宋远桥', age=null}
        //Person{name='苏星河', age=null}
        //Person{name='张二狗', age=null}
        //Person{name='张天爱', age=null}
        //Person{name='张三', age=null}
    }
}

5.5 Stream结果收集

5.5.1 结果收集到集合中

	@Test
    public void test01() {
        //收集到list
        List<String> list = Stream.of("aa", "bb", "cc", "aa")
                .collect(Collectors.toList());
        System.out.println(list);//[aa, bb, cc, aa]
        //收集到set
        Set<String> set = Stream.of("aa", "bb", "cc", "aa")
                .collect(Collectors.toSet());
        System.out.println(set);//[aa, bb, cc]
        // 如果需要获取的类型为具体的实现,比如:ArrayList hashSet
        ArrayList<String> arrayList = Stream.of("aa", "bb", "cc", "aa")
                .collect(Collectors.toCollection(ArrayList::new));
        System.out.println(arrayList);//[aa, bb, cc, aa]
        HashSet<String> hashSet = Stream.of("aa", "bb", "cc", "aa")
                .collect(Collectors.toCollection(HashSet::new));
        System.out.println(hashSet);//[aa, bb, cc]
    }

5.5.1 结果收集到数组中

    @Test
    public void test02() {
        //收集到数组
        Object[] objects = Stream.of("aa", "bb", "cc", "aa")
                .toArray();
        System.out.println(Arrays.toString(objects));//[aa, bb, cc, aa]
        String[] strings = Stream.of("aa", "bb", "cc", "aa")
                .toArray(String[]::new);
        System.out.println(Arrays.toString(strings));//[aa, bb, cc, aa]
    }

5.5.2 Stream聚合计算

  • 使用Stream流处理数据后,可以像数据库的聚合函数一样对某个字段进行操作,如最大、最小,求和,求平均,统计数量。
    @Test
    public void test03() {
        Optional<Person> maxAge = Stream.of(
                new Person("张三", 18)
                , new Person("李四", 22)
                , new Person("张三", 13)
                , new Person("王五", 15)
                , new Person("张三", 19)
        ).collect(Collectors.maxBy((p1, p2) -> p1.getAge() - p2.getAge()));
        System.out.println("最大年龄:" + maxAge.get());//最大年龄:Person{name='李四', age=22}
        Integer sumAge = Stream.of(
                new Person("张三", 18)
                , new Person("李四", 22)
                , new Person("张三", 13)
                , new Person("王五", 15)
                , new Person("张三", 19)
        ).collect(Collectors.summingInt(s -> s.getAge()));
        System.out.println("年龄之和:" + sumAge);//年龄之和:87
        Double avgAge = Stream.of(
                new Person("张三", 18)
                , new Person("李四", 22)
                , new Person("张三", 13)
                , new Person("王五", 15)
                , new Person("张三", 19)
        ).collect(Collectors.averagingInt(Person::getAge));
        System.out.println("平均年龄:" + avgAge);//平均年龄:17.4
        Long countAge = Stream.of(
                new Person("张三", 18)
                , new Person("李四", 22)
                , new Person("张三", 13)
                , new Person("王五", 15)
                , new Person("张三", 19)
        ).filter(s -> s.getAge() > 18)
                .collect(Collectors.counting());
        System.out.println("大于18人数:" + countAge);//大于18人数:2
    }

5.5.3 Stream分组计算

    @Test
    public void test04() {
        Map<String, List<Person>> map1 = Stream.of(
                new Person("张三", 18)
                , new Person("李四", 22)
                , new Person("张三", 13)
                , new Person("王五", 15)
                , new Person("张三", 19)
        ).collect(Collectors.groupingBy(Person::getName));
        map1.forEach((k, v) -> System.out.println("k=" + k + "\t" + "v=" + v));
        //根据姓名对数据进行分组
        // k=李四	v=[Person{name='李四', age=22}]
        //k=张三	v=[Person{name='张三', age=18}, Person{name='张三', age=13}, Person{name='张三', age=19}]
        //k=王五	v=[Person{name='王五', age=15}]
        Map<String, List<Person>> map2 = Stream.of(
                new Person("张三", 18)
                , new Person("李四", 22)
                , new Person("张三", 13)
                , new Person("王五", 15)
                , new Person("张三", 19)
        ).collect(Collectors.groupingBy(p -> p.getAge() >= 18 ? "成年" : "未成年"));
        map2.forEach((k, v) -> System.out.println("k=" + k + "\t" + "v=" + v));
        //根据年龄分组,18及以上为成年
        //k=未成年	v=[Person{name='张三', age=13}, Person{name='王五', age=15}]
        //k=成年	v=[Person{name='张三', age=18}, Person{name='李四', age=22}, Person{name='张三', age=19}]
        Map<String, Map<String, List<Person>>> map3 = Stream.of(
                new Person("张三", 18)
                , new Person("李四", 22)
                , new Person("张三", 13)
                , new Person("王五", 15)
                , new Person("张三", 19)
        ).collect(Collectors.groupingBy(
                Person::getName
                , Collectors.groupingBy(p -> p.getAge() >= 18 ? "成年" : "未成年")
        ));
        map3.forEach((K, v) -> {
            System.out.println(K);
            v.forEach((k1, v1) -> {
                System.out.println("\t" + k1 + "===" + v1);
            });
        });
        //李四
        //	成年===[Person{name='李四', age=22}]
        //张三
        //	未成年===[Person{name='张三', age=13}]
        //	成年===[Person{name='张三', age=18}, Person{name='张三', age=19}]
        //王五
        //	未成年===[Person{name='王五', age=15}]
    }

5.5.4 对流中的数据做分区操作

  • Collections.partitioningBy会根据值是否为true,把集合中的数据分为两个列表,一个true列表,一个false列表
    @Test
    public void test06(){
        Map<Boolean, List<Person>> map = Stream.of(
                new Person("张三", 18)
                , new Person("李四", 22)
                , new Person("张三", 13)
                , new Person("王五", 15)
                , new Person("张三", 19)
        ).collect(Collectors.partitioningBy(p -> p.getAge() > 18));
        map.forEach((k,v)-> System.out.println(k+"\t"+v));
        //false	[Person{name='张三', age=18}, Person{name='张三', age=13}, Person{name='王五', age=15}]
        //true	[Person{name='李四', age=22}, Person{name='张三', age=19}]
    }

5.5.5 对流中的数据做拼接

  • Collections.joining会根据指定的连接符,将所有的数据连接到一起
    @Test
    public void test07(){
        String s1 = Stream.of(
                new Person("张三", 18)
                , new Person("李四", 22)
                , new Person("张三", 13)
                , new Person("王五", 15)
                , new Person("张三", 19)
        ).map(Person::getName)
        		// joining(分隔符,前缀,后缀)
                .collect(Collectors.joining("_","--","==="));
        System.out.println(s1);
        //--张三_李四_张三_王五_张三===
    }

5.6 并行的Stream流

5.6.1 串行的Stream流

  • 我们前面学习的Stream都是串行的,如下:
    @Test
    public void test08(){
        Stream.of(5,6,4,8,69,12)
                .filter(s->{
                    System.out.println(Thread.currentThread()+""+s);
                    return s>3;
                }).count();
        //Thread[main,5,main]5
        //Thread[main,5,main]6
        //Thread[main,5,main]4
        //Thread[main,5,main]8
        //Thread[main,5,main]69
        //Thread[main,5,main]12
    }

5.6.2 并行的Stream流

  • parallelStream其实就是一个并行执行的流,它通过ForkJoinPool,可以提高多线程任务的速度。
  • 并行流获取方式:
    • 通过List接口直接获取并行流
    • 将已有的串行流转换为并行流
    @Test
    public void test09(){
        List<Integer> list = new ArrayList<>();
        // 通过list接口直接获取并行流
        Stream<Integer> integerStream = list.parallelStream();
        // 将已有的串行流转换为并行流
        Stream<Integer> parallel = Stream.of(1, 2, 3).parallel();
    }
    @Test
    public void test10() {
        Stream.of(1, 2, 3, 9, 6, 8, 4, 5)
                .parallel()
                .filter(s -> {
                    System.out.println(Thread.currentThread() + " s=" + s);
                    return s > 2;
                }).count();
        //Thread[main,5,main] s=8
        //Thread[ForkJoinPool.commonPool-worker-5,5,main] s=9
        //Thread[ForkJoinPool.commonPool-worker-1,5,main] s=3
        //Thread[ForkJoinPool.commonPool-worker-2,5,main] s=5
        //Thread[ForkJoinPool.commonPool-worker-6,5,main] s=1
        //Thread[ForkJoinPool.commonPool-worker-7,5,main] s=6
        //Thread[ForkJoinPool.commonPool-worker-3,5,main] s=2
        //Thread[ForkJoinPool.commonPool-worker-4,5,main] s=4
    }

5.6.2 串行流和并行流对比

  • 我们通过for循环,串行Stream,并行Stream来对20亿个数字求和,然后查看消耗时间
public class Test02 {
    private static long times = 2000000000;
    private long start;
    private long end;

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

    @After
    public void end() {
        end = System.currentTimeMillis();
        System.out.println("消耗时间:" + (end - start));
    }

    @Test
    public void test01() {
        System.out.print("普通for循环");
        long res = 0;
        for (int i = 0; i < times; i++) {
            res += 1;
        }
        //普通for循环消耗时间:494
    }

    @Test
    public void test02() {
        System.out.print("串行流serialStream");
        LongStream.rangeClosed(0, times)
                .reduce(0, Long::sum);
        //串行流serialStream消耗时间:592
    }

    @Test
    public void test03() {
        System.out.print("并行流parallelStream");
        LongStream.rangeClosed(0, times)
                .parallel()
                .reduce(0, Long::sum);
        //并行流parallelStream消耗时间:222
    }
}

5.6.3 并行流线程安全问题

  • 在多线程的处理下,肯定会出现数据安全问题。如下:
    @Test
    public void test01(){
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            list.add(i);
        }
        System.out.println(list.size());//1000
        List<Integer> listNew = new ArrayList<>();
        //使用并行流向集合中添加数据
        list.parallelStream()
                .forEach(s->listNew.add(s));
        System.out.println(listNew.size());
        //994或者直接抛异常
        //java.lang.ArrayIndexOutOfBoundsException
    }
  • 解决方案1:
    • 使用synchronized 同步代码块保证线程安全
 @Test
    public void test02(){
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            list.add(i);
        }
        System.out.println(list.size());//1000
        List<Integer> listNew = new ArrayList<>();
        Object obj = new Object();
        //使用并行流向集合中添加数据
        list.parallelStream()
                //.forEach(s->listNew.add(s));
                .forEach(s->{
                    synchronized (obj){
                        listNew.add(s);
                    }
                });
        System.out.println(listNew.size());
        //1000
    }
  • 解决方案2:
    • 将线程不安全的容器,包装成线程安全的容器
    @Test
    public void test05() {
        List list = new ArrayList();
        List synchronizedList = Collections.synchronizedList(list);
        IntStream.rangeClosed(1, 1000)
                .parallel()
                .forEach(i -> synchronizedList.add(i));
        System.out.println(synchronizedList.size());
    }
  • 解决方案3:
    • 使用同步容器
    @Test
    public void test03() {
        Vector v = new Vector();
        IntStream.rangeClosed(1, 1000)
                .parallel()
                .forEach(i -> v.add(i));
        System.out.println(v.size());
    }
  • 解决方案4:
    • 通过Stream中的toArray或者collect方法操作
    @Test
    public void test07() {
        int[] ints = IntStream.rangeClosed(1, 1000)
                .parallel()
                .toArray();
        System.out.println(ints.length);
    }
    @Test
    public void test06() {
        List<Integer> list = IntStream.rangeClosed(1, 1000)
                .parallel()
                .boxed()
                .collect(Collectors.toList());
        System.out.println(list.size());
    }

6.Optional类

6.1 Optional类初识

  • 用于解决空指针问题
  1. 以前对null的处理:
    @Test
    public void tes08() {
        String userName = null;
        if (userName != null) {
            System.out.println("字符串长度为:" + userName.length());
        } else {
            System.out.println("字符串为空!");
        }
    }
  1. Optional是一个没有子类的工具类,是一个可以为null的容器对象,它的主要作用是为了避免null检查,防止NullpointerException。

6.2 Optional类用法

  • Optional对象创建
	@Test
    public void test09() {
        //通过of方法,of方法不支持null的
        Optional<String> op1 = Optional.of("张三");
        //Optional<String> op2 = Optional.of(null);

        //通过ofNullable方法,支持null
        Optional<String> op3 = Optional.ofNullable("张三");
        Optional<String> op4 = Optional.ofNullable(null);
        
        //通过empty方法创建一个空的Optional对象
        Optional<Object> op5 = Optional.empty();
    }
  • 常用方法
    • 通过get()方法直接获取值,如果值为null报异常java.util.NoSuchElementException: No value present,通常get()和isPresent方法一块使用
    • isPresent():判断是否包含值,包含返回true,否则返回false
    • orElse(T t):如果调用对象包含值,返回该值,否则返回t
    • orElseGet(Supplier s):如果调用对象包含值,就返回该值,否则返回lambda表达式的返回值
	@Test
    public void test10() {
        Optional<String> op1 = Optional.ofNullable("张三");
        Optional<String> op2 = Optional.ofNullable(null);

        //获取Optional中的值
        System.out.println(op1.get());
        //System.out.println(op2.get());

        if (op1.isPresent()) {
            System.out.println(op1.get());
        }
        if (op2.isPresent()) {
            System.out.println(op2.get());
        } else {
            System.out.println("op2是一个空的Optional对象");
        }
        System.out.println(op1.orElse("李四"));//张三
        System.out.println(op2.orElse("王五"));//王五
        System.out.println(op2.orElseGet(() -> {
            return "666";
        }));//666
    }
  • 一个案例:
@Test
    public void test14() {
        Person p = new Person("zhangsan",18);
        System.out.println(getName(p));
        Optional<Person> p2 = Optional.of(p);
        String name1 = getNameForOptional(p2);
        System.out.println(name1);
        //ZHANGSAN
        //ZHANGSAN
    }

    /**
     * 通过Optional<Person>将name转换为大写并返回
     * @param op
     * @return
     */
    public String getNameForOptional(Optional<Person> op){
        if(op.isPresent()){
            String msg = op
                    .map(p -> p.getName())
                    .map(p -> p.toUpperCase())
                    .orElse(null);
            return msg;
        }
        return null;
    }
    /**
     * 将name转换为大写并返回
     * @param person
     * @return
     */
    public String getName(Person person) {
        if (person != null) {
            String name = person.getName();
            if (name != null) {
                return name.toUpperCase();
            } else {
                return null;
            }
        } else {
            return null;
        }
    }

7.时间和日期

  • 参照《日期相关常用类》: link

8.其他特性

  • 重复注解和类型注解:注解还没整明白,先不更,就这!
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值