Java8的新特性

在Java8中,Lambda表达式就是一个函数式接口的实例,所以以前使用匿名实现类表示的现在都可以用Lambda表达式来写

1、Lambda表达式

举例:(o1,o2) -> Integer.compare(o1, o2)

格式:

  • 操作符的左边是接口中抽象方法的形参列表
  • 右边是 Lambda体,是重写的抽象方法的方法体

总结:

  • Lambda表达式的本质就是作为接口的实例(该接口只有一个需要被重写的抽象方法),此接口被称为函数式接口
  • 操作符的左边:
    • 形参列表的数据类型可省略
    • 如果形参列表只有一个参数,其一对 () 也可以省略
  • 操作符的右边:
    • Lambda 体由一对 {} 包裹
    • 若Lambda 体只有一条执行语句,则可以省略一对大括号 {} 和 return 关键字

体会Lambda表达式

    @Test
    public void test1(){
        //体会Lambda表达式
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("小草莓");
            }
        };
        runnable.run();
        
        System.out.println("*********************");
        
        Runnable runnable1 = () -> System.out.println("大西瓜");
        runnable1.run();
    }

执行结果如下
在这里插入图片描述
体会Lambda表达式

    @Test
    public void test2(){
        Comparator comparator = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1 , o2);
            }
        };
        System.out.println(comparator.compare(1, 21));
        
        System.out.println("*********************");
        
        //Lambda表达式的写法
        Comparator<Integer> comparator1 = (o1,o2) -> Integer.compare(o1, o2);
        System.out.println(comparator1.compare(21, 1));
        
        //方法引用的写法
        Comparator<Integer> comparator2 = Integer::compare;
        System.out.println(comparator2.compare(2, 22));
    }

执行结果如下
在这里插入图片描述

1.1、无参,无返回值

语法格式一:

无参,无返回值

    //格式一:无参,无返回值
    @Test
    public void teat1(){
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("小菜鸟");
                System.out.println("小草莓");
            }
        };
        runnable.run();
        
        System.out.println("**************");
        
        Runnable runnable1 = () -> {
            System.out.println("小菜鸟");
            System.out.println("小草莓");
        };
        runnable1.run();
    }

执行结果如下
在这里插入图片描述

1.2、一个参数,无返回值

语法格式二:

有一个参数,无返回值

    @Test
    public void test2(){
        //Consumer类是工具类,其中有抽象方法void accept(T t)
        Consumer<String> consumer = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
                System.out.println("小草莓");
            }
        };
        consumer.accept("大西瓜");
        
        System.out.println("**************");
        
        Consumer<String> consumer1 = (String c) -> {
            System.out.println(c);
            System.out.println("小草莓");
        };
        consumer1.accept("大灰狼");
    }

执行结果如下
在这里插入图片描述

1.3、类型推断

语法格式二:

数据类型可以省略,因为可由编译器推断得出,称为 “ 类型推断 ”

这种 类型推断 其实之前就有碰到过,比如创建ArrayList集合的时候,推断等式右边泛型的数据类型。比如创建数组时候,可以省略等式右边的数据类型

ArrayList<String> arrayList = new ArrayList<>();
int[] a = {1,2,3};  //int[] a = new int[]{3,2,1};
    @Test
    public void tedt3(){
//        Consumer<String> consumer1 = System.out::println;  可以写成方法引用形式
        Consumer<String> consumer1 = (String c) -> {
            System.out.println(c);
        };
        consumer1.accept("大灰狼");
        
        System.out.println("**************");
        
        Consumer<String> consumer2 = (c) -> {   //省略了数据类型
            System.out.println(c);
        };
        consumer2.accept("大灰狼");
    }

1.4、一个参数可省略括号

        Consumer<String> consumer2 = c -> {
            System.out.println(c);
        };
        consumer2.accept("大灰狼");

1.5、多个参数,有返回值

    @Test
    public void teat4(){
        Comparator<Integer> comparator = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1, o2);
            }
        };
        System.out.println(comparator.compare(12, 99));
        
        System.out.println("**************");
        
        Comparator<Integer> comparator1 = (o1, o2) -> {
            return Integer.compare(o1, o2);
        };
        System.out.println(comparator1.compare(12, 99));        
    }

1.6、一条执行 语句,省略return与大括号

    @Test
    public void test4(){
        Comparator<Integer> comparator1 = (o1, o2) -> {
            return o1.compareTo(o2);
        };
        System.out.println(comparator1.compare(12, 99));
        
        System.out.println("**************");
        
        Comparator<Integer> comparator2 = (o1, o2) -> o1.compareTo(o2);
        System.out.println(comparator2.compare(12, 99));
    }
    @Test
    public void test5(){
        Consumer<String> consumer = s -> {
            System.out.println(s);
        };
        consumer.accept("小草莓");
        
        System.out.println("**************");
        
        Consumer<String> consumer1 = s ->System.out.println(s);
        consumer1.accept("小草莓");
    }

2、函数式(Functional)接口

如果一个接口中,只声明了一个抽象方法,则此接口就被称为函数式接口。比如Runnable接口,只有一个抽象方法run()。函数式接口是 JDK 1.8 之后出现的

自定义函数式接口,可以用到注解@FunctionalInterface,该注解只是做到检验的作用,比如@Override,检验作用,如果格式正确前提下,不加该注解也同样是重写方法,但是这里的注解可以给你检验提示

@FunctionalInterface
public Interface MyInterface{
	void method();
}

2.1、函数式接口介绍

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 test(T t)

