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接口又有了哪些改进。

(0)

相关推荐

  • java及C++中传值传递、引用传递和指针方式的理解

    java的值传递理解: 代码1: public class Test { /** * @param args */ public static void main(String[] args) { StringBuffer buffer= new StringBuffer("colin"); SChange(buffer); System.out.println( buffer); } public static void SChange (StringBuffer str) { st

  • 浅析Java方法传值和传引用问题

    某门户网站的一道笔试题 复制代码 代码如下: public class Test {     public static void stringUpd(String str) {         str = str.replace("j", "l");         System.out.println(str);     }        public static void stringBufferUpd(StringBuffer bf) {        

  • Java中的对象和对象引用实例浅析

    本文实例讲述了Java中的对象和对象引用.分享给大家供大家参考.具体分析如下: 在Java中,有一组名词经常一起出现,它们就是"对象和对象引用",很多朋友在初学Java的时候可能经常会混淆这2个概念,觉得它们是一回事,事实上则不然.今天我们就来一起了解一下对象和对象引用之间的区别和联系. 1.何谓对象? 在Java中有一句比较流行的话,叫做"万物皆对象",这是Java语言设计之初的理念之一.要理解什么是对象,需要跟类一起结合起来理解.下面这段话引自<Java编

  • Java中弱引用和软引用的区别以及虚引用和强引用介绍

    知道弱引用和软引用的概念与如何使用它们是两码事,引用类在垃圾回收工作的过程中有重要作用.我们都知道垃圾回收器会回收符合回收条件的对象的内存,但并不是所有的程序员都知道回收条件取决于指向该对象的引用类型.这正是Java中弱引用和软引用的主要区别.如果一个对象只有弱引用指向它,垃圾回收器会立即回收该对象,这是一种急切回收方式.相对的,如果有软引用指向这些对象,则只有在JVM需要内存时才回收这些对象.弱引用和软引用的特殊行为使得它们在某些情况下非常有用.例如:软引用可以很好的用来实现缓存,当JVM需要

  • 10分钟带你理解Java中的弱引用

    前言 本文尝试从What.Why.How这三个角度来探索Java中的弱引用,帮助大家理解Java中弱引用的定义.基本使用场景和使用方法. 一. What--什么是弱引用? Java中的弱引用具体指的是java.lang.ref.WeakReference<T>类,我们首先来看一下官方文档对它做的说明: 弱引用对象的存在不会阻止它所指向的对象被垃圾回收器回收.弱引用最常见的用途是实现规范映射(canonicalizing mappings,比如哈希表). 假设垃圾收集器在某个时间点决定一个对象是

  • 解析Java的JNI编程中的对象引用与内存泄漏问题

    JNI,Java Native Interface,是 native code 的编程接口.JNI 使 Java 代码程序可以与 native code 交互--在 Java 程序中调用 native code:在 native code 中嵌入 Java 虚拟机调用 Java 的代码. JNI 编程在软件开发中运用广泛,其优势可以归结为以下几点: 利用 native code 的平台相关性,在平台相关的编程中彰显优势. 对 native code 的代码重用. native code 底层操作

  • 全面解析Java中的GC与幽灵引用

    Java 中一共有 4 种类型的引用 : StrongReference. SoftReference. WeakReference 以及 PhantomReference (传说中的幽灵引用 呵呵), 这 4 种类型的引用与 GC 有着密切的关系,  让我们逐一来看它们的定义和使用场景 : 1. Strong ReferenceStrongReference 是 Java 的默认引用实现,  它会尽可能长时间的存活于 JVM 内, 当没有任何对象指向它时 GC 执行后将会被回收 Java代码

  • Java在制作jar包时引用第三方jar包的方法

    我用的是Eclipse打包,但在CMD窗口执行的时候报"ActiveMQ.jar中没有主清单属性"错误. 在网上搜了下,这个与MANIFEST.MF文件有关,该文件没有定义MAIN方法所在类的路径,利用好压打开jar包,果然如此.里面只有一行 Manifest-Version: 1.0 需添加Main-Class.在本例中,添加如下: Main-Class: com.luoluo.TestUse.activemq.ActiveMQStateMain 上面,有几点需要注意: 1. Mai

  • Java中的值传递和引用传递实例介绍

    复制代码 代码如下: package Object.reference; public class People {     private String name;     private int age;     public People(){     }     public People(String name, int age) {         super();         this.name = name;         this.age = age;     }    

  • java中的值传递和引用传递的区别分析

    传值---传递基本数据类型参数 复制代码 代码如下: public    class           PassValue{    static void exchange(int a, int b){//静态方法,交换a,b的值        int temp;        temp = a;        a = b;        b = temp;    }    public static void main(String[] args){       int i = 10;   

随机推荐