Java 8 相关整理

文前说明

作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。

本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。

1. Lambda 表达式

  • Lambda 允许把 函数 作为一个方法的参数传递进方法中。
(parameters) -> expression
(parameters) -> { statements; }

1.1 表达式重要特征

  • 可选类型声明
    • 不需要声明参数类型,编译器可以统一识别参数值。
public class Test {

    public static void main(String[] args) {
        Operation subtractionA = (int a, int b) -> a - b;
        Operation subtractionB = (a, b) -> a - b;
    }

    interface Operation {
        int operation(int a, int b);
    }
}
  • 可选的参数圆括号
    • 一个参数无需定义圆括号,但多个参数需要定义圆括号。
public class Test {

    public static void main(String[] args) {

        OperationA subtractionA = (int a, int b) -> a - b;
        OperationB subtractionB = a -> a * 2;

    }

    interface OperationA {
        int operation(int a, int b);
    }

    interface OperationB {
        int operation(int a);
    }
}
  • 可选的大括号
    • 如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字
    • 如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
public class Test {

    public static void main(String[] args) {

        OperationA subtractionA = (int a, int b) -> {
            return a - b;
        };

    }

    interface OperationA {
        int operation(int a, int b);
    }
}
  • 使用 Lambda 表达式需要注意以下两点
    • Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。在上面例子中,使用各种类型的 Lambda 表达式来定义 OperationA/B 接口的方法。
    • Lambda 表达式免去了使用匿名方法的麻烦,给予 Java 简单但是强大的函数化的编程能力。

1.2 变量作用域

  • Lambda 表达式只能引用标记了 final 的外层局部变量,不能在 Lambda 内部修改定义在域外的局部变量,否则会编译错误。
  • 也可以直接在 Lambda 表达式中访问外层的局部变量(隐性的具有 final 的语义)。
  • 在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
public class Test {

    private static int c = 1;

    public static void main(String[] args) {

        Operation subtractionA = (a, b) -> a + b + c;

    }

    interface Operation {
        int operation(int a, int b);
    }
}

2. 方法引用

  • 可以直接引用已有 Java 类或对象(实例)的方法或构造器。与 Lambda 联合使用,可以使语言的构造更紧凑简洁,减少冗余代码。

    • 通过方法的名字来指向一个方法。
    • 使用一对冒号 ::
  • 构造器引用

    • 语法为 Class::new,或者 Class< T >::new
public class Test {

    public static void main(String[] args) {
        Operation operation = Operation.create(Operation::new);
    }

    static class Operation {

        public static Operation create(final Supplier<Operation> supplier) {
            return supplier.get();
        }
    }
}
  • 静态方法引用
    • 语法为 Class::staticMethod
  • 特定类的任意对象的方法引用
    • 语法为 Class::method
public class Test {

    public static void main(String[] args) {
        List<Operation> operations = Arrays.asList(new Operation());
        operations.forEach(Operation::print);
    }

    static class Operation {

        public static void print(final Operation operation) {
            System.out.print(operation.toString());
        }
    }
}
public class Test {

    public static void main(String[] args) {
        final Operation operation = new Operation();
        List<Operation> operations = Arrays.asList(operation);
        operations.forEach(System.out::print);
    }

    static class Operation {
    }

}
  • 特定对象的方法引用
    • 语法为 instance::method
public class Test {

    public static void main(String[] args) {
        final Operation operation = new Operation();
        List<Operation> operations = Arrays.asList(operation);
        operations.forEach(operation::print);
    }

    static class Operation {

        public void print(final Operation Operation) {
            System.out.print(Operation.toString());
        }
    }
}

3. 函数式接口

  • 函数式接口(Functional Interface)是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。

  • 函数式接口可以被隐式转换为 Lambda 表达式。

  • JDK 1.8 之前已有函数式接口

    • java.lang.Runnable
    • java.util.concurrent.Callable
    • java.security.PrivilegedAction
    • java.util.Comparator
    • java.io.FileFilter
    • java.nio.file.PathMatcher
    • java.lang.reflect.InvocationHandler
    • java.beans.PropertyChangeListener
    • java.awt.event.ActionListener
    • javax.swing.event.ChangeListener
  • JDK 1.8 新增加函数接口

    • java.util.function
      • 包含了很多类,用来支持 Java 的 函数式编程