Consumer<T> :消费型接口,void accept(T t)

    @Test
    public void test(){
        method1(11, new Consumer<Double>() {
            @Override
            public void accept(Double aDouble) {
                System.out.println(aDouble);
            }
        });
        System.out.println("*************");
        method1(55, s -> System.out.println(s));
    }
    public void method1(double money, Consumer<Double> consumer){
        consumer.accept(money);
    }

执行结果如下
在这里插入图片描述

Supplier<T>:供给型接口,T get()

Function<T,R> :函数型接口,R apply(T t)

Predicate<T>:断定型接口boolean test(T t)

    @Test
    public void test2(){
        List<String> strings = Arrays.asList("北京", "天津", "普京", "今天", "东京", "西经");
        filterString(strings, new Predicate<String>() {
            @Override
            public boolean test(String string) {
                return string.contains("京");
            }
        });
        System.out.println("*************");
        filterString(Arrays.asList("西瓜", "西天", "冬天", "今天", "冬瓜", "西经"),s -> s.contains("西"));
    }
    //将list1中满足条件的元素添加进list2
    public void filterString(List<String> list1, Predicate<String> predicate){
        ArrayList<String> list2 = new ArrayList<>();
        for (String s : list1) {
            if(predicate.test(s))
                list2.add(s);
        }
        System.out.println(list2);
    }

执行结果如下
在这里插入图片描述

2.2、其他接口

在这里插入图片描述

3、方法引用method reference

举例:Integer :: compare;

方法引用的前提,是引用的方法和被引用的方法,需要达到功能一致,就像使用比较方法的时候,也总是在方法体中直接调用 String 或 Integer 中已经被重写的比较方法

方法引用,本质上就是 Lambda 表达式,而 Lambda 表达式是函数式接口的实例,所以方法引用也是函数式接口的实例

使用情景:

当要传递给 Lambda 体的操作,已经有实现的方法了,可以使用方法引用

要求:

要求接口中的抽象方法的参数列表和返回值类型,必须与被引用的方法的参数列表和返回值类型保持一致!

以上要求是针对于前两种情况,对于 类::实例方法,参数列表和返回值类型不一定一样

3.1、对象 :: 实例方法

Consumer 中的 void accept(T t)
PrintStream 中的 void println(T t)

    @Test
    public void test3(){
    	//Consumer<String> consumer = new Consumer<String>() {
        //     @Override
        //    public void accept(String s) {
        //        System.out.println(s);
        //    }
        //};
        
        //情景一:对象 :: 实例方法
        //Consumer 中的 void accept(T t)
        //PrintStream 中的 void println(T t)
        
        Consumer<String> consumer = s -> System.out.println(s);
        consumer.accept("小草莓");

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

        //System.out返回一个打印流PrintStream的对象,通过这个对象调用println实例方法
        PrintStream printStream = System.out;
        Consumer<String> consumer1 = printStream::println;
        //Consumer<String> consumer1 = System.out::println;
        consumer1.accept("大西瓜");
    }

执行结果如下
在这里插入图片描述

Supplier 中的 T get()
Object 中的 T getClass()

    @Test
    public void test3(){
        //情景一:对象 :: 实例方法
        //Supplier 中的 T get()
        //Object 中的 T getClass()
        Supplier supplier = () -> "小草莓".getClass();
        System.out.println(supplier.get());

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

        Supplier supplier1 = "小草莓" :: getClass;
        System.out.println(supplier1.get());
    }

执行结果如下
在这里插入图片描述

3.2、类 :: 静态方法

Comparator 中的 int compare(T t1, T t2)
Integer 中的 int compare(T t1, T t2)

    @Test
    public void test4(){
        //情况二:类 :: 静态方法
        //Comparator 中的 int compare(T t1, T t2)
        //Integer 中的 int compare(T t1, T t2)
        Comparator<Integer> comparator = (o1, o2) -> Integer.compare(o1, o2);
        System.out.println(comparator.compare(11,21));

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

        Comparator<Integer> comparator1 = Integer::compare;
        System.out.println(comparator1.compare(99,11));
    }

执行结果如下
在这里插入图片描述

Function 中的 R apply(T t)
Math 中的 Long round(Double d) 四舍五入的方法

    @Test
    public void test4(){
        //情况二:类 :: 静态方法
        //Function 中的 R apply(T t)
        //Math 中的 Long round(Double d)
//        Function function = new Function() {
//            @Override
//            public Long apply(Double o) {
//                return Math.round(o);
//            }
//        };
        Function<Double,Long> function = o -> Math.round(o);
        System.out.println(function.apply(11.8));

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

        Function<Double,Long> function1 = Math::round;
        System.out.println(function1.apply(99.1));
    }

执行结果如下
在这里插入图片描述

3.3、类 :: 实例方法

Comparator 中的 int compare(T t1, T t2)
String中的 int t1.compareTo(t2)

注意,以下中第一个参数是作为方法的调用者

    @Test
    public void test4(){
        //情景三:类 :: 实例方法
        //Comparator 中的 int compare(T t1, T t2)
        //String 中的 int t1.compareTo(t2)
        Comparator<String> comparator = (s1, s2) -> s1.compareTo(s2);
        System.out.println(comparator.compare("AA","BB"));

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

        Comparator<String> comparator1 = String::compareTo;
        System.out.println(comparator1.compare("AA","BB"));
    }

执行结果如下
在这里插入图片描述

BiPredicate 中的 boolean test(T t, U u)
String 中的 boolean t1.equals(t2)

    @Test
    public void test5(){
        //BiPredicate 中的 boolean test(T t, U u)
        //String 中的 boolean t1.equals(t2)
//        BiPredicate<String, String> biPredicate = new BiPredicate() {
//            @Override
//            public boolean test(String s1, String s2) {
//                return s1.equals(s2);
//            }
//        };
        BiPredicate<String, String> biPredicate = (s1,s2) -> s1.equals(s2);
        System.out.println(biPredicate.test("sa", "sa"));

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

        BiPredicate<String, String> biPredicate1 = String::equals;
        System.out.println(biPredicate1.test("aa", "ba"));
    }

