Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

Java8新特性

12
Jun
2014

Java8新特性

By Alex
/ in Java
/ tags 新特性
0 Comments
接口默认、静态方法

传统的Java接口是一组方法的集合,任何实现者都必须实现所有方法。这意味着库的设计者很难更新接口——这会导致很多实现类无法使用。

Java 8对接口的语法做了很大的改造:

  1. 支持提供了实现的静态方法,这让为接口提供一个工具类的做法不再必要,工具方法可以直接放在接口中,例如Java8的Stream包含很多静态方法:
    Java
    1
    2
    3
    4
    5
    6
    7
    8
    public interface Stream<T> extends BaseStream<T, Stream<T>> {
        public static<T> Builder<T> builder() {
            return new Streams.StreamBuilderImpl<>();
        }
        public static<T> Stream<T> empty() {
            return StreamSupport.stream(Spliterators.<T>emptySpliterator(), false);
        }
    }
  2. 支持提供了实现的接口方法:如果实现接口的类不明确提供实现,那么会继承来自接口的实现。这个特性让扩展接口变得很方便,例如Java8为List接口扩展了sort方法:
    Java
    1
    2
    3
    default void sort(Comparator<? super E> c){
        Collections.sort(this, c);
    }

     为Collection接口扩展了stream方法:

    Java
    1
    2
    3
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

在先前,使用抽象类来提供缺省接口实现的模式,现在很大程度上可以由接口来代替。但是带有缺省实现的接口和抽象类还是有很大的不同:

  1. 类可以实现多个接口,包括带有默认方法的接口;但是类只能继承一个抽象类
  2. 抽象类可以具有状态,即实例变量,接口则不能有实例变量
默认方法的用途

可以用来设计“可选方法(Optional methods)” ,可以有意的让接口继承者不需要实现某些方法,例如在迭代器中,remove就是可选方法:

Java
1
2
3
4
5
6
7
interface Iterator<T> {
    boolean hasNext();
    T next();
    default void remove() {
        throw new UnsupportedOperationException();
    }
}

默认方法也可以用来实现多重行为继承(Multiple inheritance of behavior)。由于Java允许实现多个接口,因此自然就可以从多个接口的默认方法中继承行为。

默认方法冲突解析

Java 8 中一个类可以从父类、接口继承得到签名相同的方法,那么到底哪个生效呢?以下是几个规则:

  1. 类优先原则:声明在当前类或者父类中的方法,总是比接口默认方法优先
  2. 子接口优先原则:如果两个接口A、B,B继承A,那么B中的默认方法比A的优先级高
  3. 如果冲突仍然存在,那么当前类必须覆盖冲突的方法,在其中明确调用某个接口的默认方法 :
    Java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    interface IHello{
        default void say(){
        }
    }
     
    interface IWorld{
        default void say(){
        }
    }
     
    class HelloWorld implements IHello, IWorld{
        @Override
        public void say(){
            IWorld.super.say(); //明确调用第二个接口的方法
        }
    }

需要注意的是,Java 8中不会存在钻石继承的问题,根本原因在于接口继承不存在对象拷贝的问题。这个可以对比C++钻石问题去理解,在C++中D继承B、C,后两者由继承自A,就意味着D拷贝了一份B、C的字段和函数,以及两份来自A的字段和函数,这就意味着冲突,到底使用来自B的A还是来自C的A?C++使用特殊的语法来去歧义。

Lambda表达式
关于行为参数化(Behavior parameterization)

在策略模式中,Strategy(策略,即行为)与Context是组合关系,前者作为后者的属性。所谓参数化,就是把这种组合关系改变为参数传递关系,即通过方法参数来传递策略类。

行为参数化的好处不言而喻,它可以让Context之间进一步解耦,关联关系被取消。不会因为共享变量Strategy导致线程安全性问题,每次调用都可以灵活切换Strategy。

行为参数化这种设计思想当然从Java诞生开始就能够支持,但是在Java8中,这种思想被更多的添加到JDK的API中:

