本节主要内容
- 中置类型(Infix Type)
- 存在类型
- 函数类型
- 抽象类型
关于语法糖的问题,在讲解程序语言时,我们常常听到“语法糖”这个术语,在百度百科中,它具有如下定义:
语法糖(Syntactic Sugar),也叫糖衣语法,
是英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语。
指的是,在计算机语言中添加某种语法,
种语法能使程序员更方便的使用语言开发程序,
同时增强程序代码的可读性,避免出错的机会;但是这种语法对语言的功能并没有影响。
例如,泛型它就是一种语法糖,即使不用泛型,也能开发出同等功能的程序,例如排序算法,我可以分别实现Double、Int等类型的排序算法,但是我们使用泛型之后,可以大大简化程序设计,减少重复代码的编写,代码可读性也有所增加。
1. 中置类型(Infix Type)
在scala中存在着中置操作符,如1+2等,+在这被称为中置操作符,前面我们说过,scala中的一切操作皆为对象调用,1+2其实是1.+(2)的对象方法调用。在scala中同样存在着中置类型,如:
class Person[T,S](val name:S,val age:T)
object InfixType extends App {
//下面的代码是一种中置表达方法,相当于
//val p:Person[String,Int]=null
val p:String Person Int=null
}
可以看到,如果类的泛型参数是两个的话,则可以使用中置表达式进行变量的定义。中置类型最常用的场景是模式匹配,例如:
//定义Person类,两个泛型参数,分别是S,T,因此
//它是可以用中置表达式进行变量定义的
case class Person[S,T](val name:S,val age:T)
object InfixType extends App {
//下面的代码是一种中置表达方法,相当于
//val p:Person[String,Int]
val p:String Person Int= Person("摇摆少年梦",18)
//中置表达式的模式匹配用法
//模式匹配时可以直接用常量,也可以直接用变量
p match {
case "摇摆少年梦" Person 18=> println("matching is ok")
case name Person age=> println("name:"+name+" age="+age)
}
}
2. 存在类型
在看一些scala语言实现的框架或别人写的程序时,我们常常会发现下列形式定义的变量,例如:
object ExisitType extends App{
//下面的Array[_]是一种存在类型,虽然用的是类型通配
//符,但它本质上等同于
//def print2(x:Array[T] forSome {type T})=println(x)
//即Array[_]中的类型通匹符也是一种语法糖,用于简化设计
def print(x:Array[_])=println(x)
}
更多的例子如:
object ExisitType extends App{
def print(x:Array[_])=println(x)
def print2(x:Array[T] forSome {type T})=println(x)
//Map[_,_]相当于Map[T,U] forSome {type T;type U}
def print3(x:Map[_,_])=println(x)
print(Array("摇摆少年梦","学途无忧网金牌讲师"))
print2(Array("摇摆少年梦","学途无忧网金牌讲师"))
print3(Map("摇摆少年梦"->"学途无忧网金牌讲师"))
}
3. 函数类型
本小节中的部分代码来自:http://hongjiang.info/scala-function-polymorphic/,感谢作者的无私奉献
在scala中函数也是具有类型的,如下面的函数定义方式
//来自API文档中的例子,Function2
object Main extends App {
//max与anonfun2是等价的,它们定义的都是输入参数是两个Int类型
//返回值也是Int类型的函数。
val max = (x: Int, y: Int) => if (x < y) y else x
//通过Funtion2定义一个输入参数为两个整型
//返回类型为Int的函数,这里是通过new创建创建函数
//而这个类正是Function2,它是函数类型类
val anonfun2 = new Function2[Int, Int, Int] {
def apply(x: Int, y: Int): Int = if (x < y) y else x
}
println(max(0, 1) == anonfun2(0, 1))
}
Function2对应的类型定义部分代码如下:
trait Function2[@specialized(scala.Int, scala.Long,
scala.Double) -T1, @specialized(scala.Int, scala.Long,
scala.Double) -T2, @specialized(scala.Unit,
scala.Boolean, scala.Int, scala.Float, scala.Long,
scala.Double) +R] extends AnyRef
在scala中还存在单个参数的Function类型即Function1,它的类型定义部分代码如下:
@annotation.implicitNotFound
(msg = "No implicit view available from ${T1} => ${R}.")
trait Function1[@specialized(scala.Int,
scala.Long, scala.Float, scala.Double/*,
(scala.Unit, scala.Boolean, scala.Int,
scala.Float, scala.Long, scala.Double/*,
scala.AnyRef*/) +R] extends AnyRef
下面的代码给出了它的用法:
object Main extends App {
val succ = (x: Int) => x + 1
val anonfun1 = new Function1[Int, Int] {
def apply(x: Int): Int = x + 1
}
//succ与anonfun1 函数是等价的,它们都定义了输入参数是Int
//返回值类型是Int的函数
assert(succ(0) == anonfun1(0))
}
通过Function1和Function2我们可以看到,其输入参数是逆变的,输出参数是协变的,我们可以通过下面的代码进行验证:
//代码给的是输出类型协变的替代使用
scala> class A; class B; class C extends B
defined class A
defined class B
defined class C
//定义一个输入类型是A,输出类型是C的函数字面量
scala> val x= (p:A)=>new C
x: A => C = <function1>
//下面的代码定义了一个变量x2,它是一个函数类型
//该函数输入是A类型,输出是B类型
//由于B是C的超类,Function1的输出参数又是协变的
//因此下面的代码是合法的
scala> val x2:A=>B = x
x2: A => B = <function1>
//代码给的是输入类型是逆变的替代使用
class R; class X; class Y extends X
//创建输入类型是X类型,输出类型是R的函数字面量
val f1 = (x:X)=>new R
//下面的代码定义的变量f2是一个输入类型是Y,返回值类型是R
//的函数字面量,它被赋值为f1,由于输入类型是逆变的,也就是
//说Y是X的子类型,X=>R则是Y=>R的子类型,因此下面的代码是合法的
val f2:Y=>R = f1
4. 抽象类型
抽象类型是指在类或特质中利用type关键字定义一个没有确定类型的标识,该标识在子类中被确定,称这种类型为抽象类型,例如:
package cn.scala.xtwy.advancedtype
//下面定义了一个抽象类
//抽象类中用type关键字声明了一个抽象类型IndentityType
abstract class Person1{
type IdentityType
//方法的返回值类型被声明为抽象类型
def getIdentityNo():IdentityType
}
//在子类中,对抽象类型进行具体化
class Student extends Person1{
//将抽象类型具体化为String类型
type IdentityType=String
def getIdentityNo()="123"
}
class Teacher extends Person1{
//将抽象类型具体化为Int类型
type IdentityType=Int
def getIdentityNo()=123
}
object AbstractType {
def main(args: Array[String]): Unit = {
//返回的是String类型
println(new Student().getIdentityNo())
}
}
上述代码的也可用泛型进行实现,如:
//使用范型参数将方法的返回值定义为抽象类型
abstract class Person2[T]{
def getIdentityNo():T
}
//子类带具体的类型String
class Student2 extends Person2[String]{
def getIdentityNo():String="123"
}
//子类带具体的类型Int
class Teacher extends Person2[Int]{
def getIdentityNo():Int=123
}
object AbstractType {
def main(args: Array[String]): Unit = {
//同样返回String类型
println(new Student2().getIdentityNo())
}
}
在实际应用中,如果类型是在实例化的时候给定的,推荐用类型参数进行类的定义,例如经常需要用到new Person[String,Int](”摇摆少年梦”,18)这种创建对象的方式,此时使用泛型更为方便;如果类型是在子类型中才被确定,则推荐使用抽象类型。例如,从代码的简洁性方面考虑,下面的代码使用抽象类型的话更”省“
//下面是抽象类型的定义方式
trait Closable{
type in
type out
def close(x:in):out
}
class File extends Closable{
type in=String
type out=Boolean
def close(x:in):out= true
//....其它方法
}
下面的代码是类型参数的定义方式:
trait Closable[S,T]{
def close(x:S):T
}
class File extends Closable[String,Boolean]{
def close(x:String):Boolean= true
//....其它方法
}
当File类中还有大量的方法要用到String及Boolean类型时,抽象类型的优越性就能表现出来。
添加公众微信号,可以了解更多最新Spark、Scala相关技术资讯