Java8新特性
传统的Java接口是一组方法的集合,任何实现者都必须实现所有方法。这意味着库的设计者很难更新接口——这会导致很多实现类无法使用。
Java 8对接口的语法做了很大的改造:
- 支持提供了实现的静态方法,这让为接口提供一个工具类的做法不再必要,工具方法可以直接放在接口中,例如Java8的Stream包含很多静态方法:
12345678public 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);}} - 支持提供了实现的接口方法:如果实现接口的类不明确提供实现,那么会继承来自接口的实现。这个特性让扩展接口变得很方便,例如Java8为List接口扩展了sort方法:
123default void sort(Comparator<? super E> c){Collections.sort(this, c);}为Collection接口扩展了stream方法:
123default Stream<E> stream() {return StreamSupport.stream(spliterator(), false);}
在先前,使用抽象类来提供缺省接口实现的模式,现在很大程度上可以由接口来代替。但是带有缺省实现的接口和抽象类还是有很大的不同:
- 类可以实现多个接口,包括带有默认方法的接口;但是类只能继承一个抽象类
- 抽象类可以具有状态,即实例变量,接口则不能有实例变量
可以用来设计“可选方法(Optional methods)” ,可以有意的让接口继承者不需要实现某些方法,例如在迭代器中,remove就是可选方法:
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 中一个类可以从父类、接口继承得到签名相同的方法,那么到底哪个生效呢?以下是几个规则:
- 类优先原则:声明在当前类或者父类中的方法,总是比接口默认方法优先
- 子接口优先原则:如果两个接口A、B,B继承A,那么B中的默认方法比A的优先级高
- 如果冲突仍然存在,那么当前类必须覆盖冲突的方法,在其中明确调用某个接口的默认方法 :
12345678910111213141516interface IHello{default void say(){}}interface IWorld{default void say(){}}class HelloWorld implements IHello, IWorld{@Overridepublic void say(){IWorld.super.say(); //明确调用第二个接口的方法}}
需要注意的是,Java 8中不会存在钻石继承的问题,根本原因在于接口继承不存在对象拷贝的问题。这个可以对比C++钻石问题去理解,在C++中D继承B、C,后两者由继承自A,就意味着D拷贝了一份B、C的字段和函数,以及两份来自A的字段和函数,这就意味着冲突,到底使用来自B的A还是来自C的A?C++使用特殊的语法来去歧义。
在策略模式中,Strategy(策略,即行为)与Context是组合关系,前者作为后者的属性。所谓参数化,就是把这种组合关系改变为参数传递关系,即通过方法参数来传递策略类。
行为参数化的好处不言而喻,它可以让Context之间进一步解耦,关联关系被取消。不会因为共享变量Strategy导致线程安全性问题,每次调用都可以灵活切换Strategy。
行为参数化这种设计思想当然从Java诞生开始就能够支持,但是在Java8中,这种思想被更多的添加到JDK的API中:
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表达式让行为参数化变得简洁:
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表达式可以理解为可以被作为出入参传递的匿名函数的简便表示形式,很多语言例如C++、Python都支持Lambda表达式,Java 8首次将其引入的Java语言中,Lambda表达式的语法如下:
1 2 3 4 5 |
(Lambda parameters) -> expression (Lambda parameters) -> { statements; } |
Lambda表达式可以用于任何函数式接口的上下文中,包括作为方法参数或者返回值。
所谓函数式接口,是指只有一个抽象方法的接口。Java 8中接口可以有默认实现,有多个方法,但是只有一个没有默认实现的接口,也被认为是函数式接口。
使用注解@FunctionalInterface可以限制一个接口为函数式接口,如果不符合函数式接口的规范,将无法通过编译:
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 的函数描述符兼容(如果参数类型兼容),例如:
1 2 3 4 |
//断言接口要求boolean返回值 Predicate<String> p = s -> list.add(s); //消费接口要求void返回值,兼容 Consumer<String> b = s -> list.add(s); |
编译器可以根据上下文进行类型推断,因此Lambda表达式的编写可以被简化:
1 2 |
//省略Lambda参数的类型声明 Comparator<String> c = ( s1, s2 ) -> s1.length() - s2.length(); |
类似于匿名类,Lambda可以使用在外部作用范围定义的局部变量,但是这些局部变量必须是final,或者相当于final(没有二次赋值语句):
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一样:
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的缩写。上面的例子等价于:
1 |
Comparator<String> c = java.util.Comparator.comparing( ( String s ) -> s.length() ); |
方法引用的写法有三大类:
- 对于静态方法的引用,例如 Integer::parseInt
- 对于某个类型的实例方法的引用,例如 String.length 。具体调用针对的对象实例由Lambda的第一个参数来提供,该方法引用等价于 (String s) -> s.length()
- 对于某个对象的实例方法的引用,例如 "".length 。具体调用针对的对象实例由上下文中的变量提供,该方法引用等价于 () -> "".length()
构造器引用类似于静态方法引用,形式为: Class::new ,示例如下:
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(); |
Java 8 中引入了很多简化Lambda编写的API:
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中,注解可以写在任何使用类型的地方:
1 2 3 4 |
// 声明,允许该注解被用在使用类型的地方、泛型类型参数上 @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface Test { } |
在类型、泛型类型参数上使用注解的例子:
1 2 3 4 5 |
// 任何类型的前面都可以附加注解(前提是该注解的ElementType允许) @Encrypted String data; List<@NonNull String> strings; graph = (@Immutable Graph) tmpGraph; Collection<? super @Existing File> c; |
类型注解可以用来在编译期进行代码检查,提高代码质量,对运行时没有影响。
Java 8引入了java.util.stream.Stream,它允许声明式的操控集合。Stream能够透明的使用多处理器并行处理。 下面是一个简明的例子:
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)的元素的序列,这些元素支持数据处理操作:
- 元素序列:Collection也是元素序列,但是Collection关心的是数据,Stream则关心操作
- 源:可以是Collection、数组、I/O资源
- 操作:Stream提供类似数据库的操作,例如filter、map、reduce、find、match、sort、limit。这些操作可以串行或者并行的被执行
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 | 化简,接收两个元素作为输入,把一个元素作为输出。化简操作会不断进行,直到只剩一个元素 |
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 ); //随机生成流 } } |
化简操作比起逐步迭代的优势在于,它能够很方便的支持并行处理。 由于化简操作限定了依据两个输入导出一个输出,并且输入输出的类型一致,这让JDK能够自由的把巨大的流分配给多个线程进行化简操作。这种Map-Reduce思想在大数据处理框架中很普遍,例如Hadoop。
Collector接口传递给Stream.collect()方法,可以用来构建Stream中元素的摘要信息。Collector也可以用来执行化简操作,对Stream元素进行化简。
Collector接口中的静态工厂方法用于创建预定义的Collector,这些Collector和Stream接口提供的功能有重叠:
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接口的主要方法如下:
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的实现:
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() 。反复调用串并行转换,则只有最后一个调用有意义:
1 2 3 4 5 6 |
stream.parallel() .filter(...) .sequential() .map(...) .parallel() //Stream会并行处理 .reduce(); |
并行Stream在内部使用默认的ForkJoinPool,该Pool具有与处理器数量相当的线程。默认ForkJoinPool的线程大小可以被修改:
1 |
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "16"); |
这是Java 8 添加的新接口,表示可分割的迭代器(Splitable Iterator),它用于支持并行迭代:
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(); } |
Java 8引入的Optional泛型类可以很好的避免一些NullPointerException或者null检查:
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:
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函数:
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" ); |
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 ); |
这是一种新的Future类,它可以手工明确的完成:
1 2 3 4 5 6 |
CompletableFuture<Integer> intFuture = new CompletableFuture<>(); new Thread( () -> { sleep( 3 ); intFuture.complete( 10 ); //手工完成,该方法只能调用一次,obtrudeValue()方法可以用来修改值 } ).start(); intFuture.get();//阻塞,直到三秒后 |
CompletableFuture提供了若干静态工厂来创建实例:
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还支持在异步操作完毕后,执行若干操作
1 2 3 4 5 |
Cat cat = new Cat( "叮当猫", 19, 4000, "Yellow" ); CompletableFuture .supplyAsync( cat::getAge ) //异步获取猫龄 .thenApply( String::valueOf )//得到猫龄后,转换为字符串,同步操作 .thenAccept( System.out::println ); //转换成字符串后,打印之,同步操作 |
异常处理也被支持:
1 2 3 |
CompletableFuture .supplyAsync( cat::getAge ) //异步获取猫龄 .exceptionally( ex -> 0 ); //如果获取猫龄出错,返回0 |
多个异步操作甚至可以被串联起来,thenCompose()方法可以用来构建异步处理管道,完全没有阻塞和等待:
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分别运行,在结果都可用时,执行一个回调:
1 2 3 4 5 6 7 |
CompletableFuture //异步获取猫龄 .supplyAsync( cat::getAge ) .thenCombine( //异步获取猫重 CompletableFuture.supplyAsync( cat::getWeight ), //两个操作都完成后,计算重龄比 ( age, weight ) -> weight / age ); |
Leave a Reply