跳至主要內容

JAVA8 新特性

holic-x...大约 28 分钟JAVA基础

JAVA8 新特性

学习核心

  • 掌握JAVA8核心特性(结合实际应用阐述)
    • lambda表达式
      • Lambda表达式有什么好处?场景应用?
    • 方法引用
      • 方法引用是什么?有什么好处?
    • stream API
      • JAVA中的stream流?有什么功能?
      • stream应用场景是什么?
    • Optional
      • Optional是什么?应用场景是什么?

学习资料

Lambda表达式和函数式接口

1.Lambda表达式基础

Lambda表达式格式

// 1.不带参数
()-> System.out.println("x");

// 2.带参数
(val)-> System.out.println(val);

// 3.带单个参数(小括号可省略)
val -> System.out.println(val);

// 4.带多个参数(小括号不能省略)
(teacher,student)-> System.out.println(teacher.getName() + student.getName());

// 5.方法体
(x,y)->{...方法实现...}

// 6.lambda表达式的返回值
(x,y)->{
  ... 方法实现 ...
    return "xxx";
}

// 如果 Lambda 表达式所做的只是计算返回值并返回它,甚至可以省略 return 语句
(a1, a2) -> { return a1 > a2; }
(a1, a2) -> { a1 > a2; }

​ 在某些情况下,Lambda 表达式能够访问在 Lambda 函数体之外声明的变量。 Lambda 可以访问以下类型的变量:

  • 局部变量
  • 实例变量
  • 静态变量

函数式接口

只有一个抽象方法的接口被称为函数是式接口,从 Java 8 开始,Java 接口中可以包含默认方法和静态方法。默认方法和静态方法都有直接在接口声明中定义的实现。这意味着,Java lambda 表达式可以实现拥有多个方法的接口——只要接口中只有一个未实现的抽象方法就行。

​ 参考案例

interface MyInterface{
    // 抽象方法
    void doSth(String value);

    // 默认方法
    default void print(){
        System.out.println("hello default print");
    }

    // 静态方法
    static void show(){
        System.out.println("hello static show");
    }
}

/**
 * Lambda表达式案例
 */
public class LambdaDemo {
    public static void main(String[] args) {
        // 1.传统方式实现接口
        new MyInterface() {
             @Override
             public void doSth( String value) {
                 System.out.println("传统方式 doSth:" + value);
             }
         }.doSth("冲冲冲");

        // 2.Lambda表达式
        MyInterface myInterface = (val) -> {
            System.out.println("Lambda doSth:" + val);
        };
        myInterface.doSth("hello");
    }
}

Lambda表达式 VS 匿名类

​ 匿名类可以指定自己的内部状态(成员变量),例如此处定义一个eventState用于记录consume方法被调用的次数

​ 但Lambda不能定义成员变量

// 接口定义
interface MyEventConsumer{
    public void consume(Object event);
}


/**
 * Lambda表达式 VS 匿名类
 */
public class LambdaAnonymousDemo {

    public static void main(String[] args) {
        // 匿名类实现
        MyEventConsumer c1 = new MyEventConsumer() {
            // 定义内部状态
            private int eventState = 0;
            @Override
            public void consume(Object event) {
                System.out.println("匿名类实现" + event.toString() + "-" + (eventState++));
            }
        };
        c1.consume("hh");
        c1.consume("kk");

        // Lambda表达式实现
        MyEventConsumer c2 = (event)->{
            System.out.println("Lambda表达式实现" + event.toString());
        };
        c2.consume("xx");
    }

}

方法引用

​ 方法引用通过方法的名字来指向一个方法,它可以使语言的构造更紧凑简洁,减少冗余代码。

​ 使用规则:一对冒号 ::

import java.util.function.Supplier;

