Lambda&Java多核编程-2-并行与组合行为一文中,我们对Stream<Contact>里的每一位联系人调用call()方法,并根据能否打通的返回结果过滤掉已经失效的项。

应该注意到此时filter(..)中Lambda的写法有些特殊:

// ...
.filter(Contact::call)
// ...

按常理我们应该使用s -> s.call(),但是这里却将参数、箭头以及对参数调用方法全部用其类型Contact的方法标签(暂且这样称呼)call来代替,而这个::就跟C++中的作用域解析运算符一样,让我们不禁猜测——此种写法大概是指定出了对传入参数调用其类型中的哪一个方法。

与传统的s -> s.call()相比,这种直接指定方法标签的写法似乎可以减少绝大多数使用场景中不必要出现的模板式的代码,从而进一步精简它。

方法与构造器引用

在确定了Stream<T>中每一个元素的类型后,如果使用filter(..)并为谓词演算提供参数的唯一用途就是将这个参数再提供给一个方法,那么参数列表在已经确定的情况下就基本没有存在的必要:

filter(Predicate<? super T> predicate)

Predicate<T>中的抽象方法(目标方法):

boolean test(T t)

编译器能够推断出,Lambda体会传入一个类型为T(或其父类)的参数,并对这个参数调用某个(或是实例或是某个类的静态)方法(此处应该是所有符合Predicate接口中test(..)的方法),在这种情况下我们只需要给出那个方法是什么即可,不必大费周章地去传递这个参数。

那么“标签”这个称呼就应该用更加正式的“引用”来代替,它指代了对某一方法的引用,更加贴合实际原理。

静态方法引用

只需要给出类名以及静态方法名即可,中间用双冒号分隔,如:

MyLogger::log // s -> MyLogger.log(s)

一个简单的示例,使用自定义的日志工具输出内容:

Arrays.asList("We will build a great wall and Mexico will pay."
        .split(" "))
        .forEach(MyLogger::log);

log(..)就是MyLogger类中的静态方法,forEach(..)则给Lambda提供了String类型的参数,当然了,log(..)接收的也是单个字符串作为参数,这样才能与之完美匹配:

private static void log(String content) {
    System.out.println("=> " + content);
}

这样的操作方法不仅仅只限制于单个参数的参数列表:

List<String> bullshits = Arrays.asList("Make America Great Again".split(" "));
bullshits.sort(MyTrumpBullshitComparator::doMyCompare);

因为sort(..)会一次给出两个参数供互相比较,所以我们需要构造一个接收两个String类型参数的静态方法:

private static int
doMyCompare(String toBeCompared, String toBeComparedTo) {
    return toBeCompared.compareTo(toBeComparedTo);
}

实例方法引用

绑定引用的接收者是一个具体的对象实例

就是再将静态方法引用中的静态方法名换成了实例方法名?

看上去似乎是这样,然而...

绑定方法引用仅仅在格式上类似于静态方法引用,使用双冒号将对象引用与其实例方法名隔开:

List<String> idiots = Arrays.stream("Trump-Pence"
        .split("-"))
        .map(MyIdiotNameModifier.upperCaser::toUpperCase)
        .collect(Collectors.toList());

对流中的每个名字调用了MyIdiotNameModifier类中的静态实例upperCaser的实例方法toUpperCase(..)

这个例子实在有些牵强,我是模仿了System.out.println(..)做出来的,为了表现对某个已经确定的实例引用其实例方法:

final class MyIdiotNameModifier {
    static final UpperCaser upperCaser = new UpperCaser();
}

class UpperCaser {
    String toUpperCase(String string) {
        return string.toUpperCase();
    }
}

类比sout:

// s -> MyIdiotNameModifier.upperCaser.toUpperCase(s)
MyIdiotNameModifier.upperCaser::toUpperCase

// s -> System.out.println(s)
System.out::println

之所以称之为绑定引用,是因为接收者已经确定为方法引用的一部分。

就是说如果我们引用了toUpperCase(..),那么这个引用的接受者已经能够确定是MyIdiotNameModifier类中的静态实例upperCaser

然而更魔性的东西来了,某些情况下不能够确定与实例的绑定关系。

不知道对谁调用引用的方法,那么只能排着队按顺序分配了,在只给出一个参数的时候,看起来就很明显——这个参数直接分配给绑定引用:

// 摘自推特账号@realDonaldTrump
Arrays.asList(
                "The President Changed. So Has Small Businesses' Confidence",
                "Great meeting with the @RepublicanStudy Committee this morning at the @WhiteHouse!",
                "North Korea is behaving very badly. They have been \"playing\" the United States for years. China has done little to help!")
                .forEach(String::toUpperCase);

每一条推文都被传给引用toUpperCase,并对自身调用了这个方法。

在这里我们不能确定引用的对象的标识符是什么(因为我们省略了传入的参数表),所以干脆将它的类型String写出来,告诉编译器我们会对一个String类型的接收者调用它的toUpperCase(..)方法。

构造器引用

这种引用使用new关键字代替方法名即可,但是前面需要使用的是类名而非某个对象实例的标识符:

// s -> new String(s)
String::new

小结

关于绑定与非绑定的区别,这里一定要摘录一下书中所给的例子,因为它简直堪称绝妙,自己看了很久才明白过来。

首先给出:

static final String string = "alpha-bravo-charlie";

然后,为了保证输出的顺序,我们实例化一个TreeMap<K, V>

private static Map<String, String> stringStringMap = new TreeMap<String, String>() {{
    put("alpha", "X");
    put("bravo", "Y");
    put("charlie", "Z");
}};

注意map里的键和一开始给出的字符串的对应关系。

使用绑定引用

stringStringMap.replaceAll(string::replace);

输出:

{
    alpha=X-bravo-charlie,
    bravo=alpha-Y-charlie,
    charlie=alpha-bravo-Z
}

很明显,这里我们需要一个二元函数,并返回一个结果。

map中每一项的key作为第一个参数传入,与其对应的value作为第二个参数传入,而对对象实例string的方法引用string::replace正是一个二元函数,也同时返回替换后作为结果的字符串。

为此我特意去看了Map类的replaceAll(..)的源码,根本就和String类的那种replaceAll(..)不是一回事...

首先是拿到一个自己的迭代器,然后开始迭代——如果有下一项的话取这一项(同时指针后移),并拿到这一项的键和值,随后将这两个量作为参数传递给传入replaceAll(..)的二元函数BiFunction<? super K, ? super V, ? extends V>(在这里就是string::replace),并将运算结果作为新值写进当前项(键不变),接着开始下一轮迭代...

这里做了张图,不准确,但是大概是这么个样子...

使用非绑定引用

stringStringMapB.replaceAll(String::concat);

输出:

{alpha=alphaX, bravo=bravoY, charlie=charlieZ}

我们都知道concat(..)只接收一个参数,但是在这里显然应该放置一个二元函数,那么这种写法是否会出错呢,答案是否定的。

注意观察我们会发现,这里对concat(..)方法的引用变成了非绑定的形式,也就是说,在concat(..)入参之前,还需要解决这个双冒号前面的String是谁的问题,此时replaceAll(..)给出的key、value参数将会向参数列表的前面挪一位,用于填补非绑定带来的空缺。

也就是说,此时key变成了concat(..)引用的接收者,而value则成为了此方法所需要的唯一参数...

同样做了一个貌似不怎么准确的图:

代码

弄完图发现已经快一点半了,虽说是周末但是防止我猝死还是先到这了吧...

MC.java

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Created by hwding on 3/18/17.
 */