执行结果如下
在这里插入图片描述

Function 中的 R apply(T t)
Object 中的 T getClass()

    @Test
    public void test6(){
        //Function 中的 R apply(T t)
        //Object 中的 T getClass()
//        Function function = new Function(){
//            @Override
//            public Object apply(Object o) {
//                return o.getClass();
//            }
//        };
        Function function = o -> o.getClass();
        System.out.println(function.apply("aa"));

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

        Function function1 = Object::getClass;
        System.out.println(function1.apply("aa"));
    }

执行结果如下
在这里插入图片描述

4、构造器引用

构造器引用,和方法引用类似

函数式接口的抽象方法的形参列表和构造器的形参列表一致,抽象方法的返回值类型即为构造器所属类的类型

构造器会自动根据抽象方法的形参列表来匹配对应的参数构造器

提供一个类

public class Person{
	String name;
	int age;
	public Person(){
		System.out.println("空参构造器");
	}
	public Person(String name){
		this.name = name;
		System.out.println("一个参构造器");
	}
	public Person(String name,int age){
		this.name = name;
		this.age = age;
		System.out.println("两个参构造器");
	}
}

构造器引用举例:

Supplier 中的 T get()

public class Test{
	@Test
	public void test1(){
		Supplier<Person> sup = new Supplier<Person>(){
			@Override
			public Person get(){
				return new Person();
			}
		}
		System.out.println(sup.get());

		System.out.println("**********************");
	
		Supplier<Person> sup1 = () -> new Person();
		System.out.println(sup1.get());

		System.out.println("**********************");
	
		Supplier<Person> sup2 = Person :: new;
		System.out.println(sup2.get());
	}
}

Function<T,R> 中的 R apply(T t)

public class Test{
	@Test
	public void test1(){
		Function<String,Person> fuc = new Function<String,Person>(){
			@Override
			public Person apply(String s){
				return new Person(s);
			}
		}
		System.out.println(fuc.apply("Tom"));

		System.out.println("**********************");
	
		Function<String,Person> fuc1 = s -> new Person(s);
		System.out.println(fuc1.apply("Tom"));

		System.out.println("**********************");
	
		Function<String,Person> fuc2 = Person :: new;
		System.out.println(fuc2.apply("Tom"));
	}
}

BiFunction<T,U,R> 中的 R apply(T t, U u)

public class Test{
	@Test
	public void test1(){
		BiFunction<String,Integer,Person> bif = new Function<String,Integer,Person>(){
			@Override
			public Person apply(String s, int i){
				return new Person(s,i);
			}
		}
		System.out.println(bif.apply("Tom",1));

		System.out.println("**********************");
	
		BiFunction<String,Integer,Person> bif1 = s -> new Person(s,i);
		System.out.println(bif1.apply("Tom",1));

		System.out.println("**********************");
	
		BiFunction<String,Integer,Person> bif2 = Person :: new;
		System.out.println(bif2.apply("Tom",1));
	}
}

4.1、数组引用

有了构造器引用的例子,就可以写出数组的引用,因为创建数组也是在创建对象

可以把数组看做是一个特殊的类,则写法与构造器引用一致

Function<T,R> 中的 R apply(T t)

public class Test{
	@Test
	public void test1(){
		Function<Integer,String[]> fuc = new Function<Integer,String[]>(){
			@Override
			public String[]apply(int length){
				return new String[length];
			}
		}
		String[] arr = fuc.apply(5);
		System.out.println(Arrays.toString(arr));

		System.out.println("**********************");
	
		Function<Integer,String[]> fuc1 = length -> new String[length];
		String[] arr1 = fuc1.apply(3);
		System.out.println(Arrays.toString(arr1));

		System.out.println("**********************");
	
		Function<Integer,String[]> fuc2 = String[] :: new;
		String[] arr2 = fuc2.apply(1);
		System.out.println(Arrays.toString(arr2));
	}
}

5、Stream API

5.1、Stream API说明

Java 8 的两大改变: Lambda表达式、Stream API

使用Stream API对集合数据进行操作,就类似于使用 SQL 执行的数据库查询

为什么要使用Stream API

在实际开发中,数据源大多数来自 Mysql、Oracle等,但有的数据源来自 MongDB、Radis等,这些NoSQL 的数据就需要Java层面去处理

Stream 和 Collection 集合的区别?
Collection 是一种静态的内存数据结构,而 Stream 是有关计算的
Collection 主要是面向内存的,存储在内存中;而 Stream 主要是面向 CPU 的,通过 CPU 实现计算

Stream 到底是什么?
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列
“ 集合讲的是数据,Stream 讲的是计算 ”

注意:

  1. Stream自己不会存储元素
  2. Stream不会改变源对象,相反,他们会返回一个持有结果的新 Stream(相当于是有可变性)
  3. Stream操作是延迟执行的,这意味着他们会等到需要结果的时候才执行(即有结束语句才执行,没有终止语句就不执行)
  4. 一个中间操作链,对数据源的数据进行处理
  5. 一旦执行终止操作,就执行中间操作链,并产生结果。注意之后不会再被使用

Stream操作的三个步骤:

  1. 创建 Stream
    一个数据源(集合、数组等),获取一个流
  2. 中间操作(操作链)
    一个中间操作链,对数据源的数据进行处理
  3. 终止操作(终端操作)
    一旦执行终止操作,就执行中间操作链,并产生结果。注意之后不会再被使用(即若想再次执行,就要从头创建)

