Java8新特性

第17章 Java8新特性

主要内容

Java8的新特征有很多,之前我们在学习接口时,学习了接口的静态方法和默认方法,在学习常用类时,学习了新版的日期时间API。今天我们来学习Java8最具革命性的两个新特性:Lambda表达式和StreamAPI。然后带领大家用Optioanl类解决最令人头疼的空指针异常。

  • Lambda表达式
  • StreamAPI
  • Optional

学习目标

17.1 函数式编程思想

1666342339083

在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事情”。编程中的函数,也有类似的概念,你调用我的时候,给我实参为形参赋值,然后通过运行方法体,给你返回一个结果。对于调用者来做,关注这个方法具备什么样的功能。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做

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

Java8引入了Lambda表达式之后,Java也开始支持函数式编程。

Lambda表达式不是Java最早使用的,很多语言就支持Lambda表达式,例如:C++,C#,Python,Scala等。如果有Python或者Javascript的语言基础,对理解Lambda表达式有很大帮助,可以这么说lambda表达式其实就是实现SAM接口的语法糖,使得Java也算是支持函数式编程的语言。Lambda写的好可以极大的减少代码冗余,同时可读性也好过冗长的匿名内部类。

备注:“语法糖”是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的for-each语法,其实
底层的实现原理仍然是迭代器,这便是“语法糖”。从应用层面来讲,Java中的Lambda可以被当做是匿名内部
类的“语法糖”,但是二者在原理上是不同的。

17.1.1 冗余的匿名内部类

当需要启动一个线程去完成任务时,通常会通过java.lang.Runnable接口来定义任务内容,并使用java.lang.Thread类来启动该线程。代码如下:

package com.atguigu.fp;

public class UseFunctionalProgramming {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("多线程任务执行!")).start(); // 启动线程
    }
}

本着“一切皆对象”的思想,这种做法是无可厚非的:首先创建一个Runnable接口的匿名内部类对象来指定任务内容,再将其交给一个线程来启动。

代码分析:

对于Runnable的匿名内部类用法,可以分析出几点内容:

  • Thread类需要Runnable接口作为参数,其中的抽象run方法是用来指定线程任务内容的核心;
  • 为了指定run的方法体,不得不需要Runnable接口的实现类;
  • 为了省去定义一个RunnableImpl实现类的麻烦,不得不使用匿名内部类;
  • 必须覆盖重写抽象run方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错;
  • 而实际上,似乎只有方法体才是关键所在

17.1.2 编程思想转换

做什么,而不是谁来做,怎么做

我们真的希望创建一个匿名内部类对象吗?不。我们只是为了做这件事情而不得不创建一个对象。我们真正希望做的事情是:将run方法体内的代码传递给Thread类知晓。

传递一段代码——这才是我们真正的目的。而创建对象只是受限于面向对象语法而不得不采取的一种手段方式。那,有没有更加简单的办法?如果我们将关注点从“怎么做”回归到“做什么”的本质上,就会发现只要能够更好地达到目的,过程与形式其实并不重要。

当我们需要从北京到上海时,可以选择高铁、汽车、骑行或是徒步。我们的真正目的是到达上海,而如何才能到达上海的形式并不重要,所以我们一直在探索有没有比高铁更好的方式——搭乘飞机。

而现在这种飞机(甚至是飞船)已经诞生:2014年3月Oracle所发布的Java 8(JDK 1.8)中,加入了Lambda表达式的重量级新特性,为我们打开了新世界的大门。

17.1.3 体验Lambda的更优写法

借助Java 8的全新语法,上述Runnable接口的匿名内部类写法可以通过更简单的Lambda表达式达到等效:

public class Demo02LambdaRunnable {
	public static void main(String[] args) {
		new Thread(() -> System.out.println("多线程任务执行!")).start(); // 启动线程
	}
}

这段代码和刚才的执行效果是完全一样的,可以在1.8或更高的编译级别下通过。从代码的语义中可以看出:我们启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。

不再有“不得不创建接口对象”的束缚,不再有“抽象方法覆盖重写”的负担,就是这么简单!

17.2 函数式接口

17.2.1 函数接口的概念

lambda表达式其实就是实现SAM接口的语法糖,所谓SAM接口就是Single Abstract Method,即该接口中只有一个抽象方法需要实现,当然该接口可以包含其他非抽象方法。

其实只要满足“SAM”特征的接口都可以称为函数式接口,都可以使用Lambda表达式,但是如果要更明确一点,最好在声明接口时,加上@FunctionalInterface。一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。

17.2.2 盘点之前学过的接口

之前学过的接口已经很多了:Cloneable、Comparable、Comparator、Runnable、Iterable、Iterator、Collection、List、Queue、Deque、Set、Map、Serializable、FileFilter、FilenameFilter等。

上述接口中,满足SAM接口特点的有:

  • java.lang.Runnable
    • public void run()
  • java.lang.Iterable
    • public Iterator iterate()
  • java.lang.Comparable
    • public int compareTo(T t)
  • java.util.Comparator
    • public int compare(T t1, T t2)
  • java.io.FileFilter
    • public boolean accept(File pathname)
  • java.io.FilenameFilter
    • public boolean accept(File dir, String name)

上述SAM接口中,标记了@FunctionalInterface注解有:Runnable,Comparator,FileFilter,FilenameFilter。

17.2.3 java.util.function包四大类函数式接口

Java8在java.util.function新增了很多函数式接口:主要分为四大类,消费型、供给型、判断型、功能型。基本可以满足我们的开发需求。当然你也可以定义自己的函数式接口。

1、消费型接口

消费型接口的抽象方法特点:有形参,但是返回值类型是void

接口名 抽象方法 描述
Consumer void accept(T t) 接收一个对象用于完成功能
BiConsumer<T,U> void accept(T t, U u) 接收两个对象用于完成功能
DoubleConsumer void accept(double value) 接收一个double值
IntConsumer void accept(int value) 接收一个int值
LongConsumer void accept(long value) 接收一个long值
ObjDoubleConsumer void accept(T t, double value) 接收一个对象和一个double值
ObjIntConsumer void accept(T t, int value) 接收一个对象和一个int值
ObjLongConsumer void accept(T t, long value) 接收一个对象和一个long值

2、供给型接口

这类接口的抽象方法特点:无参,但是有返回值

接口名 抽象方法 描述
Supplier T get() 返回一个对象
BooleanSupplier boolean getAsBoolean() 返回一个boolean值
DoubleSupplier double getAsDouble() 返回一个double值
IntSupplier int getAsInt() 返回一个int值
LongSupplier long getAsLong() 返回一个long值

3、判断型接口

这类接口的抽象方法特点:有参,但是返回值类型是boolean结果。

接口名 抽象方法 描述
Predicate boolean test(T t) 接收一个对象
BiPredicate<T,U> boolean test(T t, U u) 接收两个对象
DoublePredicate boolean test(double value) 接收一个double值
IntPredicate boolean test(int value) 接收一个int值
LongPredicate boolean test(long value) 接收一个long值

4、功能型接口

这类接口的抽象方法特点:既有参数又有返回值

