您的位置:首页 > 编程语言 > Java开发

Java8特性总结以及Java的未来

2017-04-12 23:57 375 查看
从前面的介绍中,我们知道Java8确实增加了很多有用的功能,相对于之前的版本来说,算是一个跨时代的改变了。下面就总结下Java8中的特性及谈谈Java的未来。
一.回顾Java8的语言特性
任何语言的新特性都不是突发奇想的,而是一种刻意的设计。Java8的新特性源于两种趋势:对多核处理器处理能力的需求日益增长,更简洁的调度以显示风格处理数据的数据集合。这两种诉求不能很好地得到传统的,面向对象编程的支持,命令式的方式和通过迭代器访问修改字段都不能满足新的需要。在CPU的一个核上修改数据,在另一个核上读取该数据的值,代价是非常高的,要考虑容易出错的锁。不过这两种新的潮流都能通过使用函数式编程轻松地得到支持。
1.行为参数化(Lambda以及方法引用)
为了编写可重用的方法,比如filter,你需要为其制定一个参数,它能够精确的描述过滤条件。虽然使用以前的版本也能达到同样的目的(将过滤条件封装成类的一个方法,传递该类的一个实例),但这种方案很难推广,因为它非常臃肿,难于编写和维护。
Java8通过借鉴函数式编程,提供了一种新的方式——通过向方法传递代码片段来解决这一问题。这种新的方法非常方便地提供了两种变体。
传递一个Lambda表达式,即一段精简的代码片段,比如
apple -> apple.getWeight() > 150
传递一个方法引用,该方法引用指向了一个现有的方法,比如
Apple::isHeavy
2.流
集合类,迭代器以及for-each结构在Java中历史悠久。直接在集合类中添加filter和map这样的方法,利用我们前面介绍的Lambda实现类数据库查询对于Java8的设计者而言要简单很多。不过他们并没有采用这种方式,而是引入了一套全新的Stream API,为什么要这样做呢?
集合到底有什么问题,以至于我们要另起炉灶替换他们。我们把二者之间的差异概况如下:如果你有一个数据量庞大的集合,你需要对这个集合应用三个操作,比如对这个集合中的对象进行映射,对其中两个字段进行求和等,为得到结果我们需要分三次遍历集合。Stream API则与之相反,它采用延迟算法将这些操作组成一个流水线,通过单次流遍历,一次性完成所有的操作。对于大型数据集,这种操作方式要高效的多。
还有一些其他原因会影响元素并发处理的能力,Stream 尤其是它的parallel方法能帮助将一个Stream标记为适合并行处理。并行处理和对象的可变状态时水火不容的,所以核心的函数式概念(包括无副作用操作,用内部迭代替换外部迭代)对于并行使用map,filter或者其他方法发掘Stream的处理能力非常重要。
3.CompletableFuture
Java从Java5版本就提供了Future接口。Future对于充分利用多核处理能力是非常有益的,因为它允许一个任务在一个新的核上生成一个新的子线程,新生成的任务可以和原来的任务同时运行。原来的任务需要结果时,可以通过get方法等待Future运行结束。
CompletableFuture相对于Future的意义就像Stream之于Collection
通过Stream你可以对一系列的操作进行流水线,通过map,filter或者其他类似的方法提供行为参数化,它可有效避免使用迭代器时总是出现模板代码。
类似地,CompletableFuture提供了像thenCompose,thenCombine,allOf这样的操作,对Future设计的通用设计提供了函数式编程的细粒度控制,有助于避免使用命令式编程的模板代码。
CompletableFuture是Doug Lea大师写的,所以有必要后续深入了解下,Java8实战书中介绍的不是很详细,而且目前我也没有具体使用过,后续学习后再补充吧。
4.Optional
关于Optional,设计本意应该是为了解决我们编码中遇到的大量空指针问题,不过我觉得Optional更像是一个规范,使用的时候还是要调用isPresent判断是否有数据,并不能避免我们之前判断对象是否为空的代码,大概看了下Optional的源码,本质上就是对我们正常对象做了个包装,isPresent方法的实现也就是判断包装的对象是否为null,而且使用Optional并不能减少我们的代码量。不过在接口返回值上使用,会提醒调用方,接口返回的可能是类型T的值,也可能是Optional.empty表示的缺失值。
5.默认方法
接口中新引入的默认方法对类库的设计者而言简直是如鱼得水。Java8之前,接口主要用于定义方法签名,现在他们还能为接口的使用者提供方法的默认实现,如果接口的设计者认为接口中声明的某个方法并不需要每一个接口的用户显示地提供实现,就可以考虑在接口的方法声明中为其定义默认方法。
默认方法功能虽然强大,使用不好就会成为一把双刃剑,接口如果频繁改动,到了后期维护成本必然增高,所以合理使用默认方法是非常关键的。
二.Java的未来
未来Java还会有哪些重大改变谁也说不准,不过相信Java会吸取其他语言的优势来改进自己,这点相信不会另Java爱好者失望的。下面是书中的一些未来猜想
1.集合
集合比数组功能更强大,不过也确实了部分数组的有点,比如我们可以用下面的方法声明一个数组
Double []a = {1.2,3.4,5.9};
却不好用如此简单的方法声明map等集合,后续可能会支持使用新方式声明集合,如:
Map<String,Integer> map = #{"Raoul" -> 23,"Mario" -> 40,"Alan" -> 53};
2.更多的类型推断
最初在Java中,我们使用一个变量或方法都需要同时给出它的类型,随着时间的推移,这种限制被逐渐放开了,首先你可以在一个表达式中忽略泛型参数的类型,通过上下文决定其类型。比如:Map<String,String> map = new HashMap<String,String>();
在Java7之后就可以缩略为 Map<String,String> map = new HashMap<>();
其次,利用同样的思想,可以将由上下文决定的类型交由一个表达式决定,即由Lambda表达式决定,如
Function<Integer,Boolean> p = (Integer x) -> booleanExpression; 省略类型后可以简化为 Function<Integer,Boolean> p = x -> booleanExpression;
这两种情况都是由编译器对省略的类型进行推断的。
Scala和C#都允许使用关键词var替换本地变量的初始化声明,编译器会依据右边的变量填充恰当的类型,以后Java中也可能会支持这种方式。
3.更加丰富的泛型形式
目前泛型中只支持对象,而不支持基本类型,如果Java支持ArrayList<int>这种类型的泛型,那么你就可以在堆上分配由简单数据值构成的ArrayList对象了,不过这样一来ArrayList容器就无法了解它所容纳的到底是一个对象类型的值,比如String还是一个简单的int值,比如42。
某种程度上看,这并没有什么危害。不过问题还是存在,那就是垃圾回收,因为一旦确实了ArrayList中内容的运行时信息,JVM就无法判断ArrayList中的元素13到底是一个Integer的应用(可以被垃圾回收)还是int类型的简单数据(几乎可以说是无法跟踪的)。
C#语言中,ArrayList<String> ArrayList<Integer>以及ArrayList<int>的运行时表示在原则上就是不同的,即使他们的值是相同的,也伴随着足够的运行时类型信息,这些信息可以帮助垃圾收集器判断一个字段值到底是引用还是简单数据,这呗称为具化泛型。
4.对不变性更深层的支持
目前来说我们使用final来表示不可变,不过final并未真正意义上达到这个目标,比如我们用final修饰数组,只是数组的引用不会变,却不能阻止数组中的值发生改变,修饰一个List也不能阻止像list中添加元素,删除元素等。那么我们如何解决这个问题呢,由于函数式编程对不能修改现存数据结构有非常严格的要求,所以它提供了一个更强大的关键字,比如transitively_final,该关键字用于修饰引用类型的字段,确保无论是直接对该字段本身的修改,还是对通过该字段能直接或间接访问到的对象的修改都不会发生。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: