Scala是扩展的,Scala提供了一种独特的语言机制来实现这种功能:

  • 隐式类: 允许给已有的类型添加扩展方法
  • 字符串插值: 可以让用户使用自定义的插值器进行扩展

隐式类

隐式类是在scala 2.10中引入的,隐式类指的是用implicit关键字修饰的类。在对应的作用域内,带有这个关键字的类的主构造函数可用于隐式转换。

下面举个例子:

object Helpers {
implicit class IntWithTimes(x: Int) {
def times[A](f: => A): Unit = {
def loop(current: Int): Unit =
if(current > 0) {
f
loop(current - 1)
}
loop(x)
}
}
}

这里我们定义了一个隐式类IntWithTimes, 它有一个接收Int类型的构造函数,和一个times方法。那么当我们将这个类引入到我们自己的作用域时,Int类型就拥有了新的times方法:

scala> import Helpers._
import Helpers._ scala> 5 times println("HI")
HI
HI
HI
HI
HI

限制条件

隐式类有以下限制条件:

  1. 只能在别的trait/类/对象内部定义。
    object Helpers {
implicit class RichInt(x: Int) // 正确!
}
implicit class RichDouble(x: Double) // 错误!
  1. 构造函数只能携带一个非隐式参数
implicit class RichDate(date: java.util.Date) // 正确!
implicit class Indexer[T](collecton: Seq[T], index: Int) // 错误!
implicit class Indexer[T](collecton: Seq[T])(implicit index: Index) // 正确!
  1. 在同一作用域内,不能有任何方法、成员或对象与隐式类同名,注意:这意味着隐式类不能是case class。
object Bar
implicit class Bar(x: Int) // 错误! val x = 5
implicit class x(y: Int) // 错误! implicit case class Baz(x: Int) // 错误!

字符串插值

所谓字符串插值就是将变量引用直接插入处理过的字面字符中。 这是在scala2.10.0版本引入的。

val name="James"
println(s"Hello,$name")//Hello,James

在上例中, s”Hello,$name” 是待处理字符串字面,编译器会对它做额外的工作。待处理字符串字面通过“号前的字符来标示(例如:上例中是s)。

Scala 提供了三种创新的字符串插值方法:s,f 和 raw.

s 字符串插值器

在任何字符串前加上s,就可以直接在串中使用变量了。你已经见过这个例子:

val name="James"
println(s"Hello,$name")//Hello,James

此例中,$name嵌套在一个将被s字符串插值器处理的字符串中。插值器知道在这个字符串的这个地方应该插入这个name变量的值,以使输出字符串为Hello,James。使用s插值器,在这个字符串中可以使用任何在处理范围内的名字。

字符串插值器也可以处理任意的表达式。例如:

println(s"1+1=${1+1}") 将会输出字符串1+1=2。任何表达式都可以嵌入到${}中。

f 插值器

在任何字符串字面前加上 f,就可以生成简单的格式化串,功能相似于其他语言中的 printf 函数。当使用 f 插值器的时候,所有的变量引用都应当后跟一个printf-style格式的字符串,如%d。看下面这个例子:

val height=1.9d
val name="James"
println(f"$name%s is $height%2.2f meters tall")//James is 1.90 meters tall f 插值器是类型安全的。如果试图向只支持 int 的格式化串传入一个double 值,编译器则会报错。例如: val height:Double=1.9d scala>f"$height%4d"
<console>:9: error: type mismatch;
found : Double
required: Int
f"$height%4d"
^ f 插值器利用了java中的字符串数据格式。这种以%开头的格式在 [Formatter javadoc] 中有相关概述。如果在具体变量后没有%,则格式化程序默认使用 %s(串型)格式。

raw 插值器

除了对字面值中的字符不做编码外,raw 插值器与 s 插值器在功能上是相同的。如下是个被处理过的字符串:

scala>s"a\nb"
res0:String=
a
b 这里,s 插值器用回车代替了\n。而raw插值器却不会如此处理。 scala>raw"a\nb"
res1:String=a\nb 当不想输入\n被转换为回车的时候,raw 插值器是非常实用的。

自定义插值器

在Scala中,所有处理过的字符串字面值都进行了简单编码转换。任何时候编译器遇到一个如下形式的字符串字面值:id"string content" 它都会被转换成一个StringContext实例的call(id)方法。这个方法在隐式范围内仍可用。只需要简单得 建立一个隐类,给StringContext实例增加一个新方法,便可以定义我们自己的字符串插值器。如下例:


implicit class JsonHelper(val sc:StringContext) extends AnyVal{
def json(args:Any*):JSONObject=sys.error("TODO-IMPLEMENT")
} def giveMeSomeJson(x:JSONObject):Unit=... giveMeSomeJson(json"{name:$name,id:$id}")

在这个例子中,我们试图通过字符串插值生成一个JSON文本语法。隐类 JsonHelper 作用域内使用该语法,且这个JSON方法需要一个完整的实现。只不过,字符串字面值格式化的结果不是一个字符串,而是一个JSON对象。

