scala 的泛型应用如下:
一、初解
当构建一个类或者函数时,如果我们不知道(或者说不确定)传入的参数的具体数据类型,这时候可以泛型,例子如下:
1-1例子:
object test0 extends App{
val str = "123"
val intv =123
val strTest = new Test[String](str)
val intTest = new Test[Int](intv)
strTest.check;
intTest.check;
}
class Test[T](val v:T){
def check = {
if(v.isInstanceOf[String]){
println("the param is String");
}
if(v.isInstanceOf[Int]){
println("the param is Int");
}
}
}
1-2结果:
the param is String
the param is Int
结论:
很多时候我们都需要用到泛型,在web中最常见的就是在用java构建DAO层时,我们往往不知道传入的类型是什么,但是很多时候的操作都是一样的,如增删改查。用泛型+反射的模式可以写一个基于上述操作的通用的模板让所有的需要访问数据库的service层类去调用
二、界定(Bonds)
2-1例子:
class Pair[T](val first:T,val second:T){
def smaller = if(first.compareTo(second) < 0) first else second //编译器报错,因为编译器不能确定T具有compareTo的方法
}
object test1 extends App{
val a="3"
val b = "2"
val pair = new Pair(a,b);
print(pair.smaller);
}
所以改进如下:
class Pair[T<:Compareable[T]](val first:T,val second:T){
def smaller = if(first.compareTo(second) < 0) first else second
}
修改后就可以运行了。原因是参数的界定是用来限制传入的参数的类型,代表该类型具有这样的一个特性。
2-2例子:
object test1 extends App{
val a=3 // 改成整型
val b =2 // 改成整型
val pair = new Pair(a,b); // 这里将会报错,因为整型不是Compareable的子类,不具有compareto方法
print(pair.smaller);
}
这时候把Pair的界定改成浏览界定<%,这个界定符可以隐式地转换类型
class Pair[T<%Comparable[T]](val first:T,val second:T){
def smaller = if(first.compareTo(second) < 0) first else second
}
class Pair[T : Ordering](val first: T, val second: T) {
def smaller(implicit ord: Ordering[T]) =
if (ord.compare(first, second) < 0) first else second
}
上下文界定符 “:”意味着可以隐式地转换成Ordering[T]
三、类型约束(type contraints)
类型约束也可以给你用来限制类型,方式有三:
T =:= U
T <:< U
T <%< U
These constraints test whether T equals U, is a subtype of U, or is view-convertible to U.
3-1例子:
object test3 extends App{
val first = 123
val second =346
val p = new Pair1[Int](first,second) //这里未调用smaller,不报错
p.smaller;// 这里会报错,因为first,second被检测到不符合
}
class Pair1[T](val first: T, val second: T){
// 下面的方法调用了类型约束,当被调用时才会检测约束
def smaller(implicit ev: T <:< Comparable[T]) =
if (first.compareTo( second) < 0) first else second
}
四、协变与逆变
这里主要参考文章:http://hongjiang.info/scala-covariance-and-contravariance/
原文如下:
对于一个带类型参数的类型,比如 List[T]
,如果对A
及其子类型B
,满足 List[B]
也符合 List[A]
的子类型,那么就称为covariance(协变),如果 List[A]
是 List[B]
的子类型,即与原来的父子关系正相反,则称为contravariance(逆变)
协变:
_____ _____________
| | | |
| A | | List[ A ] |
|_____| |_____________|
^ ^
| |
_____ _____________
| | | |
| B | | List[ B ] |
|_____| |_____________|
逆变:
_____ _____________
| | | |
| A | | List[ B ] |
|_____| |_____________|
^ ^
| |
_____ _____________
| | | |
| B | | List[ A ] |
|_____| |_____________|
如果一个类型支持协变或逆变,则称这个类型为variance(翻译为可变的或变型),否则称为invariant(不可变的)
在Java里,泛型类型都是invariant,比如 List<String>
并不是 List<Object>
的子类型。Java并不支持声明点变型(declaration-site variance,即在定义一个类型时声明它为可变型,也称definition-site),而scala支持,可以在定义类型时声明(用加号表示为协变,减号表示逆变),如:
trait List[+T] // 在类型定义时(declaration-site)声明为协变
这样会把List[String]
作为List[Any]
的子类型。
不过Java支持使用点变型(use-site variance),所谓“使用点“,也就是在声明变量时:
List<? extends Object> list = new ArrayList<String>();
scala为了兼容java泛型通配符的形式,引入存在类型(existential type,后边再讲)时,也支持了使用点变型(use-site variance)
scala> val a : List[_ <: Any] = List[String]("A")
a: List[_] = List(A)
要注意variance并不会被继承,父类声明为variance,子类如果想要保持,仍需要声明:
scala> trait A[+T]
scala> class C[T] extends A[T] // C是invariant的
scala> class X; class Y extends X;
scala> val t:C[X] = new C[Y]
<console>:11: error: type mismatch;
found : C[Y]
required: C[X]
Note: Y <: X, but class C is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)
必须也对C声明为协变的才行:
scala> class C[+T] extends A[T]
scala> val t:C[X] = new C[Y]
t: C[X] = C@6a079142
-----------------------------新增-----------------------------
扩展---实际应用中理解逆变和协变(-)
例子:
在scala中内置一个特征函数:
- trait Function1[-S, +T] {
- def apply(x: S): T
- }
def f1(x: Bird): Animal // instance of Function1[Bird, Animal]
def f2(x: Animal): Bird // instance of Function1[Animal, Bird]
<span style="font-family:SimSun;font-size:18px;">package fineqtbull.customer
//出版物类
class Publication(val title: String)
//书籍类
class Book(title: String) extends Publication(title)
//图书库类
object Library {
//定义图书库内所有的书籍
val books: Set[Book] =
Set(
new Book("Programming in Scala"),
new Book("Walden")
)
//打印所有图书内容,使用外部传入的函数来实现
def printBookList(info: Book => AnyRef) {
//确认Scala中一个参数的函数实际上是Function1特征的实例
assert(info.isInstanceOf[Function1[_, _]])
//打印
for (book <- books)
println(info(book))
}
//打印所有图书内容,使用外部传入的GetInfoAction特征的实例来实现
def printBokkListByTrait[P >: Book, R <: AnyRef](
action : GetInfoAction[P, R]) {
//打印
for (book <- books)
println(action(book))
}
}
//取得图书内容特征,P类型参数的类型下界是Book,R类型参数的类型上界是AnyRef
trait GetInfoAction[P >: Book, R <: AnyRef] {
//取得图书内容的文本描述,对应()操作符
def apply(book : P) : R
}
//单例对象,文件的主程序
object Customer extends Application {
//定义取得出版物标题的函数
def getTitle(p: Publication): String = p.title
//使用函数来打印
Library.printBookList(getTitle)
//使用特征GetInfoAction的实例来打印
Library.printBokkListByTrait(new GetInfoAction[Publication, String] {
def apply(p: Publication) : String = p.title })
}
</span>
printBookList的info参数是Function1类型,而 Function1的-S类型参数是逆变,+T参数是协变。printBookList方法的assert(info.isInstanceOf[Function1[_, _]])语句可以验证这一点。从printBookList方法的定义可以知道,info的S类型参数是Book,T类型参数是AnyRef。然而主函数中使用处则是Library.printBookList(getTitle),getTitle函数中对应的S是Publication,T是String。为什么可以与printBookList原来的定义不一致呢,这就是协变和逆变的威力了。由于-S是逆变,而Publication是Book的父类,所以Publication可以代替(泛化为)Book。由于+T是协变,而String是AnyRef的子类,所以String可以代替(泛化为)AnyRef。如此一来,主程序的语句也就完全正确了。