4. scala高阶之隐式转换与泛型

背景

上一节,我介绍了scala中的面向对象相关概念,还有一个特色功能:模式匹配。本文,我会介绍另外一个特别强大的功能隐式转换,并在最后介绍scala中泛型的使用

1. 隐式转换

Scala提供的隐式转换和隐式参数功能,是非常有特色的功能,是Java等编程语言所没有的功能。它可以允许手动指定将某种类型的对象转换成其他类型的对象。

Scala的隐式转换,其实最核心的就是定义隐式转换函数,即implicit conversion function。定义的隐式转换函数,只要在编写的程序内引入,就会被Scala自动使用。Scala会根据隐式转换函数的签名,在程序中使用到隐式转换函数接收的参数类型定义的对象时,会自动将其传入隐式转换函数,转换为另外一种类型的对象并返回。这就是“隐式转换”

隐式转换函数叫什么名字是无所谓的,因为通常不会由用户手动调用,而是由Scala进行调用。但是如果要使用隐式转换,则需要对隐式转换函数进行导入。因此通常建议将隐式转换函数的名称命名为“one2one的形式。

Spark源码中有大量的隐式转换和隐式参数。学好隐式转换会对读懂spark源码有很大的帮助。

1.1. 隐式转换的概念

隐式转换是将类型A转换成类型B,但并不是A真的就成了B,而是A本来的属性仍存在的同时又拥有了B的属性,这使得A本身不发生变化的同时扩大了功能,此属于蒙面设计模式。又因为A直接使用了B的功能而不需要对A进行修改,因此此转换是隐式的,使用implicit修饰。简言之,隐式转换就是增强类型、扩展功能。

1.2. 隐式转换的适用情况

隐式转换主要适用于以下两种情况:

  1. 如果表达式e是类型S,并且S不符合表达式的期望类型T。
  2. 在具有类型S的e的e.m表达中,如果m不表示S的成员。

1.3. 隐式转换的原理

当编译器第一次编译失败的时候,会在当前的环境中查找能让代码编译通过的方法,用于将类型进行转换,实现二次编译。当想调用对象功能时,如果编译错误,那么编译器会尝试在当前作用域范围内查找能调用对应功能的转换规则,这个调用过程是由编译器完成的,所以称之为隐式转换,或称之为自动转换。

1.4 隐式转换的作用域

Scala编译器仅考虑作用域之内的隐式转换。要使用某种隐式操作,必须以单一标识符的形式(一种情况例外)将其带入作用域之内。例如:

object TestImplicit {implicit def doubleToInt(x: Double) = x.toInt
}object Test {def main(args: Array[String]): Unit = {// 以单一标识符引进doubleToInt的隐式转换import TestImplicit._val i: Int = 2.3}
}

单一标识符有一个例外,编译器还将在源类型和目标类型的伴生对象中寻找隐式定义。

1.5. 隐式转换的规则

  1. 显示定义规则:在使用带有隐式参数的函数时,如果没有明确指定与参数类型匹配相同的隐式值,编译器不会通过额外的隐式转换来确定函数的要求。
  2. 作用域规则:不管是隐式值、隐式对象、隐式类还是隐式转换函数,都必须在当前的作用域使用才能起作用。
  3. 无歧义规则:不能存在多个隐式转换使代码合法。例如,代码中不应该存在两个隐式转换函数能够同时使某一类型转换为另一类型,也不应该存在相同的两个隐式值、主构造函数参数类型以及成员方法等同的两个隐式类。
  4. 一次性转换规则:隐式转换从源类型到目标类型只会经过一次转换,不会经过多次隐式转换达到。

1.6. 常见的隐式转换类型

1.6.1. 隐式转换函数

隐式转换函数的格式通常为:

implicit def 函数名(参数): 目标类型 = {// 函数体// 返回值
}

例如:

package com.wanlong.next
//引入隐式转换函数import com.wanlong.next.iTestImplicitConversionFunction._object iTestImplicitConversionFunction2 {def main(args: Array[String]): Unit = {val jack = new Man("jack")jack.fly
}/*** 隐式转换强大之处就是可以在不知不觉中加强现有类型的功能。也就是说,可以为某个类定义-个加强版的类,并定义互相之间的隐式转换,* 从而让源类在使用加强版的方法时,由Scala自动进行隐式转换为加强类,然后再调用该方法。*/
implicit def man2SuperMan(man: Man): SuperMan = {new SuperMan(man.name)
}
}class Man(val name: String)
class SuperMan(val name: String) {
def fly = println("SuperMan:" + name + " is flying")
}
//SuperMan:jack is flying