public class MC {
    public static void main(String[] args) {
        Arrays.asList("We will build a great wall and Mexico will pay."
                .split(" "))
                .forEach(MyLogger::log);

        List<String> bullshits =
                Arrays.asList("Make America Great Again"
                        .split(" "));
        bullshits
                .sort(MyTrumpBullshitComparator::doMyCompare);
        System.out.println("\n" + bullshits + "\n");

        List<String> idiots = Arrays.stream("Trump-Pence"
                .split("-"))
                .map(MyIdiotNameModifier.upperCaser::toUpperCase)
                .collect(Collectors.toList());
        idiots.forEach(System.out::println);

        Arrays.asList(
                "The President Changed. So Has Small Businesses' Confidence",
                "Great meeting with the @RepublicanStudy Committee this morning at the @WhiteHouse!",
                "North Korea is behaving very badly. They have been \"playing\" the United States for years. China has done little to help!")
                .forEach(String::toUpperCase);

    }

    private static class MyLogger {
        private static void log(String content) {
            System.out.println("=> " + content);
        }
    }

    private static class MyTrumpBullshitComparator {
        private static int
        doMyCompare(String toBeCompared, String toBeComparedTo) {
            return toBeCompared.compareTo(toBeComparedTo);
        }
    }
}

以及其运行结果:

=> We
=> will
=> build
=> a
=> great
=> wall
=> and
=> Mexico
=> will
=> pay.

[Again, America, Great, Make]

TRUMP
PENCE

EX.java

import java.util.Map;
import java.util.TreeMap;

/**
 * Created by hwding on 3/19/17.
 */
public class EX {
    static final String string = "alpha-bravo-charlie";

    private static Map<String, String> stringStringMap = new TreeMap<String, String>() {{
        put("alpha", "X");
        put("bravo", "Y");
        put("charlie", "Z");
    }};

    private static Map<String, String> stringStringMapB = new TreeMap<String, String>() {{
        put("alpha", "X");
        put("bravo", "Y");
        put("charlie", "Z");
    }};

    public static void main(String[] args) {
        stringStringMap.replaceAll(string::replace);
        System.out.println(stringStringMap);

        stringStringMapB.replaceAll(String::concat);
        System.out.println(stringStringMapB);
    }
}

以及其运行结果:

{alpha=X-bravo-charlie, bravo=alpha-Y-charlie, charlie=alpha-bravo-Z}
{alpha=alphaX, bravo=bravoY, charlie=charlieZ}

MyIdiotNameModifier.java

/**
 * Created by hwding on 3/18/17.
 */
final class MyIdiotNameModifier {
    static final UpperCaser upperCaser = new UpperCaser();
}

class UpperCaser {
    String toUpperCase(String string) {
        return string.toUpperCase();
    }
}