public class Car {
    // Supplier是jdk1.8的接口,这里和lambda一起使用
    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 class CarTest {
    public static void main(String[] args) {
        // 方式1:构造器引用:Class::new(或者一般的Class< T >::new)
        final Car car = Car.create( Car::new );
        final List< Car > cars = Arrays.asList( car );

        // 方式2:静态方法引用:Class::static_method
        cars.forEach( Car::collide );

        // 方式3:特定类的任意对象的方法引用:Class::method
        cars.forEach( Car::repair );

        // 方式4:特定对象的方法引用:instance::method
        final Car police = Car.create( Car::new );
        cars.forEach( police::follow );
    }
}

2.Lambda表达式应用

事件监听

​ 在 Java 中的事件侦听器通常被定义为具有单个方法的 Java 接口。以实现事件Listener为例

// 定义监听接口
interface StateChangeListener {
    public void onStateChange(State oldState, State newState);
}

// 定义事件监听器(可注册状态)
public class StateOwner {
    public void addStateListener(StateChangeListener listener) { ... }
}

​ 传统方式:可使用匿名类实现 StateChangeListener 接口,然后为 StateOwner 实例添加侦听器

StateOwner stateOwner = new StateOwner();
stateOwner.addStateListener(new StateChangeListener() {
    public void onStateChange(State oldState, State newState) {
        // do something with the old and new state.
        System.out.println("State changed")
    }
});

​ Lambda表达式:Java8引入Lambda之后,可以使用 Lambda 表达式实现 StateChangeListener 接口

StateOwner stateOwner = new StateOwner();

stateOwner.addStateListener(
    (oldState, newState) -> System.out.println("State changed")
);

Stream API

Stream的引入

​ 现在很多大数据量系统中都存在分表分库的情况。

​ 例如,电商系统中的订单表,常常使用用户 ID 的 Hash 值来实现分表分库,这样是为了减少单个表的数据量,优化用户查询订单的速度。

​ 但在后台管理员审核订单时,需要将各个数据源的数据查询到应用层之后进行合并操作。

​ 例如,当需要查询出过滤条件下的所有订单,并按照订单的某个条件进行排序,单个数据源查询出来的数据是可以按照某个条件进行排序的,但多个数据源查询出来已经排序好的数据,并不代表合并后是正确的排序,所以需要在应用层对合并数据集合重新进行排序。

​ 在 Java8 之前,通常是通过 for 循环或者 Iterator 迭代来重新排序合并数据,又或者通过重新定义 Collections.sorts 的 Comparator 方法来实现,这两种方式对于大数据量系统来说,效率并不是很理想。

​ Java8 中添加了一个新的接口类 Stream,他和字节流概念不太一样,Java8 集合中的 Stream 相当于高级版的 Iterator,可以通过 Lambda 表达式对集合进行各种非常便利、高效的聚合操作(Aggregate Operation),或者大批量数据操作 (Bulk Data Operation)。

​ Stream 的聚合操作与数据库 SQL 的聚合操作 sorted、filter、map 等类似。在应用层就可以高效地实现类似数据库 SQL 的聚合操作了,而在数据操作方面,Stream 不仅可以通过串行的方式实现数据操作,还可以通过并行的方式处理大批量数据,提高数据的处理效率。

1.Stream API基础概念

​ Java 的 Stream API 提供了一种处理对象集合的函数式方法。 Stream 是和 Lambda 表达式等其他几个函数式编程特性一起在 Java 8 被引入的。这个篇教程将解释 Stream API 提供的这些函数式方法是如何工作的,以及怎么使用它们。

​ 注意,Java 的 Stream API 与 Java IO 的 InputStream 和 OutputStream 没有任何关系,不要因为名字类似造成误解。 InputStream 和 OutputStream 是与字节流有关,而 Java 的 Stream API 用于处理对象流。

Stream定义

​ Java 的 Stream 是一个能够对其元素进行内部迭代的组件,这意味着它可以自己迭代其元素。相反地,当使用 Collection 的迭代功能,例如,从 Collection 获取Iterator 或者使用 Iterable 接口 的 forEach 方法这些方式进行迭代时,必须自己实现集合元素的迭代逻辑

流处理

​ 可以将 Listener 方法或者叫处理器方法附加到 Stream 上。当 Stream 在内部迭代元素时,将以元素为参数调用这些处理器。Stream 会为流中的每个元素调用一次处理器。所以每个处理器方法都可以处理 Stream 中的每个元素,称为流处理。

​ 流的多个处理器方法可以形成一个调用链。链上的前一个处理器处理流中的元素,返回的新元素会作为参数传给链中的下一个处理器处理。当然,处理器可以返回相同的元素或新元素,具体取决于处理器的目的和用途

流处理的构成

​ 一般有很多方法获取Stream,最常见的是从Collection对象中获取Stream。集合对象都实现了 Collection 接口,所以通过接口里定义的 stream 方法获救获取到由集合元素构成的 Steam

Stream<String> stream = items.stream(); 

​ 在对流进行处理时,不同的流操作以级联的方式形成处理链。一个流的处理链由一个源(source),0 到多个中间操作(intermediate operation)和一个终结操作(terminal operation)完成。

  • 源:源代表 Stream 中元素的来源,比如我们上面看到的集合对象。
  • 中间操作:中间操作,在一个流上添加的处理器方法,他们的返回结果是一个新的流。这些操作是延迟执行的,在终结操作启动后才会开始执行。
  • 终结操作:终结流操作是启动元素内部迭代、调用所有处理器方法并最终返回结果的操作
public class StreamDemo {
    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();
        stringList.add("ONE");
        stringList.add("TWO");
        stringList.add("THREE");
        // 获取流
        Stream<String> stream = stringList.stream();
        // 流处理
        long count = stream
                .map((value) -> value.toLowerCase())
                .count();

        System.out.println("count = " + count);
    }
}

​ map() 方法的调用是一个中间操作。它只是在流上设置一个 Lambda 表达式,将每个元素转换为小写形式。而对 count() 方法的调用是一个终结操作。此调用会在内部启动迭代,开始流处理,这将导致每个元素都转换为小写然后计数。

​ 将元素转换为小写实际上并不影响元素的计数。转换部分只是作为 map() 是一个中间操作的示例

流的中间操作(调用链)

​ Stream API 的中间(非终结)流操作是转换或者过滤流中元素的操作。当把中间操作添加到流上时,会得到一个新的流作为结果。如果需要将多个操作链接在一起,则只能将第二个操作应用于第一个操作产生的 Stream 实例上

List<String> stringList = new ArrayList<>();

stringList.add("ONE");
stringList.add("TWO");
stringList.add("THREE");
    
Stream<String> stream = stringList.stream();

// 流的中间操作(返回一个新的流)
Stream<String> stringStream = stream.map((value) -> value.toLowerCase());

