Jdk8新特性详解

JDK8新特征

前言:本文内容参考了很多大佬的内容,做个笔记进行整理。欢迎大家一起交流学习。

一、JDK版本变化

JDK版本名称发布时间
1.0Oak(橡树)1996/1/23
1.11997/2/19
1.2Playground(运动场)1998/12/4
1.3Kestrel(美洲红隼)2000/5/8
1.4.0Merlin(灰背隼)2002/2/13
Java SE 5.0 / 1.5Tiger(老虎)2004/9/30
Java SE 6.0 / 1.6Mustang(野马)2006/4/1
Java SE 7.0 / 1.7Dolphin(海豚)2011/7/28
Java SE 8.0 / 1.8Spider(蜘蛛)2014/3/18
Java SE 9.02017/9/21
Java SE 10.02018/3/21
Java SE 11.02018/9/25
Java SE 12.02019/3/19
Java SE 13.02019/9/17
Java SE 14.02020/3/17
Java SE 15.02020/9

从Java9这个版本开始,Java 的计划发布周期是6个月。这意味着Java的更新从传统的以特性驱动的发布周期,转变为以时间驱动的发布模式,并逐步的将Oracle JDK原商业特性进行开源。

理念:小步快跑,快速迭代。后面周期变短,对之前的东西固定下来保存下来抛弃一些,版本趋于稳定,更新内容新特性就少。

针对企业客户的需求,Oracle将以三年为周期发布长期支持版本(long term support)。

JDK8中的Lambda表达式和Stream 是自Java语言添加泛型(Generics)和注解(annotation)以来最大的变化

二、Lambda表达式

Lambda 表达式是一种匿名函数(不是匿名内部类),没有声明的方法,也即没有访问修饰符、返回值声明和名字

Lambda表达式语法

  • (parameters) -> expression或 (parameters) ->{ statements; }
  • parameters:参数列表
  • ->:lambda 运算符
  • statements:方法体

2.1、引入Lambda表达式

/*
定义一个接口
*/
public interface MyInterFace {
    public void method();
}

/*
实现MyInterFace接口
*/
class MyClass implements MyInterFace {
    @Override
    public void method() {
        System.out.println("----------MyClass method()--------------");
    }

    public static void main(String[] args) {
        // 1.定义了实现类,创建对象并使用方法
        MyClass myClass = new MyClass();
        myClass.method();

        // 2.使用匿名内部类
        MyInterFace myInterFace = new MyInterFace() {
            @Override
            public void method() {
                System.out.println("-----------匿名内部类方法---------");
            }
        };
        myInterFace.method();

        // 3. 使用Lambda表达式
        MyInterFace myInterFace1 = ()->{
            System.out.println("-----------匿名内部类方法1---------");
        };
        myInterFace1.method();
        // 4. 使用Lambda表达式
        MyInterFace myInterFace2 = () -> System.out.println("-----------匿名内部类方法1简化---------");
        myInterFace2.method();
    }
}

总结:

  • Lambda表达式是一个匿名函数,即没有函数名的函数。
  • 使用 Lambda 表达式可使代码变的更加简洁紧凑。并且Lambda表达式可和Stream API等相结合,使代码更加简洁紧凑。Lambda 表达式经常用来替代部分匿名内部类

2.2、无参无返回值

public interface MyInterFace1 {
    public void method();
    
    public static void main(String[] args) {
        MyInterFace1 myInterFace1 = () -> {
            System.out.println("无参无返回值:一条语句");
        };
        myInterFace1.method();
        
        //一条语句:{}可以省略
        MyInterFace1 myInterFace2 = () -> System.out.println("无参无返回值:一条语句");
        myInterFace2.method();
        
        //多条语句:{}不可以省略
        /*final*/
        int n = 5;
        MyInterFace1 myInterFace3 = () -> {
            System.out.println("无参无返回值:多条语句");
            System.out.println("只能引用final的外层局部变量:"+n);
            System.out.println("无参无返回值:多条语句");
        };
        myInterFace3.method();
    }

总结:

  • 如果Lambda表达式的语句体只有一条语句,{}可以省略如多条语句,{}不可以省略
  • Lambda 表达式可以引用标记了 final 的外层局部变量。
  • lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义

2.3、一个参数无返回值

public interface MyInterFace2 {
    public void method(int num);

    public static void main(String[] args) {
        MyInterFace2 myInterFace = (int x) -> {
            System.out.println(x);
        };
        myInterFace.method(2);

        // 类型可以省略
        MyInterFace2 myInterFace2 = (x)-> System.out.println(x);
        myInterFace2.method(3);

        //()可以省略
        MyInterFace2 myInterFace21 = x -> System.out.println(x);
        myInterFace21.method(4);

        MyInterFace2 myInterFace22 = x -> {
            System.out.println("============");
            System.out.println(x);
            System.out.println("============");
        };
        myInterFace22.method(5);
    }
}

总结:

  • Lambda表达式的参数类型可以省略,参数名可以是任意名称;
  • 如果只有一个参数,参数外的()也可以省略

2.4、多个参数无返回值

public interface MyInterface3 {
    public void method(int num,String str);
}

    public static void main(String[] args) {
        MyInterface3 myInterface=(int num,String str)-> System.out.println(num+" "+str);
        myInterface.method(20,"bjsxt");
        
        //参数的小括号不能省略,类型可以省略
        myInterface = (num,str)-> System.out.println(num+" "+str);
        myInterface.method(20,"bjsxt");
        
        myInterface = (num,str)-> {
            System.out.println("----------");
            System.out.println(num+" "+str);
            System.out.println("----------");
        };
        myInterface.method(20,"bjsxt");
    }

总结

  • Lambda表达式如果多个参数,参数外的 ()不可省略

2.5、有参数有返回值

public interface MyInterface4 {
    public int  method(int num);
}

    public static void main(String[] args) {
        MyInterface4 myInterface = (x)-> {return x+10;};
        int result = myInterface.method(10);
        System.out.println(result);
        
        //只有一条return语句,{}和return可以均省去
        myInterface = (x)-> x+10;
        result = myInterface.method(10);
        System.out.println(result);
        
        myInterface = (x)-> {
            x+=10;
            return x+10;
        };
        result = myInterface.method(10);
        System.out.println(result);
    }


总结:

  • 有返回值的Lambda表达式,如果方法体只有一条语句,可同时省略return和{}
  • Lambda 规定接口中只能有一个需要被实现的抽象方法,不是规定接口中只能有一个方法,称为函数式接口

总结:

左侧 -> :

  • 可选参数类型声明:参数类型可以省略不写
  • 可选参数圆括号:如果只有一个参数,()可以省略不写**(干脆就别省)**

-> 右侧:

  • 将方法体的具体内容包裹起来
  • 可选的大括号:只有一个方法体执行语句的话,{}可以省略不写,干脆就别省
  • 可选的关键字:如果一句执行语句是return语句,return可以省略不写

三、函数式接口

3.1、认识函数式接口

函数式接口:只能有一个抽象方法,其他的可以有default、static、Object里public方法等

**作用:**在Java中主要用在Lambda表达式和方法引用(实际上也可认为是Lambda表达式)上

已经使用过的函数式接口,比如Comparator等,后面在多线程阶段要学习的函数式接口有Runnable、Callable等。(注意:Comparable并没有被标记为函数式接口)

@FunctionalInterface
public interface MyFuncInterface {
    //只有一个抽象方法
    public void method1();

    //不能有第二个抽象方法
    //public void method2();

    //default方法不计,非抽象
    default  void method2(){
    }

    //static方法不计,非抽象
    static void method3(){
    }

    //是抽象方法,但是和Object类中的方法定义相同,可以有
    public boolean equals(Object obj);
    public String toString();

    public static void main(String[] args) {
        // 不是函数式接口
        List list;
        Map map;
        // 函数式接口
        Comparator comparator;
        // 虽然符合函数式接口定义,但是不认为是函数式接口
        Comparable comparable;
    }
}

image-20210421133628542

3.2、内置函数式接口

在java.util.function包里

JDK 也提供了大量的内置函数式接口,使得 Lambda 表达式的运用更加方便、高效。

