欢迎关注我的新博客地址:http://cuipengfei.me/

Lower bound,不知道这个词的确切中文翻译是怎样的。我们直接看例子吧。

1
2
3
class Pair[T](val first: T, val second: T) {
def replaceFirst[R >: T](newFirst: R): Pair[R] = new Pair[R](newFirst, second)
}

我们定义一个叫做Pair的类,其中可以包含两个元素,元素类型为泛型的T。

Pair类中有一个replaceFirst方法,用来把第二个元素和一个新的元素结合起来组成一个新的Pair。新的元素的类型是泛型的R。新组成的Pair的类型是Pair[R]。

到这里我们就要想了,一个T和一个R,它们俩怎么组成新的Pair呢?新的Pair的类型怎么能是Pair[R]呢?

replaceFirst的签名给我们说明了这一点。[R >: T]。这种标记的含义是说R是T的基类。那么一个T和一个R自然可以组合成一个R的Pair了。

单是这样干说,有点不好理解,我们看一个例子:

1
2
3
4
5
class Vehicle {}

class Car extends Vehicle {}

class Tank extends Vehicle {}

汽车和坦克都是机动车。

然后我们可以这样使用它们:

1
2
  val twoCars: Pair[Car] = new Pair(new Car(), new Car())
val tankAndCar: Pair[Vehicle] = twoCars.replaceFirst(new Tank())

首先我们用两辆汽车组成一个Pair,其类型为Pair[Car]。

然后我们用一辆坦克替代原来的Pair中的第一个元素,让坦克和第二辆车组成一个新的Pair。新的Pari的类型是Pair[Vehicle]。

这里有一点tricky。我们调用replaceFirst的时候传递的参数的类型是Tank,这是否意味着在这里R就是Tank呢?

不是的,因为很明显Tank不是Car的基类,然而Tank是一个(is a)Vehicle,Vehicle同时也是Car的基类。于是此处的R就是Vehicle。得到的新的Pair自然就是Pair[Vehicle]。

也就是说R会被什么具体类型替换呢?这取决于T和newFirst的类型。

如果newFirst的类型刚好是T的基类,那太好了,R就直接是newFirst的类型。如果newFirst的类型不是T的基类,那R就会是T和newFirst的类型的共同基类。

这个东西挺麻烦的,它有啥用呢?

保证类型安全,Java没有提供给我们的类型安全。

还是刚才的那段代码:

1
2
  val twoCars: Pair[Car] = new Pair(new Car(), new Car())
val tankAndCar: Pair[Vehicle] = twoCars.replaceFirst(new Tank())

其中的第二行,Scala可以很聪明的推断出replaceFirst的返回值类型是Pair[Vehicle]。实际上,如果我们试图把tankAndCar声明为Pair[Tank]的话,会看到编译时错误。

而类似的代码在Java里则没有这么幸运了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class PairJ<T> {
private T first;
private T second; public PairJ(T first, T second) {
this.first = first;
this.second = second;
} public T first() {
return this.first;
} public T second() {
return this.second;
} public <R> Pair<R> replaceFirst(R newFirst) {
return new Pair(newFirst, second());
}
}

为了标明区别,我们这次称之为PairJ。到这里忍不住要小小的黑Java一下,21行代码,和Scala的3行是等价的:)

我们重点看一下replaceFirst在这里的声明,其中声明了一个泛型参数R,但是R和T是没有任何关系的。实际上,在Java中,我们无法表达方法的泛型参数和类型的泛型参数之间的关系。(其原因请参看这里

我们写出如下的代码:

1
2
    PairJ<Car> twoCars = new PairJ(new Car(), new Car());
Tank actuallyACar = twoCars.replaceFirst(new Tank()).second();

先创建两辆车的Pair,然后把第一辆车替换成坦克。再把新组成的Pair里面的第二个元素(其类型是车)取出来,赋值给一个类型为坦克的变量。

如果我们编译这段代码,Java编译器会允许其通过。但是运行起来就会跑出类型转换异常。原因很明显,Car不能转换成Tank。

这个,就是刚才所说的类型安全性上的差异。

等等,脱衣服的部分呢?

之前的每一篇博客都会把Scala代码编译出的bytecode反编译成Java,来探索其语言特性是如何实现的。

而这一次颇为不同。之前的语言特性虽说Scala写起来比Java会简便一些,但是还没有超出Java的能力范围。多费点劲,用Java还是能做到。

而这一次,这种编译时类型安全检验的严格性,实在是在Java中无法表达的。这全靠了Scala编译器的功劳。

这就意味着,上面所定义的Pair这个类,如果你在Java中使用它,就会失去这种类型安全性。

Scala,这次算你牛,没扒掉你。

Desugar Scala(16) -- Lower Bound的更多相关文章

  1. Desugar Scala(15) -- unapply和unapplySeq方法

    欢迎关注我的新博客地址:http://cuipengfei.me/ 实在想不到什么动词可以当做脱衣服来讲了,所以从现在开始这系列博文就叫做Desugar Scala了.除非哪天才思泉涌,又想到了新词: ...

  2. Scala - 语言专家 - Desugar Scala code

    https://mp.weixin.qq.com/s/zwrG1MfUzXwtik7jotpQsA   介绍Intellij IDEA中的一个去除Scala语法糖的功能.     ​​   1. 去除 ...

  3. 二分查找里的upper bound与lower bound的实现与分析

    1. 问题引入 最近参选了学堂在线的课程数据结构(2015秋).课程由清华大学的邓俊辉老师主讲,在完成课后作业时,遇到了这样一个题目范围查询.在这个题目中,我需要解决这样一个子问题:给定了一组已经排好 ...

  4. ELBO surgery: yet another way to carve up the variational evidence lower bound

    目录 概 主要内容 Evidence minus posterior KL Average negative energy plus entropy Average term-by-term reco ...

  5. Desugar Scala(17) -- Option和for,以及脑子里发生的事情

    欢迎关注我的新博客地址:http://cuipengfei.me/blog/2014/08/30/options-for/ Scala里的forkeyword是个非常有趣的东西. 能够用来把多层嵌套f ...

  6. [Bayes] KL Divergence & Evidence Lower Bound

    L lower是什么? L lower, 既然大于,那么多出来的这部分是什么?如下推导: 得出了KL的概念,同时也自然地引出了latent variable q.

  7. 关于upper、lower bound 的探讨

    lower_bound(A, A+n, x) - A  返回第一个大于等于x的数的下标 lower_bound(A, A+n, x) - A - 1 返回最后一个小于x的数的下标 upper_boun ...

  8. my understanding of (lower bound,upper bound) binary search, in C++, thanks to two post 分类: leetcode 2015-08-01 14:35 113人阅读 评论(0) 收藏

    If you understand the comments below, never will you make mistakes with binary search! thanks to A s ...

  9. Beginning Scala study note(8) Scala Type System

    1. Unified Type System Scala has a unified type system, enclosed by the type Any at the top of the h ...

随机推荐

  1. docker桥接

    Ifconfig docker0 down Brctl delvr docker0 进入到网卡配置文件 修改eth0网卡参数 加入BIRDGE=br0 创建一个br0 其中ip为真实机的ip 更改do ...

  2. 检索COM类工厂的组件失败:80040111

    检索 COM 类工厂中 CLSID 为 {--} 的组件失败,原因是出现以下错误: 80040111.如图: 这种问题可能由多种情况造成,目前我遇到两种: 操作系统版本问题 如果是在Win7 64版本 ...

  3. Cocos2d-x -- 图片菜单按钮

    Scene* MainMenu::createScene() { // 'scene' is an autorelease object auto scene = Scene::create(); / ...

  4. es5 - array - unshift

    /** * 描述:该unshift()方法从数组中添加单个或多个元素,并且返回长度 * 语法:arr.unshift(element1 [,... [,elementN ]]) * 参数:要添加到数组 ...

  5. taro 填坑之路(二)taro 通过事件监听 实现组件间传值

    1.组件传值的方式 2.事件监听原理 3.事件管理器 utils/event.js /** * 事件池(事件管理器) * 通过事件监听传值 */ class Event { constructor() ...

  6. com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "ExceptionId"

    com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "Exception ...

  7. C/S 中当服务器进程崩溃的时候 SIGPIPE

    程序还是https://github.com/juniperdiego/Unix-network-programming-of-mine/tree/master/tcpserv02 参考unix网络编 ...

  8. 设置linux中tcp默认的20秒connect超时时间(转)

    无论你用任何语言或者是网络库,你都可以设置网络操作的超时时间,特别是connect.read.write的超时时间. 你可以在代码中把超时时间设置任意大小值,但是connect方法会有一点特殊. co ...

  9. CentOS 6.3下Samba服务器的安装与配置(转)

    一.简介 Samba是一个能让Linux系统应用Microsoft网络通讯协议的软件,而SMB是Server Message Block的缩写,即为服务器消息块 ,SMB主要是作为Microsoft的 ...

  10. Redis总结(四)Redis 的持久化(转载)

    前面已经总结了Redis 的安装和使用今天讲下Redis 的持久化. redis跟memcached类似,都是内存数据库,不过redis支持数据持久化,也就是说redis可以将内存中的数据同步到磁盘来 ...