// 多个操作链(stringStream1基于stream构建,stringStream2基于stringStream1构建)
Stream<String> stringStream1 = stream.map((value) -> value.toLowerCase());
Stream<String> stringStream2 = stringStream1.map((value) -> value.toUpperCase());
// 一般将Stream上所有的中间操作串联成一个调用链
Stream<String> stream1 = stream
  .map((value) -> value.toLowerCase())
  .map((value) -> value.toUpperCase())
  .map((value) -> value.substring(0,3));

2.常用方法

map

​ map() 方法将一个元素转换(或者叫映射)到另一个对象。例如,一个字符串列表,map() 可以将每个字符串转换为小写、大写或原始字符串的子字符串,或完全不同的东西

List<String> list = new ArrayList<String>();
Stream<String> stream = list.stream();
Stream<String> streamMapped = stream.map((value) -> value.toUpperCase());

filter

​ filter() 用于从 Stream 中过滤掉元素。 filter 方法接受一个 Predicate (也是一个函数式接口),filter() 为流中的每个元素调用 Predicate。如果元素要包含在 filter() 返回结果的流中,则 Predicate 应返回 true。如果不应包含该元素,则 Predicate 应返回 false

Stream<String> longStringsStream = stream.filter((value) -> {
    // 元素长度大于等于3,返回true,会被保留在 filter 产生的新流中。
    return value.length() >= 3;
});

flatMap

​ flatMap方法接受一个 Lambda 表达式, Lambda 的返回值必须也是一个stream类型,flatMap方法最终会把所有返回的stream合并。map 与 flatMap 方法很像,都是以某种方式转换流中的元素。如果需要将每个元素转换为一个值,则使用 map 方法,如果需要将每个元素转换为多个值组成的流,且最终把所有元素的流合并成一个流,则需要使用 flatMap 方法。 在效果上看是把原来流中的每个元素进行了“展平”

public class StreamDemo {
    public static void main(String[] args) {

        List<String> stringList = new ArrayList<String>();

        stringList.add("One flew over the cuckoo's nest");
        stringList.add("To kill a muckingbird");
        stringList.add("Gone with the wind");

        Stream<String> stream = stringList.stream();

        stream.flatMap((value) -> {
            String[] split = value.split(" ");
            return Arrays.asList(split).stream();
        }).forEach((value) -> System.out.println(value));

    }
}

​ 在上面的例子中,每个字符串元素被拆分成单词,变成一个 List,然后从这个 List 中获取并返回流,flatMap 方法最终会把这些流合并成一个,所以最后用流终结操作 forEach 方法,遍历并输出了每个单词

distinct

​ distinct() 会返回一个仅包含原始流中不同元素的新 Stream 实例,任何重复的元素都将会被去掉

List<String> stringList = new ArrayList<String>();

stringList.add("one");
stringList.add("two");
stringList.add("three");
stringList.add("one");

Stream<String> stream = stringList.stream();
List<String> distinctStrings = stream
        .distinct()
        .collect(Collectors.toList());
System.out.println(distinctStrings);

// output
[one, two, three]

limit

​ limit 操作会截断原始流,返回最多只包含给定数量个元素的新流

List<String> stringList = new ArrayList<String>();

stringList.add("one");
stringList.add("two");
stringList.add("three");
stringList.add("one");

Stream<String> stream = stringList.stream();
stream.limit(2)
  .forEach( element -> System.out.println(element));

// output
one
two

peek

​ peek() 方法是一个以 Consumer (java.util.function.Consumer,Consumer 代表的是消费元素但不返回任何值的方法) 作为参数的中间操作,它返回的流与原始流相同。当原始流中的元素开始迭代时,会调用 peek 方法中指定的 Consumer 实现对元素进行处理。

​ 正如 peek 操作名称的含义一样,peek() 方法的目的是查看流中的元素,而不是转换它们。跟其他中间操作的方法一样,peek() 方法不会启动流中元素的内部迭代,流需要一个终结操作才能开始内部元素的迭代。

​ peek() 方法在流处理的 DEBUG 上的应用甚广,比如我们可以利用 peek() 方法输出流的中间值,方便我们的调试。

Stream.of("one", "two", "three","four").filter(e -> e.length() > 3)
                .peek(e -> System.out.println("Filtered value: " + e))
                .map(String::toUpperCase)
                .peek(e -> System.out.println("Mapped value: " + e))
                .collect(Collectors.toList());
// output: 输出调试信息
Filtered value: three
Mapped value: THREE
Filtered value: four
Mapped value: FOUR

3.流的终结操作

​ Stream 的终结操作通常会返回单个值,一旦一个 Stream 实例上的终结操作被调用,流内部元素的迭代以及流处理调用链上的中间操作就会开始执行,当迭代结束后,终结操作的返回值将作为整个流处理的返回值被返回。

案例分析

long count = stream
  .map((value) -> value.toLowerCase())
  .map((value) -> value.toUpperCase())
  .map((value) -> value.substring(0,3))
  .count();
// Stream 的终结操作 count() 被调用后整个流处理开始执行,最后将 count() 的返回值作为结果返回,结束流操作的执行。这也是为什么把他们命名成流的终结操作的原因

anyMatch

