Java函数式编程(八):字符串及方法引用_java

第三章 字符串,比较器和过滤器

JDK引入的一些方法对写出函数式风格的代码很有帮助。JDK库里的一些的类和接口我们已经用得非常熟悉了,比如说String,为了摆脱以前习惯的那种老的风格,我们得主动寻找机会来使用这些新的方法。同样,当我们需要用到只有一个方法的匿名内部类时,我们现在可以用lambda表达式来替换它了,不用再像原来那样写的那么繁琐了。

本章我们会使用lambda表达式和方法引用来遍历字符串,实现Comparator接口,查看目录中的文件,监视文件及目录的变更。上一章中介绍的一些方法还将继续出现在这里,来帮助我们更好的完成这些任务。你学到的这些新技术有助于将冗长繁琐的代码变得简洁,不仅能快速实现而且还易于维护。

遍历字符串

chars()方法是String类里的一个新方法,它是CharSequence接口的一部分。想要快速遍历String的字符序列的话,它是一个很有用的工具。有了这个内部迭代器,我们可以方便的操作字符串中的各个字符。先用它来处理一个字符串试试。在这里顺便介绍方法引用的几种使用方式。

复制代码 代码如下:

final String str = "w00t";
str.chars()
     .forEach(ch -> System.out.println(ch));

chars()方法返回的是一个Stream对象,我们可以用它的内部迭代器forEach()来进行遍历。在迭代器里,我们可以直接访问到字符串中的字符。下面是遍历字符串并打印各个字符的输出结果。

复制代码 代码如下:

119
48
48
116

这并不是我们想要的结果。我们希望看到的是字母,而输出的却是数字。这是因为chars()方法返回的是一个整型的Stream而不是字符型的。我们先了解下这个API,再去优化输出的结果。

前面的代码中我们创建了一个lambda表达式,作为forEach方法的入参。它只是简单地把参数传给了一个println()方法。由于这个操作很常见,我们可以借助Java编译器来对这段代码进行简化。就像在25页的使用方法引用中那样,用一个方法引用来代替它,让编译器来帮我们做参数路由。

我们已经看到如何创建一个实例方法的方法引用了。比如,name.toUpperCase()方法,方法引用就是String::toUpperCase。而下面这个例子中,我们调用的是静态引用System.out的一个实例方法。方法引用的两个冒号左边,可以是一个类名或者表达式。有了这个灵活性,我们可以很容易创建一个println()方法的引用,就像下面这样。

复制代码 代码如下:

str.chars()
     .forEach(System.out::println);

可以看到,Java编译器能很聪明的完成参数的路由。回想下lambda表达式和方法引用只能出现在接收函数式接口的地方,而Java编译器会在那个地方生成一个对应的方法(译注:编译器会生成函数式接口的实现,这个实现只有一个方法)。之前我们用过的方法引用String::toUpperCase,传给那个生成方法的参数,最后会变成这个方法调用的目标对象,就像这样:parameter.toUpperCase()。这是因为这个方法引用是基于类名的(String)。而上面这个例子中的方法引用,是基于一个表达式的,它是PrintStream的一个实例,通过System.out来引用它。由于方法调用的对象已经有了,Java编译器决定用生成方法中的参数作为这个println方法的参数:System.out.println(name)。

(译注:其实主要是两种场景,同样是传递了一个方法引用,一个是把遍历的对象,当然方法调用的目标对象,比如name.toUpperCase,另外一种是作为方法调用的参数,比如System.out.println(name).)

用了方法引用之后代码简洁多了,不过我们得去深入了解下它是如何运行的。一旦我们熟悉了方法引用,就能自己想明白参数路由这些事了。

尽管这个例子中的代码已经够简洁的了,但是输出还是不如人意。我们想看到的是字母结果却出现了数字。为了解决这个问题,我们来写个方法将int输出成字母。

复制代码 代码如下:

private static void printChar(int aChar) {
      System.out.println((char)(aChar));
}

使用方法引用可以很方便的完成输出结果的优化。

复制代码 代码如下:

str.chars()
     .forEach(IterateString::printChar);