1.6.2. 隐式类

Scala 2.10后提供了隐式类,可以使用implicit声明类。隐式类的主构造函数只能有一个参数,且这个参数的类型就是将要被转换的目标类型。允许开发者在不修改现有类的原始定义的情况下,为该类添加新的功能。在集合中隐式类会发挥重要的作用。

隐式类的格式通常为:

implicit class 类名(参数){
// 类体,可以定义新的方法
}

需要注意的是,隐式类必须定义在另一个类、对象或者包对象(package object)内部,并且其构造函数只能有一个非隐式参数。此外,隐式类的命名虽然没有严格的语法规定,但通常会采用“Rich”前缀加上原始类型名的方式(如RichInt、RichString等)来命名,以清晰地表明这个类是用于扩展某个原始类型的。

object Helpers {
implicit class MyRichInt(arg: Int) {def myMax(i: Int): Int = if (arg < i) i else arg
}
}// 使用隐式类
import Helpers._
println(1.myMax(3))  // 输出: 3

在这个例子中,我们定义了一个隐式类MyRichInt,并为Int类型添加了一个myMax方法。当我们调用1.myMax(3)时,Scala编译器会自动将1转换为MyRichInt类型,并调用myMax方法

1.6.2.2 限制与注意事项
  1. 定义位置:隐式类必须定义在另一个类、对象或包对象内部。即隐式类不能是顶级的。
  2. 构造函数参数:隐式类的构造函数只能有一个非隐式参数。这是因为隐式转换是将一种类型转换为另外一种类型,源类型与目标类型是一一对应的。
  3. 命名规范:虽然命名没有严格规定,但建议使用“Rich”前缀加上原始类型名的方式来命名隐式类。
  4. 避免滥用:过度使用隐式类和隐式转换可能会导致代码的可读性变差。因为隐式转换是在编译器自动进行的,对于阅读代码的人来说,可能不容易发现代码中实际发生的转换。
  5. 与其他隐式机制的冲突:在Scala中,还有其他隐式机制(如隐式参数、隐式转换函数等),使用时需要注意避免冲突。
  6. 隐式类的运作方式是:隐式类将包裹目标类型,隐式类的所有方法都会自动“附加”到目标类型上。

1.7 注意事项

  1. 隐式转换函数的函数名可以是任意的,与函数名称无关,只与函数签名(函数参数和返回值类型)有关。即隐式函数的入参要是编译不通过的类型,返回值要是能正确编译的类型。
  2. 如果当前作用域中存在函数签名相同但函数名称不同的两个隐式转换函数,则在进行隐式转换时会报错。
  3. 在同一作用域内,不能有任何方法、成员或对象与隐式类同名。
  4. 隐式类不能是case class。
  5. Scala的隐式转换是一种灵活且强大的功能,但使用时需要谨慎,以避免引入难以调试的隐式行为。 通常建议,仅仅在需要进行隐式转换的地方,比如某个函数或者方法内,用immport导入隐式转换函数,这样可以缩小隐式转换函数的作用域,避免不需要的隐式转换。

2. 泛型

Scala的泛型是一种强大的特性,它允许在定义类、特质(Traits)或方法时使用类型参数。这种机制使得代码更加通用和灵活,可以适应不同的数据类型而无需重复编写代码。以下是对Scala泛型的详细介绍:

2.1 泛型的基本语法

在Scala中,使用方括号[]来定义类型参数。例如,定义一个名为Box的泛型类,它有一个类型参数T

class Box[T](val item: T) {def getValue(): T = item
}

在这里,T可以是任何合法的Scala类型。在创建Box类的实例时,可以指定具体的类型参数,如Box[Int]Box[String]