序号 接口 描述
1 BiConsumer<T,U> 代表了一个接受两个输入参数的操作,并且不返回任何结果。
2 BiFunction<T,U,R> 代表了一个接受两个输入参数的方法,并且返回一个结果。
3 BinaryOperator<T> 代表了一个作用于两个同类型操作符的操作,并且返回了操作符同类型的结果。
4 BiPredicate<T,U> 代表了一个两个参数的 boolean 值方法。
5 BooleanSupplier 代表了 boolean 值结果的提供方。
6 Consumer<T> 代表了接受一个输入参数并且无返回的操作。
7 DoubleBinaryOperator 代表了作用于两个 double 值操作符的操作,并且返回一个 double 值的结果。
8 DoubleConsumer 代表一个接受 double 值参数的操作,并且不返回结果。
9 DoubleFunction<R> 代表接受一个 double 值参数的方法,并且返回结果。
10 DoublePredicate<R> 代表一个拥有 double 值参数的 boolean 值方法。
11 DoubleSupplier 代表一个 double 值结构的提供方。
12 DoubleToIntFunction 接受一个 double 类型输入,返回一个 int 类型结果。
13 DoubleToLongFunction 接受一个 double 类型输入,返回一个 long 类型结果。
14 DoubleUnaryOperator 接受一个参数同为类型 double,返回值类型也为 double 。
15 Function<T,R> 接受一个输入参数,返回一个结果。
16 IntBinaryOperator 接受两个参数同为类型 int,返回值类型也为 int 。
17 IntConsumer 接受一个 int 类型的输入参数,无返回值 。
18 IntFunction<R> 接受一个 int 类型输入参数,返回一个结果 。
19 IntPredicate 接受一个 int 输入参数,返回一个 boolean 的结果。
20 IntSupplier 无参数,返回一个 int 类型结果。
21 IntToDoubleFunction 接受一个 int 类型输入,返回一个 double 类型结果 。
22 IntToLongFunction 接受一个 int 类型输入,返回一个 long 类型结果。
23 IntUnaryOperator 接受一个参数同为类型 int,返回值类型也为 int 。
24 LongBinaryOperator 接受两个参数同为类型 long,返回值类型也为 long。
25 LongConsumer 接受一个 long 类型的输入参数,无返回值。
26 LongFunction<R> 接受一个 long 类型输入参数,返回一个结果。
27 LongPredicate 接受一个 long 输入参数,返回一个 boolean 类型结果。
28 LongSupplier 无参数,返回一个结果 long 类型的值。
29 LongToDoubleFunction 接受一个 long 类型输入,返回一个 double 类型结果。
30 LongToIntFunction 接受一个 long 类型输入,返回一个int类型结果。
31 LongUnaryOperator 接受一个参数同为类型 long,返回值类型也为 long。
32 ObjDoubleConsumer<T> 接受一个 object 类型和一个 double 类型的输入参数,无返回值。
33 ObjIntConsumer<T> 接受一个 object 类型和一个 int 类型的输入参数,无返回值。
34 ObjLongConsumer<T> 接受一个 object 类型和一个 long 类型的输入参数,无返回值。
35 Predicate<T> 接受一个输入参数,返回一个 boolean 结果。
36 Supplier<T> 无参数,返回一个结果。
37 ToDoubleBiFunction<T,U> 接受两个输入参数,返回一个 double 类型结果。
38 ToDoubleFunction<T> 接受一个输入参数,返回一个 double 类型结果。
39 ToIntBiFunction<T,U> 接受两个输入参数,返回一个 int 类型结果。
40 ToIntFunction<T> 接受一个输入参数,返回一个 int 类型结果。
41 ToLongBiFunction<T,U> 接受两个输入参数,返回一个 long 类型结果。
42 ToLongFunction<T> 接受一个输入参数,返回一个 long 类型结果。
43 UnaryOperator<T> 接受一个参数为类型 T,返回值类型也为 T。
public class Test {

    public static void main(String[] args) {

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
        eval(list, n -> true);
    }

    public static void eval(List<Integer> list, Predicate<Integer> predicate) {
        for (Integer n : list) {

            if (predicate.test(n)) {
                System.out.println(n);
            }
        }
    }
}

