Java 8新特性

Java8 拥有更快的速度,代码量减少,强大的Stream API,便于并行,Optional最大程度减少空指针异常

1. Lambda 表达式

1.1 为什么使用Lambda表达式

首先需要弄明白面向对象向的思想函数式编程的思想的区别:

面向对象向的思想函数式编程的新思想
做一件事情,找一个能解决这个事情的对象,然后调用对象的方法去完成事情只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,而不是重视过程

对比一下传统写法和Lambda表达式写法的区别:

  • 匿名内部类:
	 /**
      * 传统写法:匿名内部类
      */
	  public static void runnable(){
	      Runnable task = new Runnable() {
	          @Override
	          public void run() {
	              System.out.println("传统写法:匿名内部类 -> 多线程任务执行!");
	          }
	      };
	      new Thread(task).start(); // 启动线程
	  }
	
	  /**
	   * lambda优化:匿名内部类
	   */
	  public static void lambdaRunnable(){
	  	  // 启动线程
	      new Thread(() -> System.out.println("lambda优化:匿名内部类 -> 多线程任务执行!")).start();
	  }

    public static void main(String[] args) {
        runnable();
        lambdaRunnable();
    }
//=================结果=================
//传统写法:匿名内部类 -> 多线程任务执行!
//lambda优化:匿名内部类 -> 多线程任务执行!

传统的匿名内部类写法多了很多不必要的代码,实际有用的代码只有run()方法的方法体,比如:为了指定run()的方法体,就需要 Runnable 接口的实现类,为了省去定义一个 RunnableImpl 实现类,就得使用匿名内部类,创建完匿名内部类还得覆盖重写run()方法,如此一来,就导致代码冗余,可读性差;

创建匿名内部类本不是我们的初意,我们只是不得不去创建一个匿名内部类对象,真正需要做的事只是把run()方法的方法体传递给Thread类完成调用。

传递一段代码才是我们真正的目的,Lambda表示式通过更简单书写达到相等的效果,所以Lambda表达式可以帮助我们更好的

1.2 Lambda表达式语法

Lambda表达式的标准格式:(参数类型 参数名称) ‐> { 代码语句 }

格式说明:

  • 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。(这里需要注意Lambda 表达式参数列表数据类型可以省略不写,因为javac编译器可以通过上下文推断数据类型,即:类型推断)
  • -> 是新引入的语法格式,代表指向动作。
  • 大括号内的语法与传统方法体要求基本一致。

Lambda表达式中引入了一个新的操作符"- >",可以成为箭头操作符或 Lambda操作符
“- >” 可以分为左右两侧:

左侧右侧
表达式的参数列表表达式中所需执行的功能(Lambda体)

Lambda表达式语法大致可以分为以下六种

1.2.1 无参数,无返回值

这里啰嗦一句:jdk1.7前,匿名内部类使用的同级别的局部变量必须声明为final, 1.8之后默认就是final,也就是说以下示例代码中的num在内部类中调用即默认声明成final

   /**
    * 语法格式一:无参数,无返回值
    */
    @Test
    public void test1(){
        int num = 1; 
        // 原始写法
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello word"+ num);// 局部内部类中运用了同级别的局部变量时,该变量必须是静态的final
            }
        };
        r.run();

        // Lambda表达式
        Runnable r1 = () -> {
            System.out.println("Hello word Lambda no args no return");
        };
        r1.run();
    }

1.2.2 有一个参数,且无返回值

这里用到的Consumer是Java内置的四大核心函数式接口之一,后续介绍;

   /**
    * 语法格式二:有一个参数,且无返回值
    */
    @Test
    public void test2(){
        Consumer<String> con = (x) -> System.out.println(x);
        con.accept("有一个参数,且无返回值");
    }

代码稍微说一下吧,就只声明完成Consumer函数后,调用其中的accept(T t)方法,将值(“有一个参数,且无返回值”),传递打印输出;

1.2.3 有一个参数,且无返回值,可省略括号

注意:当->左边的参数已有一个时,括号可省略不写

    /**
     * 语法格式三:有一个参数,且无返回值,(x)括号可省略不写
     */
    @Test
    public void test3(){
        Consumer<String> con = x -> System.out.println(x);
        con.accept("有一个参数,且无返回值,(x)括号可省略不写");
    }

1.2.4 有两个以上的参数,有返回值

Lambda表达式有两个参数及返回值,并且Lambda体中有多条条件语句

    /**
     * 语法格式四:有两个以上的参数,有返回值,并且Lambda 体中有多条件语句
     */
    @Test
    public void test4(){
        List<Integer> list = new ArrayList<>(Arrays.asList(3,2,4,5));
        Comparator<Integer> com = (x,y) -> {
            System.out.println("有两个以上的参数,有返回值,并且Lambda 体中有多条件语句");
            return Integer.compare(x,y);
        };
        Collections.sort(list, com);
        list.forEach(item -> System.out.println(item));
    }
  // 输出:
  // [2, 3, 4, 5]

1.2.5 Lambda体中的只有一条语句

当Lambda体中的只有一条语句,return和大括号都可以省略不写

 	@Test
    public void test5(){
        Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
    }

1.2.6 Lambda表达式参数列表的数据类型可以省略不写

Lambda表达式中不需要指定参数类型,因为javac编译器可以通过上下文推断数据类型,即:类型推断

	@Test
    public void test6(){
        Comparator<Integer> com = (Integer x, Integer y) -> Integer.compare(x,y);
    }

2.函数式接口

函数式接口概念:只有一个抽象方法的接口称为函数式接口。

注意:可以使用@FuncionalInterface注解修饰,修饰后可以检测接口是否属于函数式接口;

2.1 自定义函数式接口

2.1.1 普通函数式接口

声明普通函数式接口,使用@FuncionalInterface注解修饰:

	@FunctionalInterface
	public interface MyFunction {
	    Integer getValue(Integer num);
	}