  • Consumer:消费型接口(void accept(T t))。有参数,无返回值
  • Supplier:供给型接口(T get())。只有返回值,没有入参
  • Function<T, R>:函数型接口(R apply(T t))。一个输入参数,一个输出参数,两种类型不可不同、可以一致
  • Predicate:断言型接口(boolean test(T t))。输入一个参数,输出一个boolean类型得返回值
接口定义参数返回值描述
消费型接口 Consumervoid accept(T t)Tvoid对类型为T的对象进行操作
供给型接口 SupplierT get()voidT返回类型为T的对象
函数型接口 FunctionR apply(T t)TR对类型为T的对象进行操作,返回类型为R的对象
断言型接口 Predicateboolean test(T t)Tboolean对类型为T的对象进行操作,返回布尔类型结果

消费型接口Consumer<T>

例子1:

image-20210421140135072

public class TestFunctionInterface {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        Collections.addAll(list, 10, 40, 24, 53, 90, 70, 27);
        System.out.println(list.toString());

        // 匿名内部类
        Consumer consumer = new Consumer<Integer>() {
            @Override
            public void accept(Integer t) {
                System.out.println(t);
            }
        };

        // lambda表达式
        list.forEach(consumer);
    }
}


/*
[10, 40, 24, 53, 90, 70, 27]
10
40
24
53
90
70
27

image-20210421140715335

public class TestFunctionInterface {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        Collections.addAll(list, 10,30,50,20,70,60,30,100);
        System.out.println(list.toString());

        // 方法1:匿名内部类
        Consumer consumer = new Consumer<Integer>() {
            @Override
            public void accept(Integer t) {
                System.out.println(t);
            }
        };
        // lambda表达式
        list.forEach(consumer);

        // 方法2:lambda表达式 跟上面的效果一样
        list.forEach((elem)->{
            System.out.println(elem);
        });
        // 简化
        list.forEach( elem -> System.out.println(elem));
        
        // 再简化------------后面讲到的方法引用
        list.forEach(System.out::println); 
    }
}

例子2:Map遍历
public class TestFunctionInterface2 {
    public static void main(String[] args) {
        // 创建Map集合对象
        Map<String, String> map = new HashMap<String, String>();

        // 使用Map对象存储键值对
        map.put("cn", "China");
        map.put("jp", "Japan");
        map.put("us", "the United States");
        map.put("us", "America");
        map.put("uk", "England");
        map.put("en", "England");

        // 1.匿名内部类
        BiConsumer<String, String> biConsumer = new BiConsumer<String, String>(){
            @Override
            public void accept(String key, String value) {
                System.out.println(key + "===========" + value);
            }
        };
        map.forEach(biConsumer);

        // 方法2:lambda表达式
        map.forEach((key,value)->{
            System.out.println(key+"---------"+value);
        });
    }
}

/*jp===========Japan
uk===========England
en===========England
cn===========China
us===========America
jp---------Japan
uk---------England
en---------England
cn---------China
us---------America

断言型接口Predicate<T>

@FunctionalInterface
public interface Predicate<T> {
	boolean test(T t);
 ...}

image-20210421144048454

例子1:

image-20210421143831734

public class TestFunctionInterface3 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        Collections.addAll(list, 10,30,50,20,70,60,30,100);
        System.out.println(list.toString());

        //使用匿名内部类
        Predicate<Integer> predicate = new Predicate<Integer>() {
            @Override
            public boolean test(Integer o) {
                if(o>=60){
                    return true;
                }
                return false;
            }
        };
//        list.removeIf(predicate);
//        System.out.println(list);

        // 使用lambda表达式
        list.removeIf((elem)->{
            if(elem >=60){
                return true;
            }
            return false;
        });
        System.out.println(list);
    }
}
/*
[10, 30, 50, 20, 70, 60, 30, 100]
[10, 30, 50, 20, 30]

例子2:自定义retainIf方法

注意:是没有retainIf方法的。

public class TestFunctionInterface4 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        Collections.addAll(list, 10,30,50,20,70,60,30,100);
        System.out.println(list.toString());

        //lambda表达式
        //保留小于60
        list = retailIf((elem)->{
            return elem < 60;
        },list);
        System.out.println(list);
    }
    
    public static<T> List retailIf(Predicate<T> predicate, List<T> list){
        //定义一个新的ArrayList
        List<T> list2 = new ArrayList<>();

        //遍历旧的ArrayList的过程中,将符合条件的保留到新数组中
        for (T elem : list) {
            if (predicate.test(elem)) { 
                list2.add(elem);
            }
        }
        //返回新的引用
        return list2;
    }
}

四、方法引用

使用场景:Lambda体可能仅调用一个已存在方法,而不做任何其它事。对于这种情况,通过一个方法名字来引用这个已存在的方法会更加清晰

注意方法引用是一个 Lambda 表达式,方法引用操作符是双冒号 “::”

4.1、方法引用分类

类型类型对应的Lambda表达式
静态方法引用类名**:😗*静态方法名(args) -> 类名.staticMethod(args)
对象方法引用对象引用**:😗*实例方法名(args) -> inst.instMethod(args)
实例方法引用类名**:😗*实例方法名(特定类的任何对象)(inst,args) -> 类名.instMethod(args)
构建方法引用类名**:😗*new(args) -> new 类名(args)

4.2、例子

4.2.1、对象引用::实例方法名

常见的就是System.out::println

System里面的out属性,就是根据PrintStream创建出来的

public final static PrintStream out = null;

public class TestmethodRef2 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        Collections.addAll(list, 10,30,50);
        System.out.println(list.toString());

        // 匿名内部类
        Consumer consumer = new Consumer<Integer>() {
            @Override
            public void accept(Integer t) {
                System.out.println(t);
            }
        };
        list.forEach(consumer);

        //lambda表达式
        //list.forEach( (x)-> { System.out.println(x); } );
        list.forEach(System.out::println);

        //lambda表达式,不能写成方法引用
        list.forEach((x)->{ System.out.println(x*1.1); });
    }
}

  • println()的参数类型、返回值类型正好和 Consumer接口的accept方法的参数类型、返回值类型相同,此时可以采用方法引用来简化语法。

4.2.2、类名::静态方法名

定义一个Student类

public class Student{
    private String name;
    private String sex;
    private int age;
    private int score;
    
    public Student() {
        
    }

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

	//getter/setter/toString
}
public class TestmethodRef6 {
    public static void main(String[] args) {
        //1.匿名内部类
        Comparator<Integer> comparator = new Comparator<Integer>() {
            @Override
            public int compare(Integer n1, Integer n2) {
                return Integer.compare(n1,n2);
            }
        };
        int result = comparator.compare(20,30);
        System.out.println(result);
        
        //2.lambda表达式
        Comparator<Integer> comparator1 = (n1,n2) -> {return Integer.compare(n1,n2);};
        int result1 = comparator1.compare(30,40);
        System.out.println(result1);

        //2.方法引用的方式——静态方法引用   类名
        Comparator<Integer> comparator2 = Integer::compare;
        System.out.println(comparator2.compare(20,10));
    }
}

/*
compare是Integer类的一个静态方法

解释:

Comparator接口里面的compare方法定义为int compare(T o1, T o2);

public interface Comparator<T> {
     int compare(T o1, T o2);
}

Integer里面的compare方法定义为public static int compare(int x, int y)

public final class Integer extends Number implements Comparable<Integer> {
	public static int compare(int x, int y) {
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
    }
}

而在lambda表达式的形式里面,我们并没有对n1和n2做什么操作,只是简单的把参数放进去,因此可以写成方法引用的形式。

4.2.3、类名::实例方法名

重新写一个Student类

public class Student {
    private String name;
    private int score;
 
    public Student(){
 
    }
 
    public Student(String name,int score){
        this.name = name;
        this.score = score;
    }
 
	//getter/setter
    
 	/*
    public static int compareStudentByScore(Student student1,Student student2){
        return student1.getScore() - student2.getScore();
    }
    */
    
    public int compareByScore(Student student){
        return this.getScore() - student.getScore();

}