4. 默认方法

  • 默认方法是接口可以有实现方法,而且不需要实现类去实现其方法。
    • 只需在方法名前面加个 default 关键字即可实现默认方法。
public interface IOperation {
   default void print(){
      System.out.println("");
   }
}

4.1 多个默认方法

  • 一个类实现了多个接口,且这些接口有相同的默认方法。
public interface IOperationA {
   default void print(){
      System.out.println("");
   }
}
 
public interface IOperationB {
   default void print(){
      System.out.println("");
   }
}
  • 第一个解决方案
    • 创建自己的默认方法,覆盖重写接口的默认方法。
public class Operation implements IOperationA, IOperationB  {
   default void print(){
      System.out.println("");
   }
}
  • 第二种解决方案
    • 使用 super 来调用指定接口的默认方法。
public class Operation implements IOperationA, IOperationB  {
   public void print(){
      IOperationA.super.print();
   }
}

4.2 静态默认方法

  • 接口可以声明(并且可以提供实现)静态方法。
public interface Operation {
   default void printA(){
      System.out.println("A");
   }
    // 静态方法
   static void printB(){
      System.out.println("B");
   }
}

5. Stream

  • Java 8 API 添加了一个新的抽象称为流 Stream,可以以一种声明的方式处理数据。
    • Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
    • 这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
    • 元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
List<Integer> transactionsIds = widgets.stream().filter(b -> b.getColor() == RED).sorted((x,y) -> x.getWeight() - y.getWeight()).mapToInt(Widget::getWeight).sum();
  • 像 filter 这样只描述 Stream,最终不产生新集合的方法叫作惰性求值方法。而像 sum 这样最终会从 Stream 产生值的方法叫作及早求值方法。

    • 如果返回值是Stream,那么就是惰性求值。
    • 如果返回值不是 Stream 或者是 void,那么就是及早求值。
  • 在一个 Stream 操作中,可以有多次惰性求值,但有且仅有一次及早求值。

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

    • 元素是特定类型的对象,形成一个队列。 Java 中的 Stream 并不会存储元素,而是按需计算。
    • 数据源流的来源。 可以是集合,数组,I/O channel, 产生器 generator 等。
    • 聚合操作类似 SQL 语句一样的操作, 如 filter,map,reduce,find,match,sorted 等。
  • 和以前的 Collection 操作不同, Stream 操作还有两个基础的特征

    • Pipelining
      • 中间操作都会返回流对象本身。
      • 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。
      • 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路(short-circuiting)。
    • 内部迭代
      • 以前对集合遍历都是通过 Iterator 或者 For-Each 的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。
      • Stream 提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

5.1 生成流

5.1.1 静态工厂方法。

5.1.1.1 of

  • 其生成的 Stream 是有限长度的,Stream 的长度为其内的元素个数。
方法 说明
– of(T… values) 返回含有多个 T 元素的 Stream
– of(T t) 返回含有一个 T 元素的 Stream
Stream<Integer> integerStream = Stream.of(1, 2, 3);
Stream<String> stringStream = Stream.of("A");

5.1.1.2 generator

  • generator 方法,返回一个 无限 长度的 Stream,其元素由 Supplier 接口的提供。
    • 在 Supplier 里是一个函数接口,只封装了一个 get() 方法,其用来返回任何泛型的值,该结果在不同的时间内,返回的可能相同也可能不相同,没有特殊的要求。
    • 通常用于随机数、常量的 Stream,或者需要前后元素间维持着某种状态信息的 Stream。
    • 把 Supplier 实例传递给 Stream.generate() 生成的 Stream,默认是串行(相对 parallel 而言)但无序的(相对 ordered 而言)。
方法 说明
– generate(Supplier<T> s) 返回一个无限长度的 Stream
Stream.generate(java.lang.Math::random);

一般无限长度的 Stream 会与 filter、limit 等配合使用,否则 Stream 会无限制的执行下去。

