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. 1025 PAT Ranking (25 分)

    Programming Ability Test (PAT) is organized by the College of Computer Science and Technology of Zhe ...

  2. Python Count函数的应用

    Python Count函数的应用 通过LeetCode Origin:https://leetcode-cn.com/problems/robot-return-to-origin/ 学会了Pyth ...

  3. find的基本查询命令《二》

    Linux find命令详解 由于find具有强大的功能,所以它的选项也很多,其中大部分选项都值得我们花时间来了解一下.即使系统中含有网络文件系统( NFS),find命令在该文件系统中同样有效,只你 ...

  4. 对Web语义化的思考。

    很有意思的HTML语义化 在昨天和做SEO的同学聊了一会儿,当然我没有学会搜索引擎优化的技巧和知识,但在此之前一直对HTML5中header.footer.sidebar.article等标签嗤之以鼻 ...

  5. linux被当矿机排查案例

    1.发现服务器变的特别卡,正常服务运行很慢. 到服务器上查询一番发现top下发现     bashd的进程占用100%CPU了. find /-name bashd* //第一次查询文件占用目录kil ...

  6. python10

     一.多进程multiprocessing multiprocessing包是Python中的多进程管理包.与threading.Thread类似,它可以利用multiprocessing.Proce ...

  7. 利用xposed hook Auto.js程序、解密其js脚本

    一.原理 原理很简单就是hook auto.js的com.stardust.autojs.script.StringScriptSource类,当然前题你要逆向的auto.js程序dex没有加固,当然 ...

  8. 响应式web设计(Responsive web design)

    在全面进入互联网时代后,随着各种移动设备的普及,移动互联网更加受到大众的青睐.由于移动互联网的使用量远远超出了传统互联网的使用量,移动设备也正在逐渐超越桌面设备.因为用户在移动设备上的使用习惯不同,U ...

  9. MySQL REPLACE INTO 的使用

    前段时间写游戏合服工具时出现过一个问题,源DB和目标DB角色表中主键全部都不相同,从源DB取出玩家数据再使用 replace into 写入目标DB中,结果总有几条数据插入时会导致目标DB中原有的角色 ...

  10. hadoop(十)hdfs上传删除文件(完全分布式七)|12

    集群测试 上传小文件到集群,随便选择一个小文件上传到hdfs的根目录 [shaozhiqi@hadoop102 hadoop-3.1.2]$ bin/hdfs dfs -put wcinput/wc. ...