接口名 抽象方法 描述
Function<T,R> R apply(T t) 接收一个T类型对象,返回一个R类型对象结果
UnaryOperator T apply(T t) 接收一个T类型对象,返回一个T类型对象结果
DoubleFunction R apply(double value) 接收一个double值,返回一个R类型对象
IntFunction R apply(int value) 接收一个int值,返回一个R类型对象
LongFunction R apply(long value) 接收一个long值,返回一个R类型对象
ToDoubleFunction double applyAsDouble(T value) 接收一个T类型对象,返回一个double
ToIntFunction int applyAsInt(T value) 接收一个T类型对象,返回一个int
ToLongFunction long applyAsLong(T value) 接收一个T类型对象,返回一个long
DoubleToIntFunction int applyAsInt(double value) 接收一个double值,返回一个int结果
DoubleToLongFunction long applyAsLong(double value) 接收一个double值,返回一个long结果
IntToDoubleFunction double applyAsDouble(int value) 接收一个int值,返回一个double结果
IntToLongFunction long applyAsLong(int value) 接收一个int值,返回一个long结果
LongToDoubleFunction double applyAsDouble(long value) 接收一个long值,返回一个double结果
LongToIntFunction int applyAsInt(long value) 接收一个long值,返回一个int结果
DoubleUnaryOperator double applyAsDouble(double operand) 接收一个double值,返回一个double
IntUnaryOperator int applyAsInt(int operand) 接收一个int值,返回一个int结果
LongUnaryOperator long applyAsLong(long operand) 接收一个long值,返回一个long结果
BiFunction<T,U,R> R apply(T t, U u) 接收一个T类型和一个U类型对象,返回一个R类型对象结果
BinaryOperator T apply(T t, T u) 接收两个T类型对象,返回一个T类型对象结果
ToDoubleBiFunction<T,U> double applyAsDouble(T t, U u) 接收一个T类型和一个U类型对象,返回一个double
ToIntBiFunction<T,U> int applyAsInt(T t, U u) 接收一个T类型和一个U类型对象,返回一个int
ToLongBiFunction<T,U> long applyAsLong(T t, U u) 接收一个T类型和一个U类型对象,返回一个long
DoubleBinaryOperator double applyAsDouble(double left, double right) 接收两个double值,返回一个double结果
IntBinaryOperator int applyAsInt(int left, int right) 接收两个int值,返回一个int结果
LongBinaryOperator long applyAsLong(long left, long right) 接收两个long值,返回一个long结果

17.2.4 自定义函数式接口

只要确保接口中有且仅有一个抽象方法即可:

修饰符 interface 接口名称 {
    public abstract 返回值类型 方法名称(可选参数信息);
    // 其他非抽象方法内容
}

接口当中抽象方法的 public abstract 是可以省略的

例如:声明一个计算器Calculator<T,R>接口,内含抽象方法calculate可以对两个参数进行计算,并返回结果。其中T是参数类型,R是返回值类型。

package com.atguigu.fi;

@FunctionalInterface
public interface Calculator<T,R> {
    R calculate(T a, T b);
}

例如:声明一个转换器Convertor<T,R>,包含抽象方法change,可以将参数转换为另一个值,并返回结果。其中T是参数类型,R是返回值类型。

package com.atguigu.fi;

@FunctionalInterface
public interface Convertor<T,R> {
    R change(T t);
}

17.3 Lambda表达式

17.3.1 Lambda表达式语法

Lambda表达式是用来给【函数式接口】的变量或形参赋值用的。其实本质上,Lambda表达式是用于实现【函数式接口】的“抽象方法”,或者是给函数式接口的变量传递一段实现抽象方法的方法体代码。

Lambda表达式语法格式

(形参列表) -> {Lambda体}

语法格式说明:

  • (形参列表)它就是你要赋值的函数式接口的抽象方法的(形参列表),照抄
  • {Lambda体}就是实现这个抽象方法的方法体
  • ->称为Lambda操作符(减号和大于号中间不能有空格,而且必须是英文状态下半角输入方式)

17.3.2 使用Lamda表达式标准格式

package com.atguigu.lambda;

import com.atguigu.fi.Calculator;

public class LambdaGrammar {
    public static void main(String[] args) {
        Calculator<Integer,Integer> c1 = (Integer a, Integer b) -> {return a+b;};
        Calculator<Integer,Integer> c2 = (Integer a, Integer b) -> {return a-b;};
        Calculator<Integer,Integer> c3 = (Integer a, Integer b) -> {return a*b;};
        Calculator<Integer,Integer> c4 = (Integer a, Integer b) -> {return a/b;};

        System.out.println(c1.calculate(5, 2));
        System.out.println(c2.calculate(5, 2));
        System.out.println(c3.calculate(5, 2));
        System.out.println(c4.calculate(5, 2));
    }
}

17.3.3 Lambda表达式的简化

某些情况下Lambda表达式可以精简:

  • 当{Lambda体}中只有一句语句时,可以省略{}和
  • 当{Lambda体}中只有一句语句时,并且这个语句还是一个return语句,那么{、return、;}三者可以省略。它们三要么一起省略,要么都不省略。
  • 当Lambda表达式(形参列表)的类型已知,获取根据泛型规则可以自动推断,那么(形参列表)的数据类型可以省略。
  • 当Lambda表达式(形参列表)的形参个数只有一个,并且类型已知或可以自动推断,则形参的数据类型和()可以一起省略,但是形参名不能省略。
  • 当Lambda表达式(形参列表)是空参时,()不能省略

示例代码:

package com.atguigu.lambda;

import com.atguigu.fi.Calculator;
import com.atguigu.fi.Convertor;
import org.junit.Test;

public class LambdaGrammarSimple {
    @Test
    public void test01() {
        //使用Lambda表达式实现Calculator接口,求两个整数的和的功能
        Calculator<Integer,Integer> c1 = (Integer a, Integer b) -> {return a+b;};
        System.out.println(c1.calculate(5, 2));

        Calculator<Integer,Integer> c2 = (Integer a, Integer b) ->  a+b;
        System.out.println(c2.calculate(5, 2));

        Calculator<Integer,Integer> c3 = (a,  b) -> a+b;
        System.out.println(c3.calculate(5, 2));
        //上面三种写法完全等价
    }

    @Test
    public void test02() {
        //使用Lambda表达式实现Convertor接口,实现取字符串的首字母的功能
        Convertor<String,Character> c1 = (String str)-> {return str.charAt(0);};
        System.out.println(c1.change("hello"));

        Convertor<String,Character> c2 = (String str)-> str.charAt(0);
        System.out.println(c2.change("world"));

        Convertor<String,Character> c3 = (str)-> str.charAt(0);
        System.out.println(c3.change("atguigu"));

        Convertor<String,Character> c4 = str-> str.charAt(0);
        System.out.println(c4.change("chai"));
        //上面四种写法完全一致
    }

}

17.3.4 四大类函数式接口使用演示

1、消费型接口Consumer<T>

已知在JDK1.8中java.lang.Iterable接口中增加了一个默认方法:

  • public default void forEach(Consumer<? super T> action) 该方法功能是遍历Collection集合,并将传递给action参数的操作代码应用在每一个元素上。

因为Collection接口继承了Iterable接口,这就意味着所有Collection系列的接口都包含该方法。

package com.atguigu.four;

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

public class TestConsumer {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("java","c","python","c++","VB","C#");
        list.forEach(s -> System.out.println(s));
    }
}

2、供给型接口Supplier<T>

package com.atguigu.four;

import java.util.function.Supplier;

public class TestSupplier {
    public static void main(String[] args) {
        Supplier<String> supplier = () -> "尚硅谷";
        System.out.println(supplier.get());
    }
}

3、判断型接口Predicate<T>

已知:JDK1.8时,Collecton接口增加了一下方法,其中一个如下:

  • public default boolean removeIf(Predicate<? super E> filter) 用于删除集合中满足filter指定的条件判断的。
package com.atguigu.four;

import java.util.ArrayList;

public class TestPredicate {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("atguigu");
        list.add("ok");
        list.add("yes");

        System.out.println("删除之前:");
        list.forEach(t-> System.out.println(t));

        //删除包含o字母的元素
        list.removeIf(s -> s.contains("o"));

        System.out.println("删除包含o字母的元素之后:");
        list.forEach(t-> System.out.println(t));
    }
}

4、功能型接口Funtion<T,R>

package com.atguigu.four;

import java.util.function.Function;

