您好,登錄后才能下訂單哦!
[TOC]
類型參數(shù)是什么?類型參數(shù)其實就是Java中的泛型。大家對Java中的泛型應(yīng)該有所了解,比如我們有List list = new ArrayList(),接著list.add(1),沒問題,list.add("2"),然后我們list.get(1) == 2,對不對?肯定不對了,list.get(1)獲取的其實是個String——"2",String——"2"怎么可能與一個Integer類型的2相等呢?
所以Java中提出了泛型的概念,其實也就是類型參數(shù)的概念,此時可以用泛型創(chuàng)建List,List list = new ArrayList[Integer]()
,那么,此時list.add(1)沒問題,而list.add("2")呢?就不行了,因為類型泛型會限制傳入的參數(shù),只能往集合中l(wèi)ist添加Integer類型,這樣就避免了上述的數(shù)值的問題。
Scala中的類型參數(shù)和Java的泛型是一樣的,也是定義一種類型參數(shù)。
最后,Scala類型參數(shù)也是Spark源碼中非常常見的,因此同樣必須掌握,才能看懂spark源碼。
Java 或 C++ 一樣,類和特質(zhì)可以帶類型參數(shù)。在Scala中,我們用方括號類定義類型參數(shù)
class Student[T, S](val first: T, val second: S)
以上將定義一個帶有2個類型參數(shù)T和S的類。在類的定義中,你可以用類型參數(shù)來定義變量,方法參數(shù),以及返回值的類型。
我們把帶有一個或者多個類型參數(shù)的類,叫作泛型類。如果你把類型參數(shù)替換成實際的類型,將得到一個普通的類。比如Student[Int,String]
Scala會從構(gòu)造參數(shù)中推斷出實際類型:
val p = new Student(42, "String")
你也可以自己指定類型,測試代碼如下:
package cn.xpleaf.bigdata.p5.mygeneric
/**
* scala的類型參數(shù),即java中的泛型
* 定義方式有異,java使用使用<>,scala使用[]
* 泛型可以定義在類 特質(zhì) 方法 函數(shù)
* 泛型的作用,就是將運行期間的異常,提前到了編譯器
* 提高代碼的通用性
*/
object _01GenericOps {
def main(args: Array[String]): Unit = {
genericOps1
}
/**
* 泛型類的定義
*/
def genericOps1: Unit = {
class Student[T, S](val first: T, val second: S) {
println(first + "\t" + second)
}
new Student(23, "xpleaf") // 可以做類型的自動推斷
new Student[Int, String](22, "jieling")
new Student[Any, Any]("hadoop", "spark")
}
}
輸出結(jié)果如下:
23 xpleaf
22 jieling
hadoop spark
函數(shù)和方法也可以帶有類型參數(shù):
def getStudentInfo[T](stu: Array[T]) = stu(stu.length / 2)
和泛型類一樣,你需要把類型參數(shù)放在方法名后面。
Scala會從調(diào)用該方法使用的實際類型來推斷出類型:
def methodOps: Unit ={
def getStudentInfo[T](stu: Array[T]) = stu(stu.length / 2)
val student = getStudentInfo(Array("garry", "tom", "john", "lucy", "Richard"))
println(student)
}
在main函數(shù)中測試,輸出結(jié)果如下:
john
我們先看一個簡單的實例,用于判斷兩個變量中較大的值,其中兩個變量的類型均為Int型
/**
* 類型變量界定
*/
def typeValueOps: Unit ={
class StudentInt(val first: Int, val second: Int) {
def bigger = {
if (first.compareTo(second) > 0) first else second
}
}
val studentInt = new StudentInt(1, 2)
println(studentInt.bigger)
}
上述StudentInt類中的bigger方法調(diào)用了compare方法,如果我們想比較兩個String型的變量的大小,我們可以和上面一樣,添加StudentStr類:
class StudentStr(val first: String, val second: String) {
def bigger = {
if (first.compareTo(second) > 0) first else second
}
}
如果我們針對每種基本類型都寫一個具體的類,則代碼量太大,同時也不夠簡潔,此時我們想到泛型能比較容易解決這個問題:
class Student[T](val first: T, val second: T) {
def smaller = if (first.compareTo(second) < 0) first else second
}
然而與此同時,我們定義的泛型T并沒有指定實現(xiàn)compareTo方法,也沒有指定為某個類型的子類。在Java泛型里表示某個類型是Test類型的子類型,使用extends關(guān)鍵字:
<T extends Test>
//或用通配符的形式:
<? extends Test>
這種形式也叫upper bounds
(上限或上界),同樣的意思在Scala中的寫法為:
[T <: Test] //或用通配符: [_ <: Test]
下面的代碼結(jié)合了上限:
class Student[T <: Comparable[T]](val first: T, val second: T){
def smaller = if (first.compareTo(second) < 0) first else second
}
val studentString = new Student[String]("limu","john")
println(studentString.smaller)
val studentInt = new Student[Integer](1,2)
println(studentInt.smaller)
注意,這相當(dāng)于是對類型T加了一條限制:T必須是Comparable[T]的子類型。原來給T指定什么類型都可以,現(xiàn)在就不行了。
這樣一來,我們可以實例化Student[String]。但是不能實例化Student[File],因為String是Comparable[String]的子類型,而File并沒有實現(xiàn)Comparable[File]接口。
一個包含視圖界定的完整案例如下:
/**
* 泛型的上界
* Upper Bound
* [T <: 類] ---> [T <% 類] (視圖的界定)
*/
def genericOps3: Unit = {
class Student[T <% Comparable[T]](private val first: T, private val second: T) {
def bigger():T = {
/**
* 如果要讓first和second有compareTo方法,必須要為Comparable的子類或者是Ordered的子類
* 說白了也就是要讓這個類型參數(shù)T是Comparable或者Ordered的子類
* 一個類型是某一個類的子類,寫法就要發(fā)生對應(yīng)的變化
* java的寫法:<T/? extends Comparable>
* scala的寫法:[T <: Comparable]
*/
if(first.compareTo(second) > 0) {
first
} else {
second
}
}
}
val stu = new Student[String]("xpleaf", "jieling")
println(stu.bigger())
val stu2 = new Student[String]("李四", "王五")
println(stu2.bigger())
/**
* Error:(43, 13) type arguments [Int] do not conform to class Student's type parameter bounds [T <: Comparable[T]]
val stu3 = new Student[Int](18, 19)
說明Int不是Comparable的子類
前面Int類型可以用,實際上是scala內(nèi)部,將Int(隱士)轉(zhuǎn)換為RichInt
要想讓該程序運行通過,就需要使用視圖界定的方式
[T <% Comparable[T]]
使用這個%,其實就是強制指定將Int類型隱士轉(zhuǎn)換為RichInt,而RichInt間接實現(xiàn)了Comparable
*/
val stu3 = new Student[Int](18, 19)
println(stu3.bigger())
}
在main函數(shù)中執(zhí)行,輸出結(jié)果如下:
xpleaf
王五
19
下限很少使用,所以這里就不進行說明了。
其實上面已經(jīng)有說明和應(yīng)用,不過這里還是詳細介紹一下。
剛才將的類型變量界定建立在類繼承層次結(jié)構(gòu)的基礎(chǔ)上,但有時候這種限定不能滿足實際要求,如果希望跨越類繼承層次結(jié)構(gòu)時,可以使用視圖界定來實現(xiàn)的,其后面的原理是通過隱式轉(zhuǎn)換(我們在下一講中會詳細講解什么是隱式轉(zhuǎn)換)來實現(xiàn)。視圖界定利用<%符號來實現(xiàn)。
先看下面的一個例子:
class Student[T <: Comparable[T]](val first: T, val second: T) {
def smaller = if (first.compareTo(second) < 0) first else second
}
val student = new Student[Int](4,2)
println(student.smaller)
可惜,如果我們嘗試用Student(4,2)五實現(xiàn),編譯器會報錯。因為Int和Integer不一樣,Integer是包裝類型,但是Scala的Int并沒有實現(xiàn)Comparable。
不過RichInt實現(xiàn)了Comparable[Int],同時還有一個Int到RichInt的隱士轉(zhuǎn)換。解決途徑就是視圖界定。
class Student[T <% Comparable[T]](val first: T, val second: T) {
def smaller = if (first.compareTo(second) < 0) first else second
}
<%
關(guān)系意味著T可以被隱式轉(zhuǎn)換成Comparable[Int]
。
個人理解:不管是類型變量界定還是視圖界定,實際上都是在限制類型參數(shù)T,類型變量界定要求類型參數(shù)T必須是上界的子類或者是下界的父類;視圖界定則是要求類型參數(shù)T必須能夠隱式轉(zhuǎn)換成“類似上界”的界定,比如上面提到的,Int隱式轉(zhuǎn)換成RichInt,RichInt是Comparable[Int]的子類。這樣看來,類型變量界定對類型參數(shù)的限制比視圖界定對類型參數(shù)的限制是更大了。
直接看下面的程序代碼就能很容易理解:
package cn.xpleaf.bigdata.p5.mygeneric
/**
* scala類型參數(shù)的協(xié)變和逆變
* scala默認不支持協(xié)變和逆變
* 要想讓scala的泛型支持協(xié)變,在泛型前面再加一個"+"
* 要想讓scala的泛型支持逆變,在泛型前面再加一個"-"
* 但是一個類不能同時支持協(xié)變和逆變
*/
object _02GenericOps {
def main(args: Array[String]): Unit = {
/*
val list:List[Person] = List[Person]() // 正常的定義
val list1:List[Person] = List[Student]() // scala中的協(xié)變,java不支持
// val list2:List[Teacher] = List[Person]() // 逆變,java不支持,但是scala需要在定義泛型類的時候指定
*/
val myList1:MyList[Person] = new MyList[Person]()
val myList2:MyList[Person] = new MyList[Student]()
val yourList1:YourList[Person] = new YourList[Person]()
val yourList2:YourList[Student] = new YourList[Person]()
}
class Person{}
class Student extends Person{}
class Teacher extends Person{}
/**
* 支持協(xié)變的泛型類
*/
class MyList[+T] {
}
/**
* 支持逆變的泛型類
*/
class YourList[-T] {
}
}
當(dāng)然還有很多的理論知識和細節(jié)知識,但目前掌握這些就可以了。
1、類型通配符是指在使用時不具體指定它屬于某個類,而是只知道其大致的類型范圍,通過”_
<:” 達到類型通配的目的。
2、
def typeWildcard: Unit ={
class Person(val name:String){
override def toString()=name
}
class Student(name:String) extends Person(name)
class Teacher(name:String) extends Person(name)
class Pair[T](val first:T,val second:T){
override def toString()="first:"+first+", second: "+second;
}
//Pair的類型參數(shù)限定為[_<:Person],即輸入的類為Person及其子類
//類型通配符和一般的泛型定義不一樣,泛型在類定義時使用,而類型通配符號在使用類時使用
def makeFriends(p:Pair[_<:Person])={
println(p.first +" is making friend with "+ p.second)
}
makeFriends(new Pair(new Student("john"),new Teacher("搖擺少年夢")))
}
1、在scala語言當(dāng)中,隱式轉(zhuǎn)換是一項強大的程序語言功能,它不僅能夠簡化程序設(shè)計,也能夠使程序具有很強的靈活性。它們存在固有的隱式轉(zhuǎn)換,不需要人工進行干預(yù),例如Float在必要情況下自動轉(zhuǎn)換為Double類型
2、在前一講的視圖界定中我們也提到,視圖界定可以跨越類層次結(jié)構(gòu)進行,它背后的實現(xiàn)原理就是隱式轉(zhuǎn)換,例如Int類型會視圖界定中會自動轉(zhuǎn)換成RichInt,而RichInt實現(xiàn)了Comparable接口,當(dāng)然這里面的隱式轉(zhuǎn)換也是scala語言為我們設(shè)計好的 。
3、所謂隱士轉(zhuǎn)換函數(shù)(implicit conversion function)指的是那種以implicit關(guān)鍵字聲明的帶有單個參數(shù)的函數(shù)。正如它的名稱所表達的,這樣的函數(shù)將自動應(yīng)用,將值從一種類型轉(zhuǎn)換成另一種類型。
Doube進行到Int的轉(zhuǎn)換:
val x:Int = 3.5
implicit def double2Int(x:Double)=x.toInt
def conversionFunc: Unit ={
//Doube進行到Int的轉(zhuǎn)換
val x:Int = 3.5
println("x===> " + x)
}
1、隱式函數(shù)的名稱對結(jié)構(gòu)沒有影響,即implicitdefdouble2Int(x:Double)=x.toInt函數(shù)可以是任何名字,只不過采用source2Target這種方式函數(shù)的意思比較明確,閱讀代碼的人可以見名知義,增加代碼的可讀性。
2、Scala并不是第一個允許程序員提供自動類型轉(zhuǎn)換的語言。不過,Scala給了程序員相當(dāng)大的控制權(quán)在什么時候應(yīng)用這些模塊。
隱式轉(zhuǎn)換功能十分強大,可以快速地擴展現(xiàn)有類庫的功能.
import java.io.File
import scala.io.Source
//RichFile類中定義了Read方法
class RichFile(val file:File){
def read = Source.fromFile(file).getLines().mkString
}
//隱式函數(shù)將java.io.File隱式轉(zhuǎn)換為RichFile類
implicit def file2RichFile(file:File) = new RichFile(file)
val f = new File("E:/test/scala/wordcount.txt").read
println(f)
Java.io.File本身并沒有read方法。
1、Scala默認會考慮兩種隱式轉(zhuǎn)換,一種是源類型,或者目標(biāo)類型的伴生對象內(nèi)的隱式轉(zhuǎn)換函數(shù);一種是當(dāng)前程序作用域內(nèi)的可以用唯一標(biāo)識符表示的隱式轉(zhuǎn)換函數(shù)。
2、如果隱式轉(zhuǎn)換不在上述兩種情況下的話,那么就必須手動使用import語法引入某個包下的隱式轉(zhuǎn)換函數(shù),比如import student._。
通常建議,僅僅在需要進行隱式轉(zhuǎn)換的代碼部分,比如某個函數(shù)或者方法內(nèi),用import導(dǎo)入隱式轉(zhuǎn)換函數(shù),這樣可以縮小隱式轉(zhuǎn)換函數(shù)的作用域,避免不需要的隱式轉(zhuǎn)換。
1、隱式轉(zhuǎn)換可以定義在目標(biāo)文件當(dāng)中(一個Scala文件中)
//轉(zhuǎn)換函數(shù)
implicit def double2Int(x:Double)=x.toInt
val x:Int = 3.5
2、隱式轉(zhuǎn)換函數(shù)與目標(biāo)代碼在同一個文件當(dāng)中,也可以將隱式轉(zhuǎn)換集中放置在某個包中,在使用進直接將該包引入即可
//在com.sparkstudy.scala.demo包中定義了子包implicitConversion
//然后在object ImplicitConversion中定義所有的引式轉(zhuǎn)換方法
package implicitConversion{
object ImplicitConversion{
implicit def double2Int(x:Double)=x.toInt
implicit def file2RichFile(file:File) = new RichFile(file)
}
}
class RichFile(val file:File){
def read=Source.fromFile(file).getLines().mkString
}
//隱士轉(zhuǎn)換規(guī)則
def implicitConversionRuleOps: Unit ={
//在使用時引入所有的隱式方法
import com.sparkstudy.scala.demo.implicitConversion.ImplicitConversion._
var x:Int=3.5
println("x===> " + x)
val f=new File("E:/test/scala/wordcount.txt").read
println(f)
}
這種方式在scala語言中比較常見,在前面我們也提到,scala會默認幫我們引用Predef對象中所有的方法,Predef中定義了很多隱式轉(zhuǎn)換函數(shù)
1、當(dāng)方法中參數(shù)的類型與實際類型不一致時
def f(x:Int)=x
//方法中輸入的參數(shù)類型與實際類型不一致,此時會發(fā)生隱式轉(zhuǎn)換
//double類型會轉(zhuǎn)換為Int類型,再進行方法的執(zhí)行
f(3.14)
2、當(dāng)調(diào)用類中不存在的方法或成員時,會自動將對象進行隱式轉(zhuǎn)換
我們上面進行的那個案例(File本身是沒有read方法)
1、所謂的隱式參數(shù),指的是在函數(shù)或者方法中,定義一個用implicit修飾的參數(shù),此時Scala會嘗試找到一個指定類型的,用implicit修飾的對象,即隱式值,并注入?yún)?shù)。
2、Scala會在兩個范圍內(nèi)查找:一種是當(dāng)前作用域內(nèi)可見的val或var定義的隱式變量;一種是隱式參數(shù)類型的伴生對象內(nèi)的隱式值
//學(xué)生畢業(yè)報告
class StudentSubmitReport {
def writeReport(ctent: String) = println(ctent)
}
implicit val stuentSign = new StudentSubmitReport
def signForReport(name: String) (implicit studentSReport: StudentSubmitReport) {
studentSReport.writeReport(name + "come to here")
}
signForReport ("jack")
package cn.xpleaf.bigdata.p5
import java.io.File
import scala.io.Source
object ImplicitUtil {
implicit def double2Int(d: Double): Int = d.toInt
implicit def str2Int(str: String): Int = str.length
implicit def file2RichFile(file: File) = new RichFile(file)
implicit val swr:StudentWriteReport = new StudentWriteReport()
}
class RichFile(file: File) {
def read() = Source.fromFile(file).getLines().mkString
}
class StudentWriteReport {
def writeReport(content:String) = println(content)
}
package cn.xpleaf.bigdata.p5.implicitz
/**
* scala隱士轉(zhuǎn)換操作
* 將一種類型,轉(zhuǎn)化為另外的一種類型,這完成這一操作的背后就是隱士轉(zhuǎn)換函數(shù)
* 所謂隱士轉(zhuǎn)換函數(shù),其實就是在普通函數(shù)前面加上一個關(guān)鍵字——implicit
*
* 隱士轉(zhuǎn)換函數(shù)的導(dǎo)入:
* 1、如果隱士轉(zhuǎn)換函數(shù)和調(diào)用它的操作,在同一個文件中,我們不要做任何操作
* 2、如果不在一個文件中,需要收到導(dǎo)入,和導(dǎo)包是一樣,唯一需要注意最后以._結(jié)尾,表導(dǎo)入該類中的所有的隱士轉(zhuǎn)換函數(shù)
*
*/
import java.io.File
import cn.xpleaf.bigdata.p5.ImplicitUtil._
import cn.xpleaf.bigdata.p5.StudentWriteReport
import scala.io.Source
object implicitOps {
def main(args: Array[String]): Unit = {
// implicitOps1
// implicitOps2
implicitOps3
}
/**
* 隱士轉(zhuǎn)換參數(shù)
* 其實就非常類似于之前學(xué)習(xí)過的柯里化
*/
def implicitOps3: Unit = {
/* // 傳統(tǒng)操作方式
def signReport(name:String, swr:StudentWriteReport): Unit = {
swr.writeReport(name)
}
signReport("張三", new StudentWriteReport())*/
def signForReport(name:String)(implicit swr:StudentWriteReport): Unit = {
swr.writeReport(name)
}
signForReport("張三")
}
/*
class StudentWriteReport {
def writeReport(content:String) = println(content)
}
implicit val swr:StudentWriteReport = new StudentWriteReport()
*/
/**
* 使用隱士轉(zhuǎn)換豐富現(xiàn)在類型的API
*/
def implicitOps2: Unit ={
var file = new File("/Users/yeyonghao/test.txt")
var lines = file.read()
println(lines)
}
/**
* 隱士轉(zhuǎn)換操作
*/
def implicitOps1: Unit = {
val x:Int = 3
val y:Int = 3.5
val z:Int = "klkelfldlkfj"
println("x=" + x)
println("y=" + y)
println("z=" + z)
}
}
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。