一.引言:
正常编程时,我们的类初始化参数或者方法参数都是指定的对象,例如 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,以及一些关键字的概念还是不熟悉,需要后续继续加深理解。