一.引言:
正常编程时,我们的类初始化参数或者方法参数都是指定的对象,例如 def sum(arr: Array[Int]) 这样,这时如果传入 arr: Array[String] 就会提示参数不合规,这时候可以通过隐式转换implcit的方法,使得参数合法化,还有一种方法就是使用泛型。泛型一般是为了适配多个场景,多见于scala各种类的源码中。例如Array的构造,我们可以构造Array[Int],也可以构造Array[String]等等,翻看源码这时候经常看到原始类是这样定义的即Array[T],这里 T 就代表泛型,他可以代表任何对象,再如上一篇文章说到的 Map 的构造方法源码中,它的定义方法就是泛型,因为一个 Map的key-value总可能是各式各样的对象:
abstract class GenMapFactory[CC[A, B] <: GenMap[A, B] with GenMapLike[A, B, CC[A, B]]] {
/** The type constructor of the collection that can be built by this factory */
type Coll = CC[_, _]
/** The default builder for $Coll objects.
* @tparam A the type of the keys
* @tparam B the type of the associated values
*/
def newBuilder[A, B]: Builder[(A, B), CC[A, B]] = new MapBuilder[A, B, CC[A, B]](empty[A, B])
/** The standard `CanBuildFrom` class for maps.
*/
class MapCanBuildFrom[A, B] extends CanBuildFrom[Coll, (A, B), CC[A, B]] {
def apply(from: Coll) = newBuilder[A, B]
def apply() = newBuilder
}
}
这里A,B,CC都代表一类泛型。
二.编写泛型类常用的语法
1.高阶函数
高阶函数就是将函数当做参数传入,argFunction 代表一个参数为Double,返回值也是Double的函数,将来调用highFunction时,需要传入一个函数签名满足argFunction条件的函数。
def highFunction(argFunction: (Double) => (Double)): Double = argFunction(10)
def multiply(num: Double): Double = num * 1.0
println(highFunction(multiply))
2.闭包
闭包就是能够读取其他函数内部变量的函数,且内函数可以访问外函数的变量:
var total = 0
def outFunction(): Int = {
def innerFunction(): Int = {
total += 1
total
}
innerFunction()
}
println(outFunction())
println(outFunction())
println(outFunction())
3.柯里化
柯里化在平常使用中经常见到,就是将具有多个参数的函数转化为一条函数链 ,每个节点是一个单一参数,有点类似于构造器的设计模式。下面两种构造方式实现相同功能,其中第二中就是柯里化的样式。
def add(x: Int, y: Int): Int = x + y
def addNew(x: Int)(y: Int): Int = x + y
println(add(1,2))
println(addNew(1)(2))
三.泛型详解
1.泛型方法
泛型T的引入使得方法可以找到任意数组的中间元素,当然也要区分具体场景,如果我每次都要得到数组各个元素的和,那么就无法实现泛型。
def getMiddle(arr: Array[Int]): Int = {
arr(arr.length / 2)
def getMiddleNew[T](arr: Array[T]): T = {
arr(arr.length / 2)
}
val arr1 = Array(1,2,3,4,5)
val arr2 = Array("a","b","c","d","e")
println(getMiddleNew(arr1))
println(getMiddleNew(arr2))
当然也可以更泛型一些,这里结合了高阶函数与柯里化,除了元素的泛型外,函数的形式也只做了笼统的要求。
def getMiddle[T](handle: T)(func: T => Any): Any = {
func(handle)
}
val arr = Array(1,2,3,4,5)
def getMid(arr: Array[Int]): Unit = {
println(arr(arr.length / 2))
}
getMiddleHandle(arr)(getMid)
2.泛型类
这里构造了泛型类,使得类更容易扩展。
class commonClass {
private var int = 0
def set(num: Int): Unit = this.int = num
def get(): Int = int
}
class genericT[T] {
private var content: T = _
def set(value: T): Unit = this.content = value
def get(): T = content
}
3.泛型变量界定
翻看源码时,也会看到如下标识 S <: T 或者 S >: T,这里 :
S <: T S必须是T的子类或者同类
S >: T T必须是S的子类或者同类
下面定义了工具类,工具类的子类车辆类,以及车辆的子类轿车类以及乘坐车辆类的方法,这里要求调用driver方法的类必须是Vehicle的子类或同类。
class Tool() {
def driver(): Unit = println("Tool Using")
}
class Vehicle() extends Tool {
override def driver(): Unit = println("Driving")
}
class Car extends Vehicle {
override def driver(): Unit = println("Car Driving")
}
def takeVehicle[T <: Vehicle](v: T): Unit = v.driver()
这里初始化工具类,车辆类与轿车类, 由于 T <: Vehicle 的约束,这里vehicle和bicycle可以调用 takeVehicle方法,而tool则因为是Vehicle的父类而无法调用。
val tool = new Tool()
val vehicle = new Vehicle()
val bicycle = new Bicycle()
takeVehicle(tool)
takeVehicle(vehicle)
takeVehicle(bicycle)
Scala的HashMap的构造方法就是使用了泛型的约束:
abstract class MutableMapFactory[CC[A, B] <: mutable.Map[A, B] with mutable.MapLike[A, B, CC[A, B]]]
extends MapFactory[CC] {
/** The default builder for $Coll objects.
* @tparam A the type of the keys
* @tparam B the type of the associated values
*/
override def newBuilder[A, B]: Builder[(A, B), CC[A, B]] = empty[A, B]
}
这里CC[A,B]代表的kv结构必须是 mutable.Map[A,B]的子类或者同类。
4.视图界定
T <% M 泛型视图界定符,表示把传入不是M[T]类型的隐式传换为M[T],这里常见的就是Comparable,Oerdring等等。
class Compare[T <% Comparable[T]](val object1: T, val object2: T) {
def compare(): T = {
if (object1.compareTo(object2) > 0) object1
else object2
}
}
这样写可以通过编译,但是scala代码建议使用因残式参数更好,所以这里我们采用另一种写法,这里implicit隐藏转换其实和 <% 的作用是一样的:
class CompareImplicit[T](val object1: T, val object2: T)(implicit object2Compare: T => Comparable[T]) {
def compare(): T = {
if (object1.compareTo(object2) > 0) object1
else object2
}
}
接下来我们需要实现这个T => Comparable[T]的转换:
class person(val age: Int) {
def getAge: Int = age
}
implicit def toComparable(p: person): Comparable[person] = new Comparable[person] {
override def compareTo(o: person): Int = {
if (p.getAge > o.getAge) 1
else 0
}
}
测试一下都是ok的:
val p1 = new person(10)
val p2 = new person(20)
val compare = new Compare(p1,p2)
val compareImplicit = new CompareImplicit(p1,p2)
println(compare.compare().getAge)
println(compareImplicit.compare().getAge)
5.上下文界定
上下文界定其实和上面的视图界定比较像,区别就是视图界定需要一个隐式方法将对应对象变为M[T],而上下文界定则是要求存在一个M[T]的隐式值,区别就是方法和值。Compare类定义了一个上下界Ordering,在其作用域内,必须存在一个Ordering[T]的隐式值,而且这个隐式值可以应用与内部的方法,放到当下场景就是该隐式值可以支持两个类进行比较。这里使用了两种定义方法,第一种利用了柯里化添加了隐式参数,可以看到柯里化频繁应用在泛型类,泛型方法的定义中,第二种并没有在参数中显示的表明需要隐式的参数,而是通过implicitly关键字拿到上下文的对象M[T],然后就是上面说的,该隐式值可以应用到内部方法。
class Compare[T: Ordering] {
def compareImplicit(first: T, second: T)(implicit ord:T => Ordered[T]):T = {
if (first > second) first
else second
}
def compareImplicitly(first: T, second: T):T = {
val order = implicitly[Ordering[T]]
if (order.gt(first, second)) first
else second
}
}
PersonOrdering继承了Ordering[T],所以实现了下面的compare方法。
class PersonOrdering extends Ordering[person] {
override def compare(x: person, y: person): Int = {
if (x.age > y.age) 1 else -1
}
}
注意这里上下文界定需要的是值,所以还需要将隐式值初始化好。
implicit val order = new PersonOrdering
测试一下,由于我们实现了上下文界定要求的ord,所以这里直接初始化调用函数即可达到需求:
val c: Compare[person] = new Compare[person]
val p1 = new person(10)
val p2 = new person(20)
println(c.compareImplicit(p1, p2).getAge)
println(c.compareImplicitly(p1, p2).getAge)
6.裂变与逆变
协变:Scala的类或特征的范型定义中,如果在类型参数前面加入+符号,就可以使类或特征变为协变了。泛型变量的值可以是本身类型或者其子类的类型
逆变:在类或特征的定义中,在类型参数之前加上一个-符号,就可定义逆变范型类和特征了。泛型变量的值可以是本身类型或者其父类的类型
裂变与逆变其实和上面的泛型约束比较相似,只不过更加抽象,适用性更加广泛。
class Tool() {
def driver(): Unit = println("Tool Using")
}
class Vehicle() extends Tool {
override def driver(): Unit = println("Driving")
}
class Car extends Vehicle {
override def driver(): Unit = println("Car Driving")
}
class UsingTool[+T](t: T){}
class TakeVehicle[-T](t: T){}
继续使用汽车的例子,这里UsingTool使用+T,代表泛型的值可以是本身类型或子类,TakeVechicle使用了-T,代表参数可以是本身或父类。如果颠倒父类与子类的关系,则会提示是不符合的type,代码无法编译。
// +T 泛型的值可以是本身或者子类
val carVehicle: UsingTool[Car] = new UsingTool[Car](new Car)
val tool: UsingTool[Tool] = carVehicle
// -T 泛型的值可以是本身或者父类
val vehicle: TakeVehicle[Vehicle] = new TakeVehicle[Vehicle](new Vehicle())
val car: TakeVehicle[Car] = vehicle
7.多重界定
T <: A with B
=> A和B为T上界
T >: A with B
=> A和B为T下界
T >: A <: B
=> A为上届B为下界且A为B的子类,类似于继承关系判断
T:A:B
=> 类型变量界定,即同时满足A[T]这种隐式值和B[T]这种隐式值
T <% A <% B
=> 视图界定,即同时能够满足隐式转换的A和隐式转换的B
8.ClassTag
按照官方文档的说法,ClassTag存储给定T的,可以通过"运行时"类访问字段,对于实例化元素类型未知的Array有用。通俗一点解释就是它包含了T的类型信息,该类型信息将用于数组创建。
def apply[T](runtimeClass1: jClass[_]): ClassTag[T] =
runtimeClass1 match {
case java.lang.Byte.TYPE => ClassTag.Byte.asInstanceOf[ClassTag[T]]
case java.lang.Short.TYPE => ClassTag.Short.asInstanceOf[ClassTag[T]]
case java.lang.Character.TYPE => ClassTag.Char.asInstanceOf[ClassTag[T]]
case java.lang.Integer.TYPE => ClassTag.Int.asInstanceOf[ClassTag[T]]
case java.lang.Long.TYPE => ClassTag.Long.asInstanceOf[ClassTag[T]]
case java.lang.Float.TYPE => ClassTag.Float.asInstanceOf[ClassTag[T]]
case java.lang.Double.TYPE => ClassTag.Double.asInstanceOf[ClassTag[T]]
case java.lang.Boolean.TYPE => ClassTag.Boolean.asInstanceOf[ClassTag[T]]
case java.lang.Void.TYPE => ClassTag.Unit.asInstanceOf[ClassTag[T]]
case ObjectTYPE => ClassTag.Object.asInstanceOf[ClassTag[T]]
case NothingTYPE => ClassTag.Nothing.asInstanceOf[ClassTag[T]]
case NullTYPE => ClassTag.Null.asInstanceOf[ClassTag[T]]
case _ => new ClassTag[T]{ def runtimeClass = runtimeClass1 }
}
可以利用上面的mkArray初始化各种类型的数组:
def mkArray[T : ClassTag](elems: T*) = Array[T](elems: _*)
val numArr = mkArray(42, 13)
println(numArr.map(_ + 3).mkString(" "))
val strArr = mkArray("Japan","Brazil","Germany")
println(strArr.map(_ + "ss").mkString(" "))
val personArr = mkArray(p1,p2)
personArr.map(p => p.getAge)
初始化数组如果不添加ClassTag或者Manifest关键字,编译器会提示你无法初始化数组,因为Scala运行时,数组必须具有具体的类型。
def mkArrayNoClassTag[T](elems: T*) = Array[T](elems: _*)
编译时会提示没有隐式的推断参数供scala判断数组类型:
9.Manifest
Manifest是类型T的描述符,编译器使用它来保存必要的信息用于实例化数组,并作为参数用在方法运行的上下文。
上面classTag的例子同样也可以用Manifest实现:
def mkArrayWithManifest[T : Manifest](elems: T*) = Array[T](elems: _*)
val numArrWithManifest = mkArrayWithManifest(42, 13)
println(numArrWithManifest.map(_ + 3).mkString(" "))
val strArrWithManifest = mkArrayWithManifest("42", "13")
println(strArrWithManifest.map(_ + "3").mkString(" "))
但是换另一个例子,ClassTag和Mainfest就有区别,使用mkArray生成不用元素数组时,用ClassTag关键字的可以打出来,但是使用Manifest却会报类型异常:
mkArray(Array(0,1),Array("1","2")).foreach(x => println(x.mkString(" ")))
0 1
1 2
val mixArrManifest = mkArrayWithManifest(Array(0,1),Array("1","2"))
Manifest还有一个用处就是可以查看类型:
def manOf[T:Manifest](t:T): Manifest[T] = manifest[T]
val mixArr = mkArray(Array(0,1),Array("1","2"))
println(manOf(mixArr))
Array[Array[_ >: java.lang.String with Int <: Any]]
这里是不是看到了多重界定的影子。
10.类型约束
// A =:= B // 表示A类型等同于B类型
// A <:< B // 表示A类型是B类型的子类
class animal extends java.io.Serializable {}
def checkIsSerialzable[T](t: T)(implicit obj: T <:< java.io.Serializable): Unit = {
println("true")
}
可以调用:
checkIsSerialzable(new animal())
不可以调用:
checkIsSerialzable(new person(10))
泛型大概就说这么多,发现在学习过程中对type,以及一些关键字的概念还是不熟悉,需要后续继续加深理解。