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.pswp.cn/web/65815.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/65815.shtml
英文地址,請注明出處:http://en.pswp.cn/web/65815.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

pandas與sql對應關系【幫助sql使用者快速上手pandas】

本頁旨在提供一些如何使用pandas執行各種SQL操作的示例&#xff0c;來幫助SQL使用者快速上手使用pandas。 目錄 SQL語法一、選擇SELECT1、選擇2、添加計算列 二、連接JOIN ON1、內連接2、左外連接3、右外連接4、全外連接 三、過濾WHERE1、AND2、OR3、IS NULL4、IS NOT NULL5、B…

第432場周賽:跳過交替單元格的之字形遍歷、機器人可以獲得的最大金幣數、圖的最大邊權的最小值、統計 K 次操作以內得到非遞減子數組的數目

Q1、跳過交替單元格的之字形遍歷 1、題目描述 給你一個 m x n 的二維數組 grid&#xff0c;數組由 正整數 組成。 你的任務是以 之字形 遍歷 grid&#xff0c;同時跳過每個 交替 的單元格。 之字形遍歷的定義如下&#xff1a; 從左上角的單元格 (0, 0) 開始。在當前行中向…

《探索鴻蒙Next上開發人工智能游戲應用的技術難點》

在科技飛速發展的當下&#xff0c;鴻蒙Next系統為應用開發帶來了新的機遇與挑戰&#xff0c;開發一款運行在鴻蒙Next上的人工智能游戲應用更是備受關注。以下是在開發過程中可能會遇到的一些技術難點&#xff1a; 鴻蒙Next系統適配性 多設備協同&#xff1a;鴻蒙Next的一大特色…

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…

Vue2: el-table為每一行添加超鏈接,并實現光標移至文字上時改變形狀

為表格中的某一列添加超鏈接 一個表格通常有許多列,網上許多教程都可以實現為某一列添加超鏈接,如下,實現了當光標懸浮在“姓名”上時,改變為手形,點擊可實現跳轉。 <el-table :data="tableData"><el-table-column label="姓名" prop=&quo…

R數據分析:多分類問題預測模型的ROC做法及解釋

有同學做了個多分類的預測模型,結局有三個類別,做的模型包括多分類邏輯回歸、隨機森林和決策樹,多分類邏輯回歸是用ROC曲線并報告AUC作為模型評估的,后面兩種模型報告了混淆矩陣,審稿人就提出要統一模型評估指標。那么肯定是統一成ROC了,剛好借這個機會給大家講講ROC在多…

A3. Springboot3.x集成LLama3.2實戰

本文將介紹集成ollama官網提供的API在Springboot工程中進行整合。由于沒找到java-llama相關合適的sdk可以使用,因此只好對接官方給出的API開發一套RESTFull API服務。下面將從Ollama以下幾個API展開介紹,逐漸的了解其特性以及可以干些什么。具體llama API說明可參數我前面寫的…

面試:類模版中函數聲明在.h,定義在.cpp中,其他cpp引用引入這個頭文件,會有什么錯誤?

1、概述 類模版中函數聲明在.h&#xff0c;定義在.cpp中&#xff0c;其他cpp引用引入這個頭文件&#xff0c;會有什么錯誤?報編譯錯誤&#xff1a;error C2512: Demo<int>: no appropriate default constructor available 舉例如下代碼&#xff1a;demo.h 聲明模版類 …

記一次學習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;是全世界兩座手工建…

CentOS 和 Ubantu你該用哪個

文章目錄 **一、CentOS 和 Ubuntu 的詳細介紹****1. CentOS****1.1 基本信息****1.2 特點****1.3 缺點** **2. Ubuntu****2.1 基本信息****2.2 特點****2.3 缺點** **二、CentOS 和 Ubuntu 的異同****1. 相同點****2. 不同點****3. 使用體驗對比** **三、總結和選擇建議** Cent…

Android RIL(Radio Interface Layer)全面概述和知識要點(3萬字長文)

在Android面試時,懂得越多越深android framework的知識,越為自己加分。 目錄 第一章:RIL 概述 1.1 RIL 的定義與作用 1.2 RIL 的發展歷程 1.3 RIL 與 Android 系統的關系 第二章:RIL 的架構與工作原理 2.1 RIL 的架構組成 2.2 RIL 的工作原理 2.3 RIL 的接口與協議…

前端學習-事件對象與典型案例(二十六)

目錄 前言 事件對象 目標 事件對象是什么 語法 獲取事件對象 部分常用屬性 示例代碼 示例代碼&#xff1a;評論回車發布 總結 前言 長風破浪會有時&#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形式呈現空間。 挑戰:實現定制設計的新紀元 我們知道,將個人物品如傳家寶椅子、…

如何當前正在運行的 Elasticsearch 集群信息

要查看當前正在運行的 Elasticsearch 集群信息&#xff0c;可以通過以下幾種方法&#xff1a; 1. 使用 _cluster/health API _cluster/health API 返回集群的健康狀態、節點數量、分片狀態等信息。可以用 curl 命令直接訪問&#xff1a; curl -X GET "http://localhost…