函数式接口的调用:

	public Integer operation(Integer num, MyFunction fun){
        return fun.getValue(num);
    }
	@Test
    public void test7(){
    	// 调用operation方法传递参数值
        Integer num = operation(100, (x) -> x * x);
        System.out.println(num);
    }
    // 输出结果 :10000

2.1.2 使用泛型函数式接口

Lambda表达式作为参数传递,传递的Lambda表达式参数类型必须与函数式接口所定义的泛型类型一致。

声明使用泛型函数式接口,使用@FuncionalInterface注解修饰:

	@FunctionalInterface
	public interface MyFunctionT<T> {
	   	T getValue(T t);
	}

函数式接口的调用:

	public String operation(String str, MyFunctionT<String> fun){
        return fun.getValue(str);
    }
    @Test
    public void test7(){
        String str = operation("helloWorld", (x) -> x.toUpperCase());
        System.out.println(str);
    }
    // 输出:HELLOWORLD

2.2 Java 内置四大核心函数式接口

接口名称参数类型返回值类型用途
Consumer< T>消费型接口Tvoid对数据类型为T的对象进行操作,包含方法:
void accept(T t);
Supplier< T>供给型接口T返回类型为T的对象,包含方法:
T get();
Function< T, R>函数型接口TR对类型为T的对象应用操作,且返回结果。结果是R类型的对象。包含方法:
R apply(T t);
Predicate< T>断言型接口Tboolean确定类型为T的对象是否满足某约束,并返回boolean值。包含方法:
boolean test(T t);

2.2.1 消费型接口

	/**
     * Consumer<T> 消费型接口 :
     */
    @Test
    public void test1(){
        shopping(6999, (m) -> System.out.println("购买华为mate40,消费:" + m + "元"));
    }

    public void shopping(double money, Consumer<Double> con){
        con.accept(money);
    }
  • 运行结果:
    在这里插入图片描述

2.2.2 供给型接口

	/**
     * Supplier<T> 供给型接口:
     */
    @Test
    public void test2(){
        List<Integer> numList = getNumList(10, () -> (int)(Math.random() * 100));
        System.out.println(numList);
    }

    //需求:产生指定个数的整数,并放入集合中
    public List<Integer> getNumList(int num, Supplier<Integer> sup){
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < num; i++) {
            Integer n = sup.get();
            list.add(n);
        }
        return list;
    }

2.2.3 函数型接口

    /**
     * Function<T, R> 函数型接口:
     */
    @Test
    public void test3(){
        String newStr = strHandler("\t\t\t 爱吃胡萝卜   ", (str) -> str.trim());
        System.out.println(newStr);

        String subStr = strHandler("爱吃胡萝卜", (str) -> str.substring(2, 5));
        System.out.println(subStr);
    }

    //需求:用于处理字符串
    public String strHandler(String str, Function<String, String> fun){
        return fun.apply(str);
    }

2.2.4 断言型接口

/**
     * Predicate<T> 断言型接口:
     */
    @Test
    public void test4(){
        List<String> list = Arrays.asList("Hello", "atguigu", "Lambda", "www", "ok");
        List<String> strList = filterStr(list, (s) -> s.length() > 3);

        for (String str : strList) {
            System.out.println(str);
        }
    }

    //需求:将满足条件的字符串,放入集合中
    public List<String> filterStr(List<String> list, Predicate<String> pre){
        List<String> strList = new ArrayList<>();

        for (String str : list) {
            if(pre.test(str)){
                strList.add(str);
            }
        }

        return strList;
    }

3.方法的引用方式

3.1 方法引用

3.1.1 对象的实例方法引用

3.1.2 类名的静态方法引用

3.1.3 对象的实例方法引用

3.2 构造器引用

使用构造器引用时,需要注意构造器的参数列表,需要与函数式接口中参数列表保持一致

3.2.1 无参构造引用

 	/**
     * 构造器引用 -> 调用无参构造
     */
    @Test
    public void test5(){
        // 传统Lambda表达式写法
        Supplier<Employee> sup = () -> new Employee();
        Employee employee = sup.get();

        // 方法引用:构造器引用写法
        Supplier<Employee> sup2 = Employee::new;
        Employee employee1 = sup.get();
    }

3.2.2 有参构造引用

	/**
     * 构造器引用 -> 调用有参构造
     */
    @Test
    public void test6(){
        System.out.println("-------------------一个参数的构造方法----------------");
        // 传统Lambda表达式写法
        Function<String, Employee> fun = (x) -> new Employee(x);
        Employee e1 = fun.apply("张三");
        System.out.println(e1);

        // 方法引用:构造器引用写法
        Function<String, Employee> fun1 = (x) -> new Employee(x);
        Employee e2 = fun1.apply("李四");
        System.out.println(e2);

        System.out.println("-------------------二个参数的构造方法----------------");
        // 传统Lambda表达式写法
        BiFunction<String, Integer, Employee> bfun = (name, age) -> new Employee(name, age);
        Employee e3 = bfun.apply("张三", 18);
        System.out.println(e3);

        // 方法引用:构造器引用写法
        // 使用注意:构造器的参数列表,需要与函数式接口中参数列表保持一致
        BiFunction<String, Integer, Employee> bfun1 = (name, age) -> new Employee(name, age);
        Employee e4 = bfun1.apply("李四", 19);
        System.out.println(e4);
    }

3.3 数组引用

    /**
     * 数组引用
     * 类型[] :: new
     */
    @Test
    public void test7(){
        // 传统Lambda表达式写法
        Function<Integer, String[]> fun = (x) -> new String[x];
        String[] strs = fun.apply(10);
        System.out.println(strs.length);

        // 方法引用:数组引用写法
        Function<Integer, String[]> fun1 = String[]::new;
        String[] strs2 = fun1.apply(15);
        System.out.println(strs2.length);

        Function<Integer, Integer[]> fun3 = Integer[]::new;
        Integer[] intArr= fun3.apply(20);
        System.out.println(intArr.length);
    }