5.1.1.3 iterate

  • iterate方法,其返回的也是一个无限长度的 Stream,与 generate 方法不同的是,其是通过函数 f 迭代对给指定的元素种子而产生无限连续有序 Stream。
    • 其中包含的元素可以认为是 seed,f(seed),f(f(seed)) 无限循环。
Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println);
  • 上面示例,种子为 1,也可认为该 Stream 的第一个元素,通过 f 函数来产生第二个元素。接着,第二个元素,作为产生第三个元素的种子,从而产生了第三个元素,以此类推下去。

5.1.1.4 empty

  • empty方法返回一个空的顺序 Stream,该 Stream 里面不包含元素项。
Stream.empty();

5.1.2 Collection 接口和数组的默认方法

  • 在 Collection 接口中,定义了一个默认方法 stream(),用来生成一个 Stream。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
// 获取对应的平方数
List<Integer> squaresList = numbers.stream().map( i -> i*i).collect(Collectors.toList());
  • 在 Collection 接口中,定义了一个方法 parallelStream(),用来生成一个并行流。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
// 获取对应的平方数
List<Integer> squaresList = numbers.parallelStream().map( i -> i*i).collect(Collectors.toList());
  • 在 Arrays 类,封装了一些列的 Stream 方法,不仅针对于任何类型的元素采用了泛型,更对于基本类型作了相应的封装,以便提升 Stream 的处理效率。
int id[] = new int[]{1, 2, 3, 4};
Arrays.stream(id).forEach(System.out::println);

5.1.3 其他

  • Random.ints()
  • BitSet.stream()
  • Pattern.splitAsStream(java.lang.CharSequence)
  • JarFile.stream()

5.2 中间操作(Intermediate)

  • 主要是用来对 Stream 做出相应转换及限制流,实际上是将源 Stream 转换为一个新的Stream,以达到需求效果。

5.2.1 concat

  • concat 方法将两个 Stream 连接在一起,合成一个 Stream。
    • 若两个输入的 Stream 都是排序的,则新 Stream 也是排序的。
    • 若输入的 Stream 中任何一个是并行的,则新的 Stream 也是并行的。
    • 若关闭新的 Stream 时,原两个输入的 Stream 都将执行关闭处理。
Stream.concat(Stream.of(1, 2, 3), Stream.of(4, 5)).forEach(System.out::println);

5.2.2 distinct

  • distinct 方法以达到去除掉原 Stream 中重复的元素,生成的新 Stream 中没有没有重复的元素。
Stream.of(1, 2, 3, 1, 2, 3).distinct().forEach(System.out::println);

5.2.3 filter

  • filter 方法用于通过设置的条件过滤出元素。
// 获取空字符串的数量
Stream.of("1", "2", "3", "4", "", "", "7", "", "").filter(string -> string.isEmpty()).count();

5.2.4 map

  • map 方法用于映射每个元素到对应的结果。
// 获取对应的平方数
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).map(i -> i * i).collect(Collectors.toList());

5.2.5 flatMap

  • flatMap 方法与 map 方法类似,都是将原 Stream 中的每一个元素通过转换函数转换,不同的是,该换转函数的对象是一个 Stream,也不会再创建一个新的 Stream,而是将原 Stream的元素取代为转换的 Stream。
    • 如果转换函数生产的 Stream 为 null,应由空 Stream 取代。
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).flatMap(i -> i * i).collect(Collectors.toList());

5.2.6 peek

  • peek 方法生成一个包含原 Stream 的所有元素的新 Stream,同时会提供一个消费函数(Consumer实例),新 Stream 每个元素被消费的时候都会执行给定的消费函数,并且消费函数优先执行。
Stream.of(1, 2, 3).peek(x -> System.out.print(x));

5.2.7 skip

  • skip 方法将过滤掉原 Stream 中的前 N 个元素,返回剩下的元素所组成的新 Stream。
    • 如果原 Stream 的元素个数大于 N,将返回原 Stream 的后(原 Stream 长度 – N)个元素所组成的新 Stream。
    • 如果原 Stream 的元素个数小于或等于 N,将返回一个空 Stream。
Stream.of(1, 2, 3,4,5) .skip(2) .forEach(System.out::println); 

5.2.8 sorted

  • sorted 方法将对原 Stream 进行排序,返回一个有序列的新 Stream。
    • sorterd有两种变体 sorted(),sorted(Comparator),前者将默认使用 Object.equals(Object) 进行排序,而后者接受一个自定义排序规则函数(Comparator),可按照意愿排序。