Java
1
2
3
4
5
6
public interface Iterable<T> {
    default void forEach(Consumer<? super T> action);
}
public interface List<E> extends Collection<E> {
    default void sort(Comparator<? super E> c) {
}

Lambda表达式让行为参数化变得简洁:

Java
1
2
3
4
5
6
7
8
9
10
List<String> strList = new ArrayList<String>();
//使用Lambda表达式:
strList.sort( ( String s1, String s2 ) -> s1.length() - s2.length() );
//使用匿名类
strList.sort( new Comparator<String>() {
    public int compare( String s1, String s2 )
    {
        return s1.length() - s2.length();
    }
} );
Lambda语法

Lambda表达式可以理解为可以被作为出入参传递的匿名函数的简便表示形式,很多语言例如C++、Python都支持Lambda表达式,Java 8首次将其引入的Java语言中,Lambda表达式的语法如下:

Java
1
2
3
4
5
(Lambda parameters) -> expression
 
(Lambda parameters) -> {
    statements;
}

Lambda表达式可以用于任何函数式接口的上下文中,包括作为方法参数或者返回值。

函数式接口(Functional interface)

所谓函数式接口,是指只有一个抽象方法的接口。Java 8中接口可以有默认实现,有多个方法,但是只有一个没有默认实现的接口,也被认为是函数式接口。

使用注解@FunctionalInterface可以限制一个接口为函数式接口,如果不符合函数式接口的规范,将无法通过编译:

Java
1
2
3
4
5
@FunctionalInterface
public interface Functional {
    void operation();
    default void defaultOperation(){}
}

函数式接口的抽象方法的签名潜在的定义了Lambda表达式的结构, 我们称这样的抽象方法为函数描述符(Function Descriptor)。

Java 8 中提供了若干常用的函数式接口:

函数式接口 函数描述符 针对基本类型的特化接口 
Predicate<T> T -> boolean IntPredicate, LongPredicate, DoublePredicate
Consumer<T> T -> void IntConsumer, LongConsumer, DoubleConsumer
Function<T, R> T -> R IntFunction<R>, IntToDoubleFunction, IntToLongFunction,LongFunction<R>, LongToDoubleFunction,LongToIntFunction, DoubleFunction<R>, ToIntFunction<T>,ToDoubleFunction<T>, ToLongFunction<T>
Supplier<T> () -> T BooleanSupplier, IntSupplier, LongSupplier, DoubleSupplier
UnaryOperator<T> T -> T IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator
BinaryOperator<T> (T, T) -> T IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator
BiPredicate<L, R> (L, R) -> boolean  
BiConsumer<T, U> (T, U) -> void ObjIntConsumer<T>, ObjLongConsumer<T>,ObjDoubleConsumer<T>
BiFunction<T, U, R> (T, U) -> R ToIntBiFunction<T, U>, ToLongBiFunction<T, U>,ToDoubleBiFunction<T, U>
类型检查与推断

Lambda的类型(即函数式接口的类型)是根据使用Lambda的上下文来由编译器推断的,上下文期望的Lambda类型被称为目标类型(Target type)。

同一Lambda可能和多个抽象方法签名兼容的函数式接口关联,以一条语句作为其Lambda体的表达式,与返回void 的函数描述符兼容(如果参数类型兼容),例如:

Java
1
2
3
4
//断言接口要求boolean返回值
Predicate<String> p = s -> list.add(s);
//消费接口要求void返回值,兼容
Consumer<String> b = s -> list.add(s);

编译器可以根据上下文进行类型推断,因此Lambda表达式的编写可以被简化:

Java
1
2
//省略Lambda参数的类型声明
Comparator<String> c = ( s1, s2 ) -> s1.length() - s2.length();
使用局部变量

类似于匿名类,Lambda可以使用在外部作用范围定义的局部变量,但是这些局部变量必须是final,或者相当于final(没有二次赋值语句): 

Java
1
2
3
4
//下面的代码无法通过编译,因为hello被二次赋值
String hello = "Hello";
Runnable r = () -> System.out.println( hello );
hello = "World";

设置“final”限制的缘由是,局部变量是在线程的栈上分配的,那么作为返回值传递的Lambda可能被另外一个线程调用,这个调用可能发生在分配局部变量线程死亡之后,如果允许Lambda任意访问局部变量就会导致非法访问。Java的做法是允许Lambda访问局部变量的拷贝,而不是局部变量本身,final保证了拷贝与局部变量的一致性。 这一限制实质上也使Java不支持真正的闭包。

方法引用

Java 8引入了方法引用,这允许重用已经定义的方法并传递它们,就像Lambda一样:

Java
1
2
3
4
5
6
7
Comparator<String> c = java.util.Comparator.comparing( String::length );
//comparing需要的参数是函数式接口:Function<? super T, ? extends U>
//传递的是String::length
//编译器推导过程:(T) -> return R
//第一个参数是实例方法的实例:(String s) -> return R
//调用实例方法:(String s) -> s.length()
//假设实例方法有入参,依次声明:(String s, Object arg0, Object arg1) -> s.length(arg0, arg1)

方法引用本质上是只调用了一个方法的Lambda的缩写。上面的例子等价于:

Java
1
Comparator<String> c = java.util.Comparator.comparing( ( String s ) -> s.length() );

方法引用的写法有三大类:

  1. 对于静态方法的引用,例如 Integer::parseInt  
  2. 对于某个类型的实例方法的引用,例如 String.length 。具体调用针对的对象实例由Lambda的第一个参数来提供,该方法引用等价于 (String s) -> s.length() 
  3. 对于某个对象的实例方法的引用,例如 "".length 。具体调用针对的对象实例由上下文中的变量提供,该方法引用等价于 () -> "".length() 
构造器引用

构造器引用类似于静态方法引用,形式为: Class::new ,示例如下:

Java
1
2
3
4
5
6
Function<String, String> c = String::new; //函数引用
c = ( s ) -> new String( s ); //等价的Lambda表达式
c.apply( "Hello" ); //调用
 
Supplier<Object> o = Object::new;
o = () -> new Object();
简化Lambda编写的API

Java 8 中引入了很多简化Lambda编写的API:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface java.util.Comparator<T> {
    //返回这样的比较器:通过调用被比较对象的方法来抽取Comparable Key,进而根据Key来比较
    public static <T, U> Comparator<T> comparing();
    //反转当前比较器的顺序
    default Comparator<T> reversed();
    //链式的比较器:多重比较
    default Comparator<T> thenComparing(Comparator<? super T> other);
}
 
public interface Predicate<T> {
    //逻辑否、非、或
    default Predicate<T> and(Predicate<? super T> other);
    default Predicate<T> or(Predicate<? super T> other);
    default Predicate<T> negate();
}
 
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen( g ); //高阶函数:g(f(x))
Function<Integer, Integer> i = f.compose( g ); //高阶函数:f(g(x))
h.apply( 1 );
类型注解

在Java8中,注解可以写在任何使用类型的地方:

Java
1
2
3
4
// 声明,允许该注解被用在使用类型的地方、泛型类型参数上
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
public @interface Test {
}

 在类型、泛型类型参数上使用注解的例子:

Java
1
2
3
4
5
// 任何类型的前面都可以附加注解(前提是该注解的ElementType允许)
@Encrypted String data;
List<@NonNull String> strings;
graph = (@Immutable Graph) tmpGraph;
Collection<? super @Existing File> c;

类型注解可以用来在编译期进行代码检查,提高代码质量,对运行时没有影响。

Stream API

Java 8引入了java.util.stream.Stream,它允许声明式的操控集合。Stream能够透明的使用多处理器并行处理。 下面是一个简明的例子:

Java
1
2
3
4
5
6
7
List<Cat> cat = new ArrayList<>();
List<String> littleCats = cat  //链式调用
        .parallelStream()  //集合接口可以获取Stream,stream()返回串行处理的流
        .filter(c->c.getAge()<2)  //内部迭代
        .sorted(Comparator.comparing(Cat::getWeight))
        .map(Cat::getName)
        .collect(toList());  //直到collect调用之前,不会做任何实际操作

所谓Stream,是指来自一个源(Source)的元素的序列,这些元素支持数据处理操作:

  1. 元素序列:Collection也是元素序列,但是Collection关心的是数据,Stream则关心操作
  2. 源:可以是Collection、数组、I/O资源
  3. 操作:Stream提供类似数据库的操作,例如filter、map、reduce、find、match、sort、limit。这些操作可以串行或者并行的被执行

Stream还支持链式调用,这允许你串起多个操作;Stream支持内部迭代,不需要手工编写迭代代码。

Stream提供的操作

Stream支持两类操作:中间操作、终端操作。前者构建处理管道(Pipeline),后者导致管道被执行。中间操作包括:

操作  函数描述符 说明 
filter T -> boolean 过滤Stream中的元素
distinct   筛除重复元素
skip   跳过Stream中开始的N个元素
limit   限制Stream中元素的个数
map T -> R 把Stream中的每个元素使用map函数转换
flatMap T -> Stream<R> 把Stream中的每个元素使用map函数转换,转换结果必须是Stream,所有这些结果Stream被合并为一个Stream
sorted (T, T) -> int 对Stream中的元素进行排序
anyMatch T -> boolean 是否存在任何一个元素满足断言
allMatch T -> boolean 是否所有元素都满足断言
findAny   寻找流中的一个元素
findFirst   得到流中第一个元素

终端操作包括:

 操作 函数描述符 说明 
forEach T -> void 消费Stream中的每一个元素
count   统计Stream中元素的数量
collect   Reduce Stream为集合、映射甚至一个Integer
reduce (T, T) -> T 化简,接收两个元素作为输入,把一个元素作为输出。化简操作会不断进行,直到只剩一个元素
Stream使用示例
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import static java.util.stream.Collectors.*;
 
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
 
public class StreamExample
{
 