5.2、Stream的实例化

5.2.1、通过集合

创建 Stream 方式一:通过集合

注意:
是通过集合来调用集合 Collection 中定义的实例方法,需要一个集合对象来调用方法

default Stream<T> stream():返回一个顺序流
default Stream<T> parallelStream():返回一个并行流

void forEach(Consumer<? super T> action):Stream 接口中的一个抽象方法

<? super T> 指 T 及其父类

    @Test
    public void test1() {
        ArrayList<Employee> employees = new ArrayList<>();
        employees.add(new Employee(1,"草莓",12));
        employees.add(new Employee(2,"西瓜",88));
        employees.add(new Employee(3,"哈密瓜",20));
        employees.add(new Employee(4,"樱桃",45));

        //default Stream<T> stream():返回一个顺序流
        Stream<Employee> stream = employees.stream();

        //default Stream<T> parallelStream():返回一个并行流
        Stream<Employee> employeeStream = employees.parallelStream();
    }
5.2.2、通过数组

创建 Stream 方式二:通过数组

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

static <T>Stream<T> stream(T[] array):返回一个流

<T>Stream是指返回的数据类型的类名,类名是随着传入参数的类型变化而变化,是重载形式:
public static IntStream stream(int[] array)
public static LongStream stream(long[] array)

    @Test
    public void test2(){
        //基本数据类型的数组
        int[] ints = {1, 2, 3, 4, 5};
        //static  <T>Stream<T> stream(T[] array):返回一个流
        IntStream stream = Arrays.stream(ints);
        
        //自定义类类型的数组
        Employee e1 = new Employee(1, "草莓", 12);
        Employee e2 = new Employee(1, "草莓", 12);
        Employee e3 = new Employee(1, "草莓", 12);
        Employee[] employees = new Employee[]{e1,e2,e3};

        Stream<Employee> stream1 = Arrays.stream(employees);
    }
5.2.3、通过Stream的of()

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

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

Stream<T> of(T... values) {return Arrays.stream(values);}

    @Test
    public void test3(){
        Stream<Integer> integerStream = Stream.of(1, 2, 3, 4);
    }
5.2.4、通过Stream的iterate()、generate()

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

调用Stream类的静态方法iterate()generate()

1、 public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)迭代

返回通过将函数 f 迭代应用到初始元素seed产生的无限顺序有序Stream

Stream的第一个元素(位置0 )将是提供的seed 。 对于n > 0 ,位置n处的元素将是将函数f应用于位置n - 1处的元素的结果。

是一个无限循环的方式

为了更好的体验,来写一个完整的 Stream 执行操作

Stream<T> limit(long maxSize); Stream接口中的一个抽象方法,返回由该流的元素组成的流,其长度被截断为不超过maxSize
这是一个短路状态中间操作

    @Test
    public void test4(){
        //输出1-10
        Stream<Integer> iterate = Stream.iterate(1, new UnaryOperator<Integer>() {
            @Override
            public Integer apply(Integer integer) {
                return integer + 1;
            }
        });
        iterate.limit(10).forEach(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        });
    }
	//以上是使用匿名内部类

	//以下使用方法引用
    @Test
    public void test4(){
        //输出1-10
        Stream.iterate(1, integer -> integer + 1).limit(10).forEach(System.out :: println);
    }

执行结果如下
在这里插入图片描述

2、 public static<T> Stream<T> generate(Supplier<T> s)生成

返回一个无限连续的无序流,其中每个元素都由提供的Supplier生成。 这适用于生成恒定流、随机元素流等

为了更好的体验,来写一个完整的 Stream 执行操作

    @Test
    public void test4(){
        //生成10个随机数
        Stream<Double> generate = Stream.generate(new Supplier<Double>() {
            @Override
            public Double get() {
                return Math.random();
            }
        });
        generate.limit(10).forEach(new Consumer<Double>() {
            @Override
            public void accept(Double aDouble) {
                System.out.println(aDouble);
            }
        });
    }
    //以上是使用匿名内部类

	//以下使用方法引用
    @Test
    public void test4(){
        //生成10个随机数
        Stream.generate(Math :: random).limit(10).forEach(System.out :: println);
    }

执行结果如下
在这里插入图片描述

5.3、Stream的中间操作

多个中间操作可以连接起来,形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!

而是在终止操作时一次性全部处理,称为 “ 惰性求值 ”

5.3.1、筛选与切片
方法描述
filter(Predicate p)接收Lambda,从流中排除某些元素
distinct()筛选,通过流所生成元素的hashCode()和equals()去除重复元素
limit(long maxSize)截断流,使其元素不超过指定数量
skip(long l)跳过元素,返回一个去掉了前n个元素的流,若流中个数不足,则返回一个空流,与limit(n)互补

filter(Predicate p):接收Lambda,从流中排除某些元素

Predicate<T>:断定型接口boolean test(T t)

    @Test
    public void test5(){
        //default Stream<T> stream():返回一个顺序流
        ArrayList<Employee> employees = new ArrayList<>();
        employees.add(new Employee(1,"草莓",12));
        employees.add(new Employee(2,"西瓜",88));
        employees.add(new Employee(3,"哈密瓜",20));
        employees.add(new Employee(4,"樱桃",45));
        Stream<Employee> stream = employees.stream();

        //filter(Predicate p):接收Lambda,从流中排除某些元素
        //练习:筛选年龄50以下的员工信息
//        stream.filter(new Predicate<Employee>() {
//            @Override
//            public boolean test(Employee employee) {
//                return employee.getAge()<50;
//            }
//        }).forEach(System.out :: println);
        stream.filter(employee -> employee.getAge()<50).forEach(System.out :: println);
    }