Stream.of(5, 4, 3, 2, 1).sorted().forEach(System.out::println);

5.3 最终操作(Terminal)

  • 在一次聚合操作中,可以有多个中间操作,但是有且只有一个最终操作。

5.3.1 collect

  • 通过 collect 收集器,应用 Collector 工具。

5.3.1.1 转换成其他集合

  • toList
List<Integer> collectList = Stream.of(1, 2, 3, 4).collect(Collectors.toList());
  • toSet
Set<Integer> collectSet = Stream.of(1, 2, 3, 4).collect(Collectors.toSet());
  • toCollection
    • 其接受的函数参数必须继承于 Collection。
TreeSet<Integer> collectSet = Stream.of(1, 2, 3, 4).collect(Collectors.toCollection(TreeSet::new));
  • toMap
    • 若 Stream 中重复值,导致 Map 中 key 重复,在运行时会报异常
      java.lang.IllegalStateException: Duplicate key
public Map<Long, String> getIdNameMap(List<Account> accounts) {
    return accounts.stream().collect(Collectors.toMap(Account::getId, Account::getUsername));
}

5.3.1.2 转成值

方法 说明
averagingDouble 求平均值,Stream 的元素类型为 double。
averagingInt 求平均值,Stream 的元素类型为 int。
averagingLong 求平均值,Stream 的元素类型为 long。
counting Stream 的元素个数
maxBy 在指定条件下的,Stream 的最大元素。
minBy 在指定条件下的,Stream 的最小元素。
reducing reduce 操作。
summarizingDouble 统计 Stream 的数据(double)状态,其中包括 count,min,max,sum 和平均。
summarizingInt 统计 Stream 的数据(int)状态,其中包括 count,min,max,sum 和平均。
summarizingLong 统计 Stream 的数据(long)状态,其中包括 count,min,max,sum 和平均。
summingDouble 求和,Stream 的元素类型为 double。
summingInt 求和,Stream 的元素类型为 int。
summingLong 求和,Stream 的元素类型为 long。
Optional<Integer> collectMaxBy = Stream.of(1, 2, 3, 4).collect(Collectors.maxBy(Comparator.comparingInt(o -> o)));

5.3.1.3 分割数据块

  • collect 的一个常用操作将 Stream 分解成两个集合。
  • 使用 partitioningBy,可以将 Stream 分解。
    • partitioningBy 方法,接受一个流,并将其分成两部分:使用 Predicate 对象,指定条件并判断一个元素应该属于哪个部分,并根据布尔值返回一个 Map 到列表。
      • 对于 key 为 true 所对应的 List 中的元素,满足 Predicate 对象中指定的条件。
      • key 为 false 所对应的 List 中的元素,不满足 Predicate 对象中指定的条件。
Map<Boolean, List<Integer>> collectParti = Stream.of(1, 2, 3, 4).collect(Collectors.partitioningBy(it -> it % 2 == 0));

5.3.1.4 数据分组

  • 数据分组是一种更自然的分割数据操作, 与将数据分成 true 和 false 两部分不同,可以使用任意值对数据分组。
  • groupingBy 接受一个分类函数,用来对数据分组,就像 partitioningBy 一样,接受一个
    Predicate 对象将数据分成 true 和 false 两部分。
Map<Boolean, List<Integer>> collectGroup= Stream.of(1, 2, 3, 4).collect(Collectors.groupingBy(it -> it > 3));

5.3.1.5 字符串

  • joining 函数接受三个参数,分别表示允(用以分隔元素)、前缀和后缀。
Stream.of("1", "2", "3", "4").collect(Collectors.joining(",", "[", "]"));

5.3.1.6 组合 Collector

  • 在 partitioningBy 方法中,不仅传递了条件函数,同时还可以传入第二个收集器,用以收集最终结果的一个子集,这些收集器叫作 下游收集器
    • 收集器是生成最终结果的一剂配方,下游收集器则是生成部分结果的配方,主收集器中会用到下游收集器。
    • 这种组合使用收集器的方式,使得它们在 Stream 类库中的作用更加强大。