Lambda&Java多核编程-6-方法与构造器引用的更多相关文章

  1. Lambda&Java多核编程-5-函数式接口与function包

    从前面的总结中我们知道Lambda的使用场景是实现一个函数式接口,那么本篇就将阐述一下何为函数式接口以及Java的function包中提供的几种函数原型. 函数式接口 早期也叫作SAM(Single ...

  2. Lambda&Java多核编程-7-类型检查

    本篇主要介绍Lambda的类型检查机制以及周边的一些知识. 类型检查 在前面的实践中,我们发现表达式的类型能够被上下文所推断.即使同一个表达式,在不同的语境下也能够被推断成不同类型. 这几天在码一个安 ...

  3. 初探Lambda表达式/Java多核编程【0】从外部迭代到内部迭代

    开篇 放假前从学校图书馆中借来一本书,Oracle官方的<精通Lambda表达式:Java多核编程>. 假期已过大半才想起来还没翻上几页,在此先推荐给大家. 此书内容及其简洁干练,如果你对 ...

  4. 初探Lambda表达式/Java多核编程【1】从集合到流

    从集合到流 接上一小节初探Lambda表达式/Java多核编程[0]从外部迭代到内部迭代,本小节将着手使用"流"这一概念进行"迭代"操作. 首先何为" ...

  5. 初探Lambda表达式/Java多核编程【2】并行与组合行为

    今天又翻了一下书的目录,第一章在这之后就结束了.也就是说,这本书所涉及到的新的知识已经全部点到了. 书的其余部分就是对这几个概念做一些基础知识的补充以及更深层次的实践. 最后两个小节的内容较少,所以合 ...

  6. 初探Lambda表达式/Java多核编程【3】Lambda语法与作用域

    接上一篇:初探Lambda表达式/Java多核编程[2]并行与组合行为 本节是第二章开篇,前一章已经浅显地将所有新概念点到,书中剩下的部分将对这些概念做一个基础知识的补充与深入探讨实践. 本章将介绍L ...

  7. 初探Lambda表达式/Java多核编程【4】Lambda变量捕获

    这周开学,上了两天感觉课好多,学校现在还停水,宿舍网络也还没通,简直爆炸,感觉能静下心看书的时间越来越少了...寒假还有些看过书之后的存货,现在写一点发出来.加上假期两个月左右都过去了书才看了1/7都 ...

  8. Java并发编程核心方法与框架-CountDownLatch的使用

    Java多线程编程中经常会碰到这样一种场景:某个线程需要等待一个或多个线程操作结束(或达到某种状态)才开始执行.比如裁判员需要等待运动员准备好后才发送开始指令,运动员要等裁判员发送开始指令后才开始比赛 ...

  9. Java - 用静态工厂方法代替构造器

    Effective Item - 考虑用静态工厂方法代替构造器我们有两种常见的方法获得一个类的实例: 公有的构造器 提供静态工厂方法(static factory method) 相对公有的构造器,静 ...

随机推荐

  1. 王爽汇编语言(第三版)环境搭建(附PDF及工具下载)

    一.前言 最近在学习汇编语言,使用的是读者评价非常高的王爽老师写的<汇编语言>(第三版),为了适应现在各个版本的windows操作系统,所以采用VMWare虚拟机来搭建纯DOS环境. 二. ...

  2. easyui帮助文档地址

    http://www.jeasyui.com/documentation/index.php# http://www.jeasyui.net/tutorial/22.html http://www.j ...

  3. 通过CXF方式实现webservice服务

    一.CXF的介绍 Apache CXF 是一个开放源代码框架,提供了用于方便地构建和开发 Web 服务的可靠基础架构.它允许创建高性能和可扩展的服务,您可以将这样的服务部署在 Tomcat 和基于 S ...

  4. session工作原理简介

    session是什么 首先,我们需要知道session是什么.有比较专业的人将session称之为会话控制.说实在的,如果这么说的话,我也不清楚session到底算是什么. 其实session是一个存 ...

  5. DB2 表空间监控

    默认DB2 缓冲池信息监控是OFF, 需要开启(DB2表空间是由缓冲池分配的) CollBufferpool : ============ The CollBufferpool collector c ...

  6. 重新认识一个强大的 Gson

    从一个 Bug 说起 不知道你们发现没有,你写完的程序无论当时怎么测试,过一段时间总会出 Bug .再说一个每天都在发生的例子:在你写完一篇博客后,立即检查的话,总是查不出自己写的错别字. 据说这些都 ...

  7. Javascript面对对象. 第四篇

    原型模式创建对象也有自己的缺点,它省略看构造函数传参初始化这一过程,带来的缺点就是初始化的值都是一致的. 而原型最大的缺点就是它优点,那就是共享. 原型中所有属性是被很多实例共享的,共享对于函数非常合 ...

  8. 3553: [Shoi2014]三叉神经树(树链剖分)

    这道题特别恶心,首先我们可以发现更改的就是出现连续的一或二,那么就用线段树+树链剖分找到这个范围 想到是不难想,就是打起来恶心罢了= = CODE: #include<cstdio> #i ...

  9. 利用busybox制作根文件系统

    实际项目中可以使用Buildroot制作根文件系统 1.busybox源码下载及配置 https://busybox.net/downloads/ 1.1.修改Makefile (1) ARCH = ...

  10. 购物篮模型&Apriori算法

    一.频繁项集 若I是一个项集,I的支持度指包含I的购物篮数目,若I的支持度>=S,则称I是频繁项集.其中,S是支持度阈值. 1.应用 "尿布和啤酒" 关联概念:寻找多篇文章中 ...