当编译器遇到”{name:name,id:name,id:name,id:id”}”,它将会被重写成如下表达式:

new StringContext("{name:",",id:","}").json(name,id)

隐类则被重写成如下形式:

new JsonHelper(new StringContext("{name:",",id:","}")).json(name,id)

所以,JSON方法可以访问字符串的原生片段而每个表达式都是一个值。

更多教程请参考 flydean的博客

Scala教程之:可扩展的scala的更多相关文章

  1. Scala教程之:面向对象的scala

    文章目录 面向对象的scala Unified Types Classes Traits 面向对象的scala 我们知道Scala是一种JVM语言,可以合java无缝衔接,这也就大大的扩展了scala ...

  2. Scala教程之:可变和不变集合

    文章目录 mutable HashMap immutable HashMap 集合在程序中是非常有用的,只有用好集合才能真正感受到该语言的魅力.在scala中集合主要在三个包里面:scala.coll ...

  3. Scala教程之:静态类型

    文章目录 泛类型 型变 协变 逆变 不变 类型上界 类型下界 内部类 抽象类型 复合类型 自类型 隐式参数 隐式转换 多态方法 类型推断 Scala是静态类型的,它拥有一个强大的类型系统,静态地强制以 ...

  4. scala教程之:可见性规则

    文章目录 public Protected private scoped private 和 scoped protected 和java很类似,scala也有自己的可见性规则,不同的是scala只有 ...

  5. Scala教程之:深入理解协变和逆变

    文章目录 函数的参数和返回值 可变类型的变异 在之前的文章中我们简单的介绍过scala中的协变和逆变,我们使用+ 来表示协变类型:使用-表示逆变类型:非转化类型不需要添加标记. 假如我们定义一个cla ...

  6. Scala教程之:Either

    在之前的文章中我们提到了Option,scala中Option表示存在0或者1个元素,如果在处理异常的时候Option就会有很大的限制,因为Option如果返回None,那么我并不知道具体的异常到底是 ...

  7. Scala教程之:Future和Promise

    文章目录 定义返回Future的方法 阻塞方式获取Future的值 非阻塞方式获取Future的值 Future链 flatmap VS map Future.sequence() VS Future ...

  8. Scala教程之:PartialFunction

    Scala中有一个很有用的traits叫PartialFunction,我看了下别人的翻译叫做偏函数,但是我觉得部分函数更加确切. 那么PartialFunction是做什么用的呢?简单点说Parti ...

  9. Scala教程之:Enumeration

    Enumeration应该算是程序语言里面比较通用的一个类型,在scala中也存在这样的类型, 我们看下Enumeration的定义: abstract class Enumeration (init ...

随机推荐

  1. B. Kvass and the Fair Nut

    B. Kvass and the Fair Nut time limit per test 1 second memory limit per test 256 megabytes input sta ...

  2. 摩尔投票算法( Boyer-Moore Voting Algorithm)

    一.Majority Element题目介绍:给定一个长度为n的数组的时候,找出其中的主元素,即该元素在数组中出现的次数大于n/2的取整.题目中已经假定所给的数组一定含有元素,且主元素一定存在.一下是 ...

  3. Linux基础:Day01

    Linux操作系统的知识体系: 1.初级  2.中级  3.高级  4.*额外知识*  1.初级  1.1.OS操作系统的原理  1.2.开始了解常用的命令(系统管理的基础命令)   开机关机   时 ...

  4. 个推push数据统计(爬虫)

    该方案基于任务调度框架Gearman,采用Python开发的分布式数据统计系统. 项目的目录结构很简单: # apple at localhost in ~/Develop/getui [11:24: ...

  5. docker 服务器安装harbor

    一.Harbor是什么? 二.环境搭建 2.1在linux centos搭建服务 2.2docker安装 yum安装 yum install docker 卸载 :pip uninstall dock ...

  6. String 对象-->length 属性

    1.定义和用法 length 属性返回字符串的长度(字符数). 语法: string.length 注意:根据各国字符长度计算长度 举例: var str = 'abner pan' console. ...

  7. Spire.Cloud 私有化部署教程(二)- Ubuntu 18.04 系统

    本教程主要介绍如何在Ubuntu 18.04系统上实现Spire.Cloud私有化部署.CentOS 7系统部署请参考 这篇教程. 详细步骤如下: 一.环境配置 1.关闭防火墙 1)首先查看防火墙状态 ...

  8. Redis linux 下安装

    Redis linux 下安装 下载Redis安装包,可以从Redis中文网站中下载 下载地址:http://www.redis.cn/download.html Redis4.0 稳定版本 使用&l ...

  9. python的多线程、多进程、协程用代码详解

    前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:刘早起早起 PS:如有需要Python学习资料的小伙伴可以加点击下方链 ...

  10. python这门语言为什么要起这个名字

    我只是一只可爱的小虫 前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:Liz喵 PS:如有需要Python学习资料的小 ...