Map<Boolean, Long> partiCount = Stream.of(1, 2, 3, 4, 5).collect(Collectors.partitioningBy(it -> it.intValue() % 2 == 0, Collectors.counting()));
System.out.println("partiCount: " + partiCount);
// 打印结果
// partiCount: {false=3, true=2}

5.3.2 count

  • count 方法将返回 Stream 中元素的个数。
Stream.of(1, 2, 3, 4, 5).count();

5.3.3 forEach

  • 提供了新的方法 forEach 来迭代流中的每个数据。
Stream.of(1, 2, 3).forEach(System.out::println);

5.3.4 forEachOrdered

  • forEachOrdered 方法与 forEach 类似,都是遍历 Stream 中的所有元素,不同的是,如果该
    Stream 预先设定了顺序,会按照预先设定的顺序执行(Stream 是无序的),默认为元素插入的顺序。
Stream.of(1, 2, 3).forEachOrdered(System.out::println);

5.3.5 max

  • max 方法根据指定的 Comparator,返回一个 Optional,该 Optional 中的 value 值就是
    Stream 中最大的元素。
  • 原 Stream 根据比较器 Comparator,进行排序(升序或者是降序),所谓的最大值就是从新进行排序的,max 就是取重新排序后的最后一个值,而 min 取排序后的第一个值。
Optional<Integer> max = Stream.of(1, 2, 3, 4, 5).max((o1, o2) -> o2 - o1);

5.3.6 min

Optional<Integer> max = Stream.of(1, 2, 3, 4, 5).max((o1, o2) -> o1 - o2);

5.4 非短路操作(Short-circuiting)

5.4.1 allMatch

  • allMatch 操作用于判断 Stream 中的元素是否全部满足指定条件。如果全部满足条件返回
    true,否则返回 false。
Stream.of(1, 2, 3, 4).allMatch(integer -> integer > 2);

5.4.2 anyMatch

  • anyMatch 操作用于判断 Stream 中的是否有满足指定条件的元素。如果最少有一个满足条件返回 true,否则返回 false。
Stream.of(1, 2, 3, 4).anyMatch(integer -> integer > 3);

5.4.3 findAny

  • findAny 操作用于获取含有 Stream 中的某个元素的 Optional,如果 Stream 为空,则返回一个空的 Optional。
    • 由于此操作的行动是不确定的,其会自由的选择 Stream 中的任何元素。
    • 在并行操作中,在同一个 Stram 中多次调用,可能会不同的结果。
Stream.of(1, 2, 3, 4).findAny();

5.4.4 findFirst

  • findFirst 操作用于获取含有 Stream 中的第一个元素的 Optional,如果 Stream 为空,则返回一个空的 Optional。
    • 若 Stream 并未排序,可能返回含有 Stream 中任意元素的 Optional。
Stream.of(1, 2, 3, 4).findFirst();

5.4.5 limit

  • limit 方法将截取原 Stream,截取后 Stream 的最大长度不能超过指定值 N。
    • 如果原 Stream 的元素个数大于 N,将截取原 Stream 的前 N 个元素。
    • 如果原 Stream 的元素个数小于或等于 N,将截取原 Stream 中的所有元素。
Stream.of(1, 2, 3,4,5).limit(2).forEach(System.out::println);

5.4.6 noneMatch

  • noneMatch 方法将判断 Stream 中的所有元素是否满足指定的条件,如果所有元素都不满足条件,返回 true,否则,返回 false。
Stream.of(1, 2, 3, 4, 5).noneMatch(integer -> integer > 10);

5.4.7 reduce

  • reduce 操作可以实现从 Stream 中生成一个值,其生成的值不是随意的,而是根据指定的计算模型。
Stream.of(1, 2, 3, 4)
        .reduce((acc, item) -> {
            acc += item;
            return acc;
        });

6. Optional

  • Optional 类是一个可以为 null 的容器对象。如果值存在则 isPresent() 方法会返回 true,调用
    get() 方法会返回该对象。
  • Optional 是个容器,它可以保存类型 T 的值,或者仅仅保存 null。
    • Optional 提供很多有用的方法,这样我们就不用显式进行空值检测。
  • Optional 类的引入很好的解决空指针异常。
