目录
一.Java8
自
1998
年
JDK 1.0
(
Java 1.0
)发布以来,
Java
已经受到了学生、项目经理和程序员等一大批活跃
用户 的欢迎。这一语言极富活力,不断被用在大大小小的项目里。从
Java 1.1
(
1997
年) 一直到
Java 7
(
2011
年),
Java
通过增加新功能,不断得到良好的升级。
Java 8
则是在
2014
年
3
月发布
的。
JDK8
、
JDK11
、
JDK17
,该怎么选择?
Java 8
提供了一个新的
API
(称为
“
流
”
,
Stream
),它支持许多处理数据的并行操作,其思路和在
数据库查询语言中的思路类似
——
用更高级的方式表达想要的东西,而由
“
实现
”
(在这里是
Streams
库)来 选择最佳低级执行机制。这样就可以避免用
synchronized
编写代码,这一代码不
仅容易出错,而且在多核
CPU
上执行所需的成本也比你想象的要高。(多核
CPU
的每个处理器内
核都有独立的高速缓存。加锁需要这些高速缓存同步运行,然而这又需要在内核间进行较慢的缓
存一致性协议通 信。)
- 日新月异的计算应用背景:多核和处理大型数据集(大数据)
- 改进的压力:函数式比命令式更适应新的体系架构
- Java 8的核心新特性:Lambda(匿名函数)、流、默认方法
二.行为参数化
在软件工程中,一个众所周知的问题就是,不管你做什么,用户的需求肯定会变。比方说,有个
应用程序是帮助农民了解自己的库存的。
这位农民可能想有一个查找库存中所有绿色苹果的功能。但到了第二天, 他可能会告诉你:
“
其
实我还想找出所有重量超过
150
克的苹果。
”
又过了两天,农民又跑回来补充 道:
“
要是我可以找出所有既是绿色,重量也超过
150
克的苹果,
那就太棒了。
”
你要如何应对这样不断变 化的需求?理想的状态下,应该把你的工作量降到最少。此外,类似的
新功能实现起来还应该很简单,而且易于长期维护。
2.1 筛选绿苹果
public static List < Apple > filterGreenApples ( List < Apple > inventory ) {List < Apple > result = new ArrayList <> ();for ( Apple apple : inventory ){if ( "green" . equals ( apple . getColor () ) {result . add ( apple );}}return result ;}
如果还需要筛选红苹果,简单的解决办法就是复制这个方法,把名字改成
filterRedApples
,
然后更改
if
条件来匹配红苹 果。要是农民想要筛选多种颜色:浅绿色、暗红色、黄色等,这种方
法就应付不了了。
2.2 把颜色作为参数
public static List < Apple > filterApplesByColor ( List < Apple > inventory ,String color ) {List < Apple > result = new ArrayList <> ();for ( Apple apple : inventory ){if ( apple . getColor (). equals ( color ) ) {result . add ( apple );}}return result ;}
这位农民又跑回来和你说:
“
要是能区分轻的苹果和重 的苹果就太好了。重的苹果一般是重量大
于
150
克。
”
2.3 筛选轻重苹果
public static List < Apple > filterApplesByWeight ( List < Apple > inventory ,int weight ) {List < Apple > result = new ArrayList <> ();For ( Apple apple : inventory ){if ( apple . getWeight () > weight ){result . add ( apple );}}return result ;}
对苹果进行筛选的部分代码大量重复,从工程工作量的角 度来看,这代价太大了。
2.4 对每个属性进行筛选
public static List < Apple > filterApples ( List < Apple > inventory ,String color ,int weight ,boolean flag ) {List < Apple > result = new ArrayList <> ();for ( Apple apple : inventory ){if ( ( flag && apple . getColor (). equals ( color )) ||( ! flag && apple . getWeight () > weight ) ){result . add ( apple );}}return result ;}
List<Apple> greenApples = filterApples(inventory, "green", 0, true);List<Apple> heavyApples = filterApples(inventory
这个解决方案还是不能很好地应对变化的需求。
如果这位农民要求你对苹果的不同属性做筛选,比如大小、形 状、产地等,又怎么办?而且,如
果农民要求你组合属性,做更复杂的查询,比如绿色的重苹果?你会有好多个重复的
filter
方法,
或一个巨大的非常复杂的方法。
2.5 通过策略模式改变
虽然判断的要求各不相同,但都是通过苹果的属性来做出来的判断,我们便可以从这上面抽象出
一个条件类(谓词类)
public interface ApplePredicate{boolean test (Apple apple);}
有了这个谓词接口,便可以实现多个不同的标准
public class AppleHeavyWeightPredicate implements ApplePredicate {public boolean test ( Apple apple ){return apple . getWeight () > 150 ;}}
public class AppleGreenColorPredicate implements ApplePredicate {public boolean test ( Apple apple ){return "green" . equals ( apple . getColor ());}}
你可以把这些标准看作
filter
方法的不同行为。你刚做的这些和
“
策略设计模式
”
相关,它让你
定义一族算法,把它们封装起来(称为
“
策略
”
),然后在运行时选择一个算法。
在这里,算法族就是
ApplePredicate
,不同的策略就是
`AppleHeavyWeightPredicate
和
AppleGreenColorPredicate
。
针对策略的使用来对
filter
方法进行改造
public static List < Apple > filterApples ( List < Apple > inventory ,ApplePredicate p ){List < Apple > result = new ArrayList <> ();for ( Apple apple : inventory ){if ( p . test ( apple )){result . add ( apple );}}return result ;}
2.6 抽象行为
现在你可以创建不同的
ApplePredicate
对象,并将它们传递给
filterApples
方法。
比如,如果农民让你找出所有重量超过
150
克的红苹果,你只需要创建一个类来实现
ApplePredicate
就行了。
public class AppleRedAndHeavyPredicate implements ApplePredicate {public boolean test ( Apple apple ){return "red" . equals ( apple . getColor ())&& apple . getWeight () > 150 ;}}List < Apple > redAndHeavyApples =filterApples ( inventory , new AppleRedAndHeavyPredicate ());
分析:test方法中放的就是filterApples所需要执行的行为
什么是行为参数化?多种行为,一个参数
通过这种方式,我们可以把行为抽象出来,让代码适应需求的变化,但这个过程很繁琐,因为你
需要声明很多只要实例化一次的类。
2.7 使用匿名类
接口可以直接用匿名类的方式实例化出来
List < Apple > redApples = filterApples ( inventory , new ApplePredicate () {public boolean test ( Apple apple ){return "red" . equals ( apple . getColor ());}});
但匿名类还是不够好,它往往很笨重,因为它占用了很多空间。
三.使用lambda
可以把
Lambda
表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参
数列 表、函数主体、返回类型,可能还有一个可以抛出的异常列表。
- 匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而想得多!
- 函数——我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。但和方法
- 一样, Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。
- 传递——Lambda表达式可以作为参数传递给方法或存储在变量中。
- 简洁——无需像匿名类那样写很多模板代码。
未使用:
Comparator < Apple > byWeight = new Comparator < Apple > () {public int compare ( Apple a1 , Apple a2 ){return a1 . getWeight (). compareTo ( a2 . getWeight ());}};
使用后:
Comparator < Apple > byWeight =( Apple a1 , Apple a2 ) -> a1 . getWeight (). compareTo ( a2 . getWeight ());
四.使用lambda表达式
List result = filterApples (inventory ,( Apple apple ) -> "red" . equals ( apple . getColor ()));
4.1 lambda使用场景
我们可以将任何的函数式接口改写为lambda的调用方式。
4.2 函数式接口
函数式接口就是只定义一个抽象方法的接口。
public interface Predicate < T > {boolean test ( T t );}
public interface Comparator < T > { //java.util.Comparatorint compare ( T o1 , T o2 );}public interface Runnable { //java.lang.Runnablevoid run ();}public interface ActionListener extends EventListener {//java.awt.event.ActionListenervoid actionPerformed ( ActionEvent e );}public interface Callable < V > { //java.util.concurrent.CallableV call ();}public interface PrivilegedAction < V > {//java.security.PrivilegedActionV run ();}
需要注意的是
:
指的是有一个需要执行的方法,而不是一个方法,因为在
java8
中引入了
default
方
法
表达式允许你直接以内联的形式为函数式接口的抽象方法提供实 现,并把整个表达式作为函数式
接口的实例(具体说来,是函数式接口一个具体实现的实例)。
4.3 @Functionallnterface
函数式接口带有
@FunctionalInterface
的标注。
这个标注用于表示该接口会设计成一个函数式接 口。如果你用
@FunctionalInterface
定义了
一个接口,而它却不是函数式接口的话,编译器将返 回一个提示原因的错误。
例如,错误消息可能是
“Multiple non-overriding abstract methods found in interface Foo”
,
表明存在多个抽象方法。请注意,
@FunctionalInterface
不是必需 的,但对于为此设计的接
口而言,使用它是比较好的做法。