    private static List<Cat> prepareData()
    {
        List<Cat> cats = new ArrayList<>();
        cats.add( new Cat( "叮当猫", 19, 4000, "Yellow" ) );
        cats.add( new Cat( "多啦A梦", 38, 5800, "Blue" ) );
        cats.add( new Cat( "蓝猫", 9, 3500, "Blue" ) );
        cats.add( new Cat( "汤姆猫", 58, 2900, "Blue" ) );
        cats.add( new Cat( "凯蒂猫", 31, 1800, "White" ) );
        cats.add( new Cat( "加菲猫", 27, 9600, "Yellow" ) );
        cats.add( new Cat( "甜甜私房猫", 6, 1200, "Grey" ) );
        cats.add( new Cat( "加菲猫", 27, 9600, "Yellow" ) );
        cats.add( new Cat( "加菲猫", 27, 9600, "Yellow" ) );
        return cats;
    }
 
    public static void main( String[] args ) throws IOException
    {
        List<Cat> cats = prepareData();
 
        /** 过滤和切片操作  **/
        //使用断言(Predicate)过滤数据
        List<Cat> overweight = cats.stream().filter( cat -> cat.getWeight() > 3000 ).collect( toList() );
        System.out.println( overweight ); //[叮当猫, 多啦A梦, 蓝猫, 加菲猫]
        //筛除重复元素
        List<Cat> distinct = cats.stream().distinct().collect( toList() );
        System.out.println( distinct ); //[叮当猫, 多啦A梦, 蓝猫, 汤姆猫, 凯蒂猫, 加菲猫, 甜甜私房猫]
        //截断Stream
        List<Cat> limit = cats.stream().limit( 3 ).collect( toList() );
        System.out.println( limit ); //[叮当猫, 多啦A梦, 蓝猫]
        //跳过Stream中起始的6个元素
        List<Cat> skip = cats.stream().skip( 6 ).collect( toList() );
        System.out.println( skip ); //[甜甜私房猫, 加菲猫, 加菲猫]
 
        /** 映射(Mapping)操作 **/
        //映射为颜色
        List<String> colors = cats.stream().map( Cat::getColor ).collect( Collectors.toList() );
        System.out.println( colors ); //[Yellow, Blue, Blue, Blue, White, Yellow, Grey, Yellow, Yellow]
        //扁平化映射:把元素映射为新Stream,然后把这个新Stream中的所有元素纳入当前Stream,并关闭新Stream
        List<Character> nameChars = cats
                .stream().map( Cat::getNameAsChars ) //元素映射为Character[]
                .flatMap( Arrays::stream ) //Character[]被映射为Character,这是1:N的映射方式
                .distinct().collect( toList() );
        System.out.println( nameChars );//[叮, 当, 猫, 多, 啦, A, 梦, 蓝, 汤, 姆, 凯, 蒂, 加, 菲, 甜, 私, 房]
        /** 查找与匹配操作 **/
        //判断流中是否存在匹配断言的元素
        System.out.println( cats.stream().anyMatch( cat -> cat.getColor() == "White" ) ); //true
        //判断是否流中所有元素匹配断言
        System.out.println( cats.stream().allMatch( cat -> cat.getAge() < 100 ) ); //true
        //判断是否流中不存在任何元素匹配断言
        System.out.println( cats.stream().noneMatch( cat -> cat.getAge() > 100 ) ); //true
        //查找任何一个黄猫
        Optional<Cat> yellow = cats.stream().filter( cat -> cat.getColor() == "Yellow" ).findAny();
        System.out.println( yellow.get() ); //叮当猫
        //查找第一个元素
        System.out.println( cats.stream().findFirst().get() ); //叮当猫
 
        /** 化简(Reducing)操作 **/
        //将两个元素化简为一个元素
        //求和:年龄之和,先映射再化简
        System.out.println( cats.stream().map( Cat::getAge ).reduce( ( a1, a2 ) -> a1 + a2 ).get() ); //242
        //求最值:最老的猫
        System.out.println( cats.stream().reduce( ( c1, c2 ) -> ( c1.getAge() > c2.getAge() ) ? c1 : c2 ).get() );//汤姆猫
        /** 数字流 **/
        //避免基本类型装拆箱的开销
        IntStream primitiveStream = cats.stream().mapToInt( Cat::getAge );
        Stream<Integer> objectStream = primitiveStream.boxed(); //变回装箱类型的流
        OptionalInt max = primitiveStream.reduce( ( i, j ) -> i > j ? i : j );
        max.orElse( 0 );//如果没有最大值,设为0
        /** 构建流 **/
        IntStream.range( 0, 200 );//数字范围的流
        Stream<String> strStream = Stream.of( "Hello", "World" ); //字符串流
        IntStream intStream = Arrays.stream( new int[] { 1, 2, 3 } );//从数组生成流
        //NIO包支持Stream API
        Stream<String> fileLineStream = Files.lines( Paths.get( "~/ReadMe.txt" ), Charset.defaultCharset() );//行流
        Stream<Integer> infinite = Stream.iterate( 0, n -> n + 2 ); //由函数提供的迭代流
        infinite.limit( 100 ).forEach( i -> System.out.println( i ) );
        Stream.generate( Math::random ); //随机生成流
    }
}
Reducing的优势

化简操作比起逐步迭代的优势在于,它能够很方便的支持并行处理。 由于化简操作限定了依据两个输入导出一个输出,并且输入输出的类型一致,这让JDK能够自由的把巨大的流分配给多个线程进行化简操作。这种Map-Reduce思想在大数据处理框架中很普遍,例如Hadoop。

Collector

Collector接口传递给Stream.collect()方法,可以用来构建Stream中元素的摘要信息。Collector也可以用来执行化简操作,对Stream元素进行化简。

Collector接口中的静态工厂方法用于创建预定义的Collector,这些Collector和Stream接口提供的功能有重叠:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
List<Cat> cats = StreamExample.prepareData();
//求平均猫龄
cats.stream().collect( averagingInt( Cat::getAge ) );
//收集所有猫为列表,再转为数组
cats.stream().collect( collectingAndThen( toList(), List::toArray ) );
//求猫的总数
cats.stream().collect( counting() );
//按毛色分组,如果需要多级分组,为groupingBy添加第二个参数,类型为Collector
Map<String, List<Cat>> byColor = cats.stream().collect( groupingBy( Cat::getColor ) );
//把名字用逗号连接起来
cats.stream().map( Cat::getName ).collect( joining( "," ) );
//依据猫龄映射,然后取最大值
cats.stream().collect( mapping( Cat::getAge, maxBy( ( a1, a2 ) -> a1 > a2 ? a1 : a2 ) ) );
//依据是否白猫,分区为两个列表
Map<Boolean, List<Cat>> partition = cats.stream().collect( partitioningBy( cat -> cat.getColor() == "White" ) );
//化简,取第一只猫
cats.stream().collect( reducing( ( c1, c2 ) -> c1 ) );
//求总吨位
cats.stream().collect( summingInt( Cat::getWeight ) );

Collector接口的主要方法如下:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* 收集器接口
* @param <T> 需要进行化简操作的元素类型,即目标流的元素类型
* @param <A> 易变的累计器类型(mutable accumulation type),化简的中间结果存放在其中
* @param <R> 化简操作的结果类型
*/
public interface Collector<T, A, R>
{
 