2.2 泛型的应用场景

  1. 泛型类:如上所述的Box类就是一个泛型类的例子。泛型类允许在类的定义中引入类型参数,从而在创建对象时指定具体的类型。
  2. 泛型方法:泛型方法允许方法的参数类型或返回类型根据调用时的实际情况来确定。例如,定义一个泛型方法printList,它可以接受任何类型的列表并打印出列表中的元素:
def printList[T](list: List[T]): Unit = {list.foreach(println)
}

在调用printList方法时,可以传入不同类型的列表,如List[Int]List[String]

  1. 泛型特质:泛型特质允许在特质的定义中引入类型参数。这样,在定义特质的子类或子单例对象时,可以指定具体的类型参数。例如,定义一个泛型特质Logger,它有一个变量a和一个show方法,它们都使用Logger特质的泛型:
trait Logger[T] {val a: Tdef show(b: T): Unit = println(b)
}

然后,可以定义一个单例对象ConsoleLogger,它继承Logger特质并指定具体的类型参数为String

object ConsoleLogger extends Logger[String] {override val a: String = "张三"
}

2.3 泛型的上下界

在使用泛型时,有时需要限制泛型参数的类型范围。这时,可以使用泛型的上下界。

  1. 上界:使用T <: 类型名表示给类型添加一个上界,即泛型参数T必须是该类型或其子类。例如,定义一个泛型方法demo,它接受一个Array参数,并限定该Array的元素类型只能是Person或其子类:
class Person
class Student extends Persondef demo[T <: Person](arr: Array[T]): Unit = println(arr)
  1. 下界:使用T >: 类型名表示给类型添加一个下界,即泛型参数T必须是该类型或其父类。例如,定义一个泛型类Shelter,它接受一个类型参数T,并限定T必须是Dog或其父类:
class Animal
class Dog extends Animalclass Shelter[T >: Dog](val animal: T)

如果泛型既有上界又有下界,下界应写在前面,上界写在后面,即[T >: 类型1 <: 类型2]

2.4 协变、逆变与非变

在Scala中,泛型参数还可以声明为协变(Covariance)、逆变(Contravariance)或非变(Invariance)。

  1. 非变:默认情况下,泛型类是非变的。这意味着,如果BA的子类型,那么Pair[A]Pair[B]之间没有任何从属关系。
  2. 协变:如果类型B是类型A的子类型,那么Pair[B]可以认为是Pair[A]的子类型。这称为协变关系。在Scala中,可以通过在类型参数前加上+符号来声明协变关系(但在Scala的类声明中通常不显式声明,而是通过类型系统的规则来隐式处理)。
  3. 逆变:如果类型B是类型A的子类型,那么Pair[A]可以认为是Pair[B]的子类型(这在实际中较少见,因为通常函数参数的逆变和返回类型的协变是通过函数类型来处理的,而不是简单的泛型类)。这称为逆变关系。在Scala中,可以通过在类型参数前加上-符号来声明逆变关系(同样,在Scala的类声明中不显式声明逆变)。

需要注意的是,协变和逆变主要影响泛型类型的子类型关系,它们在Scala的类型系统中有着复杂而重要的应用,特别是在处理函数类型和集合类型时。

2.5 泛型的类型擦除

Scala的泛型系统在编译时会将泛型类型信息擦除,生成的字节码中不包含泛型类型的具体信息。这个过程被称为擦除(Type Erasure)。擦除机制是为了保持与Java的互操作性并减少运行时的开销。由于擦除机制,运行时无法获取泛型类型的具体信息,因此不能直接在泛型代码中执行某些类型特定的操作(如创建泛型类型的实例或检查泛型类型的参数类型)。然而,Scala提供了一些反射相关的工具和方法(如ManifestTypeTagClassTag等)来在一定程度上恢复泛型类型的信息。

Scala的泛型是一种强大的特性,它允许编写更加通用和灵活的代码。通过合理使用泛型的上下界、协变、逆变和非变等特性以及理解泛型的类型擦除机制,可以编写出更加健壮和可维护的Scala程序。