序号 方法 描述
1 static <T> Optional<T> empty() 返回空的 Optional 实例。
2 boolean equals(Object obj) 判断其他对象是否等于 Optional。
3 Optional<T> filter(Predicate<? super <T> predicate) 如果值存在,并且这个值匹配给定的 predicate,返回一个 Optional 用以描述这个值,否则返回一个空的 Optional。
4 <U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper) 如果值存在,返回基于 Optional 包含的映射方法的值,否则返回一个空的 Optional。
5 T get() 如果在这个 Optional 中包含这个值,返回值,否则抛出异常:NoSuchElementException
6 int hashCode() 返回存在值的哈希码,如果值不存在 返回 0。
7 void ifPresent(Consumer<? super T> consumer) 如果值存在则使用该值调用 consumer , 否则不做任何事情。
8 boolean isPresent() 如果值存在则方法会返回 true,否则返回 false。
9 <U>Optional<U> map(Function<? super T,? extends U> mapper) 如果存在该值,提供的映射方法,如果返回非 null,返回一个 Optional 描述结果。
10 static <T> Optional<T> of(T value) 返回一个指定非 null 值的 Optional。
11 static <T> Optional<T> ofNullable(T value) 如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。
12 T orElse(T other) 如果存在该值,返回值, 否则返回 other。
13 T orElseGet(Supplier<? extends T> other) 如果存在该值,返回值, 否则触发 other,并返回 other 调用的结果。
14 <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) 如果存在该值,返回包含的值,否则抛出由 Supplier 继承的异常
15 String toString() 返回一个 Optional 的非空字符串,用来调试。
Optional<Integer> a = Optional.ofNullable(value);

7. 日期时间

  • 通过发布新的 Date-Time API (JSR 310)来进一步加强对日期与时间的处理。
  • 旧版的 Java 中,日期时间 API 存在诸多问题
    • 非线程安全
      • java.util.Date 是非线程安全的,所有的日期类都是可变的,这是 Java 日期类最大的问题之一。
    • 设计很差
      • Java 的日期/时间类的定义并不一致,在 java.util 和 java.sql 的包中都有日期类,此外用于格式化和解析的类在 java.text 包中定义。java.util.Date 同时包含日期和时间,而java.sql.Date 仅包含日期,将其纳入 java.sql 包并不合理,另外这两个类都有相同的名字。
    • 时区处理麻烦
      • 日期类并不提供国际化,没有时区支持,因此 Java 引入了 java.util.Calendar 和
        java.util.TimeZone 类,但同样存在上述所有的问题。
  • Java 8 在 java.time 包下提供了很多新的 API。
    • Local(本地)
      • 简化了日期时间的处理,没有时区的问题。
    • Zoned(时区)
      • 通过制定的时区处理日期时间。
// 获取当前的日期时间
LocalDateTime currentTime = LocalDateTime.now();
System.out.println("当前时间: " + currentTime);
        
LocalDate date1 = currentTime.toLocalDate();
System.out.println("date1: " + date1);
        
Month month = currentTime.getMonth();
int day = currentTime.getDayOfMonth();
int seconds = currentTime.getSecond();
        
System.out.println("月: " + month +", 日: " + day +", 秒: " + seconds);
        
LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
System.out.println("date2: " + date2);
        
// 12 december 2014
LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
System.out.println("date3: " + date3);
        
// 22 小时 15 分钟
LocalTime date4 = LocalTime.of(22, 15);
System.out.println("date4: " + date4);
        
// 解析字符串
LocalTime date5 = LocalTime.parse("20:15:30");
System.out.println("date5: " + date5);
  • 使用时区的日期时间
// 获取当前时间日期
ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
System.out.println("date1: " + date1);
        
ZoneId id = ZoneId.of("Europe/Paris");
System.out.println("ZoneId: " + id);
        
ZoneId currentZone = ZoneId.systemDefault();
System.out.println("当期时区: " + currentZone);

8. Base64

  • Java 8 内置了 Base64 编码的编码器和解码器。
  • Base64 工具类提供了一套静态方法获取下面三种 Base64 编解码器。
    • 基本:输出被映射到一组字符 A-Za-z0-9+/,编码不添加任何行标,输出的解码仅支持 A-Za-z0-9+/。
    • URL:输出映射到一组字符 A-Za-z0-9+_,输出是 URL 和文件。
    • MIME:输出隐射到 MIME 友好格式。输出每行不超过 76 字符,并且使用 \r 并跟随 \n 作为分割。编码输出最后没有行分割。
  • 内嵌类