    //该函数创建并返回中间结果容器
    Supplier<A> supplier();
 
    //该函数把一个元素折入结果容器
    BiConsumer<A, T> accumulator();
 
    //将中间结果进行最后的转换,得到最终结果
    Function<A, R> finisher();
 
    /**
     * 返回不变的,表示该收集器特征的集合
     * 特征包括:
     *   CONCURRENT 表示此收集器支持并发,即accumulator支持多线程并发访问
     *   UNORDERED 表示此收集器不会保持元素的顺序
     *   IDENTITY_FINISH 表示finisher是“等同的(identity)”函数,可以被省略,意味着可以直接从A造型为R
     */
    Set<Characteristics> characteristics();
 
    //支持接收两个中间结果,并合并之(折叠并返回其中一个,或者创建新的容器并返回)
    BinaryOperator<A> combiner();
 
}

一个简单的Collector的实现:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import static java.util.stream.Collector.Characteristics.*;
 
public class AsListCollector<T> implements Collector<T, List<T>, List<T>>
{
 
    @Override
    public Supplier<List<T>> supplier()
    {
        // 提供中间结果容器
        return ArrayList::new;
    }
 
    @Override
    public BiConsumer<List<T>, T> accumulator()
    {
        // 把元素折叠到结果容器中
        return List::add;
    }
 
    @Override
    public BinaryOperator<List<T>> combiner()
    {
        return ( l1, l2 ) -> {
            l1.addAll( l2 );
            return l1;
        };
    }
 
    @Override
    public Function<List<T>, List<T>> finisher()
    {
        //生成一个总是返回输入的函数
        return Function.identity();
    }
 
    @Override
    public Set<java.util.stream.Collector.Characteristics> characteristics()
    {
        return Collections.unmodifiableSet( EnumSet.of( IDENTITY_FINISH, CONCURRENT ) );
    }
}
并行处理

可以把当前Stream转换为等价的并行处理版本: stream().parallel() ,反之亦可: stream().sequential() 。反复调用串并行转换,则只有最后一个调用有意义:

Java
1
2
3
4
5
6
stream.parallel()
    .filter(...)
    .sequential()
    .map(...)
    .parallel()  //Stream会并行处理
    .reduce();

并行Stream在内部使用默认的ForkJoinPool,该Pool具有与处理器数量相当的线程。默认ForkJoinPool的线程大小可以被修改:

Java
1
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "16");
其它新特性
Spliterator