以上,如有错误,请不吝指正!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/65815.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第432场周赛:跳过交替单元格的之字形遍历、机器人可以获得的最大金币数、图的最大边权的最小值、统计 K 次操作以内得到非递减子数组的数目

Q1、跳过交替单元格的之字形遍历 1、题目描述 给你一个 m x n 的二维数组 grid&#xff0c;数组由 正整数 组成。 你的任务是以 之字形 遍历 grid&#xff0c;同时跳过每个 交替 的单元格。 之字形遍历的定义如下&#xff1a; 从左上角的单元格 (0, 0) 开始。在当前行中向…

Harry技术添加存储(minio、aliyun oss)、短信sms(aliyun、模拟)、邮件发送等功能

Harry技术添加存储&#xff08;minio、aliyun oss&#xff09;、短信sms&#xff08;aliyun、模拟&#xff09;、邮件发送等功能 基于SpringBoot3Vue3前后端分离的Java快速开发框架 项目简介&#xff1a;基于 JDK 17、Spring Boot 3、Spring Security 6、JWT、Redis、Mybatis-P…

R数据分析:多分类问题预测模型的ROC做法及解释

有同学做了个多分类的预测模型,结局有三个类别,做的模型包括多分类逻辑回归、随机森林和决策树,多分类逻辑回归是用ROC曲线并报告AUC作为模型评估的,后面两种模型报告了混淆矩阵,审稿人就提出要统一模型评估指标。那么肯定是统一成ROC了,刚好借这个机会给大家讲讲ROC在多…

记一次学习skynet中的C/Lua接口编程解析protobuf过程

1.引言 最近在学习skynet过程中发现在网络收发数据的过程中数据都是裸奔&#xff0c;就想加入一种数据序列化方式&#xff0c;json、xml简单好用&#xff0c;但我就是不想用&#xff0c;于是就想到了protobuf&#xff0c;对于protobuf C/C的使用个人感觉有点重&#xff0c;正好…

SQLAlchemy

https://docs.sqlalchemy.org.cn/en/20/orm/quickstart.htmlhttps://docs.sqlalchemy.org.cn/en/20/orm/quickstart.html 声明模型 在这里&#xff0c;我们定义模块级构造&#xff0c;这些构造将构成我们从数据库中查询的结构。这种结构被称为 声明式映射&#xff0c;它同时定…

Trimble自动化激光监测支持历史遗产实现可持续发展【沪敖3D】

故事桥&#xff08;Story Bridge&#xff09;位于澳大利亚布里斯班&#xff0c;建造于1940年&#xff0c;全长777米&#xff0c;横跨布里斯班河&#xff0c;可载汽车、自行车和行人往返于布里斯班的北部和南部郊区。故事桥是澳大利亚最长的悬臂桥&#xff0c;是全世界两座手工建…

Playwright vs Selenium:全面对比分析

在现代软件开发中&#xff0c;自动化测试工具在保证应用质量和加快开发周期方面发挥着至关重要的作用。Selenium 作为自动化测试领域的老牌工具&#xff0c;长期以来被广泛使用。而近年来&#xff0c;Playwright 作为新兴工具迅速崛起&#xff0c;吸引了众多开发者的关注。那么…

Windows 程序设计3:宽窄字节的区别及重要性

文章目录 前言一、宽窄字节简介二、操作系统及VS编译器对宽窄字节的编码支持1. 操作系统2. 编译器 三、宽窄字符串的优缺点四、宽窄字节数据类型总结 前言 Windows 程序设计3&#xff1a;宽窄字节的区别及重要性。 一、宽窄字节简介 在C中&#xff0c;常用的字符串指针就是ch…

进阶——十六届蓝桥杯嵌入式熟练度练习(LED的全开,全闭,点亮指定灯,交替闪烁,PWM控制LED呼吸灯)