​ anyMatch() 方法以一个 Predicate (java.util.function.Predicate 接口,它代表一个接收单个参数并返回参数是否匹配的函数)作为参数,启动 Stream 的内部迭代,并将 Predicate 参数应用于每个元素。如果 Predicate 对任何元素返回了 true(表示满足匹配),则 anyMatch() 方法的结果返回 true。如果没有元素匹配 Predicate,anyMatch() 将返回 false

public class StreamAnyMatchExample {
    public static void main(String[] args) {
        List<String> stringList = new ArrayList<String>();
        stringList.add("One flew over the cuckoo's nest");
        stringList.add("To kill a muckingbird");
        stringList.add("Gone with the wind");

        Stream<String> stream = stringList.stream();

        boolean anyMatch = stream.anyMatch((value) -> value.startsWith("One"));
        System.out.println(anyMatch);

    }
}

// output
true

allMatch

​ allMatch() 方法同样以一个 Predicate 作为参数,启动 Stream 中元素的内部迭代,并将 Predicate 参数应用于每个元素。如果 Predicate 为 Stream 中的所有元素都返回 true,则 allMatch() 的返回结果为 true。如果不是所有元素都与 Predicate 匹配,则 allMatch() 方法返回 false

public class StreamAllMatchExample {
    public static void main(String[] args) {

        List<String> stringList = new ArrayList<String>();

        stringList.add("One flew over the cuckoo's nest");
        stringList.add("To kill a muckingbird");
        stringList.add("Gone with the wind");

        Stream<String> stream = stringList.stream();

        boolean allMatch = stream.allMatch((value) -> value.startsWith("One"));
        System.out.println(allMatch);

    }
}

// output
false

noneMatch

Match 系列里还有一个 noneMatch 方法,顾名思义,如果流中的所有元素都与作为 noneMatch 方法参数的 Predicate 不匹配,则方法会返回 true,否则返回 false

public class StreamNoneExample {

    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();

        stringList.add("abc");
        stringList.add("def");

        Stream<String> stream = stringList.stream();

        boolean noneMatch = stream.noneMatch((element) -> {
            return "xyz".equals(element);
        });

        System.out.println("noneMatch = " + noneMatch); //输出 noneMatch = true

    }

}

collect

​ collect() 方法被调用后,会启动元素的内部迭代,并将流中的元素收集到集合或对象中

public class StreamCollectExample {
    public static void main(String[] args) {

        List<String> stringList = new ArrayList<>();

        stringList.add("One flew over the cuckoo's nest");
        stringList.add("To kill a muckingbird");
        stringList.add("Gone with the wind");

        Stream<String> stream = stringList.stream();

        List<String> stringsAsUppercaseList = stream
                .map(value -> value.toUpperCase())
                .collect(Collectors.toList());

        System.out.println(stringsAsUppercaseList);

    }
}
// output
[ONE FLEW OVER THE CUCKOO'S NEST, TO KILL A MUCKINGBIRD, GONE WITH THE WIND]

​ collect() 方法将收集器 -- Collector (java.util.stream.Collector) 作为参数。在上面的示例中,使用的是 Collectors.toList() 返回的 Collector 实现。这个收集器把流中的所有元素收集到一个 List 中去

count

​ count() 方法调用后,会启动 Stream 中元素的迭代,并对元素进行计数

public class StreamCountExample {
    public static void main(String[] args) {

        List<String> stringList = new ArrayList<String>();

        stringList.add("One flew over the cuckoo's nest");
        stringList.add("To kill a muckingbird");
        stringList.add("Gone with the wind");

        Stream<String> stream = stringList.stream();

        long count = stream.flatMap((value) -> {
            String[] split = value.split(" ");
            return Arrays.asList(split).stream();
        }).count();

        System.out.println("count = " + count); // count = 14
    }
}

// output
count = 14

​ 首先创建一个字符串 List ,然后获取该 List 的 Stream,为其添加了 flatMap() 和 count() 操作。 count() 方法调用后,流处理将开始迭代 Stream 中的元素,处理过程中字符串元素在 flatMap() 操作中被拆分为单词、合并成一个由单词组成的 Stream,然后在 count() 中进行计数。所以最终打印出的结果是 count = 14

findAny

​ findAny() 方法可以从 Stream 中找到单个元素。找到的元素可以来自 Stream 中的任何位置。且它不提供从流中的哪个位置获取元素的保证

public class StreamFindAnyExample {
    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();

        stringList.add("one");
        stringList.add("two");
        stringList.add("three");
        stringList.add("one");

        Stream<String> stream = stringList.stream();

        Optional<String> anyElement = stream.findAny();
        if (anyElement.isPresent()) {
            System.out.println(anyElement.get());
        } else {
            System.out.println("not found");
        }
    }
}
// output
one

​ findAny() 方法会返回一个 Optional,意味着 Stream 可能为空,因此没有返回任何元素。我们可以通过 Optional 的 isPresent() 方法检查是否找到了元素(Optional 类是一个可以为 null 的容器对象。如果值存在则 isPresent() 方法会返回true,调用get()方法会返回容器中的对象,否则抛出异常:NoSuchElementException)

findFirst

​ findFirst() 方法将查找 Stream 中的第一个元素,跟 findAny() 方法一样,也是返回一个 Optional,我们可以通过 Optional 的 isPresent() 方法检查是否找到了元素

public class StreamFindFirstExample {
    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();

        stringList.add("one");
        stringList.add("two");
        stringList.add("three");
        stringList.add("one");