序号 内嵌类 描述
1 static class Base64.Decoder 该类实现一个解码器用于,使用 Base64 编码来解码字节数据。
2 static class Base64.Encoder 该类实现一个编码器,使用 Base64 编码来编码字节数据。
  • 方法
序号 方法名 描述
1 static Base64.Decoder getDecoder() 返回一个 Base64.Decoder ,解码使用基本型 base64 编码方案。
2 static Base64.Encoder getEncoder() 返回一个 Base64.Encoder ,编码使用基本型 base64 编码方案。
3 static Base64.Decoder getMimeDecoder() 返回一个 Base64.Decoder ,解码使用 MIME 型 base64 编码方案。
4 static Base64.Encoder getMimeEncoder() 返回一个 Base64.Encoder ,编码使用 MIME 型 base64 编码方案。
5 static Base64.Encoder getMimeEncoder(int lineLength, byte[] lineSeparator) 返回一个 Base64.Encoder ,编码使用 MIME 型 base64 编码方案,可以通过参数指定每行的长度及行的分隔符。
6 static Base64.Decoder getUrlDecoder() 返回一个 Base64.Decoder ,解码使用 URL 和文件名安全型 base64 编码方案。
7 static Base64.Encoder getUrlEncoder() 返回一个 Base64.Encoder ,编码使用 URL 和文件名安全型 base64 编码方案。
// 使用基本编码
String base64encodedString = Base64.getEncoder().encodeToString("runoob?java8".getBytes("utf-8"));
System.out.println("Base64 字符串 (基本) :" + base64encodedString);
        
// 解码
byte[] base64decodedBytes = Base64.getDecoder().decode(base64encodedString);
        
System.out.println("原始字符串: " + new String(base64decodedBytes, "utf-8"));
base64encodedString = Base64.getUrlEncoder().encodeToString("TutorialsPoint?java8".getBytes("utf-8"));
System.out.println("Base64 编码字符串 (URL) :" + base64encodedString);
        
StringBuilder stringBuilder = new StringBuilder();
        
for (int i = 0; i < 10; ++i) {
    stringBuilder.append(UUID.randomUUID().toString());
}
        
byte[] mimeBytes = stringBuilder.toString().getBytes("utf-8");
String mimeEncodedString = Base64.getMimeEncoder().encodeToString(mimeBytes);
System.out.println("Base64 编码字符串 (MIME) :" + mimeEncodedString);

9. Nashorn JavaScript

  • 从 JDK 1.8 开始,Nashorn 取代 Rhino(JDK 1.6, JDK1.7)成为 Java 的嵌入式 JavaScript
    引擎。

    • Nashorn 完全支持 ECMAScript 5.1 规范以及一些扩展。
    • 它使用基于 JSR 292 的新语言特性,其中包含在 JDK 7 中引入的 invokedynamic,将 JavaScript 编译成 Java 字节码。
  • 与先前的 Rhino 实现相比,这带来了 2 到 10 倍的性能提升。

9.1 Java 中调用 JavaScript

ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine nashorn = scriptEngineManager.getEngineByName("nashorn");
        
String name = "Runoob";
Integer result = null;
      
try {
    nashorn.eval("print('" + name + "')");
    result = (Integer) nashorn.eval("10 + 2");
}catch(ScriptException e){
    System.out.println("执行脚本错误: "+ e.getMessage());
}
      
System.out.println(result.toString());

9.2 JavaScript 中调用 Java

var BigDecimal = Java.type('java.math.BigDecimal');

function calculate(amount, percentage) {

   var result = new BigDecimal(amount).multiply(
   new BigDecimal(percentage)).divide(new BigDecimal("100"), 2, BigDecimal.ROUND_HALF_EVEN);
   
   return result.toPlainString();
}

var result = calculate(568000000000000000023,13.9);
print(result);

参考资料

Java 8 中的 Streams API 详解
Java 8 新特性

发表评论

电子邮件地址不会被公开。 必填项已用*标注