public class TestFunction {
    public static void main(String[] args) {
        //使用Lambda表达式实现Function<T,R>接口,可以实现将一个字符串首字母转为大写的功能。
        Function<String,String> fun = s -> s.substring(0,1).toUpperCase() + s.substring(1);
        System.out.println(fun.apply("hello"));
    }
}

17.4 方法引用与构造器引用

Lambda表达式是可以简化函数式接口的变量与形参赋值的语法。而方法引用和构造器引用是为了简化Lambda表达式的。

17.4.1 方法引用

当Lambda表达式满足一些特殊的情况时,还可以再简化:

(1)Lambda体只有一句语句,并且是通过调用一个对象的/类现有的方法来完成的

例如:System.out对象,调用println()方法来完成Lambda体

​ Math类,调用random()静态方法来完成Lambda体

(2)并且Lambda表达式的形参正好是给该方法的实参

例如:t->System.out.println(t)

​ () -> Math.random() 都是无参

方法引用的语法格式:

(1)实例对象名::实例方法

(2)类名::静态方法

(3)类名::实例方法

说明:

  • ::称为方法引用操作符(两个:中间不能有空格,而且必须英文状态下半角输入)
  • Lambda表达式的形参列表,全部在Lambda体中使用上了,
    • 类名.静态方法:Lambda表达式的形参列表正好全部作为所调用的静态方法的实参
    • 对象.实例方法:Lambda表达式的形参列表正好全部作为所调用的实例方法的实参
    • 对象.实例方法:Lambda表达式的形参列表的第1个形参就是调用方法的对象,Lambda表达式的形参列表剩下的形参正好作为所调用实例方法的实参。
  • 在整个Lambda体中没有额外的数据。
package com.atguigu.reference;

import org.junit.Test;

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

public class MethodReference {

    @Test
    public void test44(){
        String[] arr = {"Hello","java","chai"};
		Arrays.sort(arr, (s1,s2) -> s1.compareToIgnoreCase(s2));

        //用方法引用简化
        /*
         * Lambda表达式的形参,第一个(例如:s1),正好是调用方法的对象,剩下的形参(例如:s2)正好是给这个方法的实参
         */
        Arrays.sort(arr, String::compareToIgnoreCase);
    }

    @Test
    public void test3(){
        Random random = new Random();
        Supplier<Integer> s1 = () -> random.nextInt();
        Supplier<Integer> s2 = random :: nextInt;//用方法引用简化
        System.out.println(s1.get());
        System.out.println(s2.get());
        //上面两个写法是等价的

        Supplier<Integer> s3 = () -> random.nextInt(100);
        Supplier<Integer> s4 = random :: nextInt;//用方法引用简化  缺100
        System.out.println(s3.get());
        System.out.println(s4.get());
        //上面两个写法是不等价的
    }

    @Test
    public void test2(){
        Supplier<Double> s1 = () -> Math.random();
        Supplier<Double> s2 = Math :: random;//用方法引用简化
        System.out.println(s1.get());
        System.out.println(s2.get());
        //上面两个写法是等价的
    }

    @Test
    public void test1(){
        List<Integer> list = Arrays.asList(1,3,4,8,9);
        //list.forEach(t -> System.out.println(t));

        //用方法引用再简化
        list.forEach(System.out::println);
    }
}

17.4.2 构造器引用

当Lambda表达式是创建一个对象,并且满足Lambda表达式形参,正好是给创建这个对象的构造器的实参列表,就可以使用构造器引用:

  • 类名::new
package com.atguigu.reference;

import java.util.function.Function;

public class ConstructorReference {
    public static void main(String[] args) {
//        Function<String,Person> function = s -> new Person(s);
        Function<String,Person> function = Person::new;

        String[] names = {"张三","李四","王五"};
        Person[] people = new Person[names.length];
        for (int i = 0; i < people.length; i++) {
            people[i] = function.apply(names[i]);
        }

        for (Person person : people) {
            System.out.println(person);
        }
    }
}
package com.atguigu.reference;

public class Person {
    private String name;

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

    public String getName() {
        return name;
    }

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

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

17.4.3 数组构造引用

当Lambda表达式是创建一个数组对象,并且满足Lambda表达式形参,正好是给创建这个数组对象的长度,就可以数组构造引用:

  • 数组类型名::new

示例代码:

package com.atguigu.reference;

import java.util.Arrays;
import java.util.function.Function;

public class ArrayCreateReference {
    public static void main(String[] args) {
        Function<Integer,String[]> function = String[]::new;

        String[] arr = function.apply(5);
        System.out.println(Arrays.toString(arr));
    }
}

17.5 StreamAPI

Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API。

17.5.1 Stream特点

Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,负责存储数据,Stream流讲的是计算,负责处理数据!”

注意:

①Stream 自己不会存储元素。

②Stream 不会改变源对象。每次处理都会返回一个持有结果的新Stream。

③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

17.5.2 Stream 的操作三个步骤

1- 创建 Stream:通过一个数据源(如:集合、数组),获取一个流

2- 中间操作:每次处理都会返回一个持有结果的新Stream,即中间操作的方法返回值仍然是Stream类型的对象,因此中间操作可以是个操作链,可对数据源的数据进行n次处理,但是在终结操作前,并不会真正执行。

3- 终止操作:终止操作的方法返回值类型就不再是Stream了,因此一旦执行终止操作,就结束整个Stream操作了。一旦执行终止操作,就执行中间操作链,最终产生结果并结束Stream。

1666342377826

package com.atguigu.stream;

import org.junit.Test;

import java.util.stream.Stream;

public class StreamDemo {
    @Test
    public void test01(){
        Stream<Integer> stream = Stream.of(1,2,3,4,5,2,4,6);//创建Stream
        stream = stream.filter(num -> num%2==0);//中间处理,筛选偶数
        stream = stream.distinct();//中间处理,去重复
        stream.forEach(System.out::println);//终结操作
    }

    @Test
    public void test02(){
        //连写
        Stream.of(1,2,3,4,5,2,4,6)//创建Stream
            .filter(num -> num%2==0)//中间处理,筛选偶数
            .distinct()//中间处理,去重复
            .forEach(System.out::println);//终结操作
    }
}

17.5.3 创建StreamAPI

1、创建 Stream方式一:通过集合

Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:

  • public default Stream stream() : 返回一个顺序流
  • public default Stream parallelStream() : 返回一个并行流

2、创建 Stream方式二:通过数组

Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:

  • public static Stream stream(T[] array): 返回一个流

重载形式,能够处理对应基本类型的数组:

  • public static IntStream stream(int[] array):返回一个整型数据流
  • public static LongStream stream(long[] array):返回一个长整型数据流
  • public static DoubleStream stream(double[] array):返回一个浮点型数据流

3、创建 Stream方式三:通过Stream的of()

可以调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。

  • public static Stream of(T... values) : 返回一个顺序流

4、创建 Stream方式四:创建无限流

可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流。

  • public static Stream iterate(final T seed, final UnaryOperator f):返回一个无限流
  • public static Stream generate(Supplier s) :返回一个无限流
package com.atguigu.stream;

import org.junit.Test;

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

public class StreamCreate {
    @Test
    public void test06(){
        /*
         * Stream<T> iterate(T seed, UnaryOperator<T> f)
         * UnaryOperator接口,SAM接口,抽象方法:
         *
         * UnaryOperator<T> extends Function<T,T>
         * 		T apply(T t)
         */
        Stream<Integer> stream = Stream.iterate(1, num -> num+=2);
//		stream = stream.limit(10);
        stream.forEach(System.out::println);
    }

    @Test
    public void test05(){
        Stream<Double> stream = Stream.generate(Math::random);
        stream.forEach(System.out::println);
    }

    @Test
    public void test04(){
        Stream<Integer> stream = Stream.of(1,2,3,4,5);
        stream.forEach(System.out::println);
    }