这是Java 8 添加的新接口,表示可分割的迭代器(Splitable Iterator),它用于支持并行迭代:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public interface Spliterator<T>
{
 
    //如果仍有剩余元素,执行指定的函数并返回true;否则返回false
    boolean tryAdvance( Consumer<? super T> action );
 
    //如果当前迭代器可以分区,那么分区出一个新的迭代器,相关元素将不被当前迭代器覆盖
    Spliterator<T> trySplit();
 
    //返回一个估算的剩余元素大小
    long estimateSize();
 
    /**
     * 说明此迭代器的特征
     * ORDERED 元素具有定义好的顺序(例如来自List),因此迭代器在遍历、分割时保持顺序
     * DISTINCT 元素具有唯一性
     * SORTED 元素具有预定义的排序顺序
     * SIZED 迭代器的源具有准确的尺寸,因此estimateSize()返回精确值
     * NONNULL 元素不会为空
     * IMMUTABLE 迭代器的源不会变化,这隐含在遍历期间,元素不会被添加、删除或修改
     * CONCURRENT 迭代器的源可以被安全的并发修改,不需要额外的同步保护
     * SUBSIZED 任何分割出的子迭代器具有准确的尺寸
     */
    int characteristics();
}
使用Optional代替null

 Java 8引入的Optional泛型类可以很好的避免一些NullPointerException或者null检查:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Company c = new Company();
c.getCeo().getAddress().getZip();//潜在可能的空指针异常
if ( c.getCeo() != null ) //冗长的空指针判断
{
    Person ceo = c.getCeo();
    if ( ceo.getAddress() != null )
    {
        Address addr = ceo.getAddress();
    }
}
Optional<Person> pnull = Optional.empty(); //持有一个空值
Optional<Person> ceo = Optional.of( new Person() );//持有一个对象,传入null立即抛出NullPointerException
Optional<Person> pnullable = Optional.ofNullable( new Person() );//持有一个对象,可以是null
//使用map函数可以安全的获取到Optional对象的属性,避免异常
Optional<Address> address = pnullable.map( Person::getAddress );

如果想让链式的getter调用安全的返回,需要改造POJO:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Address
{
    private String zip;
    public String getZip(){
        return zip;
    }
    public void setZip( String zip ){
        this.zip = zip;
    }
}
class Person{
    private Optional<Address> address;
    public Optional<Address> getAddress(){
        return address;
    }
    public void setAddress( Optional<Address> address ){
        this.address = address;
    }
 
}
class Company{
    private Optional<Person> ceo;
    public Optional<Person> getCeo(){
        return ceo;
    }
    public void setCeo( Optional<Person> ceo ){
        this.ceo = ceo;
    }
}

然后使用map函数:

Java
1
2
3
4
5
6
Optional<Company> c = Optional.empty();
String zip =
    c.flatMap( Company::getCeo ) //flatMap防止optional容器的嵌套
     .flatMap( Person::getAddress )
     .map( Address::getZip )
     .orElse( "100044" );
新的时间日期API
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* 日期API */
LocalDate today = LocalDate.now(); //当前日期
LocalDate date = LocalDate.of( 2014, 6, 12 );//2014年6月12日
int year = date.getYear();//2014
Month month = date.getMonth();//JUNE
month.ordinal(); //5
int day = date.getDayOfMonth();//12
DayOfWeek dow = date.getDayOfWeek();//THURSDAY
dow.ordinal(); //3
int len = date.lengthOfMonth();//30
boolean leap = date.isLeapYear();//false
year = date.get( ChronoField.YEAR ); //2014
int mon = date.get( ChronoField.MONTH_OF_YEAR ); //6
int dowInt = date.get( ChronoField.DAY_OF_WEEK );//4
day = date.get( ChronoField.DAY_OF_MONTH );
 
/* 时间API */
LocalTime time = LocalTime.of( 23, 6, 30 );
int hour = time.getHour();//23
int minute = time.getMinute();//6
int second = time.getSecond();//30
 
/* 从文本解析日期和时间 */
date = LocalDate.parse( "2014-06-12" );
time = LocalTime.parse( "23:06:30" );
 
/* 合并使用日期时间 */
LocalDateTime dt1 = LocalDateTime.of( 2014, Month.JUNE, 12, 23, 6, 30 );
LocalDateTime dt2 = LocalDateTime.of( date, time );
LocalDateTime dt3 = date.atTime( 23, 6, 30 );
LocalDateTime dt4 = date.atTime( time );
LocalDateTime dt5 = time.atDate( date );
CompletableFuture

这是一种新的Future类,它可以手工明确的完成:

Java
1
2
3
4
5
6
CompletableFuture<Integer> intFuture = new CompletableFuture<>();
new Thread( () -> {
    sleep( 3 );
    intFuture.complete( 10 ); //手工完成,该方法只能调用一次,obtrudeValue()方法可以用来修改值
} ).start();
intFuture.get();//阻塞,直到三秒后