点亮灯的函数 void led_show(unsigned char upled) { HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOC,upled<<8,GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RE…

力扣 最大子数组和

动态规划&#xff0c;前缀和&#xff0c;维护状态更新。 题目 从题可以看出&#xff0c;找的是最大和的连续子数组&#xff0c;即一个数组中的其中一个连续部分。从前往后遍历&#xff0c;每遍历到一个数可以尝试做叠加&#xff0c;注意是尝试&#xff0c;因为有可能会遇到一个…

Homestyler 和 Tripo AI 如何利用人工智能驱动的 3D 建模改变定制室内设计

让设计梦想照进现实 在Homestyler,我们致力于为每一个梦想设计师提供灵感的源泉,而非挫折。无论是初学者打造第一套公寓,或是专业设计师展示作品集,我们的直观工具都能让您轻松以惊人的3D形式呈现空间。 挑战:实现定制设计的新纪元 我们知道,将个人物品如传家宝椅子、…

算法练习4——一个六位数

这道题特别妙 大家仔细做一做 我这里采用的是动态规划来解这道题 结合题目要求找出数与数之间的规律 抽象出状态转移方程 题目描述 有一个六位数&#xff0c;其个位数字 7 &#xff0c;现将个位数字移至首位&#xff08;十万位&#xff09;&#xff0c;而其余各位数字顺序不…

client-go 的 QPS 和 Burst 限速

1. 什么是 QPS 和 Burst &#xff1f; 在 kubernetes client-go 中&#xff0c;QPS 和 Burst 是用于控制客户端与 Kubernetes API 交互速率的两个关键参数&#xff1a; QPS (Queries Per Second) 定义&#xff1a;表示每秒允许发送的请求数量&#xff0c;即限速器的平滑速率…

太原理工大学软件设计与体系结构 --javaEE

这个是简答题的内容 选择题的一些老师会给你们题库&#xff0c;一些注意的点我会做出文档在这个网址 项目目录预览 - TYUT复习资料:复习资料 - GitCode 希望大家可以给我一些打赏 什么是Spring的IOC和DI IOC 是一种设计思想&#xff0c;它将对象的创建和对象之间的依赖关系…

深度学习知识点:LSTM

文章目录 1.应用现状2.发展历史3.基本结构4.LSTM和RNN的差异 1.应用现状 长短期记忆神经网络&#xff08;LSTM&#xff09;是一种特殊的循环神经网络(RNN)。原始的RNN在训练中&#xff0c;随着训练时间的加长以及网络层数的增多&#xff0c;很容易出现梯度爆炸或者梯度消失的问…

mmdet

一&#xff0c;configs/_base_ 1.default_runtime.py 2.schedule_1x.py 二&#xff0c;mmdet 1.datasets/coco.py/CocoDataset METAINFO {classes:(milk, red, spring, fanta, sprite, pepsi, king, ice, cola, scream ),# palette is a list of color tuples, which is us…

ElasticSearch 认识和安装ES

文章目录 一、为什么学ElasticSearch?1.ElasticSearch 简介2.ElasticSearch 与传统数据库的对比3.ElasticSearch 应用场景4.ElasticSearch 技术特点5.ElasticSearch 市场表现6.ElasticSearch 的发展 二、认识和安装ES1.认识 Elasticsearch&#xff08;简称 ES&#xff09;2.El…

第34天:安全开发-JavaEE应用反射机制攻击链类对象成员变量方法构造方法

时间轴&#xff1a; Java反射相关类图解&#xff1a; 反射&#xff1a; 1、什么是 Java 反射 参考&#xff1a; https://xz.aliyun.com/t/9117 Java 提供了一套反射 API &#xff0c;该 API 由 Class 类与 java.lang.reflect 类库组成。 该类库包含了 Field 、 Me…

汽车基础软件AutoSAR自学攻略(三)-AutoSAR CP分层架构(2)

汽车基础软件AutoSAR自学攻略(三)-AutoSAR CP分层架构(2) 下面我们继续来介绍AutoSAR CP分层架构&#xff0c;下面的文字和图来自AutoSAR官网目前最新的标准R24-11的分层架构手册。该手册详细讲解了AutoSAR分层架构的设计&#xff0c;下面让我们来一起学习一下。 Introductio…

css面试常考布局(圣杯布局、双飞翼布局、三栏布局、两栏布局、三角形)

两栏布局 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </head> &…