执行结果如下
在这里插入图片描述
distinct():筛选,通过流所生成元素的hashCode()和equals()去除重复元素

    @Test
    public void test6() {
        //default Stream<T> stream():返回一个顺序流
        ArrayList<Employee> employees = new ArrayList<>();
        employees.add(new Employee(1, "草莓", 12));
        employees.add(new Employee(2, "西瓜", 88));
        employees.add(new Employee(3, "哈密瓜", 20));
        employees.add(new Employee(3, "哈密瓜", 20));
        employees.add(new Employee(3, "哈密瓜", 20));
        Stream<Employee> stream = employees.stream();

        //distinct():筛选,通过流所生成元素的hashCode()和equals()去除重复元素
//        Stream<Employee> distinct = stream.distinct();
//        distinct.forEach(new Consumer<Employee>() {
//            @Override
//            public void accept(Employee employee) {
//                System.out.println(employee);
//            }
//        });
        stream.distinct().forEach(System.out :: println);
    }

执行结果如下
在这里插入图片描述

limit(long maxSize):截断流,使其元素不超过指定数量

    @Test
    public void test7() {
        //default Stream<T> stream():返回一个顺序流
        ArrayList<Employee> employees = new ArrayList<>();
        employees.add(new Employee(1, "草莓", 12));
        employees.add(new Employee(2, "西瓜", 88));
        employees.add(new Employee(3, "哈密瓜", 20));
        employees.add(new Employee(4, "樱桃", 45));
        Stream<Employee> stream = employees.stream();

        //limit(long maxSize):截断流,使其元素不超过指定数量
        stream.limit(2).forEach(System.out :: println);
    }

执行结果如下
在这里插入图片描述

skip(long l):跳过元素,返回一个去掉了前n个元素的流,若流中个数不足,则返回一个空流,与limit(n)互补

    @Test
    public void test8() {
        //default Stream<T> stream():返回一个顺序流
        ArrayList<Employee> employees = new ArrayList<>();
        employees.add(new Employee(1, "草莓", 12));
        employees.add(new Employee(2, "西瓜", 88));
        employees.add(new Employee(3, "哈密瓜", 20));
        employees.add(new Employee(4, "樱桃", 45));
        Stream<Employee> stream = employees.stream();

        //skip(long l):跳过元素,返回一个去掉了前n个元素的流,若流中个数不足,则返回一个空流,与limit(n)互补
        stream.skip(2).forEach(System.out :: println);
    }

执行结果如下

在这里插入图片描述

5.3.2、映射

在这里插入图片描述

map(Function f):接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素

Function<T,R> :函数型接口,R apply(T t)

    @Test
    public void test9() {
        //练习:将数组中的小写字母转换为大写字母
        List<String> strings = Arrays.asList("aa", "bb", "cc", "dd");
        Stream<String> stream = strings.stream();
        //map(Function f):接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
//        stream.map(new Function<String, Object>() {
//            @Override
//            public Object apply(String s) {
//                return s.toUpperCase();
//            }
//        }).forEach(System.out :: println);
        stream.map(s -> s.toUpperCase()).forEach(System.out :: println);

        //练习:获取员工名字长度大于等于3的员工的 “ 姓名 ”
        //default Stream<T> stream():返回一个顺序流
        ArrayList<Employee> employees = new ArrayList<>();
        employees.add(new Employee(1, "草莓", 12));
        employees.add(new Employee(2, "西瓜", 88));
        employees.add(new Employee(3, "哈密瓜", 20));
        employees.add(new Employee(4, "樱桃", 45));
        Stream<Employee> stream1 = employees.stream();
        //先筛选,再输出姓名
        stream1.filter(employee -> employee.getName().length() >= 3).forEach(employee -> System.out.println(employee.getName()));
        //先换成姓名,再筛选(注意,Stream只可以被用一次)
        //这里要重新创建,否则编译不报错,运行报错:java.lang.IllegalStateException: stream has already been operated upon or closed
        Stream<Employee> stream2 = employees.stream();
        stream2.map(Employee::getName).filter(name -> name.length() >= 3).forEach(System.out :: println);
    }

执行结果如下
在这里插入图片描述

flatMap(Function f):接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流

注意:

在List当中,有 add() 方法和 addAll() 方法,

public void test1(){
	List list1 = new ArrayList();
	List list2 = new ArrayList();
	List a = new ArrayList(1, 2, 3);
	List b = new ArrayList(4, 5, 6);
	
	//add() 方法,输出的是集合作为元素的嵌套集合
	list1.add(a);
	list1.add(b);
	System.out.println(list1);   // [ [1,2,3] , [4,5,6] ]
	
	//addAll() 方法,输出的是集合中元素展开,融合成一个新的集合,非嵌套
	list2.addAll(a);
	list2.addAll(b);
	System.out.println(list2);   // [1, 2, 3, 4, 5, 6]
}

flatMap(Function f):接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流

map(Function f)flatMap(Function f)的区别,就像上述的 List 中的 add() 方法和 addAll() 方法,下面代码体会此种说法