CompletableFuture提供了若干静态工厂来创建实例:

Java
1
2
3
4
5
6
7
8
9
//不提供Executor,则默认使用ForkJoinPool.commonPool()
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor);
static CompletableFuture<Void> runAsync(Runnable runnable);
static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor);
 
//示例:
Cat cat = new Cat( "叮当猫", 19, 4000, "Yellow" );
CompletableFuture<Integer> ageFuture = CompletableFuture.supplyAsync( cat::getAge );

 CompletableFuture还支持在异步操作完毕后,执行若干操作

Java
1
2
3
4
5
Cat cat = new Cat( "叮当猫", 19, 4000, "Yellow" );
CompletableFuture
        .supplyAsync( cat::getAge ) //异步获取猫龄
        .thenApply( String::valueOf )//得到猫龄后,转换为字符串,同步操作
        .thenAccept( System.out::println ); //转换成字符串后,打印之,同步操作

异常处理也被支持:

Java
1
2
3
CompletableFuture
        .supplyAsync( cat::getAge ) //异步获取猫龄
        .exceptionally( ex -> 0 ); //如果获取猫龄出错,返回0

多个异步操作甚至可以被串联起来,thenCompose()方法可以用来构建异步处理管道,完全没有阻塞和等待:

Java
1
2
3
4
5
6
7
CompletableFuture
        //异步获取猫龄
        .supplyAsync( cat::getAge )
        //异步的把猫龄转换为字符串
        .thenCompose( age -> CompletableFuture.supplyAsync( age::toString ) )
        //再异步的打印猫龄的字符串
        .thenCompose( age -> CompletableFuture.runAsync( () -> System.out.println( age ) ) );

可以让两个Future分别运行,在结果都可用时,执行一个回调:

Java
1
2
3
4
5
6
7
CompletableFuture
        //异步获取猫龄
        .supplyAsync( cat::getAge )
        .thenCombine( //异步获取猫重
                CompletableFuture.supplyAsync( cat::getWeight ),
                //两个操作都完成后,计算重龄比
                ( age, weight ) -> weight / age );
← Previous Post
基于Eclipse和Maven的Groovy开发 →

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Related Posts

  • Java7新特性
  • Java5新特性
  • Log4J2学习笔记
  • Maven原型系统
  • Bazel学习笔记

Recent Posts

  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
  • A Comprehensive Study of Kotlin for Java Developers
  • 背诵营笔记
  • 利用LangChain和语言模型交互
  • 享学营笔记
ABOUT ME

汪震 | Alex Wong

江苏淮安人,现居北京。目前供职于腾讯云,专注容器方向。

GitHub:gmemcc

Git:git.gmem.cc

Email:gmemjunk@gmem.cc@me.com

ABOUT GMEM

绿色记忆是我的个人网站,域名gmem.cc中G是Green的简写,MEM是Memory的简写,CC则是我的小天使彩彩名字的简写。

我在这里记录自己的工作与生活,同时和大家分享一些编程方面的知识。

GMEM HISTORY
v2.00:微风
v1.03:单车旅行
v1.02:夏日版
v1.01:未完成
v0.10:彩虹天堂
v0.01:阳光海岸
MIRROR INFO
Meta
  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
