当前位置:网站首页>Scala Generic 泛型类详解 - T
Scala Generic 泛型类详解 - T
2022-07-22 21:23:00 【武念】
一.引言:
正常编程时,我们的类初始化参数或者方法参数都是指定的对象,例如 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,以及一些关键字的概念还是不熟悉,需要后续继续加深理解。
边栏推荐
- Understand the domestic open source Magnolia license series agreement in simple terms
- Wechat campus second-hand book trading applet graduation design finished product (4) opening report
- 一文深入浅出理解国产开源木兰许可系列协议
- Codeforces Round #809 (Div. 2) A - D1
- 景联文科技提供3D点云-图像标注服务
- Excel displays the link URL of the picture as picture to
- 局域网SDN技术硬核内幕 6 分布式任意播网关
- 如何配置CANoe Network-based access模式的以太网网络拓扑
- 93.(leaflet篇)leaflet态势标绘-进攻方向修改
- 网络安全之ARP欺骗防护
猜你喜欢

Is cross modal semantic alignment optimal under comparative learning--- Adaptive sparse attention alignment mechanism IEEE trans MultiMedia

Classes et objets (1)

LeetCode(剑指 Offer)- 11. 旋转数组的最小数字

【开发技术】SpingBoot数据库与持久化技术,JPA,MongoDB,Redis

微信小程序项目实战

智能商务的数据分析平台设计与实现

2022年暑假ACM热身练习4(总结)

真人踩过的坑,告诉你避免自动化测试常犯的10个错误

Mysql无法访问,navicat提示:is not allowed to connect to this MySQL server

Wechat campus second-hand book trading applet graduation design finished product (5) assignment
随机推荐
Interpretation of URL structure
Excel displays the link URL of the picture as picture to
ROS based navigation framework
局域网SDN硬核技术内幕 22 亢龙有悔——规格与限制(下)
Part I sourcetree installation
002_Kubernetes安装配置
【开发技术】SpingBoot数据库与持久化技术,JPA,MongoDB,Redis
LeetCode(剑指 Offer)- 11. 旋转数组的最小数字
Wechat hotel reservation applet graduation project (7) Interim inspection report
测试用例设计方法合集
如何保护 JDBC 应用程序免受 SQL 注入
信息系统项目管理师必背核心考点(四十九)合同法
《postgresql指南--内幕探索》第一章 数据库集簇、数据库和数据表
GNU LD script command language (I)
[technology popularization] alliance chain layer2- on a new possibility
LAN SDN technology hard core insider 4 from computing virtualization to network virtualization
Draw a wave ball with the curve of flutter and Bessel
聊聊并发编程的12种业务场景
避错,常见Appium相关问题及解决方案
Codeforces Round #808 (Div. 2) A - D