public StreamAPITest1{
	//定义一个方法,该方法可以将传入的 String 拆开,以每个字符作为元素,形成一个新的流
	public static Stream<character> fromStringToStream(String s){
		List list = new ArrayList();
		for(character c : s){
			list.add(c);
		}
		return list.stream();
	}
	public void test2(){
		//练习:将集合中的字符串,字符拆开转换成Stream流
		
		//第一种情况,使用map(Function f):此时就相当于是 add() 方法,当传入的元素的流的形式时候,输出的结果也是嵌套的流的形式
		//上面的例子中 Stream<String> s = stream.map(s -> s.toUpperCase());
		List list = new ArrayList("aa", "bb", "cc", "dd");
//		Stream<Stream<character>> stream = list.stream().map(new Function<String, Stream<character>>() {
//			@Override
//			public Object apply(String s) {
//				return StreamAPITest1.fromStringToStream(s);
//			}
//		});
		Stream<Stream<character>> stream = list.stream().map(StreamAPITest1 :: fromStringToStream);
		//所以现在进行输出的时候,也需要嵌套来输出
		stream.forEach(() -> System.out.println(forEach(System.out :: println)));
		
		//第一种情况,使用flatMap(Function f):此时就相当于是 addAll() 方法,当传入的元素的流的形式时候,将流中的每个值都换成另一个流,然后把所有流连接成一个流
		Stream<character> stream = list.stream().flatMap(StreamAPITest1 :: fromStringToStream);
		stream.forEach(System.out :: println);
	}
}
5.3.3、排序
方法描述
sorted()产生一个新流,其中按照自然顺序排序
sorted(Comparator com)产生一个新流,其中按照比较器顺序排序

sorted():产生一个新流,其中按照自然顺序排序

    @Test
    public void test10() {
        //sorted():产生一个新流,其中按照自然顺序排序
        List<String> strings = Arrays.asList("ff", "aa", "ww", "cc");
        Stream<String> stream = strings.stream();
        stream.sorted().forEach(System.out :: println);
    }

执行结果如下
在这里插入图片描述

sorted(Comparator com):产生一个新流,其中按照比较器顺序排序

    @Test
    public void test10() {
        //sorted(Comparator com):产生一个新流,其中按照比较器顺序排序
        ArrayList<Employee> employees = new ArrayList<>();
        employees.add(new Employee(1, "草莓", 12));
        employees.add(new Employee(2, "西瓜", 88));
        employees.add(new Employee(3, "哈密瓜", 20));
        employees.add(new Employee(4, "樱桃", 45));
        Stream<Employee> stream = employees.stream();
//      sorted((o1, o2) -> o1.getName().compareTo(o2.getName()))
        stream.sorted(Comparator.comparing(Employee::getName)).forEach(System.out :: println);
    }

执行结果如下

在这里插入图片描述

5.4、Stream的终止操作

5.4.1、匹配与查找

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

流进行了终止操作后,不能再次使用

方法描述
allMatch(Predicate p)检查是否匹配所有元素
anyMatch(Predicate p)检查是否至少匹配一个元素
noneMatch(Predicate p)检查是否不匹配所有元素
findFirst()返回第一个元素(并行流和顺序流结果有可能不同)
findAny()返回当前流中的任意元素

Predicate<T>:断定型接口boolean test(T t)

    @Test
    public void test1() {
        //default Stream<T> stream():返回一个顺序流
        ArrayList<Employee> employees = new ArrayList<>();
        employees.add(new Employee(1, "草莓", 12));
        employees.add(new Employee(2, "西瓜", 88));
        employees.add(new Employee(3, "哈密瓜", 20));
        employees.add(new Employee(4, "樱桃", 45));

        //allMatch(Predicate p):检查是否匹配所有元素
        //练习:是否所有员工年龄都大于30岁
//        Stream<Employee> stream = employees.stream();
//        boolean b = stream.allMatch(new Predicate<Employee>() {
//            @Override
//            public boolean test(Employee employee) {
//                return employee.getAge() > 30;
//            }
//        });
        Stream<Employee> stream1 = employees.stream();
        boolean b1 = stream1.allMatch(employee ->employee.getAge() > 30);
        System.out.println(b1);     //false

        //anyMatch(Predicate p):检查是否至少匹配一个元素
        //练习:是否存在员工年龄大于30岁
        Stream<Employee> stream2 = employees.stream();
        boolean b2 = stream2.anyMatch(employee ->employee.getAge() > 30);
        System.out.println(b2);     //true

        //noneMatch(Predicate p):检查是否不匹配所有元素
        //练习:是否所有员工姓名都不小以 “ 阿 ” 字开头
        Stream<Employee> stream3 = employees.stream();
        boolean b3 = stream3.noneMatch(employee ->employee.getName().startsWith("阿"));
        System.out.println(b3);     //true

        //findFirst():返回第一个元素(并行流和顺序流结果有可能不同)
        //顺序流
        Stream<Employee> stream4 = employees.stream();
        Optional<Employee> first = stream4.findFirst();
        System.out.println(first);    //Optional[Employee{id=1, name='草莓', age=12}]
        //并行流
        Stream<Employee> stream = employees.parallelStream();
        Optional<Employee> first1 = stream.findFirst();
        System.out.println(first1);    //Optional[Employee{id=1, name='草莓', age=12}]

        //findAny():返回当前流中的任意元素
        Stream<Employee> stream5 = employees.stream();
        Optional<Employee> any = stream5.findAny();
        System.out.println(any);    //Optional[Employee{id=1, name='草莓', age=12}]
    }

执行结果如下
在这里插入图片描述

方法描述
count()返回流中元素总数
max(Comparator c)返回流中最大值
min(Comparator c)返回流中最小值
forEach(Consumer c)内部迭代