4.Stream API介绍

在这里插入图片描述
什么是Stream?
Stream其实就是数据流管道,用于操作数据源(集合、数组等)所生成的元素序列

注意:

  1. Stream 自己不会存储元素;
  2. Stream 不会改变源对象。相反,会返回一个持有结果的Stream;
  3. Stream 操作是延迟执行的,意味着它会等到需要的结果时才会执行;

4.1 Stream的创建

Stream创建的三个步骤:

  1. 创建获取Stream,需要一个数据源(如:数组/集合);
  2. 中间链操作,对数据源的数据进行处理;
  3. 终止操作,执行中间操作链,并产生结果;
    在这里插入图片描述
方法实现
方法一Collection 提供了两个方法 stream()串行流 与 parallelStream()并行流;
Collection.stream()/Collection.parallelStream()
方法二Arrays 中提供的 stream() 获取一个数组流;
Arrays.stream()
方法三通过 Stream 类中静态方法 of();
Stream.of()
方法四创建无限流:
1、迭代Stream.iterate();
2、生成Stream.generate();
  • 代码示例:
	/**
	 * 创建 Stream
	 */
	@Test
	public void test1(){
		// 方式一: Collection 提供了两个方法  stream()串行流 与 parallelStream()并行流
		List<String> list = new ArrayList<>();
		Stream<String> stream = list.stream(); //获取一个顺序流
		Stream<String> parallelStream = list.parallelStream(); //获取一个并行流

		// 方式二: 通过 Arrays 中的 stream() 获取一个数组流
		// 2.1
		Integer[] nums = new Integer[10];
		Stream<Integer> stream1 = Arrays.stream(nums);
		// 2.2
		Employee[] employees = new Employee[10];
		Stream<Employee> stream2 = Arrays.stream(employees);
		
		//方式三: 通过 Stream 类中静态方法 of()
		// 3.1
		Stream<String> stream3 = Stream.of("1", "2", "3", "4", "5");
		// 3.2
		String[] strs = new String[]{"1", "2", "3", "4", "5"};
		Stream<String> stream4 = Stream.of(strs);

		// 方式四:创建无限流
		// 4.1 迭代
		/*Stream<Integer> stream5 = Stream.iterate(0, (x) -> x + 2);
		// 中间操作
		stream5.limit(10);
		// 终止操作
		stream5.forEach(System.out::println);*/
		
		// 4.2生成
		Stream.generate(Math::random)
				.limit(5)
				.forEach(System.out::println);
	}

4.2 Stream的中间链操作

中间操作可以多个连接起来做链式操作,只有当链式操作中触发终止操作后才会执行链上的中间操作,否则中间操作不会执行任何的处理,且在终止操作时会一次性全部处理掉链上的中间操作,这个操作也称为“惰性求值”。

4.2.1 筛选与切片

方法描述
filter(Predicate p)接收lambda,从流中根据条件排除指定元素
distinct()筛选,筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
limit(long maxsize)截断流,使其元素不超过指定数量(maxsize)
skip(n)跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补

代码示例:

	List<Employee> emps = Arrays.asList(
				new Employee(101, "张三", 18, 9999.99),
				new Employee(102, "李四", 59, 6666.66),
				new Employee(103, "王五", 28, 3333.33),
				new Employee(104, "赵六", 31, 5555.55),
				new Employee(104, "赵六", 31, 5555.55),
				new Employee(104, "赵六", 31, 5555.55),
				new Employee(106, "田七", 24, 4444.44),
				new Employee(107, "钱八", 27, 5643.72),
				new Employee(108, "周九", 38, 6525.55)
		);
   /**
	 * 过滤掉年纪大于25
	 */
	@Test
	public void filter(){
		//所有的中间操作不会做任何的处理
		Stream<Employee> stream = emps.stream()
			.filter((e) -> e.getAge() != 31 && e.getAge() < 28);
		//只有当做终止操作时,所有的中间操作会一次性的全部执行,称为“惰性求值”
		stream.forEach(System.out::println);
	}

	/*
		Employee{id=101, name='张三', age=18, salary=9999.99, status=FREE}
		Employee{id=106, name='田七', age=24, salary=4444.44, status=BUSY}
		Employee{id=107, name='钱八', age=27, salary=5643.72, status=VOCATION}

	 */

	/**
	 * 过滤掉年纪大于25的前三人
	 */
	@Test
	public void limit(){
		Stream<Employee> stream = emps.stream()
				.filter((e) -> e.getAge() > 25)
				.limit(4);
		stream.forEach(System.out::println);
	}

	/*
		Employee{name='李四', age='59', salary='6666.66'}
		Employee{name='王五', age='28', salary='3333.33'}
		Employee{name='赵六', age='31', salary='5555.55'}
		Employee{name='赵六', age='31', salary='5555.55'}
	 */

	/**
	 * 过滤掉年纪大于25的前三人且跳过第一个人
	 */
	@Test
	public void skip(){
		Stream<Employee> stream = emps.stream()
				.filter((e) -> e.getAge() > 25)
				.limit(4)
				.skip(1);
		stream.forEach(System.out::println);
	}
	/*
		Employee{name='王五', age='28', salary='3333.33'}
		Employee{name='赵六', age='31', salary='5555.55'}
		Employee{name='赵六', age='31', salary='5555.55'}
	 */

	/**
	 * 1.过滤掉年纪大于25的前三人
	 * 2.且跳过前三人中的第一个人
	 * 3.去除重名的人
	 */
	@Test
	public void distinct(){
		Stream<Employee> stream = emps.stream()
				.filter((e) -> e.getAge() > 25)
				.limit(4)
				.skip(1)
				.distinct();
		stream.forEach(System.out::println);
	}
	/*
		Employee{name='王五', age='28', salary='3333.33'}
		Employee{name='赵六', age='31', salary='5555.55'}
	 */