之前,无论是通过对象调用实例方法,还是通过类名调用静态方法,都是符合Java的语法,使用起来也比较清晰。但是我们可以看一下compareStudentByScore方法

public static int compareStudentByScore(Student student1,Student student2){
    return student1.getScore() - student2.getScore();
}

你发现这个方法,无论在哪个类都可以正常使用,也就是说没有从属于Student类。

如果修改为

public int compareByScore(Student student){
    return this.getScore() - student.getScore();

这个方法接受一个Student对象和当前调用改方法的Student对象进行分数比较。——类名::实例方法名的方式来替换lambda表达式。

Student student1 = new Student("zhangsan",60);
Student student2 = new Student("lisi",70);
Student student3 = new Student("wangwu",80);
Student student4 = new Student("zhaoliu",90);
List<Student> students = Arrays.asList(student1,student2,student3,student4);
 
students.sort(Student::compareByScore);
students.forEach(student -> System.out.println(student.getScore()));

当使用 类名::实例方法名 方法引用时,一定是lambda表达式所接收的第一个参数来调用实例方法,如果lambda表达式接收多个参数,其余的参数作为方法的参数传递进去。

4.2.4、构造方法引用

注意:需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致。

Supplier函数式接口的get方法,不接收参数有返回值,正好符合无参构造方法的定义

@FunctionalInterface
public interface Supplier<T> {

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

Student类构造方法引用创建了supplier实例,以后通过supplier.get()就可以获取一个Student类型的对象,前提是Student类中存在无参构造方法。

public class TestmethodRef7 {
    public static void main(String[] args) {
        Supplier<Student> supplier = Student::new;
        System.out.println(supplier.get());
    }
}

/*
Student{name='null', sex='null', age=0, score=0}

4.3 例子2

class Car {
    @FunctionalInterface
    public interface Supplier<T> {
        T get();
    }
 
    //Supplier是jdk1.8的接口,这里和lamda一起使用了
    public static Car create(final Supplier<Car> supplier) {
        return supplier.get();
    }
 
    public static void collide(final Car car) {
        System.out.println("Collided " + car.toString());
    }
 
    public void follow(final Car another) {
        System.out.println("Following the " + another.toString());
    }
 
    public void repair() {
        System.out.println("Repaired " + this.toString());
    }
 
    public static void main(String[] args) {
        //构造器引用:它的语法是Class::new,或者更一般的Class< T >::new实例如下:
         Car car  = Car.create(Car::new);
         Car car1 = Car.create(Car::new);
         Car car2 = Car.create(Car::new);
         Car car3 = new Car();
        List<Car> cars = Arrays.asList(car,car1,car2,car3);
        System.out.println("===================构造器引用========================");
        
        //静态方法引用:它的语法是Class::static_method,实例如下:
        cars.forEach(Car::collide);  
        System.out.println("===================静态方法引用========================");
        
        //特定类的任意对象的方法引用:它的语法是Class::method实例如下:
        cars.forEach(Car::repair);
        System.out.println("==============特定类的任意对象的方法引用================");
        
        //特定对象的方法引用:它的语法是instance::method实例如下:
        final Car police = Car.create(Car::new);
        cars.forEach(police::follow);
        System.out.println("===================特定对象的方法引用===================");
 
    }
}

五、流式编程(Stream API)

5.1、引入

Stream(流)是一个来自数据源的元素队列并支持聚合操作

Stream与java.io包里的InputStream和OutputStream是完全不同的概念。

5.2、Stream的操作步骤

一、创建Stream:从一个数据源,如集合、数组中获取流。

二、中间操作:一个操作的中间链,对数据源的数据进行操作。

  • 当数据源中的数据上了流水线后,这个过程对数据进行的所有操作都称为“中间操作”。
  • 每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换)

三、终止操作:一个终止操作,执行中间操作链,并产生结果。

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

image-20210421211119761

5.3 创建流的方式

创建流最常用的方式是调用集合的 Stream 方法

/**
* 通过集合创建流
*/
private void createStream() {
	List<Integer> list = new ArrayList<>();
	Stream<Integer> stream = list.stream();
}

也可以通过数组创建流

/**
* 通过数组创建流
*/
private void createStream1() {
	Integer[] intArrays = {1, 2, 3};
	Stream<Integer> stream = Stream.of(intArrays);
	Stream<Integer> stream1 = Arrays.stream(intArrays);
}

创建并行流

/**
* 通过集合创建流
*/
private void createStream() {
	List<Integer> list = new ArrayList<>();
	Stream<Integer> stream = list.parallelStream();
}

5.3、中间操作和终止操作的具体分类

image-20210421213919508

现在有一个List,里面存放着各种Integer类型。

List<Integer > list = new ArrayList<>();
Collections.addAll(list,34,56,89,65,87,80,87,95,100,34,45);
System.out.println("当前的list = " + list);
Stream<Integer> stream = list.stream();
//找出>60的数据
stream.filter((elem)->{
    return elem > 60;
}).forEach(System.out::println);

输出结果:

当前的list = [34, 56, 89, 65, 87, 80, 87, 95, 100, 34, 45]
89
65
87
80
87
95
100

5.4、中间操作

5.4.1、筛选与切片

  • filterfilter 对原始Stream进行过滤,符合条件4的元素被留下来生成一个新Stream。
  • limit:截断流,使其元素不超过给定对象
  • skip(n):跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补
  • distinct:筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素。
5.4.1.1、limit举例

从大于60的数据中取出两个

public class TestStreamApi {
    public static void main(String[] args) {
        List<Integer > list = new ArrayList<>();
        Collections.addAll(list,34,56,89,65,87,80,87,95,100,34,45);
        System.out.println("当前的list = " + list);
		
        //创建流
        Stream<Integer> stream = list.stream();
        //找出>60的数据,并取出两个
        stream.filter((elem)->{
            return elem > 60;
        }).limit(2).forEach(System.out::println);
        
        System.out.println("流终止操作之后的list:"+list);
    }
}

可以看到list并没有发生任何变化

输出结果:
当前的list = [34, 56, 89, 65, 87, 80, 87, 95, 100, 34, 45]
89
65
流终止操作之后的list:[34, 56, 89, 65, 87, 80, 87, 95, 100, 34, 45]
5.4.1.2、skip举例
public class TestStreamApi {
    public static void main(String[] args) {
        List<Integer > list = new ArrayList<>();
        Collections.addAll(list,34,56,89,65,87,80,87,95,100,34,45);
        System.out.println("当前的list = " + list);
	
        //创建流
        Stream<Integer> stream = list.stream();
        //找出>60的数据
        stream.filter((elem)->{
            return elem > 60;
        }).skip().limit(2).forEach(System.out::println);
        System.out.println("流终止操作之后的list:"+list);
    }
}

//输出结果
当前的list = [34, 56, 89, 65, 87, 80, 87, 95, 100, 34, 45]
100
流终止操作之后的list:[34, 56, 89, 65, 87, 80, 87, 95, 100, 34, 45]
  • filter操作之后,数据源为89,65,87,80,87,95,100。如果skip超过数据的范围,就会输出空流
5.4.1.3、distinct举例
// filter操作之后,数据源为89,65,87,80,87,95,100
public class TestStreamApi {
    public static void main(String[] args) {
        List<Integer > list = new ArrayList<>();
        Collections.addAll(list,34,56,89,65,87,80,87,95,100,34,45);
        System.out.println("当前的list = " + list);

        //创建流
        Stream<Integer> stream = list.stream();
        //找出>60的数据
        stream.filter((elem)->{
            return elem > 60;
        }).distinct().forEach(System.out::println);
        System.out.println("流终止操作之后的list:"+list);
    }
}
// 只留下了一个87

5.4.2、映射

  • map定义:map()方法的参数为Function(函数式接口)对象,map()方法将流中的所有元素用Function对象进行运算,生成新的流对象(流的元素类型可能改变)

  • flatMap——接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。flatMap()能够将一个二维的集合映射成一个一维的集合,比map()方法拥有更高的映射深度

1.map举例

要求:每个值都加上5

public class TestStreamApi2 {
    public static void main(String[] args) {
        //原始数据
        List<Integer > list = new ArrayList<>();
        Collections.addAll(list,34,56,89,65,87,80,87,95,100,34,45);
        System.out.println("当前的list = " + list);
        //创建Stream
        Stream<Integer> stream = list.stream();
        //进行中间操作
        stream = stream.filter((x)->{return x>60;})
                .map((x)->x+5);//每个成绩加5分
        //进行终止操作:stream has already been operated upon or closed
        stream.forEach(System.out::println);
        System.out.println("流终止操作后的list = " + list);
    }
}
/*
当前的list = [34, 56, 89, 65, 87, 80, 87, 95, 100, 34, 45]
94
70
92
85
92
100
105
流终止操作后的list = [34, 56, 89, 65, 87, 80, 87, 95, 100, 34, 45]
//上面例子的中间操作,也可以写成......
System.out.println("当前的list = " + list);
//创建Stream
Stream<Integer> stream = list.stream();
//进行中间操作
stream.filter((x)->{return x>60;})
    .map((x)->x+5).forEach(System.out::println);//每个成绩加5分
System.out.println("流终止操作后的list = " + list);

注意:

  • 出现stream has already been operated upon or closed报错,意味流已经结束或者关闭了,因此我们在流中间操作创建为更新的流,然后最新流在关闭
2.flatMap举例
public class TestStreamApi2 {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("1 2", "3 4", "5 6");
        Stream<String> streamFlatMap = list.stream();
        Stream<String> streamMap = list.stream();
        streamFlatMap.flatMap((item) -> Arrays.stream(item.split(" "))).forEach(System.out::println);
        streamMap.map((item) -> Arrays.stream(item.split(" "))).forEach(System.out::println);
    }
}

输出结果

1
2
3
4
5
6
java.util.stream.ReferencePipeline$Head@3b9a45b3
java.util.stream.ReferencePipeline$Head@7699a589
java.util.stream.ReferencePipeline$Head@58372a00
  • 用map()方法,返回了一个“流中流”,需要在每个Stream元素遍历时,再加一层forEach进行遍历
    • map在接收到流后,直接将Stream放入到一个Stream中,最终整体返回一个包含了多个Stream的Stream
  • flatMap在接收到Stream后,会将接收到的Stream中的每个元素取出来放入一个Stream中,最后返回一个包含多个元素的Stream。

image-20210421224549012

5.4.3、排序

  • sorted()–自然排序(Comparable)
  • sorted(Comparator com)–定制排序(Comparator)
自然排序例子
public class TestStreamApi {
    public static void main(String[] args) {
        List<Integer > list = new ArrayList<>();
        Collections.addAll(list,34,56,89,65,87,80,87,95,100,34,45);
        System.out.println("当前的list = " + list);

        Stream<Integer> stream = list.stream();
        //找出>60的数据
        stream.filter((elem)->{
            return elem > 60;
        }).distinct().sorted().forEach(System.out::println);
        System.out.println("流终止操作之后的list:"+list);
    }
}
定制排序例子
public class TestStreamApi {
    public static void main(String[] args) {
        List<Integer > list = new ArrayList<>();
        Collections.addAll(list,34,56,89,65,87,80,87,95,100,34,45);
        System.out.println("当前的list = " + list);

        Stream<Integer> stream = list.stream();
        //找出>60的数据
        stream.filter((elem)->{
            return elem > 60;
        })
                .distinct()
                .sorted((x1,x2)->{return -Integer.compare(x1,x2);})
                .forEach(System.out::println);
        System.out.println("流终止操作之后的list:"+list);
    }
}

/*
输出结果是倒序

5.5、终止操作

  • allMatch–检查是否匹配所有元素
  • anyMatch–检查是否至少匹配一个元素
  • noneMatch–检查是否没有匹配所有元素
  • findFirst–返回第一个元素
  • findAny–返回当前流中的任意元素
  • count–返回流中元素的总个数
  • max–返回流中最大值
  • min–返回流中最小值

随便举一些例子

public class TestStreamApi3 {
    public static void main(String[] args) {
        List<Integer > list = new ArrayList<>();
        Collections.addAll(list,34,56,89,65,87,80,87,95,100,34,45);
        //创建Stream
        Stream<Integer> stream = list.stream();
        //进行中间操作
        stream = stream.filter((x)->{if(x>=60) return true; return false;})
                .distinct()
                .sorted((x1,x2)->{return -Integer.compare(x1,x2);})
                .limit(4)
                .map((x)->x+5)
                .skip(2);
        //进行终止操作:stream has already been operated upon or closed
        stream.forEach(System.out::println);//遍历 
//        System.out.println(stream.max((x1,x2)->x1-x2));;
//        System.out.println(stream.count());
//        System.out.println(stream.findFirst());

    }
}
/*
遍历的结果是 94 92
stream.max((x1,x2)->x1-x2): Optional[94]
stream.count(): 2
System.out.println(stream.findFirst()): Optional[94]

5.5.1、forEach

//使用forEach 输出了10个随机数
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);

5.5.2、归约——reduce()

Stream API的归约操作可以将流中元素反复结合起来,得到一个值

reduce()方法参数为BinaryOperator类型的累加器(它接受两个类型相同的参数,返回值类型跟参数类型相同),返回一个Optional对象。
实际上,Stream API中的mapToInt()方法返回的IntStream接口有类似的 average()、count()、sum()等方法就是做reduce操作,类似的还有mapToLong()、mapToDouble() 方法。当然,我们也可以用reduce()方法来自定义reduce操作

Optional<T> reduce(BinaryOperator<T> accumulator);

T reduce(T identity, BinaryOperator<T> accumulator);   //参数 identity,是进行后续计算的初始值

<U> U reduce(U identity,
                 BiFunction<U, ? super T, U> accumulator,
                 BinaryOperator<U> combiner);   // 用于并行流计算

从接口的角度

//Optional<T> reduce(BinaryOperator<T> accumulator);
private Integer calcTotal(List<Integer> numbers) {
	return numbers.stream()
		.reduce(new BinaryOperator<Integer>() {
			@Override
			public Integer apply(Integer integer, Integer integer2) {
				return integer + integer2;
			}
		}).get();
}

从Lambda表达式——将匿名内部类修改

//Optional<T> reduce(BinaryOperator<T> accumulator);
private Integer calcTotal(List<Integer> numbers) {
	return numbers.stream()
		.reduce((total, number) -> total + number).get();
}

5.5.3、收集——collect

collect:将流转换为其他形式,接收一个Collector接口实现 ,用于给Stream中汇总的方法

<R, A> R collect(Collector<? super T, A, R> collector);

<R> R collect(Supplier<R> supplier,
                  BiConsumer<R, ? super T> accumulator,
                  BiConsumer<R, R> combiner);

Collectors 不但有 toList 方法能将流转换为集合,还包括 toMap 转换为Map数据集合,还能分组

public class Example {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 1, 2, 3);
    }

    public static List<Integer> filterByStream(List<Integer> list){
        return list.stream()
                .filter((number)-> number > 1)
                .collect(Collectors.toList());
    }
}

5.6、流关闭

最好将流的操作放到try-with-resources,本章前面内容为了方便,没有放到try-with-resources中。


部分内容来自:【java8新特性】Stream API详解

部分内容来自:Java8新特性之三:Stream API

推荐资料:Java8新特性:无恨之都

推荐资料:《Java8 Stream编码实战》正式推出

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值