使用Collection接口需要用户去做迭代,称为外部迭代,相反,StreamAPI使用内部迭代,它帮你把迭代做了

    @Test
    public void test2(){
        //default Stream<T> stream():返回一个顺序流
        ArrayList<Employee> employees = new ArrayList<>();
        employees.add(new Employee(1, "草莓", 12));
        employees.add(new Employee(2, "西瓜", 88));
        employees.add(new Employee(3, "哈密瓜", 20));
        employees.add(new Employee(4, "樱桃", 45));

        //count():返回流中元素总数
        //练习:员工年龄大于30岁的员工个数
        Stream<Employee> stream1 = employees.stream();
        System.out.println(stream1.filter(employee -> employee.getAge() > 30).count());   //2

        //max(Comparator c):返回流中最大值
        //练习:返回最大的年龄
        Stream<Employee> stream2 = employees.stream();
        //直接拿着Employee对象进行排序
//        System.out.println(stream2.max(Comparator.comparingInt(Employee::getAge)));   //Optional[Employee{id=2, name='西瓜', age=88}]
        //先筛选出年龄,再进行排序。此时输出的是Integer对象,就不是Employee对象
        System.out.println(stream2.map(Employee::getAge).max(Integer::compareTo));   //Optional[88]

        //min(Comparator c):返回流中最小值
        //练习:返回最小的年龄
        Stream<Employee> stream3 = employees.stream();
        System.out.println(stream3.min((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge())));   //Optional[Employee{id=1, name='草莓', age=12}]

        //forEach(Consumer c):内部迭代
        Stream<Employee> stream4 = employees.stream();
        //流内的迭代
        stream4.forEach(System.out :: println);
        //集合中的一个方法
        employees.forEach(System.out :: println);
    }

执行结果如下
在这里插入图片描述

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

map 和 reduce 通常会结合起来,称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名

对一串数字求和可以表示为:Integer sum = integers.reduce(0, (a, b) -> a+b);
或者:Integer sum = integers.reduce(0, Integer::sum);

    @Test
    public void test3(){
        //reduce(T iden, BinaryOperator b):可以将流中元素反复结合起来,得到一个值,返回T
        //BinaryOperator<T,T,T>:包含方法 T apply(T t1, T t2)
        //练习1:求和 1-10
        List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        Integer reduce = integers.stream().reduce(10, (i1, i2)-> i1+i2);
        System.out.println(reduce);     //65

        //reduce(BinaryOperator b):可以将流中元素反复结合起来,得到一个值,返回Optional<T>
        //练习2:计算所有员工 年龄总和
        //default Stream<T> stream():返回一个顺序流
        ArrayList<Employee> employees = new ArrayList<>();
        employees.add(new Employee(1, "草莓", 12));
        employees.add(new Employee(2, "西瓜", 88));
        employees.add(new Employee(3, "哈密瓜", 20));
        employees.add(new Employee(4, "樱桃", 45));
        Stream<Employee> stream = employees.stream();
        Optional<Integer> reduce1 = stream.map(Employee::getAge).reduce(Integer::sum);
        System.out.println(reduce1);     //Optional[165]
    }

执行结果如下
在这里插入图片描述

5.4.3、收集

收集就是将数据装进一个容器里

方法描述
collect(Collector c)将流转换为其他形式,接受一个Collector接口的实现,用于给Stream中元素做汇总的方法

Collector接口中方法的实现决定了如何对流执行收寄的操作(如收集到List、Set、Map)

另外,Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表

在这里插入图片描述

    @Test
    public void test4(){
        ArrayList<Employee> employees = new ArrayList<>();
        employees.add(new Employee(1, "草莓", 12));
        employees.add(new Employee(2, "西瓜", 88));
        employees.add(new Employee(3, "哈密瓜", 20));
        employees.add(new Employee(4, "樱桃", 45));
        Stream<Employee> stream = employees.stream();
        //collect(Collector c):将流转换为其他形式,接受一个Collector接口的实现,用于给Stream中元素做汇总的方法
        //练习1:查找年龄大于30的关公,结果返回一个 List或者Set
        List<Employee> collect = stream.filter(employee -> employee.getAge() > 30).collect(Collectors.toList());
        //这里使用的是 Iterable 中的forEach()方法,Collection<E> extends Iterable<E>
        collect.forEach(System.out :: println);    //[Employee{id=2, name='西瓜', age=88}, Employee{id=4, name='樱桃', age=45}]
    }

执行结果如下
在这里插入图片描述

6、Optional 类

6.1、Optional 类介绍

Optional<T> 类(java.util.Optional)是一个容器类,它可以保存类型T的值,代表这个值存在,或者仅仅保存 null ,表示这个值不存在,原来用 null 表示一个只不存在,现在 Optional 可以更好的表达这个概念,并且可以避免空指针异常

这是一个可以为 null 的容器对象,如果值存在,则 isPresent() 方法会返回 true,调用get()方法会返回该对象

  • 创建 Optional 类对象的方法
    • Optional.of(T t):创建一个 Optional 实例,t 必须为非空
    • Optional.empty():创建一个空的 Optional 实例
    • Optional.ofNullable(T t):t 可以为 null
  • 判断 Optional 容器中是否包含对象
    • boolean isPresent():判断是否包含对象
    • void ifPresent(Consumer<? super T> con):如果有值,就执行 Consumer 接口的实现代码,并且该值会作为参数传给它
  • 获取 Optional 容器中的对象
    • T get(): 如果调用对象包含值,返回该值,否则抛异常(一般和 of(T t)配合使用)
    • T orElse(T other):如果有值则将其返回,否则返回指定的 other 对象
    • T orElseGet(Supllier<? extends T> other):如果有值则将其返回,否则返回由 Supllier 接口实现提供的对象
    • T orElseThrow(Supllier<? extends X> exceptionSupllier):如果有值则将其返回,否则抛出由 Supllier 接口实现提供的异常

Optional.of(T t):创建一个 Optional 实例,t 必须为非空
一般和 T get()配合使用