        Stream<String> stream = stringList.stream();

        Optional<String> anyElement = stream.findFirst();
        if (anyElement.isPresent()) {
            System.out.println(anyElement.get());
        } else {
            System.out.println("not found");
        }
    }
}
// output
one

forEach

​ forEach() 方法我们在介绍 Collection 的迭代时介绍过,当时主要是拿它来迭代 List 的元素。它会启动 Stream 中元素的内部迭代,并将 Consumer (java.util.function.Consumer, 一个函数式接口,上面介绍过) 应用于 Stream 中的每个元素。 注意 forEach() 方法的返回值是 void

public class StreamForEachExample {
    public static void main(String[] args) {
        List<String> stringList = new ArrayList<String>();

        stringList.add("one");
        stringList.add("two");
        stringList.add("three");

        Stream<String> stream = stringList.stream();

        stream.forEach(System.out::println);
    }
}
// output
one
two
three

min

​ min() 方法返回 Stream 中的最小元素。哪个元素最小是由传递给 min() 方法的 Comparator 接口open in new window实现来确定的

public class StreamMinExample {
    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();

        stringList.add("abc");
        stringList.add("def");
        Stream<String> stream = stringList.stream();

        // 作为 min 方法参数的Lambda 表达式可以简写成 String::compareTo
        // Optional<String> min = stream.min(String::compareTo);
        Optional<String> min = stream.min((val1, val2) -> {
            return val1.compareTo(val2);
        });

        String minString = min.get();

        System.out.println(minString); // abc
    }
}
// output
abc

​ min() 方法返回的是一个 Optional ,也就是它可能不包含结果。如果为空,直接调用 Optional 的 get() 方法将抛出 异常--NoSuchElementException。比如我们把上面的 List 添加元素的两行代码注释掉后,运行程序就会报

Exception in thread "main" java.util.NoSuchElementException: No value present
	at java.util.Optional.get(Optional.java:135)
	at com.example.StreamMinExample.main(StreamMinExample.java:21)

​ 所以最好先用 Optional 的 ifPresent() 判断一下是否包含结果,再调用 get() 获取结果

max

​ 与 min() 方法相对应,max() 方法会返回 Stream 中的最大元素,max() 方法的参数和返回值跟 min() 方法的也都一样,只需要把上面求最小值的方法替换成求最大值的方法 max() 即可

Optional<String> min = stream.max(String::compareTo);

reduce

​ reduce() 方法,是 Stream 的一个聚合方法,它可以把一个 Stream 的所有元素按照聚合函数聚合成一个结果。reduce()方法接收一个函数式接口 BinaryOperator 的实现,它定义的一个apply()方法,负责把上次累加的结果和本次的元素进行运算,并返回累加的结果

public class StreamReduceExample {
    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();

        stringList.add("One flew over the cuckoo's nest");
        stringList.add("To kill a muckingbird");
        stringList.add("Gone with the wind");

        Stream<String> stream = stringList.stream();

        Optional<String> reduced = stream.reduce((value, combinedValue) -> combinedValue + " + " + value);
        // 写程序的时候记得别忘了 reduced.ifPresent() 检查结果里是否有值
        System.out.println(reduced.get());
    }
}
// output
Gone with the wind + To kill a muckingbird + One flew over the cuckoo's nest

​ reduce() 方法的返回值同样是一个 Optional 类的对象,所以在获取值前别忘了使用 ifPresent() 进行检查。

​ streadm 实现了多个版本的reduce() 方法,还有可以直接返回元素类型的版本,比如使用 reduce 实现整型Stream的元素的求和

public class IntegerStreamReduceSum {
    public static void main(String[] args) {
        List<Integer> intList = new ArrayList<>();
        intList.add(10);
        intList.add(9);
        intList.add(8);
        intList.add(7);
        Integer sum = intList.stream().reduce(0, Integer::sum);
        System.out.printf("List 求和,总和为%s\n", sum);
    }
}
// output
List 求和,总和为34

toArray

toArray() 方法是一个流的终结操作,它会启动流中元素的内部迭代,并返回一个包含所有元素的 Object 数组

List<String> stringList = new ArrayList<String>();

stringList.add("One flew over the cuckoo's nest");
stringList.add("To kill a muckingbird");
stringList.add("Gone with the wind");

Stream<String> stream = stringList.stream();

// 方法1
Object[] objects = stream.toArray();

// 方法2(允许传入指定类型数组的构造方法,比如用 toArray 把流中的元素收集到字符串数组中)
String[] strArray = stream.toArray(String[]::new);

4.流的拼接

Java 的Stream 接口包含一个名为 concat() 的静态方法,它可以将两个流连接成一个

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

public class StreamConcatExample {

    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();

        stringList.add("One flew over the cuckoo's nest");
        stringList.add("To kill a muckingbird");
        stringList.add("Gone with the wind");

        Stream<String> stream1 = stringList.stream();

        List<String> stringList2 = new ArrayList<>();
        stringList2.add("Lord of the Rings");
        stringList2.add("Planet of the Rats");
        stringList2.add("Phantom Menace");

        Stream<String> stream2 = stringList2.stream();

        Stream<String> concatStream = Stream.concat(stream1, stream2);

        List<String> stringsAsUppercaseList = concatStream
                .collect(Collectors.toList());

        System.out.println(stringsAsUppercaseList);
    }
}

