1、变量声明
1.1、 声明val变量
例如,val result = 1 + 1
后续这些常量是可以继续使用的,例如,2 * result
但是注意常量声明后,是无法改变它的值的,例如,result = 1,会返回error: reassignment to val的错误信
1.2、 声明var变量
如果要声明值可以改变的引用,可以使用var变量。例如,val result = 1,result = 2。但是在scala程序中,通常建议使用val,也就是常量,因为在网络传输中,如果是变量,可能会担心值被修改
1.3、 指定类型
无论声明val变量,还是声明var变量,都可以手动指定其类型,如果不指定的话,scala会自动根据值,进行类型的推断
例如,val name: String = null
例如,val name: Any = "Tom"
声明多个变量:可以将多个变量放在一起进行声明。
例如,val name1, name2:String = null
例如,val num1, num2 = 100
2、基本类型
基本数据类型:Byte、Char、Short、Int、Long、Float、Double、Boolean
注意 : scala没有基本数据类型与包装类型的概念,统一都是类。scala自己会负责基本数据类型和引用类型的转换操作
包装类型都是可以这样调用 : 例如,100.toString(),1.to(10)
2.1、类型的加强版
例如,String类通过StringOps类增强了大量的函数,"Hello".intersect(" World")
例如,Scala还提供了RichInt、RichDouble、RichChar等类型,RichInt就提供了to函数,1.to(10),此处Int先隐式转换为RichInt,然后再调用其to函数
2.2、基本操作符
scala的算术操作符与java的算术操作符也没有什么区别,比如+、-、*、/、%等,以及&、|、^、>>、<<等
注意,在scala中,这些操作符其实是数据类型的函数,比如1 + 1,可以写做1.+(1),例如,1.to(10),又可以写做1 to 10
scala中没有提供++、--操作符,我们只能使用+和-,比如counter++是错误的,必须写做counter += 1
3、函数调用与apply()函数
3.1、函数调用方式
在scala中,函数调用如下 :
例如,import scala.math._,sqrt(2),pow(2, 4),min(3, Pi)
如果调用函数时,不需要传递参数,则scala允许调用函数时省略括号的,例如,"Hello World".distinct
3.2、apply函数
Scala中的apply函数是非常特殊的一种函数,在Scala的object中(半生对象),可以声明apply函数。而使用“类名()”的形式,其实就是“类名.apply()”的一种缩写。通常使用这种方式来构造类的对象,而不是使用“new 类名()”的方式
例如,Array(1, 2, 3, 4),实际上是用Array object的apply()函数来创建Array类的实例,也就是一个数组
4、条件控制与循环
4.1、if表达式
if表达式的定义 :
在Scala中,if表达式是有值的,就是if或者else中最后一行语句返回的值
if表达式赋予一个变量,例如,val isAdult = if (age > 18) 1 else 0
if表达式的类型推断:
由于if表达式是有值的,而if和else子句的值类型可能不同,Scala会自动进行推断,取两个类型的公共父类型
例如,if(age > 18) 1 else 0,表达式的类型是Int,因为1和0都是Int
例如,if(age > 18) "adult" else 0,此时if和else的值分别是String和Int,则表达式的值是Any,Any是String和Int的公共父类型
注意 : 如果if后面没有跟else,则默认else的值是Unit,也用()表示,类似于java中的void或者null。例如,val age = 12; if(age > 18) "adult"。此时就相当于if(age > 18) "adult" else ()
将if语句放在多行中:
默认情况下,REPL只能解释一行语句,但是if表达式通常需要放在多行。可以使用{}的方式
result = if (age > 18) {
println("成年人")
"aduit"
} else if (age > 12) {
println("未成年")
"teenager"
} else {
println("儿童")
"child"
}
4.2、语句终结符、块表达式
默认情况下,Scala不需要语句终结符,默认将每一行作为一个语句
一行多条语句,就必须使用语句终结符;,比如 var a, b, c = 0; if(a < 10) { b = b + 1; c = c + 1 },对于多行语句,还是会适应花括号的方式
if(a < 10) {
b = b + 1
c = c + 1
}
块表达式,块表达式,指的就是{}中的值,其中可以包含多条语句,最后一个语句的值就是块表达式的返回值
val a = 9
var b = 0
val result = if (a < 10) {
b += 1
a + 1
} else {
b -= 1
a - 1
}
最终 result = 10
4.3、输入和输出
print和println:print打印时不会加换行符,而println打印时会加一个换行符
printf: printf可以用于进行格式化(和C中一样)
例如 : printf("Hi, my name is %s, I'm %d years old.", "Tom", 30)
readLine: readLine允许我们从控制台读取用户输入的数据,类似于java中的System.in和Scanner的作用
println("Your names is : ")
val name: String = scala.io.StdIn.readLine()
println("Your age is : ")
val age: Int = scala.io.StdIn.readInt()
println(
s"""
| name : ${name}
| age : ${age}
|""".stripMargin)
输出结果 :
Your names is :
zhangsan
Your age is :
30
name : zhangsan
age : 30
4.4、循环
while do循环 : Scala有while do循环,基本与Java相同
var n = 10
while (n > 0) {
println(n)
n -= 1
}
Scala没有for循环,只能使用while替代for循环,或者使用简易版的for语句
val count = 10
for (num <- 1 to count)
println(num)
// 1 until count,说明不能到count
for (num <- 1 until count)
println(num)
Scala没有提供类似于Java中的break语句,但是可以使用boolean、return或者Breaks的break函数来替代
for (num <- 1 to count) {
if (num == 5) {
return
}
println("num : " + num)
}
4.5、多重循环
冒泡排序 :
val arrs: Array[Int] = Array(10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
for (i <- 0 until(10); j <- 0 until(9 - i)) {
if (arrs(j) > arrs(j + 1)) {
var temp = arrs(j)
arrs(j) = arrs(j + 1)
arrs(j + 1) = temp
}
}
println(arrs.mkString(","))
if守卫 : 取偶数
for (k <- 1 to 10 if k % 2 == 0) println(k)
for推导式:构建集合
val ints: immutable.IndexedSeq[Int] = for (i <- 1 to 10) yield i
5、函数入门
5.1、函数的定义与调用
在Scala中定义函数时,需要定义函数的函数名、参数、函数体
def main(args: Array[String]): Unit = {
sayHello("Tom", 30)
}
def sayHello(name : String, age : Int) = {
if (age > 18) {
printf("hi %s,you are adult\n", name)
} else {
printf("hi %s,you are not adult\n", name)
}
}
Scala要求必须给出所有参数的类型,但是不一定给出函数返回值的类型,只要右侧的函数体中不包含递归的语句,Scala就可以自己根据右侧的表达式推断出返回类型
单行函数 :
def sayHello2(name : String) = println("hello " + name)
如果函数体有多行代码,可以使用代码块的方式包裹多行代码,代码块中最后一行的返回值就是整个函数的返回值
累加功能:
def sum(n : Int) = {
var sum = 0
for (i <- 1 to n) sum += i
sum
}
5.2、递归函数与返回类型
如果在函数体内递归调用函数自身,则必须手动给出函数的返回类型
例如,实现经典的斐波那契数列:
9 + 8; 8 + 7 + 7 + 6; 7 + 6 + 6 + 5 + 6 + 5 + 5 + 4; ....
def fab(n : Int) : Int = {
if (n <= 1) 1
else fab(n - 1) + fab(n - 2)
}
5.3、默认参数
在Scala中,有时我们调用某些函数时,不希望给出参数的具体值,而希望使用参数自身默认的值,此时就定义在定义函数时使用默认参数
def say(name : String, age : Int, hobby : String = "coding")= {
s"my name is ${name}, age is : ${age}, hobby is : ${hobby}"
}
如果给出的参数不够,则会从作往右依次应用参数
5.4、带名参数
在调用函数时,也可以不按照函数定义的参数顺序来传递参数,而是使用带名参数的方式来传递
比如 : println(say(age = 30, name = "zhangsan", hobby = "hehe"))
还可以混合使用未命名参数和带名参数,但是未命名参数必须排在带名参数前面
println(say("zhagnsan", age = 20))
5.5、变长参数
在Scala中,有时我们需要将函数定义为参数个数可变的形式,则此时可以使用变长参数定义函数
def sum(nums: Int*) = {
var result = 0
for (num <- nums) result += num
result
}
使用序列调用变长参数
在如果想要将一个已有的序列直接调用变长参数函数,是不对的。比如val s = sum(1 to 5)。此时需要使用Scala特殊的语法将参数定义为序列,让Scala解释器能够识别
val s = sum(1 to 5: _*)
案例:使用递归函数实现累加
def sum2(nums: Int*): Int = {
if (nums.length == 0) 0
else nums.head + sum2(nums.tail: _*)
}
5.6、过程
在Scala中,定义函数时,如果函数体直接包裹在了花括号里面,而没有使用=连接,则函数的返回值类型就是Unit。这样的函数就被称之为过程。过程通常用于不需要返回值的函数
def sayHello4(name : String){
println("Hello," + name)
}
def sayHello5(name: String): Unit = {
println("Hello," + name)
}
6、lazy值
在Scala中,提供了lazy值的特性,也就是说,如果将一个变量声明为lazy,则只有在第一次使用该变量时,变量对应的表达式才会发生计算。这种特性对于特别耗时的计算操作特别有用,比如打开文件进行IO,进行网络IO等
import scala.io.Source._
// datas/user1.json文件是不存在的,也不会保持,只有到真正使用运行的时候才会报错
lazy val lines1: Iterator[String] = fromFile("datas/user1.json").getLines()
val lines2: Iterator[String] = fromFile("datas/user.json").getLines()
7、异常
使用match case的方式进行异常匹配
try {
throw new IOException("user defined exception.")
} catch {
case e : IllegalArgumentException => println("illegal argument")
case e : IOException => println("illegal argument")
case _ => println("other exception.")
}
8、Array & ArrayBuffer
8.1、Array
在Scala中,Array代表的含义与Java中类似,也是长度不可改变的数组。此外,由于Scala与Java都是运行在JVM中,双方可以互相调用,因此Scala数组的底层实际上是Java数组。例如字符串数组在底层就是Java的String[],整数数组在底层就是Java的Int[]
// 数组初始化后,长度就固定下来了,而且元素全部根据其类型初始化
val a = new Array[Int](10)
a(0)
a(0) = 1
val a = new Array[String](10)
// 可以直接使用Array()创建数组,元素类型自动推断
val a = Array("hello", "world")
a(0) = "hi"
val a = Array("tom", 30)
8.2、ArrayBuffer
在Scala中,如果需要类似于Java中的ArrayList这种长度可变的集合类,则可以使用ArrayBuffer
val arrayBuffer = ArrayBuffer[String]()
// += 就是加入一个元素
arrayBuffer += "zhagnsan"
arrayBuffer += "lisi"
arrayBuffer += "wangwu"
// -= 就是去掉一个元素
arrayBuffer -= "zhagnsan"
val arrayBuffer2 = ArrayBuffer[String]("aa", "bb")
// 这个其实相当于Java中的addAll
arrayBuffer ++= arrayBuffer2
for (item <- arrayBuffer) {
println(item)
}
结果输出 :
lisi
wangwu
aa
bb
// 使用trimEnd()函数,可以从尾部截断指定个数的元素
arrayBuffer.trimEnd(2)
println(arrayBuffer)
结果输出 :
ArrayBuffer(lisi, wangwu)
其他功能 :
// 使用insert()函数可以在指定位置插入元素
// 但是这种操作效率很低,因为需要移动指定位置后的所有元素
b.insert(5, 6)
b.insert(6, 7, 8, 9, 10)
// 使用remove()函数可以移除指定位置的元素
b.remove(1)
b.remove(1, 3)
// Array与ArrayBuffer可以互相进行转换
b.toArray
a.toBuffer
8.3、遍历
// 使用for循环和until遍历Array / ArrayBuffer
// 使until是RichInt提供的函数
for (i <- 0 until b.length)
println(b(i))
// 跳跃遍历Array / ArrayBuffer
for(i <- 0 until (b.length, 2))
println(b(i))
// 从尾部遍历Array / ArrayBuffer
for(i <- (0 until b.length).reverse)
println(b(i))
// 使用“增强for循环”遍历Array / ArrayBuffer
for (e <- b)
println(e)
val a = Array(1, 2, 3, 4, 5)
val sum = a.sum
// 获取数组最大值
val max = a.max
// 对数组进行排序
scala.util.Sorting.quickSort(a)
// 获取数组中所有元素内容
a.mkString
a.mkString(", ")
a.mkString("<", ",", ">")
// toString函数
a.toString
b.toString
9、数组操作之数组转换
9.1、使用yield方式
// 对Array进行转换,获取的还是Array
val arr1 = Array(1,2,3,4,5)
val arr2 = for (ele <- arr1) yield ele * ele
// 对ArrayBuffer进行转换,获取的还是ArrayBuffer
val arr3 = ArrayBuffer[Int]()
arr3 += (1,2,3,4,5)
val arr4 = for (ele <- arr3) yield ele * ele
// 结合if守卫,仅转换需要的元素
val arr5 = for (ele <- arr3 if ele % 2 == 0) yield ele * ele
9.2、函数式编程转换数组
// 使用函数式编程转换数组
val arr6: Array[Int] = arr1.filter(_ % 2 == 0).map(_ * 2)
10、Map与Tuple
10.1、Map
创建Map:
// 创建一个不可变的Map
val users1: Map[String, Int] = Map("zhangsan" -> 23, "lisi" -> 30, "wangwu" -> 40)
// users1("zhagnsan") = 20 因为Map默认是不可变的,所以这个是不能改变的
val users2: mutable.Map[String, Int] = scala.collection.mutable.Map("zhagnsan" -> 23, "lisi" -> 30, "wangwu" -> 40)
users2("zhagnsan") = 20
users2 += (("zhaoliu", 20))
// 使用另外一种方式来定义Map元素
val users3: Map[String, Int] = Map(("zhangsan", 20), ("lisi", 30), ("wangwu", 40))
// 创建一个空的HashMap
val users4 = new mutable.HashMap[String, Int]()
访问Map的元素:
// 获取指定key对应的value,如果key不存在,会报错
val tomAge = ages("Tom")
// 使用contains函数检查key是否存在
val tomAge = if (ages.contains("Tom")) ages("Tom") else 0
// getOrElse函数
val tomAge = ages.getOrElse("Tom", 0)
修改Map的元素:
// 更新Map的元素
users2("Tom") = 31
// 增加多个元素
users2 += ("Mike" -> 35, "Tom" -> 40)
// 移除元素
users2 -= "Mike"
// 更新不可变的map
val users5 = users1 + ("Mike" -> 36, "Tom" -> 40)
// 移除不可变map的元素
val users6 = users1 - "Tom"
遍历Map :
// 遍历map的entrySet
for ((key, value) <- users1) println(key + " " + value)
// 遍历map的key
for (key <- users1.keySet) println(key)
// 遍历map的value
for (value <- users1.values) println(value)
// 生成新map,反转key和value
for ((key, value) <- users1) yield (value, key)
10.2、SortedMap和LinkedHashMap
// SortedMap可以自动对Map的key进行排序
val users5 = mutable.SortedMap("zhangsan" -> 30, "lisi" -> 34, "wangwu" -> 45)
val user6 = new mutable.LinkedHashMap[String, Int]
user6("zhangsan") = 30
user6("lisi") = 34
user6("wangwu") = 45
10.3、Tuple
其实就是简单的键值对,简单理解就是Map中的一个元素,可以通过_1、_2、_n...下标来访问
val t = ("zhangsan", 20)
println(t._1)
println(t._2)
// zip操作
val names = Array("zhangsan", "lisi", "wagnwu")
val ages = Array(30, 20, 25)
val users = names.zip(ages)
for ((name, age) <- users) println(name + "->" + age)
11、面向对象
11.1、基础
object User {
var user : User = new User
def apply(): User = {
user
}
def adjustSalary(sal : Int): Unit = {
user.salary = sal
}
def getName = user.name
def setName(name : String): Unit = {
user.name = name
}
def info = user.info()
}
class User {
// 国家,默认中国,不能改哈
private[this] val country = "中国"
// 薪水,薪水是不能随便改的,比如说只能公司给你改
private var salary = 1000
// 姓名是可以修改的,自己修改即可
var name : String = _
def info(): Unit = {
println("username : " + name + ", your country is : " + country + ", salary is : " + salary)
}
}
调用处 :
// 普通用户操作
val commonUser = new User
commonUser.name = "zhangsan"
commonUser.info
// 管理用户操作
val adminUser = User
adminUser.setName("zhangsan")
adminUser.adjustSalary(10000)
adminUser.info
打印结果 :
username : zhangsan, your country is : 中国, salary is : 1000
username : zhangsan, your country is : 中国, salary is : 10000
注意 :
- 定义不带private的var field,此时scala生成的面向JVM的类时,会定义为private的name字段,并提供public的getter和setter方法
- 而如果使用private修饰field,则生成的getter和setter也是private的,只有伴生对象可以访问
- 如果定义val field,则只会生成getter方法
- 如果不希望生成setter和getter方法,则将field声明为private[this],谁都修改不了
11.2、自定义getter与setter
Scala的getter和setter方法的命名与java是不同的,是field和field_=的方式
注意 :
如果只是希望拥有简单的getter和setter方法,那么就按照scala提供的语法规则,根据需求为field选择合适的修饰符就好:var、val、private、private[this]
但是如果希望能够自己对getter与setter进行控制,则可以自定义getter与setter方法
自定义setter方法的时候一定要注意scala的语法限制,签名、=、参数间不能有空格
class Stu {
private var username: String = "wuming"
def name = "my name is :" + username
def name_=(newName:String) {
username = newName
}
}
调用处 :
val stu = new Stu
println(stu.name)
stu.name = "zhangsan"
println(stu.name)
11.3、仅暴露field的getter方法
如果你不希望field有setter方法,则可以定义为val,但是此时就再也不能更改field的值了
但是如果希望能够仅仅暴露出一个getter方法,并且还能通过某些方法更改field的值,那么需要综合使用private以及自定义getter方法
此时,由于field是private的,所以setter和getter都是private,对外界没有暴露;自己可以实现修改field值的方法;自己可以覆盖getter方法
class Stu {
private var username: String = "wuming"
def name = "my name is :" + username
def updateName(newName:String) {
if (newName == "zhangsan") username = newName
else println("newName : " + newName + " deny")
}
}
11.4、private[this]的使用
如果将field使用private来修饰,那么代表这个field是类私有的,在类的方法中,可以直接访问类的其他对象的private field
这种情况下,如果不希望field被其他对象访问到,那么可以使用private[this],意味着对象私有的field,只有本对象内可以访问到
11.4、Java风格的getter和setter方法
class Stu {
@BeanProperty var username: String = "wuming"
}
11.5、辅助constructor
Scala中,可以给类定义多个辅助constructor,类似于java中的构造函数重载。辅助constructor之间可以互相调用,而且必须第一行调用主constructor
class Stu {
private var name = ""
private var age = 0
def this(name : String) {
this()
this.name = name
}
def this(name : String, age : Int) {
this()
this.name = name
this.age = age
}
}
11.6、主constructor
Scala中,主constructor是与类名放在一起的,与java不同。而且类中,没有定义在任何方法或者是代码块之中的代码,就是主constructor的代码
class Stu(val name : String, val age : Int) {
println(s"my name is : ${name}, age is : ${age}")
}
**主constructor中还可以通过使用默认参数,来指定参数的默认值
**
class Stu(val name : String = "Jerry", val age : Int = 30) {
println(s"my name is : ${name}, age is : ${age}")
}
注意 :
如果主constrcutor传入的参数什么修饰都没有,比如name: String,那么如果类内部的方法使用到了,则会声明为private[this] name;否则没有该field,就只能被constructor代码使用而已
11.7、内部类
class Animal {
class Dog(val name : String) {}
val dogs = new ArrayBuffer[Dog]()
def getDog(name : String) = {
new Dog(name)
}
}
val animal = new Animal
val dog1: animal.Dog = animal.getDog("doudoud")
animal.dogs += dog1
println(animal.dogs)
11.8、object
object,相当于class的单个实例,通常在里面放一些静态的field或者method
第一次调用object的方法时,就会执行object的constructor,也就是object内部不在method中的代码(其实就是想说静态代码块);但是object不能定义接受参数的constructor
注意,object的constructor只会在其第一次被调用时执行一次,以后再次调用就不会再次执行constructor了。object通常用于作为单例模式的实现,或者放class的静态成员,比如工具方法
object Person {
private var eyeNum = 2
println("this Person object.")
def apply() = new Person("xx")
def getEyeNum = eyeNum
}
class Person(val username:String) {
println(s"username : ${username}")
}
调用 :
val person = Person()
结果显示 :
this Person object.
username : xx
11.9、伴生对象
如果有一个class,还有一个与class同名的object,那么就称这个object是class的伴生对象,class是object的伴生类
伴生类和伴生对象必须存放在一个.scala文件之中
伴生类和伴生对象,最大的特点就在于,互相可以访问private field
object Person {
private val eyeNum = 2
def getEyeNum = eyeNum
}
class Person(val name:String, val age:Int) {
def sayHello = println("my name : " + name + "; age is : " + age + "; eyeNum : " + Person.eyeNum)
}
11.10、object继承抽象类
object的功能其实和class类似,除了不能定义接受参数的constructor之外。object也可以继承抽象类,并覆盖抽象类中的方法
abstract class UserService (var token : String){
def addUser(username : String) :Unit
}
object UserServiceImpl extends UserService ("xxxx111"){
override def addUser(username: String) = {
println(token + ":" + username)
}
}
11.11、apply方法
object中非常重要的一个特殊方法,就是apply方法。通常在伴生对象中实现apply方法,并在其中实现构造伴生类的对象的功能而创建伴生类的对象时,通常不会使用new Class的方式,而是使用Class()的方式,隐式地调用伴生对象得apply方法,这样会让对象创建更加简洁
比如,Array类的伴生对象的apply方法就实现了接收可变数量的参数,并创建一个Array对象的功能
val a = Array(1, 2, 3, 4, 5)
比如,自己定义的伴生类和伴生对象
object Person {
def apply(name : String) = new Person3(name)
}
class Person(val name : String) {
}
11.12、main方法
就如同Java中,如果要运行一个程序,必须编写一个包含main方法类一样;在scala中,如果要运行一个应用程序,那么必须有一个main方法,作为入口
scala中的main方法定义为def main(args: Array[String]),而且必须定义在object中
object HelloWorld {
def main(args: Array[String]) {
println("Hello World!!!")
}
}
除了自己实现main方法之外,还可以继承App Trait,然后将需要在main方法中运行的代码,直接作为object的constructor代码;而且用args可以接受传入的参数
object HelloWorld extends App {
if (args.length > 0) println("hello, " + args(0))
else println("Hello World!!!")
}
11.13、用object来实现枚举功能
Scala没有直接提供类似于Java中的Enum这样的枚举特性,如果要实现枚举,则需要用object继承Enumeration类,并且调用Value方法来初始化枚举值
object Season extends Enumeration {
val SPRING, SUMMER, AUTUMN, WINTER = Value
}
还可以通过Value传入枚举值的id和name,通过id和toString可以获取; 还可以通过id和name来查找枚举值
object Season extends Enumeration {
val SPRING = Value(0, "spring")
val SUMMER = Value(1, "summer")
val AUTUMN = Value(2, "autumn")
val WINTER = Value(3, "winter")
}
Season(0)
Season.withName("spring")
使用枚举object.values可以遍历枚举值
for (ele <- Season.values) println(ele)
11.14、继承
Scala中,让子类继承父类,与Java一样,也是使用extends关键字。继承就代表,子类可以从父类继承父类的field和method;然后子类可以在自己内部放入父类所没有,子类特有的field和method;使用继承可以有效复用代码
子类可以覆盖父类的field和method;但是如果父类用final修饰,field和method用final修饰,则该类是无法被继承的,field和method是无法被覆盖的
Scala中,如果子类要覆盖一个父类中的非抽象方法,则必须使用override关键字
override关键字可以帮助我们尽早地发现代码里的错误,比如:override修饰的父类方法的方法名我们拼写错了;比如要覆盖的父类方法的参数我们写错了;等等
此外,在子类覆盖父类方法之后,如果我们在子类中就是要调用父类那就可以使用super关键字,显式地指定要调用父类的方法
Scala中,子类可以覆盖父类的val field,而且子类的val field还可以覆盖父类的val field的getter方法;只要在子类中使用override关键字即可
class Person {
private var name = "Tom"
val age = 0
def getName = name
}
class Student extends Person {
private var score = "A"
def getScore = score
override val age: Int = 10
override def getName: String = "Hi,I'm " + super.getName
}
11.15、继承isInstanceOf和asInstanceOf
如果我们创建了子类的对象,但是又将其赋予了父类类型的变量。则在后续的程序中,我们又需要将父类类型的变量转换为子类类型的变量,应该如何做?
首先,需要使用isInstanceOf判断对象是否是指定类的对象,如果是的话,则可以使用asInstanceOf将对象转换为指定类型
注意 :
如果对象是null,则isInstanceOf一定返回false,asInstanceOf一定返回null
如果没有用isInstanceOf先判断对象是否为指定类的实例,就直接用asInstanceOf转换,则可能会抛出异常
class Person
class Student extends Person
val person: Person = new Student
val stu : Student = if (person.isInstanceOf[Student]) person.asInstanceOf[Student]
else null
println(stu.getClass)
结果 :
class com.journey.spark.scala.extend.Student
isInstanceOf只能判断出对象是否是指定类以及其子类的对象,而不能精确判断出,对象就是指定类的对象
如果要求精确地判断对象就是指定类的对象,**那么就只能使用getClass和classOf了
对象.getClass可以精确获取对象的类,classOf[类]可以精确获取类,然后使用==操作符即可判断**
class Person
class Student extends Person
val person: Person = new Student
person.isInstanceOf[Person] // true
person.getClass == classOf[Person] // false
person.getClass == classOf[Student] //true
11.16 使用模式匹配进行类型判断
使用模式匹配,功能性上来说,与isInstanceOf一样,也是判断主要是该类以及该类的子类的对象即可,不是精准判断的
class Person
class Student extends Person
val person: Person = new Student
val student: Student = new Student
person match {
case p: Person => println("it's Person's object")
case _ => println("unknown type")
}
不管是 match 之前是person或者student,结果都是打印it's Person's object,其实很简单,就是都是Person或者Person的子类
11.17 protected
跟java一样,scala中同样可以使用protected关键字来修饰field和method,这样在子类中就不需要super关键字,直接就可以访问field和method
还可以使用protected[this],则只能在当前子类对象中访问父类的field和method,无法通过其他子类对象访问父类的field和method
class Person {
protected var name: String = "Tom"
protected[this] var hobby: String = "game"
}
class Student extends Person {
def sayHello = println("Hello, " + name)
def makeFriends(student: Student) {
println("my hobby is " + hobby + ", your hobby is " + student.hobby)
}
}
11.18 调用父类的constructor
Scala中,每个类可以有一个主constructor和任意多个辅助constructor,而每个辅助constructor的第一行都必须是调用其他辅助constructor或者是主constructor;因此子类的辅助constructor是一定不可能直接调用父类的constructor的
只能在子类的主constructor中调用父类的constructor,以下这种语法,就是通过子类的主构造函数来调用父类的构造函数
注意:
如果是父类中接收的参数,比如name和age,子类中接收时,就不要用任何val或var来修饰了,否则会认为是子类要覆盖父类的field
11.19 匿名内部类
匿名子类,也就是说,可以定义一个类的没有名称的子类,并直接创建其对象,然后将对象的引用赋予一个变量。之后甚至可以将该匿名子类的对象传递给其他函数
class Person(protected val name: String) {
def sayHello = "Hello, I'm " + name
}
val person = new Person("Tom") {
override def sayHello = "Hi, I'm " + name
}
11.20 抽象类
如果在父类中,有某些方法无法立即实现,而需要依赖不同的子来来覆盖,重写实现自己不同的方法实现。此时可以将父类中的这些方法不给出具体的实现,只有方法签名,这种方法就是抽象方法
而一个类中如果有一个抽象方法,那么类就必须用abstract来声明为抽象类,此时抽象类是不可以实例化的
在子类中覆盖抽象类的抽象方法时,不需要使用override关键字
如果在父类中,定义了field,但是没有给出初始值,则此field为抽象field,抽象field意味着,scala会根据自己的规则,为var或val类型的field生成对应的getter和setter方法,但是父类中是没有该field的
子类必须覆盖field,以定义自己的具体field,并且覆盖抽象field,不需要使用override关键字
abstract class Person(val name : String) {
val nation : String
def sayHello : Unit
}
class Student(name : String) extends Person(name) {
override def sayHello: Unit = println("Hello " + name)
override val nation: String = "China"
}
12、Trait
12.1、将trait作为接口使用
Scala中的Triat是一种特殊的概念,首先我们可以将Trait作为接口来使用,此时的Triat就与Java中的接口非常类似。在Triat中可以定义抽象方法,就与抽象类中的抽象方法一样,只要不给出方法的具体实现即可。类可以使用extends关键字继承Trait
注意:
这里不是implement,而是extends,在scala中没有implement的概念,无论继承类还是Trait,统一都是extends。类继承Trait后,必须实现其中的抽象方法,实现时不需要使用override关键字。scala不支持对类进行多继承,但是支持多重继承Trait,使用with关键字即可
trait PersonTrait {
def eat(food : String)
}
trait MakeFriendsTrait {
def makeFriends(person : Person)
}
class Person (val name : String) extends PersonTrait with MakeFriendsTrait with Cloneable with Serializable {
override def eat(food: String): Unit = {
println(s"person eat ${food}")
}
override def makeFriends(person: Person): Unit = {
println(s"hello my name is ${name}, your name is ${person.name}")
}
}
12.2、在Trait中定义具体方法
Scala中的Triat可以不是只定义抽象方法,还可以定义具体方法,此时trait更像是包含了通用工具方法的东西。有一个专有的名词来形容这种情况,就是说Trait的功能混入了类
举例来说,Trait中可以包含一些很多类都通用的功能方法,比如打印日志等等,spark中就使用了Trait来定义了通用的日志打印方法
trait Logger {
def log(message : String) = println(message)
}
class Person (val name : String) extends Logger {
def makeFriends(person: Person): Unit = {
println(s"hello my name is ${name}, your name is ${person.name}")
log(s"makeFriends method is invoked with paramter Person[name=${person.name}]")
}
}
12.3、在Trait中定义具体字段
Scala中的Triat可以定义具体field,此时继承Trait的类就自动获得了Trait中定义的field
但是这种获取field的方式与继承class是不同的:如果是继承class获取的field,实际是定义在父类中的;而继承trait获取的field,就直接被添加到了类中
trait Person {
val eyeNum: Int = 2
}
class Student(val name: String) extends Person {
def sayHello = println("Hi, I'm " + name + ", I have " + eyeNum + " eyes.")
}
12.4 在Trait中定义抽象字段
Scala中的Triat可以定义抽象field,而Trait中的具体方法则可以基于抽象field来编写,但是继承Trait的类,则必须覆盖抽象field,提供具体的值
trait Human {
val msg: String
def say(name: String) = println(msg + ", " + name)
}
class Person(val name : String) extends Human {
override val msg: String = "hello"
def makeFriends(person : Person) {
say(person.name)
println("I'm " + name + ", I want to make friends with you!")
}
}
12.5 Trait混入
有时我们可以在创建类的对象时,指定该对象混入某个Trait,这样,就只有这个对象混入该trait的方法,而类的其他对象则没有
trait Logged {
def log(msg : String) {}
}
trait MyLogger extends Logged {
override def log(msg: String): Unit = {
println("log : " + msg)
}
}
class Person(val name : String) extends Logged {
def say = {
println("Hi, I'm " + name )
log("say method is invoked.")
}
}
调用 :
val person1 = new Person("Tom")
person1.say
val person2 = new Person("Tom") with MyLogger
person2.say
显示 :
Hi, I'm Tom
Hi, I'm Tom
log : say method is invoked.
12.6 Trait调用链
Scala中支持让类继承多个Trait后,依次调用多个Trait中的同一个方法,只要让多个Trait的同一个方法中,在最后都执行super.方法即可
类中调用多个Trait中都有的这个方法时,首先会从最右边的trait的方法开始执行,然后依次往左执行,形成一个调用链条
这种特性非常强大,其实就相当于设计模式中的责任链模式的一种具体实现依赖
trait Handler {
def handle(data : String) {}
}
trait DataValidHandler extends Handler {
override def handle(data: String): Unit = {
println("check data : " + data)
super.handle(data)
}
}
trait SignatureValidHandler extends Handler {
override def handle(data: String): Unit = {
println("check signature: " + data)
super.handle(data)
}
}
class Person(val name : String) extends SignatureValidHandler with DataValidHandler {
def say = {
println("Hello " + name)
handle(name)
}
}
12.7 混合使用trait的具体方法和抽象方法
在Trait中,可以混合使用具体方法和抽象方法。可以让具体方法依赖于抽象方法,而抽象方法则放到继承Trait的类中去实现。这种Trait其实就是设计模式中的模板设计模式的体现
trait Valid {
def getName : String
def valid : Boolean = {
getName == "Tom"
}
}
class Person(val name : String) extends Valid {
override def getName: String = name
}
12.8 Trait的构造机制
在Scala中,Trait也是有构造代码的,也就是Trait中的,不包含在任何方法中的代码。而继承了Trait的类的构造机制如下:
1、父类的构造函数执行
2、trait的构造代码执行,多个Trait从左到右依次执行
3、构造Trait时会先构造父Trait,如果多个Trait继承同一个父Trait,则父Trait只会构造一次
4、所有Trait构造完毕之后,子类的构造函数执行
核心 : 构造是从左到右,执行是从右到左
class Person { println("Person constructor!") }
trait Logger { println("Logger constructor!") }
trait MyLogger extends Logger { println("MyLogger constructor!") }
trait TimeLogger extends Logger { println("TimeLogger constructor!") }
class Student extends Person with MyLogger with TimeLogger {
println("Student constructor!")
}
执行结果 :
Logger constructor!
MyLogger constructor!
TimeLogger constructor!
Student constructor!
12.9、Trait继承class
在Scala中,Trait也可以继承自class,此时这个class就会成为所有继承该Trait的类的父类
class MyUtil {
def printMessage(msg: String) = println(msg)
}
trait Logger extends MyUtil {
def log(msg: String) = printMessage("log: " + msg)
}
class Person(val name: String) extends Logger {
def sayHello {
log("Hi, I'm " + name)
printMessage("Hi, I'm " + name)
}
}
13、函数式编程
Scala是一门既面向对象,又面向过程的语言。因此在Scala中有非常好的面向对象的特性,可以使用Scala来基于面向对象的思想开发大型复杂的系统和工程;而且Scala也面向过程,因此Scala中有函数的概念。在Scala中,函数与类、对象等一样,都是一等公民。Scala中的函数可以独立存在,不需要依赖任何类和对象。
Scala的函数式编程,就是Scala面向过程的最好的佐证。也正是因为函数式编程,才让Scala具备了Java所不具备的更强大的功能和特性
13.1、将函数赋值给变量
Scala中的函数是一等公民,可以独立定义,独立存在,而且可以直接将函数作为值赋值给变量。Scala的语法规定,将函数赋值给变量时,必须在函数后面加上空格和下划线
def main(args: Array[String]): Unit = {
// String => Unit,其实就是输入是String,无返回
val sayHellFunc: String => Unit = sayHello _
sayHellFunc("Tom")
}
def sayHello(name : String): Unit = {
println("Hello, " + name)
}
13.2、匿名函数
Scala中,函数也可以不需要命名,此时函数被称为匿名函数。可以直接定义函数之后,将函数赋值给某个变量;也可以将直接定义的匿名函数传入其他函数之中
Scala定义匿名函数的语法规则就是,(参数名: 参数类型) => 函数体
val sayHelloFunc = (name : String) => println("Hello," + name)
13.3、高阶函数
Scala中,由于函数是一等公民,因此可以直接将某个函数传入其他函数。接收其他函数作为参数的函数,也被称作高阶函数(higher-order function)
def main(args: Array[String]): Unit = {
// 这是一个输入为String,无返回只有打印的一个函数
val sayHelloFunc = (name : String) => println("Hello, " + name)
// 传入一个函数和一个String
greeting(sayHelloFunc, "Tom")
}
// 函数的第一个参数是函数,第二个参数是一个String
def greeting(func : (String) => Unit, name : String) {
func(name)
}
输出 :
Hello, Tom
Array(1, 2, 3, 4, 5, 6).map((num: Int) => num * num)
// 高阶函数的另外一个个能是将函数作为返回值
def getGreetingFunc(msg : String) = (name : String) => println(msg + ";" + name)
val greetingFunc : String => Unit = getGreetingFunc("hello")
greetingFunc("Tom")
13.4、高阶函数类型推断
高阶函数可以自动推断出参数类型,而不需要写明类型;而且对于只有一个参数的函数,还可以省去其小括号;如果仅有的一个参数在右侧的函数体内只使用一次,则还可以将接收参数省略,并且将参数用_来替代
def greeting(func : (String) => Unit, name : String) {
func(name)
}
greeting((name : String) => println("Hello," + name), "Tom")
greeting((name) => println("Hello," + name), "Tom")
greeting(name => println("Hello," + name), "Tom")
def triple(func: (Int) => Int) = { func(3) }
triple(3 * _)
13.5、Scala的常用高阶函数
// map: 对传入的每个元素都进行映射,返回一个处理后的元素
Array(1, 2, 3, 4, 5).map(2 * _)
// foreach: 对传入的每个元素都进行处理,但是没有返回值
(1 to 9).map("*" * _).foreach(println _)
// filter: 对传入的每个元素都进行条件判断,如果对元素返回true,则保留该元素,否则过滤掉该元素
(1 to 20).filter(_ % 2 == 0)
// reduceLeft: 从左侧元素开始,进行reduce操作,即先对元素1和元素2进行处理,然后将结果与元素3处理,再将结果与元素4处理,依次类推,即为reduce;reduce操作必须掌握!spark编程的重点!!!
// 下面这个操作就相当于1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9
(1 to 9).reduceLeft( _ * _)
// sortWith: 对元素进行两两相比,进行排序
Array(3, 2, 5, 4, 10, 1).sortWith(_ < _)
13.6、闭包
函数在变量不处于其有效作用域时,还能够对变量进行访问,即为闭包
def getGreetingFunc(msg : String) = (name : String) => println(msg + "," + name)
val greetingFunc = getGreetingFunc("hello")
greetingFunc("Tom")
两次调用getGreetingFunc函数,传入不同的msg,并创建不同的函数返回。然而,msg只是一个局部变量,却在getGreetingFunc执行完之后,还可以继续存在创建的函数之中;**greetingFunc("leo"),调用时,值为"hello"的msg被保留在了函数体内部,可以反复的使用
这种变量超出了其作用域,还可以使用的情况,即为闭包**
Scala通过为每个函数创建对象来实现闭包,实际上对于getGreetingFunc函数创建的函数,msg是作为函数对象的变量存在的,因此每个函数才可以拥有不同的msg
Scala编译器会确保上述闭包机制
13.7、SAM转换
在Java中,不支持直接将函数传入一个方法作为参数,通常来说,唯一的办法就是定义一个实现了某个接口的类的实例对象,该对象只有一个方法;而这些接口都只有单个的抽象方法,也就是single abstract method,简称为SAM
由于Scala是可以调用Java的代码的,因此当我们调用Java的某个方法时,可能就不得不创建SAM传递给方法,非常麻烦;但是Scala又是支持直接传递函数的。此时就可以使用Scala提供的,在调用Java方法时,使用的功能,SAM转换,即将SAM转换为Scala函数
要使用SAM转换,需要使用Scala提供的特性,隐式转换
import javax.swing._
import java.awt.event._
val button = new JButton("Click")
button.addActionListener(new ActionListener {
override def actionPerformed(event: ActionEvent) {
println("Click Me!!!")
}
})
implicit def getActionListener(actionProcessFunc: (ActionEvent) => Unit) = new ActionListener {
override def actionPerformed(event: ActionEvent) {
actionProcessFunc(event)
}
}
button.addActionListener((event: ActionEvent) => println("Click Me!!!"))
13.8、Currying函数
Curring函数,指的是,将原来接收两个参数的一个函数,转换为两个函数,第一个函数接收原先的第一个参数,然后返回接收原先第二个参数的第二个函数
在函数调用的过程中,就变为了两个函数连续调用的形式
def sum(a : Int, b : Int) = a + b
def sum2(a : Int) = (b : Int) => a + b
def sum3(a :Int)(b : Int) = a + b
14、集合
Scala中的集合体系主要包括:Iterable、Seq、Set、Map。其中Iterable是所有集合根Trait
Scala中的集合是分成可变和不可变两类集合的,其中可变集合就是说,集合的元素可以动态修改,而不可变集合的元素在初始化之后,就无法修改了。分别对应scala.collection.mutable和scala.collection.immutable两个包
Seq下包含了Range、ArrayBuffer、List等子trait
其中Range就代表了一个序列,通常可以使用“1 to 10”这种语法来产生一个Range ArrayBuffer就类似于Java中的ArrayList
List代表一个不可变的列表
14.1、List
List有head和tail,head代表List的第一个元素,tail代表第一个元素之后的所有元素,list.head,list.tail
List有特殊的::操作符,可以用于将head和tail合并成一个List,0 :: list
如果一个List只有一个元素,那么它的head就是这个元素,它的tail是Nil
def decorator(list : List[Int], prefix : String) {
if (list != Nil) {
println(prefix + list.head)
decorator(list.tail, prefix)
}
}
14.2、Nil/null/None/noting
Nil : 在Scal中,Nil是一个空列表,它是List[Noting]类型的一个实例,它表示一个空的集合或列表
null : null是Java中的一个关键字,表示一个空引用。在Scal中,null是一个特殊的类型,它可以被赋值给任何引用类型的变量
None : none是Option类型的一个实例,它表示一个空的Option。Option类型是Scala中的一个容器类型,它可以包含一个值或者空值
nothing : noghting是Scala中的一个特殊类型,它表示一个不存在的值。它通常用于标识一个函数永远不会反悔值,或者表示一个异常的类型
val map = Map("Japan" -> "Tokyo", "China" -> "Beijing")
map.get("France") match {
case Some(provice) => println(provice)
case None => println("unknow")
}
// val nameMaybe: Option[String] = None
val nameMaybe: Option[String] = Some("Tom")
nameMaybe match {
case Some(_) => println("yes")
case None => println("No name")
}
14.3、LinkedList
LinkedList代表一个可变的列表,使用elem可以引用其头部,使用next可以引用其尾部
val l = scala.collection.mutable.LinkedList(1, 2, 3, 4, 5); l.elem; l.next
案例:使用while循环将LinkedList中的每个元素都乘以2
val list = scala.collection.mutable.LinkedList(1, 2, 3, 4, 5)
var currentList = list
while (currentList != Nil) {
currentList.elem = currentList.elem * 2
currentList = currentList.next
}
案例:使用while循环将LinkedList中,从第一个元素开始,每隔一个元素,乘以2
val list = scala.collection.mutable.LinkedList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
var currentList = list
var first = true
while (currentList != Nil && currentList.next != Nil) {
if (first) { currentList.elem = currentList.elem * 2; first = false }
currentList = currentList.next.next
if (currentList != Nil) currentList.elem = currentList.elem * 2
}
14.4、Set
// Set代表一个没有重复元素的集合
// 将重复元素加入Set是没有用的,比如val s = Set(1, 2, 3); s + 1; s + 4
// 而且Set是不保证插入顺序的,也就是说,Set中的元素是乱序的,val s = new scala.collection.mutable.HashSet[Int](); s += 1; s += 2; s += 5
// LinkedHashSet会用一个链表维护插入顺序,val s = new scala.collection.mutable.LinkedHashSet[Int](); i += 1; s += 2; s += 5
// SrotedSet会自动根据key来进行排序,val s = scala.collection.mutable.SortedSet("orange", "apple", "banana")
15、模式匹配
15.1、简单模式匹配
Scala是没有Java中的switch case语法的,相对应的,Scala提供了更加强大的match case语法,即模式匹配,类替代switch case,match case也被称为模式匹配
Scala的match case与Java的switch case最大的不同点在于,Java的switch case仅能匹配变量的值,比1、2、3等;而Scala的match case可以匹配各种情况,比如变量的类型、集合的元素、有值或无值
match case的语法如下:变量 match { case 值 => 代码 }。如果值为下划线,则代表了不满足以上所有情况下的默认情况如何处理。此外,match case中,只要一个case分支满足并处理了,就不会继续判断下一个case分支了。(与Java不同,java的switch case需要用break阻止)
match case语法最基本的应用,就是对变量的值进行模式匹配
def judgeGrade(grade: String) {
grade match {
case "A" => println("Excellent")
case "B" => println("Good")
case "C" => println("Just so so")
case _ => println("you need work harder")
}
}
15.2、在模式匹配中使用if守卫
Scala的模式匹配语法,有一个特点在于,可以在case后的条件判断中,不仅仅只是提供一个值,而是可以在值后面再加一个if守卫,进行双重过滤
def judgeGrade(name: String, grade: String) {
grade match {
case "A" => println(name + ", you are excellent")
case "B" => println(name + ", you are good")
case "C" => println(name + ", you are just so so")
case _ if name == "Tom" => println(name + ", you are a good boy, come on")
case _ => println("you need to work harder")
}
}
15.3、在模式匹配中进行变量赋值
Scala的模式匹配语法,有一个特点在于,可以将模式匹配的默认情况,下划线,替换为一个变量名,此时模式匹配语法就会将要匹配的值赋值给这个变量,从而可以在后面的处理语句中使用要匹配的值
def judgeGrade(name: String, grade: String) {
grade match {
case "A" => println(name + ", you are excellent")
case "B" => println(name + ", you are good")
case "C" => println(name + ", you are just so so")
case _grade if name == "Tom" => println(name + ", you are a good boy, come on, your grade is " + _grade)
case _grade => println("you need to work harder, your grade is " + _grade)
}
}
15.4、对类型进行模式匹配
Scala的模式匹配一个强大之处就在于,可以直接匹配类型,而不是值
import java.io._
def processException(e: Exception) {
e match {
case e1: IllegalArgumentException => println("you have illegal arguments! exception is: " + e1)
case e2: FileNotFoundException => println("cannot find the file you need read or write!, exception is: " + e2)
case e3: IOException => println("you got an error while you were doing IO operation! exception is: " + e3)
case _: Exception => println("cannot know which exception you have!" )
}
}
15.5、对Array和List进行模式匹配
对Array进行模式匹配,分别可以匹配带有指定元素的数组、带有指定个数元素的数组、以某元素打头的数组
对List进行模式匹配,与Array类似,但是需要使用List特有的::操作符
def greeting(arr: Array[String]) {
arr match {
case Array("Tom") => println("Hi, Tom!")
case Array(girl1, girl2, girl3) => println("Hi, girls, nice to meet you. " + girl1 + " and " + girl2 + " and " + girl3)
case Array("Tom", _*) => println("Hi, Leo, please introduce your friends to me.")
case _ => println("hey, who are you?")
}
}
def greeting(list: List[String]) {
list match {
case "Tom" :: Nil => println("Hi, Tom!")
case girl1 :: girl2 :: girl3 :: Nil => println("Hi, girls, nice to meet you. " + girl1 + " and " + girl2 + " and " + girl3)
case "Tom" :: tail => println("Hi, Tom, please introduce your friends to me.")
case _ => println("hey, who are you?")
}
}
15.6、case class与模式匹配
Scala中提供了一种特殊的类,用case class进行声明,称作样例类。case class其实有点类似于Java中的JavaBean的概念。即只定义field,并且由Scala编译时自动提供getter和setter方法,但是没有method
case class的主构造函数接收的参数通常不需要使用var或val修饰,Scala自动就会使用val修饰(但是如果你自己使用var修饰,那么还是会按照var来)
Scala自动为case class定义了伴生对象,也就是object,并且定义了apply()方法,该方法接收主构造函数中相同的参数,并返回case class对象
class Person
case class Teacher(name: String, subject: String) extends Person
case class Student(name: String, classroom: String) extends Person
def judgeIdentify(person: Person) {
person match {
case Teacher(name, subject) => println("Teacher, name is " + name + ", subject is " + subject)
case Student(name, classroom) => println("Student, name is " + name + ", classroom is " + classroom)
case _ => println("Illegal access, please go out of the school!")
}
}
15.7、Option与模式匹配
Scala有一种特殊的类型,叫做Option。Option有两种值,一种是Some,表示有值,一种是None,表示没有值。
Option通常会用于模式匹配中,用于判断某个变量是有值还是没有值,这比null来的更加简洁明了
val grades = Map("Leo" -> "A", "Jack" -> "B", "Jen" -> "C")
def getGrade(name: String) {
val grade = grades.get(name)
grade match {
case Some(grade) => println("your grade is " + grade)
case None => println("Sorry, your grade information is not in the system")
}
}
16、类型参数
16.1、泛型类
泛型类,顾名思义,其实就是在类的声明中,定义一些泛型类型,然后在类内部,比如field或者method,就可以使用这些泛型类型
使用泛型类,通常是需要对类中的某些成员,比如某些field和method中的参数或变量,进行统一的类型限制,这样可以保证程序更好的健壮性和稳定性
如果不使用泛型进行统一的类型限制,那么在后期程序运行过程中,难免会出现问题,比如传入了不希望的类型,导致程序出问题。
在使用类的时候,比如创建类的对象,将类型参数替换为实际的类型,即可
Scala自动推断泛型类型特性:直接给使用了泛型类型的field赋值时,Scala会自动进行类型推断
class Student[T](val schoolId: T,val stuId: T) {
def getIdentify= "S-" + schoolId + "-" + stuId
}
val student = new Student[Int](11, 101)
student.getIdentify
16.2、泛型函数
泛型函数,与泛型类类似,可以给某个函数在声明时指定泛型类型,然后在函数体内,多个变量或者返回值之间,就可以使用泛型类型进行声明,从而对某个特殊的变量,或者多个变量,进行强制性的类型限制
与泛型类一样,你可以通过给使用了泛型类型的变量传递值来让Scala自动推断泛型的实际类型,也可以在调用函数时,手动指定泛型类型
def getCard[T](content : T) = {
content match {
case e : Int => "card: 001, " + e
case e : String => "card: this is your card, " + e
case _ => "card: " + content
}
}
getCard[String]("xxx")
16.3、上边界Bounds
在指定泛型类型的时候,有时,我们需要对泛型类型的范围进行界定,而不是可以是任意的类型。 比如,我们可能要求某个泛型类型,它就必须是某个类的子类,这样在程序中就可以放心地调用泛型类型继承的父类的方法,程序才能正常的使用和运行。此时就上边界Bounds
Scala的上下边界特性允许泛型类型必须是某个类的子类,或者必须是某个类的父类
class Person(val name: String) {
def sayHello = println("Hello, I'm " + name)
def makeFriends(person: Person) {
sayHello
person.sayHello
}
}
class Student(name: String) extends Person(name)
class Party[T <: Person](p1: T, p2: T) {
def play = p1.makeFriends(p2)
}
val stu1 = new Student("zhangsan")
val stu2 = new Student("lisi")
val party = new Party[Student](stu1, stu2)
party.play
注意 : T <: Person 是可以是Person或者Person的子类
16.4、下边界Bounds
除了指定泛型类型的上边界,还可以指定下边界,即指定泛型类型必须是某个类的父类
def getIDCard[R >: Child](person: R) {
person match {
case _: Child => println("please tell us your parents' names.")
case _: Father => println("sign your name for your child's id card.")
case _ => println("sorry, you are not allowed to get id card.")
}
}
class Father(val name: String)
class Child(name: String) extends Father(name)
注意 : R >: Child 是可以是Child的父类和自己的
16.5、View Bounds
上下边界Bounds,虽然可以让一种泛型类型,支持有父子关系的多种类型。但是,在某个类与上下边界Bounds指定的父子类型范围内的类都没有任何关系,则默认是肯定不能接受的
然而,View Bounds作为一种上下边界Bounds的加强版,支持可以对类型进行隐式转换,将指定的类型进行隐式转换后,再判断是否在边界指定的类型范围内
class Person(val name: String) {
def sayHello = println("Hello, I'm " + name)
def makeFriends(p: Person) {
sayHello
p.sayHello
}
}
class Student(name: String) extends Person(name)
class Dog(val name: String) { def sayHello = println("Wang, Wang, I'm " + name) }
implicit def dog2person(dog: Object): Person = if(dog.isInstanceOf[Dog]) {val _dog = dog.asInstanceOf[Dog]; new Person(_dog.name) } else Nil
class Party[T <% Person](p1: T, p2: T)
16.6、Context Bounds
Context Bounds是一种特殊的Bounds,它会根据泛型类型的声明,比如“T:类型”要求必须存在一个类型为“类型[T]”的隐式值。Context Bounds之所以叫Context,是因为它基于的是一种全局的上下文,需要使用到上下文中的隐式值以及注入
class Calculator[T: Ordering] (val number1: T, val number2: T) {
def max(implicit order: Ordering[T]) = if(order.compare(number1, number2) > 0) number1 else number2
}
16.7、Manifest Context Bounds
在Scala中,如果要实例化一个泛型数组,就必须使用Manifest Context Bounds。也就是说,如果数组元素类型为T的话,需要为类或者函数定义[T: Manifest]泛型类型,这样才能实例化Array[T]这种泛型数组
class Meat(val name: String)
class Vegetable(val name: String)
def packageFood[T: Manifest] (food: T*) = {
val foodPackage = new Array[T](food.length)
for(i <- 0 until food.length) foodPackage(i) = food(i)
foodPackage
}
16.8、协变和逆变
Scala的协变和逆变是非常有特色的!完全解决了Java中的泛型的一大缺憾!
举例来说,Java中,如果有Professional是Master的子类,那么Card[Professionnal]是不是Card[Master]的子类?答案是:不是。因此对于开发程序造成了很多的麻烦。
而Scala中,只要灵活使用协变和逆变,就可以解决Java泛型的问题
class Master
class Professional extends Master
// 大师以及大师级别以下的名片都可以进入会场
class Card[+T] (val name: String)
def enterMeet(card: Card[Master]) {
println("welcome to have this meeting!")
}
// 只要专家级别的名片就可以进入会场,如果大师级别的过来了,当然可以了!
class Card[-T] (val name: String)
def enterMeet(card: Card[Professional]) {
println("welcome to have this meeting!")
}
16.9、Existential Type
在Scala里,有一种特殊的类型参数,就是Existential Type,存在性类型
Array[T] forSome { type T }
Array[_] 表示某一种存在的类型,其实就是一个占位符
17、隐式转换
17.1、隐式转换基本原理
要实现隐式转换,只要程序可见的范围内定义隐式转换函数即可。Scala会自动使用隐式转换函数。隐式转换函数与普通函数唯一的语法区别就是,要以implicit开头,而且最好要定义函数返回类型
class SpecialPerson(val name: String)
class Student(val name: String)
class Older(val name: String)
// 其实就是可以将Student、Older转换为SpecialPerson
implicit def object2SpecialPerson(obj: Object): SpecialPerson = {
if (obj.getClass == classOf[Student]) {
val stu = obj.asInstanceOf[Student]; new SpecialPerson(stu.name)
}
else if (obj.getClass == classOf[Older]) {
val older = obj.asInstanceOf[Older]; new SpecialPerson(older.name)
}
else null
}
var ticketNumber = 0
def buySpecialTicket(p: SpecialPerson) = {
ticketNumber += 1
"T-" + ticketNumber
}
调用 :
val stu = new Student("Student")
println(buySpecialTicket(stu))
val older = new Student("Older")
println(buySpecialTicket(older))
val specialPerson = new SpecialPerson("SpecialPerson")
println(buySpecialTicket(specialPerson))
17.2、使用隐式转换加强现有类型
隐式转换非常强大的一个功能,就是可以在不知不觉中加强现有类型的功能。也就是说,可以为某个类定义一个加强版的类,并定义互相之间的隐式转换,从而让源类在使用加强版的方法时,由Scala自动进行隐式转换为加强类,然后再调用该方法
class Man(val name: String)
class Superman(val name: String) {
def emitLaser = println("emit a laster!")
}
implicit def man2superman(man: Man): Superman = new Superman(man.name)
val tom = new Man("Tom")
tom.emitLaser
17.3、隐式转换函数作用域与导入
Scala默认会使用两种隐式转换,一种是源类型,或者目标类型的伴生对象内的隐式转换函数;一种是当前程序作用域内的可以用唯一标识符表示的隐式转换函数
如果隐式转换函数不在上述两种情况下的话,那么就必须手动使用import语法引入某个包下的隐式转换函数,比如import test._。通常建议,仅仅在需要进行隐式转换的地方,比如某个函数或者方法内,用import导入隐式转换函数,这样可以缩小隐式转换函数的作用域,避免不需要的隐式转换
17.4、隐式参数
所谓的隐式参数,指的是在函数或者方法中,定义一个用implicit修饰的参数,此时Scala会尝试找到一个指定类型的,用implicit修饰的对象,即隐式值,并注入参数
Scala会在两个范围内查找:一种是当前作用域内可见的val或var定义的隐式变量;一种是隐式参数类型的伴生对象内的隐式值
class SignPen {
def write(content: String) = println(content)
}
implicit val signPen = new SignPen
def signForExam(name: String)(implicit signPen: SignPen) {
signPen.write(name + " come to exam in time.")
}
调用 :
val signPen = new SignPen
signForExam("xxx")(signPen)