    @Test
    public void test03(){
        String[] arr = {"hello","world"};
        Stream<String> stream = Arrays.stream(arr);
    }

    @Test
    public void test02(){
        int[] arr = {1,2,3,4,5};
        IntStream stream = Arrays.stream(arr);
    }

    @Test
    public void test01(){
        List<Integer> list = Arrays.asList(1,2,3,4,5);

        //JDK1.8中,Collection系列集合增加了方法
        Stream<Integer> stream = list.stream();
    }
}

17.5.4 中间操作API

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。

方 法 描 述
filter(Predicate p) 接收 Lambda , 从流中排除某些元素
distinct() 筛选,通过流所生成元素的equals() 去除重复元素
limit(long maxSize) 截断流,使其元素不超过给定数量
skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
peek(Consumer action) 接收Lambda,对流中的每个数据执行Lambda体操作
sorted() 产生一个新流,其中按自然顺序排序
sorted(Comparator com) 产生一个新流,其中按比较器顺序排序
map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。
mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。
mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。
flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
package com.atguigu.stream;

import org.junit.Test;

import java.util.Arrays;
import java.util.stream.Stream;

public class StreamMiddleOperate {

    @Test
    public void test12(){
        String[] arr = {"hello","world","java"};
        Arrays.stream(arr)
                .flatMap(t -> Stream.of(t.split("|")))//Function<T,R>接口抽象方法 R apply(T t)  现在的R是一个Stream
                .forEach(System.out::println);
    }


    @Test
    public void test11(){
        String[] arr = {"hello","world","java"};

        Arrays.stream(arr)
                .map(t->t.toUpperCase())
                .forEach(System.out::println);
    }

    @Test
    public void test10(){
        Stream.of(1,2,3,4,5)
                .map(t -> t+=1)//Function<T,R>接口抽象方法 R apply(T t)
                .forEach(System.out::println);
    }

    @Test
    public void test09(){
        //希望能够找出前三个最大值,前三名最大的,不重复
        Stream.of(11,2,39,4,54,6,2,22,3,3,4,54,54)
                .distinct()
                .sorted((t1,t2) -> -Integer.compare(t1, t2))//Comparator接口  int compare(T t1, T t2)
                .limit(3)
                .forEach(System.out::println);
    }

    @Test
    public void test08(){
        long count = Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
                .distinct()
                .peek(System.out::println)  //Consumer接口的抽象方法  void accept(T t)
                .count();
        System.out.println("count="+count);
    }


    @Test
    public void test07(){
        Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
                .skip(5)
                .distinct()
                .filter(t -> t%3==0)
                .forEach(System.out::println);
    }

    @Test
    public void test06(){
        Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
                .skip(5)
                .forEach(System.out::println);
    }

    @Test
    public void test05(){
        Stream.of(1,2,2,3,3,4,4,5,2,3,4,5,6,7)
                .distinct()  //(1,2,3,4,5,6,7)
                .filter(t -> t%2!=0) //(1,3,5,7)
                .limit(3)
                .forEach(System.out::println);
    }


    @Test
    public void test04(){
        Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
                .limit(3)
                .forEach(System.out::println);
    }


    @Test
    public void test03(){
        Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
                .distinct()
                .forEach(System.out::println);
    }


    @Test
    public void test02(){
        Stream.of(1,2,3,4,5,6)
                .filter(t -> t%2==0)
                .forEach(System.out::println);
    }

    @Test
    public void test01(){
        //1、创建Stream
        Stream<Integer> stream = Stream.of(1,2,3,4,5,6);

        //2、加工处理
        //过滤:filter(Predicate p)
        //把里面的偶数拿出来
        /*
         * filter(Predicate p)
         * Predicate是函数式接口,抽象方法:boolean test(T t)
         */
        stream = stream.filter(t -> t%2==0);

        //3、终结操作:例如:遍历
        stream.forEach(System.out::println);
    }
}

17.5.5 终结操作API

终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void。流进行了终止操作后,不能再次使用。

方法 描述
boolean allMatch(Predicate p) 检查是否匹配所有元素
boolean anyMatch(Predicate p) 检查是否至少匹配一个元素
boolean noneMatch(Predicate p) 检查是否没有匹配所有元素
Optional findFirst() 返回第一个元素
Optional findAny() 返回当前流中的任意元素
long count() 返回流中元素总数
Optional max(Comparator c) 返回流中最大值
Optional min(Comparator c) 返回流中最小值
void forEach(Consumer c) 迭代
T reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 T
U reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 Optional
R collect(Collector c) 将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法

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

package com.atguigu.stream;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.junit.Test;

public class StreamEndding {

    @Test
    public void test14(){
        List<Integer> list = Stream.of(1,2,4,5,7,8)
                .filter(t -> t%2==0)
                .collect(Collectors.toList());

        System.out.println(list);
    }


    @Test
    public void test13(){
        Optional<Integer> max = Stream.of(1,2,4,5,7,8)
                .reduce((t1,t2) -> t1>t2?t1:t2);//BinaryOperator接口   T apply(T t1, T t2)
        System.out.println(max);
    }

    @Test
    public void test12(){
        Integer reduce = Stream.of(1,2,4,5,7,8)
                .reduce(0, (t1,t2) -> t1+t2);//BinaryOperator接口   T apply(T t1, T t2)
        System.out.println(reduce);
    }

    @Test
    public void test11(){
        Optional<Integer> max = Stream.of(1,2,4,5,7,8)
                .max((t1,t2) -> Integer.compare(t1, t2));
        System.out.println(max);
    }

    @Test
    public void test10(){
        Optional<Integer> opt = Stream.of(1,2,4,5,7,8)
                .filter(t -> t%3==0)
                .findFirst();
        System.out.println(opt);
    }

    @Test
    public void test09(){
        Optional<Integer> opt = Stream.of(1,2,3,4,5,7,9)
                .filter(t -> t%3==0)
                .findFirst();
        System.out.println(opt);
    }

    @Test
    public void test08(){
        Optional<Integer> opt = Stream.of(1,3,5,7,9).findFirst();
        System.out.println(opt);
    }

    @Test
    public void test04(){
        boolean result = Stream.of(1,3,5,7,9)
                .anyMatch(t -> t%2==0);
        System.out.println(result);
    }


    @Test
    public void test03(){
        boolean result = Stream.of(1,3,5,7,9)
                .allMatch(t -> t%2!=0);
        System.out.println(result);
    }

    @Test
    public void test02(){
        long count = Stream.of(1,2,3,4,5)
                .count();
        System.out.println("count = " + count);
    }

    @Test
    public void test01(){
        Stream.of(1,2,3,4,5)
                .forEach(System.out::println);
    }
}

17.6 Optional类

到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。

Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

1、如何创建Optional对象?

如何用Optional来装值对象或null值呢?

(1)static Optional empty() :用来创建一个空的Optional

(2)static Optional of(T value) :用来创建一个非空的Optional

(3)static Optional ofNullable(T value) :用来创建一个可能是空,也可能非空的Optional

2、如何从Optional容器中取出所包装的对象呢?

(1)T get() :要求Optional容器必须非空

T get()与of(T value)使用是安全的

(2)T orElse(T other) :

orElse(T other) 与ofNullable(T value)配合使用,

如果Optional容器中非空,就返回所包装值,如果为空,就用orElse(T other)other指定的默认值(备胎)代替

(3)T orElseGet(Supplier<? extends T> other) :

如果Optional容器中非空,就返回所包装值,如果为空,就用Supplier接口的Lambda表达式提供的值代替

(4) T orElseThrow(Supplier<? extends X> exceptionSupplier)

如果Optional容器中非空,就返回所包装值,如果为空,就抛出你指定的异常类型代替原来的NoSuchElementException

3、其他方法

(1)boolean isPresent() :判断Optional容器中的值是否存在

(2)void ifPresent(Consumer<? super T> consumer) :

判断Optional容器中的值是否存在,如果存在,就对它进行Consumer指定的操作,如果不存在就不做

(3) Optional map(Function<? super T,? extends U> mapper)

判断Optional容器中的值是否存在,如果存在,就对它进行Function接口指定的操作,如果不存在就不做

package com.atguigu.optional;

import java.util.Optional;

import org.junit.Test;

public class TestOptional {
    @Test
    public void test9(){
        String str = "Hello1";
        Optional<String> opt = Optional.ofNullable(str);
        //判断是否是纯字母单词,如果是,转为大写,否则保持不变
        String result = opt.filter(s->s.matches("[a-zA-Z]+"))
                .map(s -> s.toUpperCase()).orElse(str);
        System.out.println(result);
    }