4.2.2 映射

方法描述
map(Function f)接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
mapToDouble(ToDoubleFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream
mapToInt(ToIntFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream
mapToLong(ToLongFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream
flatMap(Function f)接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流

代码示例:

	/**
     * 映射
     * 	 map-接收lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,
     *   该函数会被应用到每个元素上,并将其映射成一个新的元素
     */
    @Test
    public void map(){
        List<String> list = Arrays.asList("aaa", "bbb", "ccc");
        list.stream()
                .map((str) -> str.toUpperCase())
                .forEach(System.out::println);

        System.out.println("---------------------------");
        emps.stream()
                .map(Employee::getName)
                .forEach(System.out::println);
    }

    /**
     * 映射
     * 	 flatMap- 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
     */
    @Test
    public void flatMap(){
        List<String> list = Arrays.asList("aaa", "bbb", "ccc");
        Stream<Character> sm =
                list.stream().flatMap(TestStreamaAPI::filterCharacter);
        sm.forEach(System.out::println);
    }

    public static Stream<Character> filterCharacter(String str){
        List<Character> characters = new ArrayList<>();

        for (Character ch : str.toCharArray()){
            characters.add(ch);
        }
        return characters.stream();
    }

4.2.3 排序

方法描述
sorted()产生一个新流,其中自然顺序排序
mapToDouble(ToDoubleFunction f)产生一个新流,其中按比较器顺序排序

代码示例:

	/*
        排序
        sorted()-自然排序(Comparable)
        sorted(Comparator com)-定制排序(Comparator)
     */
    @Test
    public void sorted(){
        // 自然排序
        List<String> list = Arrays.asList("aaa", "bbb", "ccc");
        list.stream()
                .sorted()
                .forEach(System.out::println);

        System.out.println("----------------------------");
       
        // 定制排序
        emps.stream()
                .sorted((emp1, emp2) -> {
                    if (emp1.getAge() == emp2.getAge()){
                        return emp1.getName().compareTo(emp2.getName());
                    } else {
//                        return emp1.getAge()-emp2.getAge();
                        return emp2.getAge()-emp1.getAge();
                    }
                }).forEach(System.out::println);
    }

4.2 Stream的终止操作

终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是void。

4.2.1 查找与匹配

方法描述
allMatch(Predicate p)检查是否匹配所有元素
anyMatch(Predicate p)检查是否至少匹配一个元素
noneMatch(Predicate p)检查是否没有匹配所有元素
findFirst()返回第一个元素
findAny()返回当前流中的任意元素
count()返回流中元素的总个数
max(Comparator c)返回流中最大值
min(Comparator c)返回流中最小值
foreach(consumer c)内部迭代

代码示例:

List<Employee> emps = Arrays.asList(
			new Employee(101, "张三", 18, 9999.99, Status.FREE),
			new Employee(102, "李四", 59, 6666.66, Status.VOCATION),
			new Employee(103, "王五", 28, 3333.33, Status.BUSY),
			new Employee(104, "赵六", 31, 5555.55, Status.FREE),
			new Employee(104, "赵六", 31, 5555.55, Status.FREE),
			new Employee(104, "赵六", 31, 5555.55, Status.FREE),
			new Employee(106, "田七", 24, 4444.44, Status.BUSY),
			new Employee(107, "钱八", 27, 5643.72, Status.VOCATION),
			new Employee(108, "周九", 38, 6525.55, Status.BUSY)
	);
@Test
    public void test5(){
        // 是否全部匹配 Status.BUSY
        boolean b1 = emps.stream()
                .allMatch((emp) -> emp.getStatus().equals(Status.BUSY));
        System.out.println(b1);

        // 是否至少一个匹配 Status.BUSY
        boolean b2 = emps.stream()
                .anyMatch((emp) -> emp.getStatus().equals(Status.BUSY));
        System.out.println(b2);

        // 是否没有匹配匹配 Status.BUSY
        boolean b3 = emps.stream()
                .noneMatch((emp) -> emp.getStatus().equals(Status.BUSY));
        System.out.println(b3);

        // 获取工资最高的人
        Optional<Employee> op1 = emps.stream()
                .sorted((emp1, emp2) -> Double.compare(emp2.getSalary(), emp1.getSalary()))
                .findFirst();
        System.out.println(op1.get());

        // 获取当前工作状态为空闲的人(串行流获取 -> stream)
        Optional<Employee> op2 = emps.stream()
                .filter((emp) -> emp.getStatus().equals(Status.FREE))
                .findAny();
        System.out.println(op2.get());

		// 获取当前工作状态为空闲的人(并行流获取 -> parallelStream)
		Optional<Employee> op3 = emps.parallelStream()
				.filter((emp) -> emp.getStatus().equals(Status.FREE))
				.findAny();
		System.out.println(op3.get());

		// 当前公司中共有多少个人
		long count = emps.stream()
				.distinct().count();
		System.out.println(count);

		// 获取工资最高的人
		Optional<Employee> max = emps.stream()
				.max((emp1, emp2) -> Double.compare(emp1.getSalary(), emp2.getSalary()));
		System.out.println(max.get());

		// 获取工资最低的数目是多少
		Optional<Double> min = emps.stream()
				.map(Employee::getSalary).min(Double::compareTo);
		System.out.println(min.get());
	}

4.2.2 规约

map和reduce的连接通常成为map-reduce模式,因为Google用它来进行网络搜索而出名,map进行提取,reduce进行规约操作。

方法描述
reduce(T iden, BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回T
reduce(BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回Optional< T>

代码示例:

	/*
		规约 ——可以将流中元素反复结合起来,得到一个值。
		reduce(T identity, BinaryOperator)
		reduce(BinaryOperator)
	 */
	@Test
	public void test6(){
		List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
		Integer reduce = list.stream()
				.reduce(0, (x, y) -> x + y);
		System.out.println(reduce);

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

		// 计算公司中所有员工工资总和
		Optional<Double> reduce1 = emps.stream()
				.map((emp) -> emp.getSalary())
				.reduce(Double::sum);
		System.out.println(reduce1.get());
	}

4.2.3 收集

Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到List、Set、Map)。但是Collectors实用提供了很多静态方法,可以方便地创建常见收集器实例,具体方法和实例如下表:

方法返回值类型作用
toList()List< T>把流中元素收集到List中
toSet()Set< T>把流中元素收集到Set中
toCollection()List< T>把流中元素收集到创建的集合
counting()Long计算流中元素的个数
summingIntInteger对流中元素的整数属性求和
averagingIntDouble计算流中元素Integer属性的平均值
summarizingIntIntSummaryStatistics收集流中Integer属性的统计值。如:平均值、总和、最大最小值
joiningString连接流中每个字符串
maxByOptional< T>根据比较器选择最大值
minByOptional< T>根据比较器选择最小值
partitioningByMap< Boolean, List< T>>根据true或false进行分区
groupingByMap< K,List< T>>根据某属性值对流分组,属性为K,结果为V
collectingAndThen转换函数返回的类型包裹另一个收集器,对其结果转换函数

代码展示:

		// 将流转换成list
        List<String> list = emps.stream()
                .map(Employee::getName)
                .collect(Collectors.toList());
        list.forEach(System.out::println);
        System.out.println("--------------------------------------");

        // 将流转换成set
        Set<String> set = emps.stream()
                .map(Employee::getName)
                .collect(Collectors.toSet());
        set.forEach(System.out::println);
        System.out.println("--------------------------------------");

        // 将流转换成hashSet
        HashSet<String> hashSet = emps.stream()
                .map(Employee::getName)
                .collect(Collectors.toCollection(HashSet::new));
        hashSet.forEach(System.out::println);
        System.out.println("--------------------------------------");

        // 获取总数
        Long collect = emps.stream()
                .collect(Collectors.counting());
        System.out.println(collect);
        System.out.println("--------------------------------------");

        // 获取平均值
        Double double1 = emps.stream()
                .collect(Collectors.averagingDouble(Employee::getSalary));
        System.out.println(double1);
        System.out.println("--------------------------------------");

        // 获取总和
        Double double2 = emps.stream()
                .collect(Collectors.summingDouble(Employee::getSalary));
        System.out.println(double2);
        System.out.println("--------------------------------------");

        // 获取最大值
        Optional<Employee> op1 = emps.stream()
                .collect(Collectors.maxBy((emp1, emp2) -> Double.compare(emp1.getSalary(), emp2.getSalary())));
        System.out.println(op1.get());
        System.out.println("--------------------------------------");

        // 获取最大值
        Optional<Employee> op2 = emps.stream()
                .collect(Collectors.minBy((emp1, emp2) -> Double.compare(emp1.getSalary(), emp2.getSalary())));
        System.out.println(op2.get());
        System.out.println("--------------------------------------");

        // 分组:根据员工工作状态进行分组
        Map<Status, List<Employee>> g1 =
                emps.stream().collect(Collectors.groupingBy(Employee::getStatus));
        System.out.println(g1);
		System.out.println("--------------------------------------");

		/*
			g1结果:
			{
				BUSY=[Employee{id=103, name='王五', age=28, salary=3333.33, status=BUSY},
					Employee{id=106, name='田七', age=24, salary=4444.44, status=BUSY},
					Employee{id=108, name='周九', age=38, salary=6525.55, status=BUSY}],

				FREE=[Employee{id=101, name='张三', age=18, salary=9999.99, status=FREE},
					Employee{id=104, name='赵六', age=31, salary=5555.55, status=FREE},
					Employee{id=104, name='赵六', age=31, salary=5555.55, status=FREE},
					Employee{id=104, name='赵六', age=31, salary=5555.55, status=FREE}],

				VOCATION=[Employee{id=102, name='李四', age=59, salary=6666.66, status=VOCATION},
				Employee{id=107, name='钱八', age=27, salary=5643.72, status=VOCATION}]
			}
		 */

        // 多级分组:先根据员工工作状态进行分组,再根据年龄段分组
        Map<Status, Map<String, List<Employee>>> g2 =
                emps.stream().collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy((emp) -> {
                    if (emp.getAge() < 30) {
                        return "青年";
                    } else {
                        return "中年";
                    }
                })));
		System.out.println(g2);
		System.out.println("--------------------------------------");
		/*
			g2结果:
			{
				BUSY={
					青年=[Employee{id=103, name='王五', age=28, salary=3333.33, status=BUSY},
					     Employee{id=106, name='田七', age=24, salary=4444.44, status=BUSY}],
				    中年=[Employee{id=108, name='周九', age=38, salary=6525.55, status=BUSY}]
				    },
			    FREE={
			    	青年=[Employee{id=101, name='张三', age=18, salary=9999.99, status=FREE}],
			    	中年=[Employee{id=104, name='赵六', age=31, salary=5555.55, status=FREE},
			    		 Employee{id=104, name='赵六', age=31, salary=5555.55, status=FREE},
			    		 Employee{id=104, name='赵六', age=31, salary=5555.55, status=FREE}]
			    	},
			    VOCATION={
			    	青年=[Employee{id=107, name='钱八', age=27, salary=5643.72, status=VOCATION}],
			    	中年=[Employee{id=102, name='李四', age=59, salary=6666.66, status=VOCATION}]
			    	}
			}
		 */

		// 分区: 满足条件的分一个区,不满足条件的分一个区
		Map<Boolean, List<Employee>> p1 =
				emps.stream().collect(Collectors.partitioningBy((emp) -> emp.getSalary() > 4000));
		System.out.println(p1);

		/*
		{
			false=[Employee{id=103, name='王五', age=28, salary=3333.33, status=BUSY}],
			true=[Employee{id=101, name='张三', age=18, salary=9999.99, status=FREE},
				Employee{id=102, name='李四', age=59, salary=6666.66, status=VOCATION},
				Employee{id=104, name='赵六', age=31, salary=5555.55, status=FREE},
				Employee{id=104, name='赵六', age=31, salary=5555.55, status=FREE},
				Employee{id=104, name='赵六', age=31, salary=5555.55, status=FREE},
				Employee{id=106, name='田七', age=24, salary=4444.44, status=BUSY},
				Employee{id=107, name='钱八', age=27, salary=5643.72, status=VOCATION},
				Employee{id=108, name='周九', age=38, salary=6525.55, status=BUSY}]
		}
		 */

		// 汇总
		DoubleSummaryStatistics d1 = emps.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
		System.out.println(d1.getMax());
		System.out.println(d1.getAverage());
		System.out.println(d1.getSum());

		// 连接
		String s1 = emps.stream()
				.map(Employee::getName)
				.collect(Collectors.joining(",", "<", ">"));
		System.out.println(s1);
		/*
			<张三,李四,王五,赵六,赵六,赵六,田七,钱八,周九>
		 */

5.并行流和串行流

5.1 并行流

并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。

在Java8中将并进流进行了优化,可以很容易的对数据进行并行操作。Stream API可以声明性地通过paralle()与sequential() 在并行流与顺序流之间进行切换。

说到并行流,这里顺便聊一聊Fork/Join模式,Fork/Join模式的优势在于可以充分利用cpu,利用fork方法把任务拆分成多个小任务,拆分后的小任务放到多个cpu上并行执行,当多个小任务执行完成之后,再将这些执行结果join合并起来即可。

在这里插入图片描述
Java7 提供了ForkJoinPool来支持将一个任务拆分成多个“小任务”并行计算,再把多个“小任务”的结果合并成总的计算结果。

ForkJoinPool是ExecutorService的实现类,是一种特殊的线程池。其中提供了submit(ForkJoinTask< T> task)invoke(ForkJoinTask< T> task)方法来执行指定任务,其中ForkJoinTask代表一个可以并行、合并的任务。

ForkJoinTask是一个抽象类,它还有两个抽象子类:RecusiveAction和RecusiveTask。其中RecusiveTask代表有返回值的任务,而RecusiveAction代表没有返回值的任务。

ForkJoinPool与ForkJoinTask之间的关系:
在这里插入图片描述
ForkJosinPool同ThreadPoolExecutor一样,也实现了Executor和ExecutorService接口。它使用了一个无限队列来保存需要执行的任务,而线程的数量则是通过构造函数传入,如果没有向构造函数中传入希望的线程数量,那么当前计算机可用的CPU数量会被设置为线程数量作为默认值。

代码示例:

public class TestForkJoin {

    /**
     * 定义任务
     */
    class ForkJoinCalculate extends RecursiveTask<Long>{
        private static final long serialVersionUID = 123655995645L;

        // 起始标志位
        private long start;
        // 终止标志位
        private long end;

        // 设置阀值
        private static final long  THRESHOLD = 100000;

        public ForkJoinCalculate(long start, long end) {
            this.start = start;
            this.end = end;
        }

        /**
         * The main computation performed by this task.
         *
         * @return the result of the computation
         */
        @Override
        protected Long compute() {
            long length = end - start;
            if(length <= THRESHOLD){
                long sum = 0;
                for (long i = start; i <= end ; i++) {
                    sum += i;
                }
                return sum;
            } else {
                long middle = (start + end)/2;
                ForkJoinCalculate left = new ForkJoinCalculate(start, middle);
                ForkJoinCalculate right = new ForkJoinCalculate(middle + 1, end);

                // 拆分子任务,同时压入队列
                left.fork();
                right.fork();

                return left.join() + right.join();
            }
        }
    }
    /**
     * forkJoinPool
     */
    @Test
    public void test1() {
        // 计算时间戳
        Instant start = Instant.now();

        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinCalculate forkJoinCalculate = new ForkJoinCalculate(0, 10000000000L);
        // 传入定时任务
        Long num = forkJoinPool.invoke(forkJoinCalculate);
        System.out.println(num);

        Instant end = Instant.now();
        System.out.println("消耗时长:"+ Duration.between(start, end).toMillis());
        /*
            -5340232216128654848
            消耗时长:5737
         */
    }

    /**
     * 普通for循环
     */
    @Test
    public void test2() {
        Instant start = Instant.now();

        long sum = 0L;

        for (long i = 0; i <= 10000000000L; i++) {
            sum += i;
        }
        System.out.println(sum);

        Instant end = Instant.now();
        // 计算时间戳
        System.out.println("消耗时长:"+ Duration.between(start, end).toMillis());
         /*
            -5340232216128654848
            消耗时长:6427
         */
    }

    /**
     * Java8-并行流
     */
    @Test
    public void test3() {
        Instant start = Instant.now();
        long sum = LongStream.rangeClosed(0, 10000000000L)
                .parallel()
                .reduce(0, Long::sum);
        Instant end = Instant.now();
        // 计算时间戳
        System.out.println(sum);
        System.out.println("消耗时长:"+ Duration.between(start, end).toMillis());
        /*
            -5340232216128654848
            消耗时长:5516
         */
    }

}

5.2 串行流

串行流只需要将.parallel()方法更换成.sequential()方法即可;

6.接口中的默认方法 / 静态方法

Java8接口中提供了默认方法和静态方法:

public class TestFun {

    interface MyFunction{
        default String getString(){
            return "我是接口中的默认方法";
        }

        /**
         * 接口中的静态方法
         */
        public static void show(){
            System.out.println("接口中的静态方法");
        }
    }

    interface MyFunctionTwo{
        default String getString(){
            return "我是MyFunctionTwo接口中的默认方法";
        }
    }

    class MyClass{
        public String getString(){
            return "我是类中的方法";
        }
    }

    class SubClass extends MyClass implements MyFunction{
    }

    class SubClassTwo implements MyFunction, MyFunctionTwo{
        @Override
        public String getString() {
            // 当一个类同时实现两个接口且当两个接口中有同名默认方法时,此类就必须重写接口中的重名默认方法,且要指定具体是哪一个接口中的默认方法;
            return MyFunctionTwo.super.getString();
        }
    }

    @Test
    public void test1() {
        SubClass subClass = new SubClass();
        String string = subClass.getString();
        System.out.println(string);
        /*
            我是类中的方法
         */
    }

    @Test
    public void test2() {
        SubClassTwo subClassTwo = new SubClassTwo();
        String string = subClassTwo.getString();
        System.out.println(string);
        /*
            我是MyFunctionTwo接口中的默认方法
         */
    }

    @Test
    public void test3() {
        MyFunction.show();
        /*
            接口中的静态方法
         */
    }
}

当接口中定义一个默认方法,而另一个父类或接口中也定义了一个同名的方法时:

  1. 如果父类提供了具体的实现,那么接口中的同名方法就会被忽略,如上述代码test1所示;
  2. 如果同时实现两个接口且当两个接口中有同名方法时(不管方法是否默认),此类就必须重写接口中的重名方法,且要指定具体是哪一个接口中的方法;

7.新时间日期 API

类名作用
LocalDate一个不可变的日期-时间对象,表示日期(该类是可不变和线程安全的)
LocalTime一个不可变的日期-时间对象,它表示时间,通常被视为小时-分钟-秒(该类是可不变和线程安全的)
LocalDateTime一个不可变的日期-时间对象,它表示一个日期-时间,通常被视为年-月-日-小时-分钟-秒。还可以访问其他日期和时间字段,如day-of-year、day-of-week和week-of-year。

常用方法:

方法描述
now()静态方法,根据当前时间创建对象
of()静态方法,根据指定日期/时间创建对象
plusDays、plusWeeks、plusMonths、plusYears向当前LocalDate 对象添加几天/几周/几个月/几年
minusDays、minusWeeks、minusMonths、minusYears向当前LocalDate 对象添加几天/几周/几个月/几年/几周/几个月/几年
withDayOfMonth、withDayOfYear、withMonth、withYear将月份天数、年份天数、月份、年份修改为指定的值并返回新的LocalDate对象
getDayOfMonth()获得月份天数(1-31)
getDayOfYear()获得年份天数(1-366)
getDayOfWeek()获得星期几(返回一个DayOfWeek枚举值)
getMonth()获得月份,返回一个Month枚举值
getMonthValue()获得月份(1-12)
getYear()获得年份
until()获得两个日期之间的Period 对象,或者指定ChronoUnits的数字
isBefore(), isAfter()比较两个LocalDate
isLeapYear()判断是否是闰年

7.2 时间实例化

创建时间实例化对象

	/**
     * LocalDateTime实例化方法
     */
    @Test
    public void test1(){
        // 可获取到当前时间
        LocalDateTime now = LocalDateTime.now();
        System.out.println(now);

        // of()方法可以指定具体的时间
        LocalDateTime of = LocalDateTime.of(2021, 1, 1, 12, 59, 59);
        System.out.println(of);
    }

7.2 时间戳

时间戳(以Unix元年:1970年1月1日00:00:00到某个时间之间的毫秒值)

	 @Test
    public void test2(){
        Instant now = Instant.now(); // 默认获取UTC(比shanghai时区晚8个小时)时区
        System.out.println(now);

        // 偏移量运算 当前时间+8小时
        OffsetDateTime offsetDateTime = now.atOffset(ZoneOffset.ofHours(8));
        System.out.println(offsetDateTime);

        // 获取毫秒时间戳
        System.out.println(now.toEpochMilli());

        Instant instant = Instant.ofEpochSecond(60);
        System.out.println(instant);
    }

7.3 时间间隔

类名作用
Duration用于计算两个时间的间隔
Period用于计算两个日期的间隔

代码展示:

	 @Test
    public void test3(){
        Instant now1 = Instant.now();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Instant now2 = Instant.now();
        Duration between = Duration.between(now1, now2);
        System.out.println(between.toMillis());

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

        LocalTime now3 = LocalTime.now();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LocalTime now4 = LocalTime.now();
        Duration between1 = Duration.between(now3, now4);
        System.out.println(between1.toMillis());

        LocalDate now5 = LocalDate.of(2020, 1, 1);
        LocalDate now6 = LocalDate.now();
        Period between2 = Period.between(now5, now6);
        System.out.println(between2);
        System.out.println(between2.getYears());
        System.out.println(between2.getMonths());
        System.out.println(between2.getDays());
    }

7.4 时间校正器

TemporalAdjuster 将时间调整到下一个周六等等的操作;
TemporalAdjusters :通过静态方法提供大量的常用TemporalAdjuster实现;

代码展示:

	/**
     * 时间校验器
     */
    @Test
    public void test4(){
        LocalDateTime now = LocalDateTime.now();
        System.out.println(now);

        // 指定时间为当前月的第十天
        LocalDateTime localDateTime = now.withDayOfMonth(10);
        System.out.println(localDateTime);

        // 获取下一个周日的日期
        LocalDateTime nextDay = now.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
        System.out.println(nextDay);

        // 自定义时间校验器
        LocalDateTime with = now.with((date) -> {
            LocalDateTime l = (LocalDateTime) date;
            DayOfWeek dayOfWeek = l.getDayOfWeek();
            if (dayOfWeek.equals(DayOfWeek.FRIDAY)) {
                l = l.plusDays(3);
            } else if (dayOfWeek.equals(DayOfWeek.SATURDAY)) {
                l = l.plusDays(3);
            } else {
                l = l.plusDays(1);
            }
            return l;
        });
        System.out.println(with);
    }

7.5 解析与格式化

DateTimeFormatter类提供了三种格式化方法:

  1. 预定义标准;
  2. 语言环境相关的格式;
  3. 自定义的格式;

8.其他新特性

8.1 Optinal容器类

Optional是一个容器类,代表一个值的存在或者不存在,java8前用null表示一个值不存在,现在Optional可以更好的表达这个概念,也可以更好的避免空指针异常。

常用方法:

方法作用
of(T t)of方法创建一个 Optional 带泛型的实例,注意:of()方法传入null构建optional对象也会报空指针异常
empty()创建一个空的 Optional 实例
isPresent()判断是否包含值
orElse(T t)如果调用对象包含值则返回所包含的值,否则返回t(默认值)
orElseGet(Supplier s)ofNullable(T t)其实就是of()和empty()的中和方法,若 t 不为 null,创建 Optional 实例,否则创建空实例
map(Function f)如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
flatMap(Function mapper)与 map 类似,要求返回值必须是Optional

代码示例:

 	@Test
    public void test1() {
        // of方法创建一个 Optional 带泛型的实例,注意:of()方法传入null构建optional对象也会报空指针异常
        Optional<Employee> op1 = Optional.of(new Employee());
        Employee emp = op1.get();
        System.out.println(emp);

        // of方法传入null时一样会报空指针异常
        Optional<Employee> op2 = Optional.of(null);
        Employee emp2 = op2.get();
        System.out.println(emp2);
    }

    @Test
    public void test2() {
        // empty()方法可以构建一个空的Optional实例
        Optional<Employee> op2 = Optional.empty();
        System.out.println(op2.get());
    }

    @Test
    public void test3() {
        // ofNullable(T t)其实就是of()和empty()的中和方法,若 t 不为 null,创建 Optional 实例,否则创建空实例
        Optional<Employee> op3 = Optional.ofNullable(null);
        // isPresent() 判断实例是否包含值
        if (op3.isPresent()) {
            System.out.println(op3.get());
        }

    }

    @Test
    public void test4() {
        Optional<Employee> op4 = Optional.ofNullable(null);
        // orElse(T t) 如果调用对象包含值则返回所包含的值,否则返回默认值
        // 例子:当op4中有值时就会返回op4中的值,否则就会返回"张哈哈"
        Employee emp4 = op4.orElse(new Employee("张哈哈", 18, 2000));
        System.out.println(emp4);

    }

    @Test
    public void test5() {
        Optional<Employee> op5 = Optional.ofNullable(null);
    // orElseGet(Supplier s) 需要提供一个供给型接口,如果调用对象包含值则返回所包含的值,否则返回t(默认值)
        Employee emp = op5.orElseGet(() -> new Employee());
        System.out.println(emp);
    }

    @Test
    public void test6() {
        Optional<Employee> op6 = Optional.ofNullable(new Employee("张哈哈", 18, 2000));
        Optional<String> str = op6.map((emp) -> emp.getName());
        System.out.println(str.get());

        //与 map 类似,但是要求返回值必须是Optional
        Optional<String> str2 = op6.flatMap((emp) -> Optional.ofNullable(emp.getName()));
        System.out.println(str2.get());
    }

练习题:

// -------------------------------------练习题-----------------------------------------
    // 班级类
    class ClassGrade{
        private Teacher teacher;

        public ClassGrade() {
            this.teacher = teacher;
        }

        public ClassGrade(Teacher teacher) {
            this.teacher = teacher;
        }

        public Teacher getTeacher() {
            return teacher;
        }

        public void setTeacher(Teacher teacher) {
            this.teacher = teacher;
        }

        @Override
        public String toString() {
            return "ClassGrade{" +
                    "teacher=" + teacher +
                    '}';
        }
    }
	// 新的班级类(Optional)
    class NewClassGrade{
        private Optional<Teacher> teacher = Optional.empty();

        public NewClassGrade() {
        }

        public NewClassGrade(Optional<Teacher> teacher) {
            this.teacher = teacher;
        }

        public Optional<Teacher> getTeacher() {
            return teacher;
        }

        public void setTeacher(Optional<Teacher> teacher) {
            this.teacher = teacher;
        }

        @Override
        public String toString() {
            return "NewClassGrade{" +
                    "teacher=" + teacher +
                    '}';
        }
    }
    // 老师类
    class Teacher{
        private String name;

        public Teacher() {
        }

        public Teacher(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

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

    /**
     * 例题:获取班级一个的老师
     */
    @Test
    public void test7() {
    	// 传统写法
        ClassGrade classGrade = new ClassGrade();
        String teacherName = getTeacherName(classGrade);
        System.out.println(teacherName);
        
        // 当传入的老师对象为空实例时,返回默认的王老师
        Optional<NewClassGrade> op2 = Optional.ofNullable(new NewClassGrade());
        String teacherName2 = getTeacherName2(op2);
        System.out.println(teacherName2);

        // 当传入的老师对象有值时,返回传入的老师
        Optional<Teacher> teacherEntity = Optional.ofNullable(new Teacher("赵老师"));
        Optional<NewClassGrade> op3 = Optional.ofNullable(new NewClassGrade(teacherEntity));
        String teacherName3 = getTeacherName2(op3);
        System.out.println(teacherName3);
    }

    private String getTeacherName2(Optional<NewClassGrade> newClassGrade){
        return newClassGrade
                .orElse(new NewClassGrade())
                .getTeacher()
                .orElse(new Teacher("王老师"))
                .getName();
    }

    private String getTeacherName(ClassGrade classGrade){
        if (classGrade != null){
            Teacher teacher = classGrade.getTeacher();
            if(teacher != null){
                return teacher.getName();
            }
        }
        return "李老师";
    }

9.注解

Java8对于注解的处理提供了两点改进,分别是可重复的注解与可用于类型的注解。

9.1 重复注解

9.2 类型注解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhuzicc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值