现在虽然chars()返回的结果是int,但是也无所谓了,需要打印的时候,我们会将它转化成字符。这回的输出终于是字母了。

复制代码 代码如下:

w
0
0
t

如果我们希望从一开始就处理的就是字符而不是int,可以在调用完chars后直接将int转化成字符:

复制代码 代码如下:

str.chars()
     .mapToObj(ch -> Character.valueOf((char)ch))
     .forEach(System.out::println);

这里我们用到了chars()返回的Stream的一个内部迭代器,当然能用的可不止这一个方法。拿到Stream对象后,它的那些方法就任凭我们使用了,比如map(),filter(),reduce()等。我们可以使用filter()方法来过滤出那些是数字的字符:

复制代码 代码如下:

str.chars()
     .filter(ch -> Character.isDigit(ch))
     .forEach(ch -> printChar(ch));

这样输出的时候我们就只能看到数字了:

复制代码 代码如下:

0
0

同样的,除了将lambda表达式传给filter()和forEach()方法外,我们还可以使用方法引用。

复制代码 代码如下:

str.chars()
     .filter(Character::isDigit)
     .forEach(IterateString::printChar);

这里的方法引用把多余的参数路由给省掉了。在本例中,我们还看到了和前面两个方法的引用不同的用法。第一次我们引用的是一个实例方法,第二次是一个静态引用(System.out)上的方法。而这次则是一个静态方法的引用——方法引用一直在默默的付出。

实例方法和静态方法的引用看起来都一样:比方说String::toUpperCase和Character::isDigit。编译器会判断方法是实例方法还是静态方法,来决定如何路由参数。如果是实例方法,它会将生成方法的入参用作方法调用的目标对象,比如 parameter,toUpperCase();(当然也有例外,比如方法调用的目标对象已经指定了,像System::out.println())。另外如果是静态方法的话,生成方法的入参就会作为这个引用的方法的参数,比如Character.isDigit(parameter)。152页的附录2,有详细的方法引用的使用方法及语法说明。

尽管方法引用用起来很方便,但还有一个问题——方法命名冲突导致的二义性 。如果匹配的方法既有实例方法也有静态方法,由于方法存在歧义编译器会报错。比如这么写,Double::toString,我们其实是想要把一个double类型转化成字符串,但编译器就不知道到底是该调用public String toString()的实例方法好,还是去调用public static String toString(double)方法,因为两个方法都是Double类的。如果你碰到这样的情况,别灰心,就用lambda表达式来完成就好了。

一旦我们适应了函数式编程,我们就可以在lambda表达式和方法引用之间随心所欲地来回切换了。

本节中我们用了Java 8中的一个新方法来遍历字符串。下面我们来看下Comparator接口又有了哪些改进。

时间: 2024-09-13 03:23:51

Java函数式编程(八):字符串及方法引用_java的相关文章

Java函数式编程(三):列表的转化_java

列表的转化 将集合转化成一个新的集合就和遍历它一样简单.假设我们要将列表中的名字转化成全大写的.我们看下都有哪些实现方式. Java中的字符串是不可变的,所以它没法改变.我们可以生成新的字符串,用来替换列表中原有的元素.然而这样做的话,原来列表就没了;还有一个问题,原来的列表可能也是不可变的,比如Arrays.asList()生成的,所以修改原来的列表这招不行.还有一个缺点就是这样做很难并行操作. 生成一个新的全大写的列表是个不错的选择. 乍听起来这个建议弱爆了;性能是我们都很关注的一个问题.令

Java函数式编程(二):集合的使用_java

第二章:集合的使用 我们经常会用到各种集合,数字的,字符串的还有对象的.它们无处不在,哪怕操作集合的代码要能稍微优化一点,都能让代码清晰很多.在这章中,我们探索下如何使用lambda表达式来操作集合.我们用它来遍历集合,把集合转化成新的集合,从集合中删除元素,把集合进行合并. 遍历列表 遍历列表是最基本的一个集合操作,这么多年来,它的操作也发生了一些变化.我们使用一个遍历名字的小例子,从最古老的版本介绍到现在最优雅的版本. 用下面的代码我们很容易创建一个不可变的名字的列表: 复制代码 代码如下:

java获取昨天日期字符串的方法_java

本文实例讲述了java获取昨天日期字符串的方法.分享给大家供大家参考,具体如下: import java.text.SimpleDateFormat; import java.util.Date; public class Test { /** * 获取昨天的日期字符串 * * @param nowDate * 当前日期的字符串 * @return */ public static String getLastDayInfo(String nowDate) { String yesterday

Java函数式编程(七):MapReduce_java

译注:map(映射)和reduce(归约,化简)是数学上两个很基础的概念,它们很早就出现在各类的函数编程语言里了,直到2003年Google将其发扬光大,运用到分布式系统中进行并行计算后,这个组合的名字才开始在计算机界大放异彩(那些函数式粉可能并不这么认为).本文我们会看到Java 8在摇身一变支持函数式编程后,map和reduce组合的首次亮相(这里只是初步介绍,后续还会有针对它们的专题). 对集合进行归约 现在为止我们已经介绍了几个操作集合的新技巧了:查找匹配元素,查找单个元素,集合转化.这

Java函数式编程(一):你好,Lambda表达式_java

第一章 你好,lambda表达式! 第一节 Java的编码风格正面临着翻天覆地的变化. 我们每天的工作将会变成更简单方便,更富表现力.Java这种新的编程方式早在数十年前就已经出现在别的编程语言里面了.这些新特性引入Java后,我们可以写出更简洁,优雅,表达性更强,错误更少的代码.我们可以用更少的代码来实现各种策略和设计模式. 在本书中我们将通过日常编程中的一些例子来探索函数式风格的编程.在使用这种全新的优雅的方式进行设计编码之前,我们先来看下它到底好在哪里. 改变了你的思考方式 命令式风格--

Java函数式编程(十一):遍历目录_java

列出目录中的文件 用File类的list()方法可以很容易的列出目录中的所有文件的文件名.如果想要获取文件而不止是文件名的话,可以使用它的listFiles()方法.这很简单,难的是怎么去处理这个返回的列表.我们不再使用传统的冗长的外部迭代器,而是使用优雅的函数式来实遍历这个列表.这里我们还得用到JDK的新的CloseableStream接口以及一些相关的高阶函数. 下面这段代码可以列出当前目录下所有文件的名字. 复制代码 代码如下: Files.list(Paths.get(".")

Java函数式编程(十二):监控文件修改_java

使用flatMap列出子目录 前面已经看到如何列出指定目录下的文件了.我们再来看下如何遍历指定目录的直接子目录(深度为1),先实现一个简单的版本,然后再用更方便的flatMap()方法来实现. 我们先用传统的for循环来遍历一个指定的目录.如果子目录中有文件,就添加到列表里:否则就把子目录添加到列表里.最后,打印出所有文件的总数.代码在下面--这个是困难模式的. 复制代码 代码如下: public static void listTheHardWay() {      List<File> f

Java 8必将掀起Java函数式编程热潮

Java 8给Java带来了一场变革.很明显,这个版本是过去十年以来推出的最具份量的Java更新,其中囊括了海量新特性,包括默认方法.方法与构造函数引用以及Lambda函数等等. 其中最有趣的一项特性当数全新java.util.streamAPI,它作为Javadoc状态存在,能够对元素流进行函数式操作,例如在集合中进行map-reduce变换. 将这个新API与Lambda表达式相结合,我们就得到了一条简洁但却强大的语法,能够对应用程序中的代码进行大幅简化. 就以表面上看起来相当简单的集合过滤

整理Java编程中字符串的常用操作方法_java

字符一般情况下,当我们处理字符时,我们用原始数据类型 char. 示例 char ch = 'a'; // Unicode for uppercase Greek omega character char uniChar = '\u039A'; // an array of chars char[] charArray ={ 'a', 'b', 'c', 'd', 'e' }; 然而在开发中,我们会遇到需要使用对象而不是原始数据类型的情况.为了达到这个需求.Java 为原始数据类型 char 提