    @Test
    public void test8(){
        String str = null;
        Optional<String> opt = Optional.ofNullable(str);
        String string = opt.orElseThrow(()->new RuntimeException("值不存在"));
        System.out.println(string);
    }


    @Test
    public void test7(){
        String str = null;
        Optional<String> opt = Optional.ofNullable(str);
        String string = opt.orElseGet(String::new);
        System.out.println(string);
    }

    @Test
    public void test6(){
        String str = "hello";
        Optional<String> opt = Optional.ofNullable(str);
        String string = opt.orElse("atguigu");
        System.out.println(string);
    }

    @Test
    public void test5(){
        String str = null;
        Optional<String> opt = Optional.ofNullable(str);
//		System.out.println(opt.get());//java.util.NoSuchElementException: No value present
    }

    @Test
    public void test4(){
        String str = "hello";
        Optional<String> opt = Optional.of(str);

        String string = opt.get();
        System.out.println(string);
    }


    @Test
    public void test3(){
        String str = null;
        Optional<String> opt = Optional.ofNullable(str);
        System.out.println(opt);
    }

    @Test
    public void test2(){
        Optional<String> opt = Optional.empty();
        System.out.println(opt);
    }

    @Test
    public void test1(){
        String str = "hello";
        Optional<String> opt = Optional.of(str);
        System.out.println(opt);
    }
}

巩固题

第1题:系统时间

1、定义一个函数式接口CurrentTimePrinter,其中抽象方法void printCurrentTime(),使用注解@FunctionalInterface

2、通过lambda表达式分别实现CurrentTimePrinter,完成

(1)实现一:打印当前系统时间毫秒值,用System. currentTimeMillis()

(2)实现二:打印当前系统时间,用Date

(3)实现三:打印本地化当前系统时间,用LocalDateTime

package com.atguigu.homework1;

@FunctionalInterface
public interface CurrentTimePrinter{
    void printCurrentTime();
}
package com.atguigu.homework1;

import java.time.LocalDateTime;
import java.util.Date;

public class Homework1 {
    public static void main(String[] args) {
        CurrentTimePrinter c1 = () -> System.out.println(System.currentTimeMillis());
        CurrentTimePrinter c2 = () -> System.out.println(new Date());
        CurrentTimePrinter c3 = () -> System.out.println(LocalDateTime.now());

        c1.printCurrentTime();
        c2.printCurrentTime();
        c3.printCurrentTime();
    }
}

第2题:List集合排序

(1)定义学生类Student:包含属性name(姓名)和score(成绩),属性私有化,提供有参构造,get/set方法,重写toString方法。

(2)在测试类中,将五名学生添加到ArrayList集合

(3)使用Collections.sort(List list, Comparator<? super T> c)方法将学生成绩从小到大进行排序,分别使用以下三种形式实现:

  • 使用匿名内部类
  • 使用Lambda表达式
  • 使用方法引用 //tips:借助Comparator接口中静态方法comparingInt()方法

学生信息与成绩如下:

姓名 数学
谢霆锋 85
章子怡 63
刘亦菲 77
黄晓明 33
岑小村 92
package com.atguigu.homework2;

public class Student {
    private String name;
    private int score;

    public Student() {
    }

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

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

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" + "name='" + name + '\'' + ", score=" + score + '}';
    }
}
package com.atguigu.homework2;

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

public class Homework2 {
    public static void main(String[] args) {
        ArrayList<Student> list = new ArrayList<>();
        list.add(new Student("谢霆锋", 85));
        list.add(new Student("章子怡", 63));
        list.add(new Student("刘亦菲", 77));
        list.add(new Student("黄晓明", 33));
        list.add(new Student("岑小村", 92));

        // 使用匿名内部类将成绩从小到大排序
        Collections.sort(list, new Comparator<Student>() {

            @Override
            public int compare(Student o1, Student o2) {
                return o1.getScore() - o2.getScore();
            }
        });

        // 使用Lambda为英语成绩从小到大排序
        Collections.sort(list, (o1, o2) -> o1.getScore() - o2.getScore());

        // 借助comparingInt()使用方法引用
        Collections.sort(list, Comparator.comparingInt(Student::getScore));

        System.out.println(list);
    }
}

第3题:最佳影片

(1)以下是某不知名机构评出的全球最佳影片及华人最佳影片前十名 :

全球
  1、 《教父》
  2、 《肖申克的救赎》
  3、 《辛德勒的名单》
  4、 《公民凯恩》
  5、 《卡萨布兰卡》
  6、 《教父续集》
  7、 《七武士》
  8、 《星球大战》
  9、 《美国美人》
  10、 《飞跃疯人院》
 

华人

  1、 《霸王别姬》
  2、 《大闹天宫》
  3、 《鬼子来了》
  4、 《大话西游》
  5、 《活着》
  6、 《饮食男女》
  7、 《无间道》
  8、 《天书奇谭》
  9、 《哪吒脑海》
  10、 《春光乍泄》

(1)定义电影Film类,包含name属性,属性私有化,提供有参构造,get/set方法,重写toString方法。

(2)现将两个榜单中的影片名,分别按排名顺序依次存入两个ArrayList集合

(3)通过流的方式

  • 打印全球影片排行榜中的前三甲影片名
  • 打印华人影片排行榜中倒数5名的影片名
  • 将两个排行榜中的前5名挑出来共同存入新的集合
  • 以影片名为name创建Film对象并保存至集合
package com.atguigu.homework3;

public class Film {
    private String name;

    public Film() {
    }

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

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Film [name=" + name + "]";
    }

}

package com.atguigu.homework3;

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

public class Homework3 {
    public static void main(String[] args) {
        // 将原始数据存入集合
        ArrayList<String> global = new ArrayList<>();
        global.add("《教父》");
        global.add("《肖申克的救赎》");
        global.add("《辛德勒的名单》");
        global.add("《公民凯恩》");
        global.add("《卡萨布兰卡》");
        global.add("《教父续集》");
        global.add("《七武士》");
        global.add("《星球大战》");
        global.add("《美国美人》");
        global.add("《飞跃疯人院》");

        ArrayList<String> china = new ArrayList<>();
        china.add("《霸王别姬》");
        china.add("《大闹天宫》");
        china.add("《鬼子来了》");
        china.add("《大话西游》");
        china.add("《活着》");
        china.add("《饮食男女》");
        china.add("《无间道》");
        china.add("《天书奇谭》");
        china.add("《哪吒脑海》");
        china.add("《春光乍泄》");

        // 1)打印全球影片排行榜中的前三甲影片名
        global.stream().limit(3).forEach(System.out::println);
        System.out.println();

        // 2)打印华人影片排行榜中倒数5名的影片名
        china.stream().skip(china.size() - 5).forEach(System.out::println);
        System.out.println();

        // 3)将两个排行榜中的前5名挑出来共同存入新的集合
        Stream.concat(global.stream().limit(5), china.stream().limit(5))
                .collect(Collectors.toList())
                .forEach(System.out::println);
        System.out.println();

        // 4)将所有影片以影片名为name创建Film对象并保存至集合
       Stream.concat(global.stream(), china.stream()).map(Film::new)
                .collect(Collectors.toList())
               .forEach(System.out::println);
    }
}

