Java8新特性学习(一)

Java 中的函数
Java 8中新增了函数——值的一种新形式。
想想Java程序可能操作的值吧。首先有原始值,比如42( int 类型)和3.14( double 类型)。其次,值可以是对象(更严格地说是对象的引用)。获得对象的唯一途径是利用 new ,也许是通过工厂方法或库函数实现的;对象引用指向类的一个实例。例子包括 "abc" ( String 类型), new Integer(1111) ( Integer 类型),以及 new HashMap<Integer,String>(100) 的结果——它
显然调用了 HashMap 的构造函数。甚至数组也是对象。那么有什么问题呢?
为了帮助回答这个问题,我们要注意到,编程语言的整个目的就在于操作值,要是按照历史上编程语言的传统,这些值因此被称为一等值(或一等公民)。前面所说的值是Java中的一等公民,但其他很多Java概念(如方法和类等)则是二等公民。用方法来定义类很不错,类还可以实例化来产生值,但方法和类本身都不是值。这又有什么关系呢?还真有,人们发现,在运行时传递方法能将方法变成一等公民。这在编程中非常有用,因此Java 8的设计者把这个功能加入到了Java中。
1、方法和 Lambda 作为一等公民
Scala和Groovy等语言的实践已经证明,让方法等概念作为一等值可以扩充程序员的工具库,从而让编程变得更容易。因此,Java 8的设计者决定允许方法作为值,让编程更轻松。此外,让方法作为值也构成了其他若干Java 8功能(如 Stream )的基础。
我们介绍的Java 8的第一个新功能是方法引用。比方说,你想要筛选一个目录中的所有隐藏文件。你需要编写一个方法,然后给它一个 File ,它就会告诉你文件是不是隐藏的。幸好, File类里面有一个叫作 isHidden 的方法。我们可以把它看作一个函数,接受一个 File ,返回一个布尔值。但要用它做筛选,你需要把它包在一个 FileFilter 对象里,然后传递给 File.listFiles
方法,如下所示:
File[] hiddenFiles = new File("D://").listFiles(new FileFilter() {
            public boolean accept(File file) {
                return file.isHidden();// 筛选隐藏文件
            }
        });


显然这样做非常麻烦且不符合我们的思维逻辑,我们只需要一个筛选隐藏文件的方法就可以了,为什么还需要把它放到一个类中再实例化呢?在Java8之前我们必须要这样做,如今在Java 8里,你可以把代码重写成这个样子:
File[] files = new File("D://").listFiles(File::isHidden);
你已经有了函数 isHidden ,因此只需用Java 8的方法引用 :: 语法(即“把这个方法作为值”)将其传给 listFiles 方法;请注意,我们也开始用函数代表方法了。显然这样更符合我们的思维逻辑了,在这个例子中,方法变为了一等公民,与用对象引用传递对象类似(对象引用是用 new 创建的),在Java 8里写下File::isHidden 的时候,你就创建了一个方法引用,你同样可以传递它。

除了允许(命名)函数成为一等值外,Java 8还体现了更广义的将函数作为值的思想,包括Lambda(或匿名函数)。
2、传递代码:一个例子
假设你有一个 Apple 类,它有一个 getColor 方法,还有一个变量 inventory 保存着一个 Apples 的列表。你可能想要选出所有的绿苹果,并返回一个列表。通常我们用筛选(filter)一词来表达这个概念。在Java 8之前,你可能会写这样一个方法 :
public static List<Apple> filterGreenApples(List<Apple> inventory) {
        List<Apple> result = new ArrayList<>();//result是用来累积结果的List,开始为空,然后一个个加入绿苹果
		for (Apple apple: inventory) {
            if ("green".equals(apple.getColor())) {//仅仅选出绿苹果
                result.add(apple);
            }
        }
        return result;
    }

但是接下来,有人可能想要选出重的苹果,比如超过150克,于是你心情沉重地写了下面这个方法,甚至用了复制粘贴:
public static List<Apple> filterHeavyApples(List<Apple> inventory) {
        List<Apple> result = new ArrayList<>();
		for (Apple apple: inventory) {
            if (apple.getWeight() > 150) {
                result.add(apple);
            }
        }
        return result;
    }


我们都知道软件工程中复制粘贴的危险——给一个做了更新和修正,却忘了另一个。
Java 8会把条件代码作为参数传递进去,这样可以避免 filter 方法出现重复的代码。现在你可以写:
public static boolean isGreenApple(Apple apple) {
	return "green".equals(apple.getColor());
}
public static boolean isHeavyApple(Apple apple) {
	return apple.getWeight() > 150;
}
static List<Apple> filterApples(List<Apple> inventory,Predicate<Apple> p) {//方法作为Predicate参数p传递进去
	List<Apple> result = new ArrayList<>();
	for (Apple apple: inventory){
		if (p.test(apple)) {//苹果符合p所代表的条件吗
			result.add(apple);
		}
	}
	return result;
}
要用它的话,你可以写:
filterApples(inventory, Apple::isGreenApple);
或者
filterApples(inventory, Apple::isHeavyApple);

什么是谓词?
前面的代码传递了方法 Apple::isGreenApple (它接受参数 Apple 并返回一个boolean )给 filterApples ,后者则希望接受一个 Predicate<Apple> 参数。词 谓词(predicate)在数学上常常用来代表一个类似函数的东西,它接受一个参数值,并返回 true 或 false 。你在后面会看到,Java 8也会允许你写 Function<Apple,Boolean> ——在学校学过函数却没学过谓词的读者对此可能更熟悉,但用 Predicate<Apple> 是更标准的方式,效率也会更高一点儿,这避免了把 boolean 封装在 Boolean 里面。
3、从传递方法到 Lambda
把方法作为值来传递显然很有用,但要是为类似于 isHeavyApple 和 isGreenApple 这种可能只用一两次的短方法写一堆定义有点儿烦人。不过Java 8也解决了这个问题,它引入了一套新记法(匿名函数或Lambda),让你可以写
filterApples(inventory, (Apple a) -> "green".equals(a.getColor()) );
或者
filterApples(inventory, (Apple a) -> a.getWeight() > 150 );
甚至
filterApples(inventory, (Apple a) -> a.getWeight() < 80 || "brown".equals(a.getColor()) );

所以,你甚至都不需要为只用一次的方法写定义;代码更干净、更清晰,因为你用不着去找自己到底传递了什么代码。但要是Lambda的长度多于几行(它的行为也不是一目了然)的话,那你还是应该用方法引用来指向一个有描述性名称的方法,而不是使用匿名的Lambda。你应该以代码的清晰度为准绳。
——本文出自《JAVA8 IN ACTION》
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值