从数组创建流

​ 上面关于 Stream 的例子都是从 Collection 实例的 stream() 方法获取的集合包含的所有元素的流,除了这种方法之外,Java 的 Stream 接口中提供了一个名为 of 的静态方法,能支持从单个,多个对象或者数组对象快速创建流

import java.util.stream.Stream;

public class StreamExamples {

    public static void main(String[] args) {
        Stream<String> stream1 = Stream.of("one", "two", "three");

        Stream<String> stream2 = Stream.of(new String[]{"one", "two"});

        System.out.println(stream1.count()); // 输出3
        System.out.println(stream2.count()); // 输出2
    }
}

5.项目中高频使用的StreamAPI操作

场景1:求两个对象List的交集/差集

// 定义对象
class Person {
    String id;
    String nickName;

    public Person(String id, String nickName) {
        this.id = id;
        this.nickName = nickName;
    }

    @Override
    public String toString() {
        return "A{" +
                "id='" + id + '\'' +
                ", nickName='" + nickName + '\'' +
                '}';
    }

    public String getId() {
        return id;
    }

    public String getNickName() {
        return nickName;
    }
}

求交集


/**
 * 场景1:求两个对象List的交集/差集
 */
public class GetObjectListIntersection {
    public static void main(String[] args) {
        List<Person> aList = new ArrayList<>(Arrays.asList(
                new Person("1", "张三"),
                new Person("2", "李四"),
                new Person("3", "王五")
        ));

        List<Person> bList = new ArrayList<>(Arrays.asList(
                new Person("2", "李四"),
                new Person("3", "王五"),
                new Person("4", "赵六")
        ));

        // aList 与 bList 的交集 (在两个集合中都存在的元素)
        List<Person> intersections = aList
                .stream() //获取第一个集合的Stream1
                .filter(  //取出Stream1中符合条件的元素组成新的Stream2,lambda表达式1返回值为true时为符合条件
                        a ->  //lambda表达式1,a为lambda表达式1的参数,是Stream1中的每个元素
                                bList.stream() //获取第二个集合的Stream3
                                        .map(Person::getId) //将第二个集合每个元素的id属性取出来,映射成新的一个Stream4
                                        .anyMatch( //返回值(boolean):Stream4中是否至少有一个元素使lambda表达式2返回值为true
                                                id -> //lambda表达式2,id为lambda表达式2的参数,是Stream4中的每个元素
                                                        Objects.equals(a.getId(), id) //判断id的值是否相等
                                        )
                )
                .collect(Collectors.toList()); //将Stream2转换为List
        System.out.println("----------aList 与 bList 的交集为:");
        System.out.println(intersections);
    }
}

// output
----------aList 与 bList 的交集为:
[A{id='2', nickName='李四'}, A{id='3', nickName='王五'}]

求差集

List<Person> differences = bList.
    stream().
    filter(
    	b -> 
    	aList.stream()
        	.map(Person::getId)
        	.noneMatch(
                id -> 
                Objects.equals(b.getId(), id)
            )
    ).collect(Collectors.toList());

System.out.println("----------bList 与 aList 的差集为:");
System.out.println(differences);
// 求差集(优化高效版)
Map<String, A> aMap = aList.stream().collect(Collectors.toMap(A::getId, Function.identity())) ;
List<A> diffEffective = bList.stream().filter(b -> !aMap.containsKey(b.getId())).collect(Collectors.toList());
System.out.println("----------bList 与 aList 的差集为:");
System.out.println(diffEffective);

场景2:Stream中排序对象集合

class User {
    private Long userId;
    private String name;
    private Integer age;
    private Double salary;

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public User(Long userId, String name, Integer age, Double salary) {
        this.userId = userId;
        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;
    }
}

正序排序

Stream API 里有一个 sorted 方法,通过该方法就能Stream 元素按照对象的指定的字段进行排序

public class GetSortObjDemo {

    public static void main(String[] args) {
        List<User> userList = Arrays.asList(new User(1000L, "First", 25, 30000D),
                new User(2000L, "Second", 30, 45000D),
                new User(3000L, "Third", 35, 25000D));

        // 使用字段包装类型的 compareTo 进行排序, 这个方式可以简化成下面的使用 Comparator.comparing 的例子
        userList.stream().sorted(
                        (p1, p2) ->
                                (p1.getUserId().compareTo(p2.getUserId()))
                )
                .forEach(user -> System.out.println(user.getName()));

    }

}

Stream 的 sorted 方法内部,这个例子使用的是Person对象personId字段的包装类型实现的compareTo方法进行的比较。 这个方式可以进一步简化成下面的使用Comparator.comparing方法的方式

personList.stream()
    .sorted(
        Comparator.comparing(Person::getPersonId)
    ).forEach(person -> System.out.println(person.getName()));

Comparator.comparing()该方法是一个泛型方法,会根据参数的类型,内部调用相应类型实现的CompareTo方法进行两个参数的比较。

​ 上面的例子是把Stream里的元素按照Person对象personId的正序进行排序,运行例程后会看到下面的输出,按照personId的正序输出了每个Person对象的名字

倒序排序

​ 如果需要按照倒序对Stream中的元素进行排列,可以在Comparator.comparing()后面追加reversed()调用

personList.stream().sorted(
    Comparator.comparing(
        Person::getPersonId).reversed()
    ).forEach(person -> System.out.println(person.getName()));

多个字段排序

​ 在Streamsorted步骤里使用Comparator.comparing()的好处是,除了倒序排序非常方便,我们还可以根据多字段进行排--比如先按Person对象的personId字段,如果再按Person对象的年龄age字段进行排序。这个时候可以在Comparator.comparaing调用后,再接上thenComparing调用,实现元素按照多字段进行排序的功能。

public static void main(String[] args) {
    List<Person> personList = Arrays.asList(new Person(1000L, "First", 25, 30000D),
                new Person(1000L, "Second", 22, 45000D),
                new Person(3000L, "Third", 35, 25000D));
    // 先按personId 排序,再按 age 排序
    personList.stream().sorted(
        Comparator.comparing(Person::getPersonId)
        .thenComparing(Person::getAge)
    ).forEach(person -> System.out.println(person.getName()));

场景3:在Stream 迭代中使用元素索引

​ 在 Stream 操作中没办法像 For 循环那样拿到当前迭代的元素的自然索引,虽然 Java 11 里有办法可以当前迭代元素的自然索引,因为国内大部分公司的 JDK 版本还是 8 ,所以只能另外用其他办法。

​ 有另外一个办法,程序可以借助AtomicInteger的获取并自增方法getAndIncrement方法实现在 Stream 迭代中拿到当前迭代元素的自然索引的效果,看下面这个示例程序

import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;

// Java8 里没办法直接访问 stream 里的元素,不过可以用下面这种方式实现
public class AtomicIntegerAsStreamIndices {
    public static void main(String[] args)
    {
        String[] array = { "A", "B", "C", "D"  };

        AtomicInteger index = new AtomicInteger();
        Arrays.stream(array)
                .map(str -> index.getAndIncrement() + " -> " + str)
                .forEach(System.out::println);
    }
}

// output
0 -> A
1 -> B
2 -> C
3 -> D

场景4:把对象 List 转换为对象 Map

​ 一般为了提高程序效率的时候,我们会把对象 List 转换成以对象 Id 为 Key 的对象 Map。用 Stream 我们能简单便捷的把 List 转换成 Map ,免去了在程序里写两层循环的窘境。

class Animal {
    private int id;
    private String name;

    public Animal(int id, String name) {
        this.id = id;
        this.name = name;
    }

    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 class ListStreamToMap {
    public static void main(String[] args) {
        List<Animal> aList = new ArrayList<>();
        aList.add(new Animal(1, "Elephant"));
        aList.add(new Animal(2, "Bear"));

        Map<Integer, Animal> map = aList.stream()
                .collect(Collectors.toMap(Animal::getId, Function.identity()));

        map.forEach((integer, animal) -> {
            System.out.println(animal.getName());
        });

    }
}

Optional类

NPE(NullPointerException)

场景分析

​ 在对一个对象进行非空判断,对比传统方式和Optional

// 定义会员类
class Member {
    private String name;
    Member(String name) {this.name = name;}
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

class WithoutOptionalDemo {
    // 定义一个方法模拟获取会员信息
    public static Member getMemberByIdFromDB() {
        // 当前 ID 的会员不存在
        return null;
    }

    public static void main(String[] args) {
        // 传统方式NPE判断
        Member mem = getMemberByIdFromDB();
        if (mem != null) {
            System.out.println(mem.getName());
        }
    }
}

​ 如果代码中没有对member对象做null校验,则在访问该对象的时候就会报NPE,而在程序设计过程中比较让人头痛的就是这个问题。Java8提供的Optional则是为了解决NPE应运而生

class WithOptionalDemo {
    public static Optional<Member> getMemberByIdFromDB(boolean hasName) {
        if (hasName) {
            return Optional.of(new Member("noob"));
        }
        return Optional.empty();
    }
    public static void main(String[] args) {
        // 通过Optional处理NPE
        Optional<Member> member = getMemberByIdFromDB(false);
        member.ifPresent((m)->{System.out.println("会员姓名:" + m.getName());});
    }
}

​ 从上面的代码可以看到,通过控制hasName参数来模拟会员是否存在,hasName为true会员存在则默认返回一个会员信息。如果hasName为false,则会员信息不存在,此时返回一个Optional.empty()。

​ 而在调用的时候则是通过Optional去接收对象,随后通过Optional提供的ifPresent方法来进一步对对象进行操作。

1.Optional语法规则

创建Optional对象

// 1.使用静态方法 empty() 创建一个空的 Optional 对象
Optional<String> emptyObj = Optional.empty();
System.out.println(emptyObj);
// 2.使用静态方法 of() 创建一个非空的 Optional 对象
Optional<Member> member = Optional.of(new Member("noob"));
System.out.println(member);
// 如果使用of()方法,传递的参数必须非空(如果传递参数为null还是会抛出NPE)
// Optional<Member> nullMember = Optional.of(null);// 错误-抛出NPE
// 3.使用ofNullable() 创建一个即可空又可非空的 Optional 对象
String name = null;
Optional<String> optOrNull = Optional.ofNullable(name);
System.out.println(optOrNull); // 输出:Optional.empty

判断值是否存在

​ 通过方法 isPresent() 判断一个 Optional 对象是否存在,如果存在,该方法返回 true,否则返回 false——取代了 obj != null 的判断

// 判断值是否存在
Optional<String> opt = Optional.of("noob");
System.out.println(opt.isPresent()); // 输出:true
Optional<String> optOrNullObj = Optional.ofNullable(null);
System.out.println(optOrNullObj.isPresent()); // 输出:false

​ Java11之后可以通过方法 isEmpty() 判断与 isPresent() 相反的结果

System.out.println(opt.isEmpty());

非空表达式

​ Optional 类有一个非常现代化的方法——ifPresent(),允许开发者使用函数式编程的方式执行一些代码,称为非空表达式。如果没有该方法的话,我们通常需要先通过 isPresent() 方法对 Optional 对象进行判空后再执行相应的代码

// 传统方式:判断对象是否存在,然后执行操作
Optional<String> optOrNull = Optional.ofNullable(null);
if (optOrNull.isPresent()) {
    System.out.println(optOrNull.get().length());
}

// 引入非空表达式(上面的实现可以简化为下述形式)
Optional<String> optOrNull = Optional.ofNullable(null);
optOrNull.ifPresent(str -> System.out.println(str.length()));

// Java9之后通过方法 ifPresentOrElse(action, emptyAction) 执行两种结果,非空时执行 action,空时执行 emptyAction
Optional<String> opt = Optional.of("noob");
opt.ifPresentOrElse(str -> System.out.println(str.length()), () -> System.out.println("为空"));

设置(获取)默认值

​ 有时候,在创建(获取) Optional 对象的时候,需要一个默认值,orElse()orElseGet() 方法就派上用场了。orElse() 方法用于返回包裹在 Optional 对象中的值,如果该值不为 null,则返回;否则返回默认值。该方法的参数类型和值的类型一致。

String nullName = null;
String name = Optional.ofNullable(nullName).orElse("noob");
System.out.println(name); // 输出:noob

orElseGet() 方法与 orElse() 方法类似,但参数类型不同。如果 Optional 对象中的值为 null,则执行参数中的函数。

String nullName = null;
String name = Optional.ofNullable(nullName).orElseGet(()->"noob");
System.out.println(name); // 输出:noob

获取值

​ 直观从语义上来看,get() 方法才是最正宗的获取 Optional 对象值的方法,但很遗憾,该方法是有缺陷的,因为假如 Optional 对象的值为 null,该方法会抛出 NoSuchElementException 异常。这完全与我们使用 Optional 类的初衷相悖

public class GetOptionalDemo {
    public static void main(String[] args) {
        String name = null;
        Optional<String> optOrNull = Optional.ofNullable(name);
        System.out.println(optOrNull.get());
    }
}
Exception in thread "main" java.util.NoSuchElementException: No value present
	at java.base/java.util.Optional.get(Optional.java:141)
	at com.cmower.dzone.optional.GetOptionalDemo.main(GetOptionalDemo.java:9)

​ 尽管抛出的异常是 NoSuchElementException 而不是 NPE,但在我们看来,显然是在“五十步笑百步”。建议 orElseGet() 方法获取 Optional 对象的值

过滤值

​ filter方法用于过滤

public class FilterOptionalDemo {
    public static void main(String[] args) {
        String password = "12345";
        Optional<String> opt = Optional.ofNullable(password);
        System.out.println(opt.filter(pwd -> pwd.length() > 6).isPresent());
    }
}

filter() 方法的参数类型为 Predicate(Java 8 新增的一个函数式接口),也就是说可以将一个 Lambda 表达式传递给该方法作为条件,如果表达式的结果为 false,则返回一个 EMPTY 的 Optional 对象,否则返回过滤后的 Optional 对象。

​ 在上例中,由于 password 的长度为 5 ,所以程序输出的结果为 false。假设密码的长度要求在 6 到 10 位之间,那么还可以再追加一个条件。

Predicate<String> len6 = pwd -> pwd.length() > 6;
Predicate<String> len10 = pwd -> pwd.length() < 10;

password = "1234567";
opt = Optional.ofNullable(password);
boolean result = opt.filter(len6.and(len10)).isPresent();
System.out.println(result);

转化值

map() 方法:可以按照一定的规则将原有 Optional 对象转换为一个新的 Optional 对象,原有的 Optional 对象不会更改。

public class OptionalMapDemo {
    public static void main(String[] args) {
        String name = "noob";
        Optional<String> nameOptional = Optional.of(name);
        Optional<Integer> intOpt = nameOptional
                .map(String::length);
        
        System.out.println( intOpt.orElse(0));
    }
}

​ 在上面这个例子中,map() 方法的参数 String::length,意味着要 将原有的字符串类型的 Optional 按照字符串长度重新生成一个新的 Optional 对象,类型为 Integer。

​ 把 map() 方法与 filter() 方法结合起来用,前者用于将密码转化为小写,后者用于判断长度以及是否是“password”。

public class OptionalMapFilterDemo {
    public static void main(String[] args) {
        String password = "password";
        Optional<String>  opt = Optional.ofNullable(password);

        Predicate<String> len6 = pwd -> pwd.length() > 6;
        Predicate<String> len10 = pwd -> pwd.length() < 10;
        Predicate<String> eq = pwd -> pwd.equals("password");

        boolean result = opt.map(String::toLowerCase).filter(len6.and(len10 ).and(eq)).isPresent();
        System.out.println(result);
    }
}
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3