拔高题

第4题:员工类

(1)声明一个员工类型Employee,包含编号、姓名、薪资、年龄、性别,属性私有化,提供有参构造,get/set方法,重写toString方法。

(2)在测试类中

  • 添加5个员工对象到管理的集合中,遍历集合

  • 筛选出

​ ①编号是偶数的员工

​ ②薪资低于10000的员工

​ ③年龄大于30岁的女员工

​ ④姓张的员工

  • 删除

​ ①年龄大于30岁的女员工

​ ②删除“张三”这个员工

  • 给剩下的员工涨薪10%
package com.atguigu.homework4;

public class Employee {
    private int id;
    private String name;
    private double salary;
    private int age;
    private char gender;
    public Employee(int id, String name, double salary, int age, char gender) {
        super();
        this.id = id;
        this.name = name;
        this.salary = salary;
        this.age = age;
        this.gender = gender;
    }
    public Employee() {
        super();
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public double getSalary() {
        return salary;
    }
    public void setSalary(double salary) {
        this.salary = salary;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public char getGender() {
        return gender;
    }
    public void setGender(char gender) {
        this.gender = gender;
    }
    @Override
    public String toString() {
        return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + ", age=" + age + ", gender=" + gender
                + "]";
    }
}

package com.atguigu.homework4;

import java.util.ArrayList;

public class Homework4 {
    public static void main(String[] args) {
        ArrayList<Employee> es = new ArrayList<>();
        es.add(new Employee(1, "张三", 8000, 23, '男'));
        es.add(new Employee(2, "王小二", 12000, 22, '男'));
        es.add(new Employee(3, "李四", 12000, 24, '女'));
        es.add(new Employee(4, "王五", 11000, 34, '女'));
        es.add(new Employee(5, "赵六", 6000, 34, '女'));

        System.out.println("目前员工数量:" + es.size());
        es.forEach(System.out::println);

        System.out.println("编号是偶数的员工有:");
        es.stream().filter(e->e.getId()%2==0).forEach(System.out::println);

        System.out.println("薪资低于10000的员工有:");
        es.stream().filter(e->e.getSalary()<10000).forEach(System.out::println);

        System.out.println("年龄大于30岁的女员工有:");
        es.stream().filter(e->e.getAge()>30 && e.getGender()=='女').forEach(System.out::println);

        System.out.println("姓张的员工有:");
        es.stream().filter(e->e.getName().startsWith("张")).forEach(System.out::println);

        System.out.println("删除年龄大于30岁的女员工后:");
        es.removeIf(e->e.getAge()>30 && e.getGender()=='女');
        es.forEach(System.out::println);

        System.out.println("删除“张三”这个员工后:");
        es.removeIf(e->e.getName().equals("张三"));
        es.forEach(System.out::println);

        System.out.println("给剩下的员工涨薪10%后:");
        es.forEach(e->e.setSalary(e.getSalary()*(1+0.1)));
        es.forEach(System.out::println);
    }
}

第5题:省份题

我国有34个省级行政区,分别是:

23个省:

河北省、山西省、吉林省、辽宁省、黑龙江省、陕西省、甘肃省、青海省、山东省、福建省、浙江省、台湾省、河南省、湖北省、湖南省、江西省、江苏省、安徽省、广东省、海南省、四川省、贵州省、云南省。

4个直辖市:

北京市、天津市、上海市、重庆市。

5个自治区:

内蒙古自治区、新疆维吾尔自治区、[夏回族自治区、广西壮族自治区、西藏自治区

2个特别行政区:

香港特别行政区、澳门特别行政区

使用流:

1、统计三个字的省份的个数

2、统计名字中包含方位名词的省份(东西南北)的个数

3、打印名字中包含方位名词的普通省份(非自治区直辖市特别行政区)的名字

4、将所有的特殊省份(自治区直辖市特别行政区)提取出来并放到新数组中

package com.atguigu.homework5;

import java.util.Arrays;
import java.util.stream.Stream;

public class Homework5 {
    public static void main(String[] args) {
        String[] provinces = { "河北省", "山西省", "吉林省", "辽宁省", "黑龙江省", "陕西省", "甘肃省", "青海省", "山东省", "福建省", "浙江省", "台湾省",
                "河南省", "湖北省", "湖南省", "江西省", "江苏省", "安徽省", "广东省", "海南省", "四川省", "贵州省", "云南省", "北京市", "天津市", "上海市", "重庆市",
                "内蒙古自治区", "新疆维吾尔自治区", "宁夏回族自治区", "广西壮族自治区", "西藏自治区", "香港特别行政区", "澳门特别行政区" };

        // 1、统计三个字的省份的个数
        long threeCount = Stream.of(provinces).filter(s -> s.length() == 3).count();
        System.out.println("三个字的省份的个数:" + threeCount);

        // 2、统计名字中包含方位名词的省份(东西南北)的个数
        long count = Stream.of(provinces)
                .filter(s -> s.contains("东") || s.contains("西") || s.contains("南") || s.contains("北")).count();
        System.out.println("包含方位名词的省份(东西南北)的个数:" + count);

        // 3、打印名字中包含方位名词的普通省份(非自治区直辖市特别行政区)的名字
        System.out.println("包含方位名词的普通省份有:");
        Stream.of(provinces).filter(s -> s.contains("东") || s.contains("西") || s.contains("南") || s.contains("北"))
                .filter(s -> s.contains("省")).forEach(System.out::println);

        // 4、将所有的特殊省份(自治区直辖市特别行政区)提取出来并放到新数组中
        String[] pros = Stream.of(provinces).filter(s -> !s.contains("省")).toArray(String[]::new);
        System.out.println("新数组:" + Arrays.toString(pros));
    }
}

Java8新特性

第1题:自定义函数接口

(1)声明函数式接口Call,并用@FunctionalInterface注解标记。Call接口包含抽象方法void call()。

(2)在测试类中使用Lambda表达式为Call接口的变量赋值,并调用call方法。分别实现如下功能:

  • 输出我爱尚硅谷
  • 输出你妈喊你回家吃饭
  • 输出柴老师喊你写代码
package com.atguigu.exer1;

@FunctionalInterface
public interface Call {
    void call();
}
package com.atguigu.exer1;

public class Exercise1 {
    public static void main(String[] args) {
        Call c1 = () -> System.out.println("我爱你尚硅谷");
        c1.call();

        Call c2 = () -> System.out.println("你妈喊你回家吃饭");
        c2.call();

        Call c3 = () -> System.out.println("柴老师喊你写代码");
        c3.call();
    }
}

第2题:Consumer接口和BiConsumer接口

(1)知识提要:

已知在JDK1.8中java.lang.Iterable接口中增加了一个默认方法:

  • public default void forEach(Consumer<? super T> action) 该方法功能是遍历Collection集合,并将传递给action参数的操作代码应用在每一个元素上。

因为Collection接口继承了Iterable接口,这就意味着所有Collection系列的接口都包含该方法。

已知:在JDK1.8中Map集合接口中增加了一个默认方法:

  • public default void forEach(BiConsumer<? super K,? super V> action):该方法功能是遍历Map集合,并将传递给action参数的操作代码应用在每一对(key,value)映射关系上。

(2)案例需求:

  • 创建一个ArrayList,并添加26个小写字母到list中,并使用forEach遍历输出
  • 创建一个HashMap,并添加如下编程语言排名和语言名称到map中,并使用forEach遍历输出
排名 语言
1 Java
2 C
3 Python
4 C++
5 C#
package com.atguigu.exer2;

import org.junit.Test;

import java.util.ArrayList;
import java.util.HashMap;

public class Exercise2 {
    @Test
    public void test01(){
        ArrayList<Character> list = new ArrayList<>();
        for (int i = 0; i <26 ; i++) {
            list.add((char)('a' + i));
        }
        list.forEach(c -> System.out.print(c+" "));
        System.out.println();
    }

    @Test
    public void test02(){
        HashMap<Integer,String> map = new HashMap<>();
        map.put(1,"Java");
        map.put(2,"C");
        map.put(3,"Python");
        map.put(4,"C++");
        map.put(5,"C#");

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

第3题:Supplier接口

使用Lambda表达式给Supplier接口赋值,实现产生1个100以内的整数功能。

package com.atguigu.exer3;

import java.util.Random;
import java.util.function.Supplier;

public class Exercise3 {
    public static void main(String[] args) {
        Random random = new Random();
        Supplier<Integer> s = () -> random.nextInt(100);
        System.out.println(s.get());
    }
}

第4题:BiFunction接口

(1)知识提要

已知:在JDK1.8时Map接口如下方法:

public default void replaceAll(BiFunction<? super K,? super V,? extends V> function) 该方法功能是遍历Map集合,并将传递给function参数的操作代码应用在每一对(key,value)映射关系上,返回修改后的value。

public default void forEach(BiConsumer<? super K,? super V> action)该方法功能是遍历Map集合,并将传递给action参数的操作代码应用在每一对(key,value)映射关系上。

(2)案例需求

  • 声明一个Employee员工类型,包含属性编号、姓名、薪资,属性私有化,提供有参构造,get/set,重写toString。

  • 添加n个员工对象到一个HashMap<Integer,Employee>集合中,其中员工编号为key,员工对象为value。

  • 调用Map的forEach遍历集合

  • 调用Map的replaceAll方法,将其中薪资低于10000元的,薪资设置为10000。

  • 再次调用Map的forEach遍历集合查看结果

Employee类:

package com.atguigu.exer4;

class Employee{
    private int id;
    private String name;
    private double salary;
    public Employee(int id, String name, double salary) {
        super();
        this.id = id;
        this.name = name;
        this.salary = salary;
    }
    public Employee() {
        super();
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public double getSalary() {
        return salary;
    }
    public void setSalary(double salary) {
        this.salary = salary;
    }
    @Override
    public String toString() {
        return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + "]";
    }

}

测试类:

package com.atguigu.exer4;

import java.util.HashMap;

public class Exercise4 {
    public static void main(String[] args) {
        HashMap<Integer,Employee> map = new HashMap<>();
        Employee e1 = new Employee(1, "张三", 8000);
        Employee e2 = new Employee(2, "李四", 9000);
        Employee e3 = new Employee(3, "王五", 10000);
        Employee e4 = new Employee(4, "赵六", 11000);
        Employee e5 = new Employee(5, "钱七", 12000);

        map.put(e1.getId(), e1);
        map.put(e2.getId(), e2);
        map.put(e3.getId(), e3);
        map.put(e4.getId(), e4);
        map.put(e5.getId(), e5);

        map.forEach((k,v) -> System.out.println(k+"="+v));
        System.out.println();

        map.replaceAll((k,v)->{
            if(v.getSalary()<10000){
                v.setSalary(10000);
            }
            return v;
        });
        map.forEach((k,v) -> System.out.println(k+"="+v));
    }
}

第5题:Predicate接口

(1)声明一个Employee员工类型,包含编号、姓名、性别,年龄,薪资。属性私有化,提供有参构造,get/set,重写toString。

(2)声明一个EmployeeSerice员工管理类

  • 包含一个private 的属性ArrayList all。
  • 包含public void add(Employee emp)方法,可以添加员工到all集合中
  • 包含public ArrayList get(Predicate p),即将满足p指定的条件的员工,添加到一个新的ArrayList 集合中返回。

(3)在测试类中创建EmployeeSerice员工管理类的对象,添加一些员工对象,之后调用get方法,分别获取:

  • 所有员工对象
  • 所有年龄超过35的员工
  • 所有薪资高于15000的女员工
  • 所有编号是偶数的员工
  • 名字是“张三”的员工
  • 年龄超过25,薪资低于10000的男员工

示例代码:

Employee类:

package com.atguigu.exer5;

public class Employee{
    private int id;
    private String name;
    private char gender;
    private int age;
    private double salary;

    public Employee(int id, String name, char gender, int age, double salary) {
        super();
        this.id = id;
        this.name = name;
        this.gender = gender;
        this.age = age;
        this.salary = salary;
    }
    public Employee() {
        super();
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public double getSalary() {
        return salary;
    }
    public void setSalary(double salary) {
        this.salary = salary;
    }

    public char getGender() {
        return gender;
    }

    public void setGender(char gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "Employee [id=" + id + ", name=" + name + ", gender=" + gender + ", age=" + age + ", salary=" + salary
                + "]";
    }
}

员工管理类:

package com.atguigu.exer5;

import java.util.ArrayList;
import java.util.function.Predicate;

public class EmployeeService{
    private ArrayList<Employee> all = new ArrayList<>();

    public void add(Employee emp){
        all.add(emp);
    }

    public ArrayList<Employee> get(Predicate<Employee> p){
        ArrayList<Employee> result = new ArrayList<Employee>();
        for (Employee emp : all) {
            if(p.test(emp)){
                result.add(emp);
            }
        }
        return result;
    }
}

测试类:

package com.atguigu.exer5;

public class Exercise5 {
    public static void main(String[] args) {
        EmployeeService es = new EmployeeService();

        es.add(new Employee(1, "张三", '男', 33, 8000));
        es.add(new Employee(2, "翠花", '女', 23, 18000));
        es.add(new Employee(3, "无能", '男', 46, 8000));
        es.add(new Employee(4, "李四", '女', 23, 9000));
        es.add(new Employee(5, "老王", '男', 23, 15000));
        es.add(new Employee(6, "大嘴", '男', 23, 11000));

        System.out.println("所有员工对象:");
        es.get(e -> true).forEach(e -> System.out.println(e));
        System.out.println();

        System.out.println("所有年龄超过35的员工:");
        es.get(e -> e.getAge() > 35).forEach(e -> System.out.println(e));
        System.out.println();

        System.out.println("所有薪资高于15000的女员工:");
        es.get(e -> e.getSalary() > 15000 && e.getGender() == '女').forEach(e -> System.out.println(e));
        System.out.println();

        System.out.println("所有编号是偶数的员工:");
        es.get(e -> e.getId() % 2 == 0).forEach(e -> System.out.println(e));
        System.out.println();

        System.out.println("名字是“张三”的员工:");
        es.get(e -> "张三".equals(e.getName())).forEach(e -> System.out.println(e));
        System.out.println();

        System.out.println("年龄超过25,薪资低于10000的男员工:");
        es.get(e -> e.getAge() > 25 && e.getSalary() < 10000 && e.getGender() == '男').forEach(e -> System.out.println(e));
    }
}

第6题:员工类排序

(1)声明员工类,包含姓名、年龄、薪资,属性私有化,提供有参构造,get/set,重写toString。

(2)在测试类中,添加几个员工到一个ArrayList集合中

(3)调用集合的forEach方法,遍历集合元素,使用方法引用精简代码

(4)调用Collections集合工具类的sort(List list, Comparator<? super T> c)方法,按照薪资排序,使用方法引用精简代码

(5)调用Collections集合工具类的sort(List list, Comparator<? super T> c)方法,按照年龄排序,使用方法引用精简代码

package com.atguigu.exer6;

public class Employee {
    private String name;
    private int age;
    private double salary;

    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

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

package com.atguigu.exer6;

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

public class Exercise6 {
    public static void main(String[] args) {
        ArrayList<Employee> list = new ArrayList<>();
        list.add(new Employee("张三",23,10000.0));
        list.add(new Employee("李四",24,8000.0));
        list.add(new Employee("王五",25,11000.0));

        System.out.println("员工如下:");
        list.forEach(System.out::println);

        System.out.println("按照薪资排序如下:");
        Collections.sort(list, Comparator.comparingDouble(Employee::getSalary));
        list.forEach(System.out::println);

        System.out.println("按照年龄排序如下:");
        Collections.sort(list, Comparator.comparingInt(Employee::getAge));
        list.forEach(System.out::println);
    }
}

第7题:创建2的n次的数组

已知有如下方法,可以实现创建一个长度为2的n次的数组

	//createArray()的作用是,创建一个长度为2的n次方的数组
	public static <R> R[] createArray(Function<Integer,R[]> fun,int length){
		int n = length - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        length = n < 0 ? 1 : n + 1;
		return fun.apply(length);
	}

使用上面的方法,实现创建一个长度为10的数组,查看返回的数组长度。

package com.atguigu.exer7;

import java.util.function.Function;

public class Exercise7 {
    //createArray()的作用是,创建一个长度为2的n次方的数组
    public static <R> R[] createArray(Function<Integer,R[]> fun, int length){
        int n = length - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        length = n < 0 ? 1 : n + 1;
        return fun.apply(length);
    }

    public static void main(String[] args) {
        String[] array = createArray(String[]::new, 10);
        System.out.println(array.length);//16
    }
}

第8题:Stream

现在有两个 ArrayList 集合存储队伍当中的多个成员姓名:

        //第一支队伍
        ArrayList<String> one = new ArrayList<>();
        one.add("迪丽热巴");
        one.add("宋远桥");
        one.add("苏星河");
        one.add("石破天");
        one.add("石中玉");
        one.add("老子");
        one.add("庄子");
        one.add("洪七公");

        //第二支队伍
        ArrayList<String> two = new ArrayList<>();
        two.add("古力娜扎");
        two.add("张无忌");
        two.add("赵丽颖");
        two.add("张三丰");
        two.add("尼古拉斯赵四");
        two.add("张天爱");
        two.add("张二狗");

还有Person类代码如下:

package com.atguigu.exer8;

public class Person {
    private String name;

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

    public String getName() {
        return name;
    }

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

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

针对上述集合数据进行如下操作:

  • 第一个队伍只要名字为小于等于3个字的成员姓名;第一个队伍筛选之后只要前3个人;存储到一个新集合中。
  • 第二个队伍只要姓张的成员姓名;第二个队伍筛选之后去掉前2个人;存储到一个新集合中。
  • 将两个队伍合并为一个队伍;存储到一个新集合中,打印合并队伍后成员姓名。
  • 将两个队伍合并为一个队伍,并根据姓名创建 Person 对象;存储到一个新集合中,打印整个队伍的Person对象信息。

参考答案:

package com.atguigu.exer8;

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

public class Exercise8 {
    public static void main(String[] args) {
        //第一支队伍
        ArrayList<String> one = new ArrayList<>();
        one.add("迪丽热巴");
        one.add("宋远桥");
        one.add("苏星河");
        one.add("石破天");
        one.add("石中玉");
        one.add("老子");
        one.add("庄子");
        one.add("洪七公");

        //第二支队伍
        ArrayList<String> two = new ArrayList<>();
        two.add("古力娜扎");
        two.add("张无忌");
        two.add("赵丽颖");
        two.add("张三丰");
        two.add("尼古拉斯赵四");
        two.add("张天爱");
        two.add("张二狗");

        // 第一个队伍只要名字小于等于3个字的成员姓名;
        // 第一个队伍筛选之后只要前3个人;
        System.out.println("第一个队伍处理后结果:");
        one.stream().filter(s -> s.length() <= 3).limit(3).collect(Collectors.toList()).forEach(System.out::println);

        // 第二个队伍只要姓张的成员姓名;
        // 第二个队伍筛选之后去掉前2个人;
        System.out.println("第二个队伍处理后结果:");
       two.stream().filter(s -> s.startsWith("张")).skip(2).collect(Collectors.toList()).forEach(System.out::println);

        // 将两个队伍合并为一个队伍;存储到一个新集合中,打印合并队伍后成员姓名。
        System.out.println("两个队伍合并后结果:");
        Stream.concat(one.stream(), two.stream()).collect(Collectors.toList()).forEach(System.out::println);

//        将两个队伍合并为一个队伍;
        // 根据姓名创建Person对象;
        // 打印整个队伍的Person对象信息。
        System.out.println("两个队伍合并后,创建Person对象结果:");
        Stream.concat(one.stream(), two.stream()).map(Person::new).collect(Collectors.toList()).forEach(System.out::println);
    }
}

第9题:女朋友

(1)声明一个Girl类型,包含姓名(String)属性

(2)声明一个Boy类型,包含姓名(String),女朋友(Girl)属性

(3)在测试类中声明方法public static Optional getGirlFriend(Boy boy)

(4)在测试类的main中创建一个Boy对象,并调用getGirlFriend方法获取他的女朋友。

如果他有女朋友,显示他女朋友名称;

如果他没有女朋友,他的女朋友默认为“嫦娥”,即只能欣赏“嫦娥”了

package com.atguigu.exer9;

public class Girl {
    private String name;

    public Girl(String name) {
        super();
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Girl [name=" + name + "]";
    }

}


package com.atguigu.exer9;

public class Boy {
    private String name;
    private Girl girlFriend;

    public Boy(String name, Girl girlFriend) {
        super();
        this.name = name;
        this.girlFriend = girlFriend;
    }

    public String getName() {
        return name;
    }

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

    public Girl getGirlFriend() {
        return girlFriend;
    }

    public void setGirlFriend(Girl girlFriend) {
        this.girlFriend = girlFriend;
    }

    @Override
    public String toString() {
        return "Boy [name=" + name + ", girlFriend=" + girlFriend + "]";
    }

}


测试类

package com.atguigu.exer9;

import com.sun.istack.internal.NotNull;

import java.util.Optional;

public class Exercise9 {
    public static Optional<Girl> getGirlFriend(Boy boy){
        if(boy == null){
            return Optional.empty();
        }
        return Optional.ofNullable(boy.getGirlFriend());
    }

    public static void main(String[] args) {
        //		Boy boy = new Boy("张三",null);
        Boy boy = new Boy("张三",new Girl("翠翠"));
        Girl girl = getGirlFriend(boy).orElse(new Girl("嫦娥"));
        Optional.of(girl).ifPresent(System.out::println);
    }
}

本文作者:傻孩子不吃辣

本文链接:https://www.cnblogs.com/Asillychild/p/16814039.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   傻孩子不吃辣  阅读(35)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 The Flood Joshua Hyslop
The Flood - Joshua Hyslop
00:00 / 00:00
An audio error has occurred.

作词 : Joshua Hyslop

作曲 : Joshua Hyslop

Here comes the flood again

Watch it fall from the sky

Feel it soak through my flesh and my blood

Feel it burn in my eyes

When I say how much more can I take?

I know the water’s rising up, watch the waves crest and break

And though I’ve made nothing but a sound

I fear that I may drown

I fear that I may drown

.

Here comes the wind again

Cold that cuts to the bone

Pack my bags and I’ll head out the door

Here I am on my own

When I say how much more can I stand?

I know my walls are falling down; I left the rocks and chose the sand

And though I’ve no one left to blame

Still, I cursed your name

I cursed your name

.

So now the end

What I’ve been running from

Though I’ve tried I cannot lift my head

Oh, what have I become?

When I say how much more can I take?

I know my time is running short, I am broken and I’ll break

And though I’ve worn myself so thin

I’m coming home again

And though I do not know my heart

Well, I know myself down to my bones, but if my bones should come apart

Then I’ll have nothing left to give

But if you take me in,

I’m coming home again

If you take me in

I’m coming home again