Recent Posts
  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
    In this blog post, I will walk ...
  • A Comprehensive Study of Kotlin for Java Developers
    Introduction Purpose of the Study Understanding the Mo ...
  • 背诵营笔记
    Day 1 Find Your Greatness 原文 Greatness. It’s just ...
  • 利用LangChain和语言模型交互
    LangChain是什么 从名字上可以看出来,LangChain可以用来构建自然语言处理能力的链条。它是一个库 ...
  • 享学营笔记
    Unit 1 At home Lesson 1 In the ...
  • K8S集群跨云迁移
    要将K8S集群从一个云服务商迁移到另外一个,需要解决以下问题: 各种K8S资源的迁移 工作负载所挂载的数 ...
  • Terraform快速参考
    简介 Terraform用于实现基础设施即代码(infrastructure as code)—— 通过代码( ...
  • 草缸2021
    经过四个多月的努力,我的小小荷兰景到达极致了状态。

  • 编写Kubernetes风格的APIServer
    背景 前段时间接到一个需求做一个工具,工具将在K8S中运行。需求很适合用控制器模式实现,很自然的就基于kube ...
  • 记录一次KeyDB缓慢的定位过程
    环境说明 运行环境 这个问题出现在一套搭建在虚拟机上的Kubernetes 1.18集群上。集群有三个节点: ...
  • eBPF学习笔记
    简介 BPF,即Berkeley Packet Filter,是一个古老的网络封包过滤机制。它允许从用户空间注 ...
  • IPVS模式下ClusterIP泄露宿主机端口的问题
    问题 在一个启用了IPVS模式kube-proxy的K8S集群中,运行着一个Docker Registry服务 ...
  • 念爷爷
      今天是爷爷的头七,十二月七日、阴历十月廿三中午,老人家与世长辞。   九月初,回家看望刚动完手术的爸爸,发

  • 6 杨梅坑

  • liuhuashan
    深圳人才公园的网红景点 —— 流花山

  • 1 2020年10月拈花湾

  • 内核缺陷触发的NodePort服务63秒延迟问题
    现象 我们有一个新创建的TKE 1.3.0集群,使用基于Galaxy + Flannel(VXLAN模式)的容 ...
  • Galaxy学习笔记
    简介 Galaxy是TKEStack的一个网络组件,支持为TKE集群提供Overlay/Underlay容器网 ...
TOPLINKS
  • Zitahli's blue 91 people like this
  • 梦中的婚礼 64 people like this
  • 汪静好 61 people like this
  • 那年我一岁 36 people like this
  • 为了爱 28 people like this
  • 小绿彩 26 people like this
  • 杨梅坑 6 people like this
  • 亚龙湾之旅 1 people like this
  • 汪昌博 people like this
  • 彩虹姐姐的笑脸 24 people like this
  • 2013年11月香山 10 people like this
  • 2013年7月秦皇岛 6 people like this
  • 2013年6月蓟县盘山 5 people like this
  • 2013年2月梅花山 2 people like this
  • 2013年淮阴自贡迎春灯会 3 people like this
  • 2012年镇江金山游 1 people like this
  • 2012年徽杭古道 9 people like this
  • 2011年清明节后扬州行 1 people like this
  • 2008年十一云龙公园 5 people like this
  • 2008年之秋忆 7 people like this
  • 老照片 13 people like this
  • 火一样的六月 16 people like this
  • 发黄的相片 3 people like this
  • Cesium学习笔记 90 people like this
  • IntelliJ IDEA知识集锦 59 people like this
  • 基于Kurento搭建WebRTC服务器 38 people like this
  • Bazel学习笔记 37 people like this
  • PhoneGap学习笔记 32 people like this
  • NaCl学习笔记 32 people like this
  • 使用Oracle Java Mission Control监控JVM运行状态 29 people like this
  • 基于Calico的CNI 27 people like this
  • Ceph学习笔记 27 people like this
  • Three.js学习笔记 24 people like this
Tag Cloud
ActiveMQ AspectJ CDT Ceph Chrome CNI Command Cordova Coroutine CXF Cygwin DNS Docker eBPF Eclipse ExtJS F7 FAQ Groovy Hibernate HTTP IntelliJ IO编程 IPVS JacksonJSON JMS JSON JVM K8S kernel LB libvirt Linux知识 Linux编程 LOG Maven MinGW Mock Monitoring Multimedia MVC MySQL netfs Netty Nginx NIO Node.js NoSQL Oracle PDT PHP Redis RPC Scheduler ServiceMesh SNMP Spring SSL svn Tomcat TSDB Ubuntu WebGL WebRTC WebService WebSocket wxWidgets XDebug XML XPath XRM ZooKeeper 亚龙湾 单元测试 学习笔记 实时处理 并发编程 彩姐 性能剖析 性能调优 文本处理 新特性 架构模式 系统编程 网络编程 视频监控 设计模式 远程调试 配置文件 齐塔莉
Recent Comments
  • qg on Istio中的透明代理问题
  • heao on 基于本地gRPC的Go插件系统
  • 黄豆豆 on Ginkgo学习笔记
  • cloud on OpenStack学习笔记
  • 5dragoncon on Cilium学习笔记
  • Archeb on 重温iptables
  • C/C++编程:WebSocketpp(Linux + Clion + boostAsio) – 源码巴士 on 基于C/C++的WebSocket库
  • jerbin on eBPF学习笔记
  • point on Istio中的透明代理问题
  • G on Istio中的透明代理问题
  • 绿色记忆:Go语言单元测试和仿冒 on Ginkgo学习笔记
  • point on Istio中的透明代理问题
  • 【Maven】maven插件开发实战 – IT汇 on Maven插件开发
  • chenlx on eBPF学习笔记
  • Alex on eBPF学习笔记
  • CFC4N on eBPF学习笔记
  • 李运田 on 念爷爷
  • yongman on 记录一次KeyDB缓慢的定位过程
  • Alex on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • haolipeng on 基于本地gRPC的Go插件系统
  • 吴杰 on 基于C/C++的WebSocket库
©2005-2025 Gmem.cc | Powered by WordPress | 京ICP备18007345号-2