Optional.empty():创建一个空的 Optional 实例
Optional.ofNullable(T t):t 可以为 null

public class Test{
	@Test
	public void test1(){
		//Optional.of(T t):创建一个 Optional 实例,t 必须为非空
		
		Girl girl1 = new Girl();
		//这种情况会报空指针异常,t 必须为非空
		//java.lang.NullPointerException
		//girl1 = null;
		Optional<Girl> optionalgirl1 = Optional.of(girl1);
		System.out.println(optionalgirl1);     //Optional[Girl{name='null'}]
	}
	@Test
	public void test2(){
		//Optional.ofNullable(T t):t 可以为 null
		
		//当存放的不是null的时候,输出结果与Optional.of(T t)一致
		Girl girl1 = new Girl();
		Optional<Girl> optionalgirl1 = Optional.ofNullable(girl1);
		System.out.println(optionalgirl1);     //Optional[Girl{name='null'}]

		//当存放的为 null 的时候,输出结果 Optional.empty
		Girl girl2 = new Girl();
		girl2 = null;
		Optional<Girl> optionalgirl2 = Optional.ofNullable(girl2);
		System.out.println(optionalgirl2);     //Optional.empty
	}
}

6.2、Optional 使用举例

以下代码体会 Optional 是可以作为 null 的容器对象

先创建Girl类和Boy类

//Girl类
public class Girl {
    private String name;

    public Girl() {}
    public Girl(String name) { this.name = name; }
    @Override
    public String toString() {
        return "Girl{" +
                "name='" + name + '\'' +
                '}';
    }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}
//Boy类
public class Boy {
    private Girl girl;

    public Boy(Girl girl) { this.girl = girl; }
    public Boy() { }
    @Override
    public String toString() {
        return "Boy{" +
                "girl=" + girl +
                '}';
    }
    public Girl getGirl() { return girl; }
    public void setGirl(Girl girl) { this.girl = girl; }
}

在不使用 Optional 类的情况下,

创建测试testBoy类

public class Test{
	//创建一个方法,返回Boy中的Girl名字
	/*
	如果直接这样写,调用时候会报空指针异常
	public String getGirlName(Boy boy){
		return boy.getGirl().getName();
	}
	*/
	//优化之后的getGirlName()方法
	public String getGirlName(Boy boy){
		if(boy != null)
			Girl girl = boy.getGirl();
			if(girl != null)
				return girl.getName();
		return null
	}
	
	@Test
	public void testBoy(){
		Boy boy = new Boy();
		String girlname = getGirlName(boy);
		System.out.println(girlname);
	}
}

使用 Optional 类的情况下

T orElse(T other):如果有值则将其返回,否则返回指定的 other 对象

public class Test{
	//创建一个方法,返回Boy中的Girl名字
	public String getGirlName(Boy boy){
		//第一层:除去 Boy 为空的情况
		
		//ofNullable(T t):t 可以为 null
		Optional<Boy> optionalboy = Optional.ofNullable(boy);
		//orElse(T other):如果有当前的Optional内部封装的t是非空的,则返回内部的t
		//如果内部的t是空的,则返回orElse(T other)方法中的other
		Boy boy1 = optionalboy.orElse(new Boy(new Girl("小草莓")));
		
		//此时  Boy 必定不为空,则可以获取里面的Girl对象
		Girl girl = boy1.getGirl();
		
		//第二层:除去 Girl 为空的情况
		
		//ofNullable(T t):t 可以为 null
		Optional<Boy> optionalboy = Optional.ofNullable(girl);
		Girl girl1 = optionalboy.orElse(new Girl("小西瓜"));
		
		//此时  Girl 必定不为空,则可以获取里面的姓名name属性
		String name = girl1.getName();
		return name;
	}
	@Test
	public void test1(){
		Boy boy = new Boy();
		String girlname = getGirlName(boy);
		System.out.println(girlname);    //小西瓜
		
		Boy boy1 = null;
		String girlname1 = getGirlName(boy1);
		System.out.println(girlname1);    //小草莓
		
		Boy boy2 = new Boy(new Girl("小樱桃"));
		String girlname2 = getGirlName(boy2);
		System.out.println(girlname2);    //小樱桃
	}
}

7、小结

Functional

  1. Consumer<T> :消费型接口,void accept(T t)
  2. Supplier<T>:供给型接口,T get()
  3. Function<T,R> :函数型接口,R apply(T t)
  4. Predicate<T>:断定型接口boolean test(T t)

method reference

  1. 对象 :: 实例方法
  2. 类 :: 静态方法
  3. 类 :: 实例方法

Stream

  1. Stream 关注的是对数据的运算,与CPU打交道
  2. Stream
    1. 自己不会存储元素
    2. 不会改变源对象。相反,会返回一个持有结果的新Stream
    3. 操作是延迟执行的。意味着这会等到需要结果的时候才会执行
  3. Stream的执行流程
    1. Stream的实例化
    2. 一系列中间操作(过滤、映射、排序)
    3. 终止操作
  4. 说明
    1. 一个中间操作链,对源数据的数据进行处理
    2. 一旦执行终止操作,就执行中间操作链,并产生结果,之后,不会再被使用
  5. Stream的实例化
    1. 通过集合—— Collection 中定义的实例方法
      default Stream<T> stream():返回一个顺序流
      default Stream<T> parallelStream():返回一个并行流

    2. 通过数组—— Arrays 的静态方法stream()
      static <T>Stream<T> stream(T[] array):返回一个流

    3. 调用Stream类的静态方法of()

    4. 调用Stream类的静态方法iterate()generate()

Optional

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值