Scala简单编程

  1. 云栖社区>
  2. 博客>
  3. 正文

Scala简单编程

得失与否 2018-01-01 21:52:22 浏览1595
展开阅读全文

1. Scala简单概述
Scala是多范式编程语言,其设计的初衷是要集成面向对象编程和函数式编程以及各种特性.
需要说明的一点是:
Scala是基于java平台(JVM) ,并兼容现有的Java程序
下面是Scala官方网址
http://www.scala-lang.org/

2.Scala编译器安装

  • 安装JDK
    因为Scala是运行在JVM平台,所以这里JDK是必需的
  • 安装Scala
    访问上面提供的Scala官网下载Scala编译器安装包
  • Windows系统环境安装Scala编译器

    • 解压下载好的安装包
    • 配置环境变量
      右键我的电脑->属性->高级系统设置->环境变量
  • Linux系统环境安装Scala编译器

    • 解压下载好的安装包
tar -zxvf scala-2.x.x.tgz -C /usr/scala
  • 配置环境变量
vi /etc/profle
export SCALA_HOME=/usr/scala
export PATH=$PATH:$SCALA_HOME/bin

3. Scala基础

  • 声明变量
package cn.itcast.scala
object VariableDemo {
  def main(args: Array[String]) {
    //使用val定义的变量值是不可变的,相当于java里用final修饰的变量
    val i = 1
    //使用var定义的变量是可变得,在Scala中鼓励使用val
    var s = "hello"
    //Scala编译器会自动推断变量的类型,必要的时候可以指定类型
    //变量名在前,类型在后
    val str: String = "itcast"
  }
}
  • 常用类型
    Scala 和 Java一样 有7种数值类型, Byte、Char、Short、Int、Long、Float和Double(注意没有包装类)和一个 Boolean类型
  • 条件表达式
package cn.itcast.scala
object ConditionDemo {
  def main(args: Array[String]) {jk1
,    val x = 1
    //判断x的值,将结果赋给y
    val y = if (x > 0) 1 else -1
    //打印y的值
    println(y)

    //支持混合类型表达式
    val z = if (x > 1) 1 else "error"
    //打印z的值
    println(z)

    //如果缺失else,相当于if (x > 2) 1 else ()
    val m = if (x > 2) 1
    println(m)

    //在scala中每个表达式都有值,scala中有个Unit类,写做(),相当于Java中的void
    val n = if (x > 2) 1 else ()
    println(n)

    //if和else if
    val k = if (x < 0) 0
    else if (x >= 1) 1 else -1
    println(k)
  }
}
  • 块表达式
    定义变量时用{} 包含一系列表达式,其中块的最后一个表达式的值就是块的值
package cn.itcast.scala
object BlockExpressionDemo {
  def main(args: Array[String]) {
    val x = 0
    //在scala中{}中包含一系列表达式,块中最后一个表达式的值就是块的值
    //下面就是一个块表达式
    val result = {
      if (x < 0){
        -1  
      } else if(x >= 1) {
        1
      } else {
        "error"
      }
    }
    //result的值就是块表达式的结果
    println(result)
  }
}
  • 循环
    在Scala中同样有for循环和while循环, for循环比较常用

for循环语法结构: for (i <- 表达式/数组/集合)

object ForDemo {
  def main(args: Array[String]) {
    //for(i <- 表达式),表达式1 to 10返回一个Range(区间)
    //每次循环将区间中的一个值赋给i
    for (i <- 1 to 10)
      println(i)

    //for(i <- 数组)
    val arr = Array("a", "b", "c")
    for (i <- arr)
      println(i)

    //高级for循环
    //每个生成器都可以带一个条件,注意:if前面没有分号
    for(i <- 1 to 3; j <- 1 to 3 if i != j)
      print((10 * i + j) + " ")
    println()

    //for推导式:如果for循环的循环体以yield开始,则该循环会构建出一个集合
    //每次迭代生成集合中的一个值
    val v = for (i <- 1 to 10) yield i * 10
    println(v)

  }

}

while循环语法结构: while(表达式){ statement(s);}

object Test {
   def main(args: Array[String]) {
      // 局部变量
      var a = 10;

      // while 循环执行
      while( a < 20 ){
         println( "Value of a: " + a );
         a = a + 1;
      }
   }
}

do...while循环语法结构:do { } while( 表达式 );

object Test {
   def main(args: Array[String]) {
      // 局部变量
      var a = 10;

      // do 循环
      do{
         println( "Value of a: " + a );
         a = a + 1;
      }while( a < 20 )
   }
}
  • 调用方法和函数
    Scala中的+ - * / %等操作符的作用与Java一样,位操作符 & | ^ >> <<也一样。只是有一点特别的:这些操作符实际上是方法。

例如:
a+b 是 a.+(b) 的缩写

  • 定义方法和函数

    • 定义方法
      格式: def 方法名(参数列表) : 返回值类型 = 方法体
def sum(x:Int,y: Int):Int ={
    x+y
  }

方法的返回值类型可以不写,编译器可以自动推送出来,但是 特别注意的是 对于递归函数,必须制定返回值类型

  • 定义函数
    格式: val 函数名 =(参数列表) => 函数体
val fun = (x:Int,y:Int) => {x+y}
  • 方法和函数的区别
    函数式编程语言中,它可以像任何其他数据类型一样被传递和操作,函数可以看做是一个对象,继承自 FunctionN

函数对象有apply,curried,toString,tupled这些方法
而方法不具有这些特性. 如果想把方法转换成一个函数,可以用 方法名 下划线的方式
案例: 首先定义一个方法. 再定义一个函数, 然后将 函数传递到方法里
image

  • 将方法转换成函数
    image

5. 数组、映射、元组、集合

  • 数组

    • 定长数组和变长数组
      (1)定长数组定义格式:
val arr=new Array[T](数组长度)

(2)变长数组定义格式:

val arr = ArrayBuffer[T]()

注意需要导包:import scala.collection.mutable.ArrayBuffer

package cn.itcast.scala
import scala.collection.mutable.ArrayBuffer
object ArrayDemo {
def main(args: Array[String]) {
   //初始化一个长度为8的定长数组,其所有元素均为0
    val arr1 = new Array[Int](8)
    //直接打印定长数组,内容为数组的hashcode值
    println(arr1)
    //将数组转换成数组缓冲,就可以看到原数组中的内容了
    //toBuffer会将数组转换长数组缓冲
    println(arr1.toBuffer)

    //注意:如果new,相当于调用了数组的apply方法,直接为数组赋值
    //初始化一个长度为1的定长数组
    val arr2 = Array[Int](10)
    println(arr2.toBuffer)

    //定义一个长度为3的定长数组
    val arr3 = Array("hadoop", "storm", "spark")
    //使用()来访问元素
    println(arr3(2))

    //变长数组(数组缓冲)
    //如果想使用数组缓冲,需要导入import scala.collection.mutable.ArrayBuffer包
    val ab = ArrayBuffer[Int]()
    //向数组缓冲的尾部追加一个元素
    //+=尾部追加元素
    ab += 1
    //追加多个元素
    ab += (2, 3, 4, 5)
    //追加一个数组++=
    ab ++= Array(6, 7)
    //追加一个数组缓冲
    ab ++= ArrayBuffer(8,9)
    //打印数组缓冲ab

    //在数组某个位置插入元素用insert,从某下标插入
    ab.insert(0, -1, 0)
    //删除数组某个位置的元素用remove  按照下标删除
    ab.remove(0)
    println(ab)
  }
}
  • 遍历数组
    1.增强for循环

2.好用的until会生成脚标,0 until 10 包含0不包含10

package cn.itcast.scala
object ForArrayDemo {
  def main(args: Array[String]) {
    //初始化一个数组
    val arr = Array(1,2,3,4,5,6,7,8)
    //增强for循环
    for(i <- arr)
      println(i)

    //好用的until会生成一个Range
    //reverse是将前面生成的Range反转
    for(i <- (0 until arr.length).reverse)
      println(arr(i))
  }
}
  • 数组转换
    yield关键字将原始的数组进行转换会产生一个新的数组,原始的数组不变
package cn.itcast.scala
object ArrayYieldDemo {
  def main(args: Array[String]) {
    //定义一个数组
    val arr = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)
    //将偶数取出乘以10后再生成一个新的数组
    val res = for (e <- arr if e % 2 == 0) yield e * 10
    println(res.toBuffer)

    //更高级的写法,用着更爽
    //filter是过滤,接收一个返回值为boolean的函数
    //map相当于将数组中的每一个元素取出来,应用传进去的函数
    val r = arr.filter(_ % 2 == 0).map(_ * 10)
    println(r.toBuffer)
  }
}
  • 数组常用算法
    在Scala中,数组上的某些方法对数组进行相应的操作非常方便
  • 映射
    在Scala中,把哈希表这种数据结构叫做映射

    • 构建映射
      1)构建映射格式

1、val map=Map(键->值,键->值....)
2、利用元组构建 val map=Map((键,值),(键,值),(键,值)....)
image

  • 获取和修改映射中的值
    1)获取映射中的值:

值=map(键)
image
好用的getOrElse
image
注意:在Scala中,有两种Map,一个是immutable包下的Map,该Map中的内容不可变;另一个是mutable包下的Map,该Map中的内容可变
例子:
image
注意:通常我们在创建一个集合是会用val这个关键字修饰一个变量(相当于java中的final),那么就意味着该变量的引用不可变,该引用中的内容是不是可变,取决于这个引用指向的集合的类型

  • 元组
    映射是K/V对偶的集合,对偶是元组的最简单形式,元组可以装着多个不同类型的值。

    • 创建元组
      (1)元组是不同类型的值的聚集;对偶是最简单的元组。

(2)元组表示通过将不同的值用小括号括起来,即表示元组。
创建元组格式:
val tuple=(元素,元素...)
image

  • 获取元组中的值
    (1) 获取元组中的值格式:

使用下划线加脚表 ,例如 t._1 t._2 t._3
注意:元组中的元素脚标是从1开始的
image

  • 将对偶的集合转换成映射
    将对偶的集合装换成映射:

调用其toMap 方法
image

  • 拉链操作
    1.使用zip命令可以将多个值绑定在一起

image
注意:如果两个数组的元素个数不一致,拉链操作后生成的数组的长度为较小的那个数组的元素个数
2.如果其中一个元素的个数比较少,可以使用zipAll用默认的元素填充
image

  • 集合
    Scala的集合有三大类:序列Seq、Set、映射Map,所有的集合都扩展自Iterable特质

在Scala中集合有可变(mutable)和不可变(immutable)两种类型,immutable类型的集合初始化后就不能改变了(注意与val修饰的变量进行区别)

  • List
    (1)不可变的序列 import scala.collection.immutable._

在Scala中列表要么为空(Nil表示空列表)要么是一个head元素加上一个tail列表。
9 :: List(5, 2) :: 操作符是将给定的头和尾创建一个新的列表
注意::: 操作符是右结合的,如9 :: 5 :: 2 :: Nil相当于 9 :: (5 :: (2 :: Nil))
list常用的操作符:
++B: List[B] 从列表的尾部添加另外一个列表
++: B >: A, That(implicit bf: CanBuildFrom[List[A], B, That]): That 在列表的头部添加一个列表
+: (elem: A): List[A] 在列表的头部添加一个元素
:+ (elem: A): List[A] 在列表的尾部添加一个元素
:: (x: A): List[A] 在列表的头部添加一个元素
::: (prefix: List[A]): List[A] 在列表的头部添加另外一个列表
val left = List(1,2,3)
val right = List(4,5,6)
//以下操作等价
left ++ right // List(1,2,3,4,5,6)
left ++: right // List(1,2,3,4,5,6)
right.++:(left) // List(1,2,3,4,5,6)
right.:::(left) // List(1,2,3,4,5,6)
//以下操作等价
0 +: left //List(0,1,2,3)
left.+:(0) //List(0,1,2,3)
//以下操作等价
left :+ 4 //List(1,2,3,4)
left.:+(4) //List(1,2,3,4)
//以下操作等价
0 :: left //List(0,1,2,3)
left.::(0) //List(0,1,2,3)
例子:

package cn.itcast.collect
object ImmutListDemo {
  def main(args: Array[String]) {
    //创建一个不可变的集合
    val lst1 = List(1,2,3)
    //补充:另一种定义list方法
    val other_lst=2::Nil
    //获取集合的第一个元素
    val first=lst1.head
    //获取集合中除第一个元素外的其他元素集合,
    val tail=lst1.tail
    //补充:其中如果 List 中只有一个元素,那么它的 head 就是这个元素,它的 tail 就是 Nil;
    println(other_lst.head+"----"+other_lst.tail)
    //将0插入到lst1的前面生成一个新的List
    val lst2 = 0 :: lst1
    val lst3 = lst1.::(0)
    val lst4 = 0 +: lst1
    val lst5 = lst1.+:(0)    
//将一个元素添加到lst1的后面产生一个新的集合
    val lst6 = lst1 :+ 3
    val lst0 = List(4,5,6)    
    //将2个list合并成一个新的List
    val lst7 = lst1 ++ lst0
    //将lst0插入到lst1前面生成一个新的集合
    val lst8 = lst1 ++: lst0
    //将lst0插入到lst1前面生成一个新的集合
    val lst9 = lst1.:::(lst0)
    println(other_lst)
    println(lst1)
    println(first)
    println(tail)
    println(lst2)
    println(lst3)
    println(lst4)
    println(lst5)
    println(lst6)
    println(lst7)
    println(lst8)
    println(lst9)
  }
}

(2)可变的序列 import scala.collection.mutable._

package cn.itcast.collect
import scala.collection.mutable.ListBuffer
object MutListDemo extends App{
  //构建一个可变列表,初始有3个元素1,2,3
  val lst0 = ListBuffer[Int](1,2,3)
  //创建一个空的可变列表
  val lst1 = new ListBuffer[Int]
  //向lst1中追加元素,注意:没有生成新的集合
  lst1 += 4
  lst1.append(5)
  //将lst1中的元素最近到lst0中, 注意:没有生成新的集合
  lst0 ++= lst1
  //将lst0和lst1合并成一个新的ListBuffer 注意:生成了一个集合
  val lst2= lst0 ++ lst1
  //将元素追加到lst0的后面生成一个新的集合
  val lst3 = lst0 :+ 5
  //删除元素,注意:没有生成新的集合
  val lst4 = ListBuffer[Int](1,2,3,4,5)
  lst4 -= 5
  //删除一个集合列表,生成了一个新的集合
  val lst5=lst4--List(1,2)
  //把可变list 转换成不可变的list 直接加上toList
  val lst6=lst5.toList
  //把可变list 转变数组用toArray
  val lst7=lst5.toArray
  println(lst0)
  println(lst1)
  println(lst2)
  println(lst3)
  println(lst4)
  println(lst5)
  println(lst6)
  println(lst7)
}
  • Set
    (1)不可变的Set import scala.collection.immutable._
Set代表一个没有重复元素的集合;将重复元素加入Set是没有用的,而且 Set 是不保证插入顺序的,即 Set 中的元素是乱序的。
定义:val set=Set(元素,元素,.....)
//定义一个不可变的Set集合
scala> val set =Set(1,2,3,4,5,6,7)
set: scala.collection.immutable.Set[Int] = Set(5, 1, 6, 2, 7, 3, 4)
//元素个数
scala> set.size
res0: Int = 7
//取集合最小值
scala> set.min
res1: Int = 1
//取集合最大值
scala> set.max
res2: Int = 7
//将元素和set1合并生成一个新的set,原有set不变
scala> set + 8
res3: scala.collection.immutable.Set[Int] = Set(5, 1, 6, 2, 7, 3, 8, 4)
scala> val set1=Set(7,8,9)
set1: scala.collection.immutable.Set[Int] = Set(7, 8, 9)
//两个集合的交集
scala> set & set1
res4: scala.collection.immutable.Set[Int] = Set(7)
//两个集合的并集
scala> set ++ set1
res5: scala.collection.immutable.Set[Int] = Set(5, 1, 6, 9, 2, 7, 3, 8, 4)
//在第一个set基础上去掉第二个set中存在的元素
scala> set -- set1
res6: scala.collection.immutable.Set[Int] = Set(5, 1, 6, 2, 3, 4)
//返回第一个不同于第二个set的元素集合
scala> set &~ set1
res7: scala.collection.immutable.Set[Int] = Set(5, 1, 6, 2, 3, 4)
//计算符合条件的元素个数
scala> set.count(_ >5)
res8: Int = 2
/返回第一个不同于第二个的元素集合
scala> set.diff(set1)
res9: scala.collection.immutable.Set[Int] = Set(5, 1, 6, 2, 3, 4)
/返回第一个不同于第二个的元素集合
scala> set1.diff(set)
res10: scala.collection.immutable.Set[Int] = Set(8, 9)
//取子set(2,5为元素位置, 从0开始,包含头不包含尾)
scala> set.slice(2,5)
res11: scala.collection.immutable.Set[Int] = Set(6, 2, 7)
//迭代所有的子set,取指定的个数组合
scala> set1.subsets(2).foreach(x=>println(x))
Set(7, 8)
Set(7, 9)
Set(8, 9)

(2)可变的Set import scala.collection.mutable._

//导入包
scala> import scala.collection.mutable
import scala.collection.mutable
//定义一个可变的Set
scala> val set1=new HashSet[Int]()
set1: scala.collection.mutable.HashSet[Int] = Set()
//添加元素
scala> set1 += 1
res1: set1.type = Set(1)
//添加元素  add等价于+=
scala> set1.add(2)
res2: Boolean = true
scala> set1
res3: scala.collection.mutable.HashSet[Int] = Set(1, 2)
//向集合中添加元素集合
scala> set1 ++=Set(1,4,5)
res5: set1.type = Set(1, 5, 2, 4)
//删除一个元素
scala> set1 -=5
res6: set1.type = Set(1, 2, 4)
//删除一个元素
scala> set1.remove(1)
res7: Boolean = true
scala> set1
res8: scala.collection.mutable.HashSet[Int] = Set(2, 4)
  • Map
    (1)不可变的Map import scala.collection.immutable._
定义Map集合
1.val map=Map(键->值,键->值...)
2.利用元组构建  val map=Map((键,值),(键,值),(键,值)....)
展现形式:
val  map = Map(“zhangsan”->30,”lisi”->40)
val  map = Map((“zhangsan”,30),(“lisi”,40))
3.操作map集合
获取值: 值=map(键)
原则:通过先获取键,在获取键对应值。
4.遍历map集合
scala> val imap=Map("zhangsan" -> 20,"lisi" ->30)
imap: scala.collection.immutable.Map[String,Int] = Map(zhangsan -> 20, lisi -> 30)
//方法一:显示所有的key
scala> imap.keys
res0: Iterable[String] = Set(zhangsan, lisi)
//方法二:显示所有的key
scala> imap.keySet
res1: scala.collection.immutable.Set[String] = Set(zhangsan, lisi)
//通过key获取value
scala> imap("lisi")
res2: Int = 30
//通过key获取value 有key对应的值则返回,没有就返回默认值0,
scala> imap.getOrElse("zhangsan",0)
res4: Int = 20
//没有对应的key,返回默认0
scala> imap.getOrElse("zhangsan1",0)
res5: Int = 0
//由于是不可变map,故不能向其添加、删除、修改键值对

(2)可变的Map import scala.collection.mutable._

//导包
import scala.collection.mutable
//声明一个可变集合
scala> val user =mutable.HashMap("zhangsan"->50,"lisi" -> 100)
user: scala.collection.mutable.HashMap[String,Int] = Map(lisi -> 100, zhangsan -> 50)
//添加键值对
scala> user +=("wangwu" -> 30)
res0: user.type = Map(lisi -> 100, zhangsan -> 50, wangwu -> 30)
//添加多个键值对
scala> user += ("zhangsan0" -> 30,"lisi0" -> 20)
res1: user.type = Map(zhangsan0 -> 30, lisi -> 100, zhangsan -> 50, lisi0 -> 20,wangwu -> 30)
//方法一:显示所有的key
scala> user.keys
res2: Iterable[String] = Set(zhangsan0, lisi, zhangsan, lisi0, wangwu)
//方法二:显示所有的key
scala> user.keySet
res3: scala.collection.Set[String] = Set(zhangsan0, lisi, zhangsan, lisi0, wangwu)
//通过key获取value
scala> user("zhangsan")
res4: Int = 50
//通过key获取value 有key对应的值则返回,没有就返回默认值0,
scala> user.getOrElse("zhangsan",0)
res5: Int = 50
//没有对应的key,返回默认0
scala> user.getOrElse("zhangsan1",0)
res6: Int = 0
//新键值对
scala> user("zhangsan") = 55
scala> user("zhangsan")
res8: Int = 55
//更新多个键值对
scala> user += ("zhangsan" -> 60, "lisi" -> 50)
res9: user.type = Map(zhangsan0 -> 30, lisi -> 50, zhangsan -> 60, lisi0 -> 20,wangwu -> 30)
//删除key
scala> user -=("zhangsan")
res14: user.type = Map(zhangsan0 -> 30, lisi -> 50, lisi0 -> 20, wangwu -> 30)
//删除key
scala>user.remove("zhangsan0")
//遍历map 方法一:模式匹配
scala> for((x,y) <- user) println(x+" -> "+y)
lisi -> 50
lisi0 -> 20
wangwu -> 30
//遍历map 方法二:通过key值
scala> for(x<- user.keys) println(x+" -> "+user(x))
lisi -> 50
lisi0 -> 20
wangwu -> 30
//遍历map 方法三:通过foreach
scala>  user.foreach{case (x,y) => println(x+" -> "+y)}
lisi -> 50
lisi0 -> 20
wangwu -> 30

5. 类、对象、继承、特质

    • 类的定义
//在Scala中,类并不用声明为public。
//Scala源文件中可以包含多个类,所有这些类都具有公有可见性。
class Person {
  //用val修饰的变量是只读属性,有getter但没有setter
  //(相当与Java中用final修饰的变量)
  val id = "9527"
  //用var修饰的变量既有getter又有setter
  var age: Int = 18
  //类私有字段,只能在类的内部使用
  private var name: String = "唐伯虎"
  //对象私有字段,访问权限更加严格的,Person类的方法只能访问到当前对象的字段
  private[this] val pet = "小强"
}
  • 构造器
    注意:主构造器会执行类定义中的所有语句
/**
  *每个类都有主构造器,主构造器的参数直接放置类名后面,与类交织在一起
  */
class Student(val name: String, val age: Int){
  //主构造器会执行类定义中的所有语句
  println("执行主构造器")
  private var gender = "male"
  //用this关键字定义辅助构造器
  def this(name: String, age: Int, gender: String){
    //每个辅助构造器执行必须以主构造器或其他的辅助构造器的调用开始
   this(name, age)
    println("执行辅助构造器")
    this.gender = gender
  }
}
/**
  *构造器参数可以不带val或var,如果不带val或var的参数至少被一个方法所使用,
  *那么它将会被提升为字段
  */
class Queen (val name: String, prop: Array[String], private var age: Int = 18){  
  println(prop.size)
 //prop被下面的方法使用后,prop就变成了不可变的对象私有字段,等同于private[this] val prop
  //如果没有被方法使用该参数将不被保存为字段,仅仅是一个可以被主构造器中的代码访问的普通参数
  def description = name + " is " + age + " years old with " + prop.toBuffer
}
object Queen{
  def main(args: Array[String]) {
    //私有的构造器,只有在其伴生对象中使用
    val q = new Queen("itcast", Array("黑马", "博学谷"), 10)
    println(q.description())
  }
}
  • Scala面向对象编程->对象

    • Scala 中的 object

      • Scala中的object
  1. 相当于 class 的单个实例,通常在里面放一些静态的 field 或者 method;

在Scala中没有静态方法和静态字段,但是可以使用object这个语法结构来达到同样的目的
目的
1.存放工具方法和常量
2.高效共享单个不可变的实例
3.单例模式
举例说明:

package cn.itcast.object_demo
import scala.collection.mutable.ArrayBuffer
class Session{}
object SingletonDemo {
  def main(args: Array[String]) {
    //单例对象,不需要new,用【类名.方法】调用对象中的方法
    val session = SessionFactory.getSession()
    println(session)
  }
}
object SessionFactory{
  //该部分相当于java中的静态块
  var counts = 5
  val sessions = new ArrayBuffer[Session]()
  while(counts > 0){
    sessions += new Session
    counts -= 1
  }
  //在object中的方法相当于java中的静态方法
  def getSession(): Session ={
    sessions.remove(0)
  }
- Scala中的伴生对象

1.如果有一个class文件,还有一个与class同名的object文件,那么就称这个object是class的伴生对象,class是object的伴生类;
2.伴生类和伴生对象必须存放在一个.scala文件中;
3.伴生类和伴生对象的最大特点是,可以相互访问;
举例说明:

package cn.itcast.object_demo
//伴生类
class Dog {
  val id = 1
  private var name = "itcast"
  def printName(): Unit ={
    //在Dog类中可以访问伴生对象Dog的私有属性
    println(Dog.CONSTANT + name )
  }
}
//伴生对象
object Dog {
  //伴生对象中的私有属性
  private val CONSTANT = "汪汪汪 : "
  def main(args: Array[String]) {
    val p = new Dog
    //访问私有的字段name
    p.name = "123"
    p.printName()
  }
}
//执行结果 汪汪汪 : 123
  • Scala面向对象编程之继承

    • Scala中继承(extends)的概念
      1.Scala 中,让子类继承父类,与 Java 一样,也是使用 extends 关键字;

2.继承就代表,子类可继承父类的 field 和 method ,然后子类还可以在自己的内部实现父类没有的,子类特有的 field 和method,使用继承可以有效复用代码;
3.子类可以覆盖父类的 field 和 method,但是如果父类用 final 修饰,或者 field 和 method 用 final 修饰,则该类是无法被继承的,或者 field 和 method 是无法被覆盖的。
4.private 修饰的 field 和 method 不可以被子类继承,只能在类的内部使用;
5.field 必须要被定义成 val 的形式,才能被继承,并且还要使用 override 关键字。 因为 var 修饰的 field 是可变的,在子类中可直接引用被赋值,不需要被继承;即 val 修饰的才允许被继承,var 修饰的只允许被引用。继承就是改变、覆盖的意思。
Java 中的访问控制权限,同样适用于 Scala
举例说明:

package cn.itcast.extends_demo
class Person {
  val name="super"
  def getName=this.name
}
class Student extends Person{
  //继承加上关键字
  override
  val name="sub"
  //子类可以定义自己的field和method
  val score="A"
  def getScore=this.score
}
  • Scala中override 和 super 关键字
    1.Scala 中,如果子类要覆盖父类中的一个非抽象方法,必须要使用 override 关键字;

2.Scala中,子类可以覆盖父类的 val field,而且子类的 val field 还可以覆盖父类的 val field 的 getter 方法,只要在子类中使用 override 关键字即可。
3.override 关键字可以帮助开发者尽早的发现代码中的错误,比如, override 修饰的父类方法的方法名拼写错误;比如要覆盖的父类方法中的参数拼写错误等等;
4.此外,在子类覆盖父类方法后,如果在子类中要调用父类中被覆盖的方法,则必须要使用 super 关键字,显示的指出要调用的父类方法。
举例说明:

package cn.itcast.extends_demo
class Person1 {
  private val name = "leo"
  val age=50
  def getName = this.name
}
class Student extends Person1{
  private val score = "A"
  //子类可以覆盖父类的 val field,使用override关键字
  override
  val age=30
  def getScore = this.score
  //覆盖父类非抽象方法,必须要使用 override 关键字
  //同时调用父类的方法,使用super关键字
  override def getName = "your name is " + super.getName
}
  • Scala中isInstanceOf 和 asInstanceOf
    如果实例化了子类的对象,但是将其赋予了父类类型的变量,在后续的过程中,又需要将父类类型的变量转换为子类类型的变量,应该如何做?

1.首先,需要使用 isInstanceOf 判断对象是否为指定类的对象,如果是的话,则可以使用 asInstanceOf 将对象转换为指定类型;
2.注意: p.isInstanceOf[XX] 判断 p 是否为 XX 对象的实例;P.asInstanceOf[XX] 把 p 转换成 XX 对象的实例
3.注意:如果没有用 isInstanceOf 先判断对象是否为指定类的实例,就直接用 asInstanceOf 转换,则可能会抛出异常;
4.注意:如果对象是 null,则 isInstanceOf 一定返回 false, asInstanceOf 一定返回 null;
5.Scala与Java类型检查和转换
举例说明:

package cn.itcast.extends_demo
class Person3 {}
class Student3 extends Person3
object Student3{
    def main (args: Array[String] ) {
    val p: Person3 = new Student3
    var s: Student3 = null
    //如果对象是 null,则 isInstanceOf 一定返回 false
    println (s.isInstanceOf[Student3])
    // 判断 p 是否为 Student3 对象的实例
  if (p.isInstanceOf[Student3] ) {
    //把 p 转换成 Student3 对象的实例
      s = p.asInstanceOf[Student3]
  }
  println (s.isInstanceOf[Student3] )
  }
}
  • Scala中getClass 和 classOf
    1.isInstanceOf 只能判断出对象是否为指定类以及其子类的对象,而不能精确的判断出,对象就是指定类的对象;

2.如果要求精确地判断出对象就是指定类的对象,那么就只能使用 getClass 和 classOf 了;
3.p.getClass 可以精确地获取对象的类,classOf[XX] 可以精确的获取类,然后使用 == 操作符即可判断;
举例说明:

package cn.itcast.extends_demo
class Person4 {}
class Student4 extends Person4
object Student4{
  def main(args: Array[String]) {
    val p:Person4=new Student4
    //判断p是否为Person4类的实例
    println(p.isInstanceOf[Person4])//true
    //判断p的类型是否为Person4类
    println(p.getClass == classOf[Person4])//false
    //判断p的类型是否为Student4类
    println(p.getClass == classOf[Student4])//true
  }
}
  • Scala中使用模式匹配进行类型判断
    1.在实际的开发中,比如 spark 源码中,大量的地方使用了模式匹配的语法进行类型的判断,这种方式更加地简洁明了,而且代码的可维护性和可扩展性也非常高;

2.使用模式匹配,功能性上来说,与 isInstanceOf 的作用一样,主要判断是否为该类或其子类的对象即可,不是精准判断。
3.等同于 Java 中的 switch case break default 语法;
举例说明:

package cn.itcast.extends_demo
class Person5 {}
class Student5 extends Person5
object Student5{
  def main(args: Array[String]) {
    val p:Person5=new Student5
    p match {
      // 匹配是否为Person类或其子类
      case per:Person5 => println("This is a Person5's Object!")
      // 匹配所有剩余情况
      case _  =>println("Unknown type!")
    }
  }
}
  • Scala中protected
    1.跟 Java 一样,Scala 中同样可使用 protected 关键字来修饰 field 和 method。在子类中,可直接访问父类的 field 和 method,而不需要使用 super 关键字;

2.还可以使用 protected[this] 关键字, 访问权限的保护范围。只允许在当前子类中访问父类的 field 和 method,不允许通过其他子类对象访问父类的 field 和 method。
举例说明:

package cn.itcast.extends_demo
class Person6{
  protected var name:String="tom"
  protected[this] var hobby:String ="game"
  def sayBye=println("再见...")
}
class Student6 extends Person6{
  //父类使用protected 关键字来修饰 field可以直接访问
  def  sayHello =println("Hello "+name)
  //父类使用protected 关键字来修饰method可以直接访问
  def  sayByeBye=sayBye
  def makeFriends(s:Student6)={
    println("My hobby is "+hobby+", your hobby is UnKnown")
  }
}
object Student6{
  def main(args: Array[String]) {
    val s:Student6=new Student6
    s.sayHello
    s.makeFriends(s)
    s.sayByeBye
  }
}
  • Scala中调用父类的constructor
    1.Scala中,每个类都可以有一个主constructor和任意多个辅助constructor,而且每个辅助constructor的第一行都必须调用其他辅助constructor或者主constructor代码;因此子类的辅助constructor是一定不可能直接调用父类的constructor的;

2.只能在子类的主constructor中调用父类的constructor,以下这种语法,就是通过子类的主构造函数来调用父类的构造函数;
3.如果父类的构造函数已经定义过的 field,比如name和age,子类再使用时,就不要用 val 或 var 来修饰了,否则会被认为,子类要覆盖父类的field,且要求一定要使用 override 关键字。
举例说明:

package cn.itcast.extends_demo
class Person7(val name:String,val age:Int){
  var score :Double=0.0
  var address:String="beijing"
  def this(name:String,score:Double)={
    //每个辅助constructor的第一行都必须调用其他辅助constructor或者主constructor代码
    //主constructor代码
      this(name,30)
      this.score=score
  }
  //其他辅助constructor
  def this(name:String,address:String)={
      this(name,100.0)
      this.address=address
  }
}
class Student7(name:String,score:Double) extends Person7(name,score)
  • Scala中匿名内部类
    1.在Scala中,匿名内部类是非常常见的,而且功能强大。Spark的源码中大量的使用了匿名内部类;

2.匿名内部类,就是定义一个没有名称的子类,并直接创建其对象,然后将对象的引用赋予一个变量,即匿名内部类的实例化对象。然后将该对象传递给其他函数使用。
举例说明:

package cn.itcast.extends_demo
class Person8(val name:String) {
  def sayHello="Hello ,I'm "+name
}
class GreetDemo{
  //接受Person8参数,并规定Person8类只含有一个返回String的sayHello方法
  def greeting(p:Person8{
      def sayHello:String})={
          println(p.sayHello)
  }
}
object GreetDemo {
  def main(args: Array[String]) {
    //创建Person8的匿名子类对象
    val p=new Person8("tom")
    val g=new GreetDemo
    g.greeting(p)
  }
}
  • Scala中抽象类
    1.如果在父类中,有某些方法无法立即实现,而需要依赖不同的子类来覆盖,重写实现不同的方法。此时,可以将父类中的这些方法编写成只含有方法签名,不含方法体的形式,这种形式就叫做抽象方法;

2.一个类中,如果含有一个抽象方法或抽象field,就必须使用abstract将类声明为抽象类,该类是不可以被实例化的;
3.在子类中覆盖抽象类的抽象方法时,可以不加override关键字;
举例说明:

package cn.itcast.extends_demo
abstract class Person9(val name:String) {
  //必须指出返回类型,不然默认返回为Unit
  def sayHello:String
  def sayBye:String
}
class Student9(name:String) extends Person9(name){
  //必须指出返回类型,不然默认
  def sayHello: String = "Hello,"+name
  def sayBye: String ="Bye,"+name
}
object Student9{
  def main(args: Array[String]) {
    val s = new Student9("tom")
    println(s.sayHello)
    println(s.sayBye)
  }
}
  • Scala中抽象field
    1.如果在父类中,定义了field,但是没有给出初始值,则此field为抽象field;

举例说明:

package cn.itcast.extends_demo
abstract class Person10 (val name:String){
//抽象fields
    val age:Int
}
class Student10(name: String) extends Person10(name) {
   val age: Int = 50
}
  • Scala中面向对象编程之trait

    • 将trait作为接口使用
      1.Scala中的trait是一种特殊的概念;

2.首先先将trait作为接口使用,此时的trait就与Java中的接口 (interface)非常类似;
3.在trait中可以定义抽象方法,就像抽象类中的抽象方法一样,只要不给出方法的方法体即可;
4.类可以使用extends关键字继承trait,注意,这里不是 implement,而是extends ,在Scala中没有 implement 的概念,无论继承类还是trait,统一都是 extends;
5.类继承后,必须实现其中的抽象方法,实现时,不需要使用 override 关键字;
6.Scala不支持对类进行多继承,但是支持多重继承 trait,使用 with 关键字即可。
举例说明:

package cn.itcast.triat
trait HelloTrait {
  def sayHello(): Unit
}
trait MakeFriendsTrait {
  def makeFriends(c: Children): Unit
}
//多重继承 trait
class Children(val name: String) extends HelloTrait with MakeFriendsTrait with Cloneable with Serializable{
  def sayHello() =println("Hello, " + this.name)
  def makeFriends(c: Children) = println("Hello, my name is " + this.name + ", your name is " + c.name)
}
object Children{
  def main(args: Array[String]) {
    val c1=new Children("tom")
    val c2=new Children("jim")
    c1.sayHello()//Hello, tom
    c1.makeFriends(c2)//Hello, my name is tom, your name is jim
  }
}
  • 在trait中定义具体的方法
    1.Scala中的trait不仅可以定义抽象方法,还可以定义具体的方法,此时 trait 更像是包含了通用方法的工具;此时,可以认为trait还包含了类的功能;

举例说明:

package cn.itcast.triat
/**
  比如 trait 中可以包含很多子类都通用的方法,例如打印日志或其他工具方法等等。
  spark就使用trait定义了通用的日志打印方法;
 */
trait Logger {
  def log(message: String): Unit = println(message)
}
class PersonForLog(val name: String) extends Logger {
  def makeFriends(other: PersonForLog) = {
    println("Hello, " + other.name + "! My name is " + this.name + ", I miss you!!")
    this.log("makeFriends method is invoked with parameter PersonForLog[name = " + other.name + "]")
  }
}
object PersonForLog{
  def main(args: Array[String]) {
    val p1=new PersonForLog("jack")
    val p2=new PersonForLog("rose")
    p1.makeFriends(p2)
    //Hello, rose! My name is jack, I miss you!!
    //makeFriens method is invoked with parameter PersonForLog[name = rose]
  }
}
  • 在trait中定义具体field
    Scala 中的 trait 可以定义具体的 field,此时继承 trait 的子类就自动获得了 trait 中定义的 field;

但是这种获取 field 的方式与继承 class 的是不同的。 如果是继承 class 获取的 field ,实际上还是定义在父类中的;而继承 trait获取的 field,就直接被添加到子类中了。
举例说明:

package cn.itcast.triat
trait PersonForField {
  val  age:Int=50
}
//继承 trait 获取的field直接被添加到子类中
class StudentForField(val name: String) extends PersonForField {
  def sayHello = println("Hi, I'm " + this.name + ", my  age  is "+ age)
}
object StudentForField{
  def main(args: Array[String]) {
    val s=new StudentForField("tom")
    s.sayHello
  }
}
  • 在trait中定义抽象field
    1.Scala中的trait也能定义抽象field, 而trait中的具体方法也能基于抽象field编写;

2.继承trait的类,则必须覆盖抽象field,提供具体的值;
举例说明:

package cn.itcast.triat
trait SayHelloTrait {
  val msg:String
  def sayHello(name: String) = println(msg + ", " + name)
}
class PersonForAbstractField(val name: String) extends SayHelloTrait {
  //必须覆盖抽象 field
  val msg = "Hello"
  def makeFriends(other: PersonForAbstractField) = {
    this.sayHello(other.name)
    println("I'm " + this.name + ", I want to make friends with you!!")
  }
}
object PersonForAbstractField{
  def main(args: Array[String]) {
    val p1=new PersonForAbstractField("Tom")
    val p2=new PersonForAbstractField("Rose")
    p1.makeFriends(p2)
  }
}
  • 在实例对象指定混入某个trait
    1.可在创建类的对象时,为该对象指定混入某个trait,且只有该对象混入了trait中的方法,其他该类的对象则没有;

2.在创建对象时,使用 with 关键字指定混入某个 trait;
举例说明:

package cn.itcast.triat
trait LoggedTrait {
  // 该方法为实现的具体方法
  def log(msg: String) = {}
}
trait MyLogger extends LoggedTrait{
  // 覆盖 log() 方法
override def log(msg: String) = println("log: " + msg)
}
class PersonForMixTraitMethod(val name: String) extends LoggedTrait {
  def sayHello = {
    println("Hi, I'm " + this.name)
    log("sayHello method is invoked!")
  }
}
object PersonForMixTraitMethod{
  def main(args: Array[String]) {
    val tom= new PersonForMixTraitMethod("Tom").sayHello //结果为:Hi, I'm Tom
    // 使用 with 关键字,指定混入MyLogger trait
    val rose = new PersonForMixTraitMethod("Rose") with MyLogger
    rose.sayHello
// 结果为:     Hi, I'm Rose
// 结果为:     log: sayHello method is invoked!
  }
}
  • trait 调用链
    1.Scala中支持让类继承多个trait后,可依次调用多个trait中的同一个方法,只要让多个trait中的同一个方法,在最后都依次执行 super 关键字即可;

类中调用多个trait中都有的这个方法时,首先会从最右边的trait的方法开始执行,然后依次往左执行,形成一个调用链条;
3.这种特性非常强大,其实就是设计模式中责任链模式的一种具体实现;
案例说明:

package cn.itcast.triat
trait HandlerTrait {
  def handle(data: String) = {println("last one")}
}
trait DataValidHandlerTrait extends HandlerTrait {
  override def handle(data: String) = {
              println("check data: " + data)
              super.handle(data)
}
}
trait SignatureValidHandlerTrait extends HandlerTrait {
  override def handle(data: String) = {
          println("check signature: " + data)
          super.handle(data)
  }
}
class PersonForRespLine(val name: String) extends SignatureValidHandlerTrait with DataValidHandlerTrait {
  def sayHello = {
        println("Hello, " + this.name)
        this.handle(this.name)
  }
}
object PersonForRespLine{
  def main(args: Array[String]) {
     val p=new PersonForRespLine("tom")
      p.sayHello
      //执行结果:
//    Hello, tom
//    check data: tom
//    check signature: tom
//    last one
  }
}
  • 混合使用 trait 的具体方法和抽象方法
    1.在 trait 中,可以混合使用具体方法和抽象方法;

2.可以让具体方法依赖于抽象方法,而抽象方法则可放到继承 trait的类中去实现;
3.这种 trait 特性,其实就是设计模式中的模板设计模式的体现;
举例说明:8

package cn.itcast.triat
trait ValidTrait {
 //抽象方法
  def getName: String     
//具体方法,具体方法的返回值依赖于抽象方法                        
 def valid: Boolean = {"Tom".equals(this.getName)
  }
}
class PersonForValid(val name: String) extends ValidTrait {
  def getName: String = this.name
}
object exe {
  def main(args: Array[String]): Unit = {
    val person = new PersonForValid("Rose")
    println(person.valid)
  }
}
  • trait的构造机制
    1.在Scala中,trait也是有构造代码的,即在trait中,不包含在任何方法中的代码;

2.而继承了trait的子类,其构造机制如下:
3.父类的构造函数先执行,即 class 类必须放在最左边;trait的构造代码执行,多个trait从左向右依次执行;构造trait时,先构造父 trait,如果多个trait继承同一个父trait,则父trait只会构造一次;所有trait构造完毕之后,子类的构造函数最后执行。
举例说明:

package cn.itcast.triat
class Person_One {
  println("Person's constructor!")
}
trait Logger_One {
  println("Logger's constructor!")
}
trait MyLogger_One extends Logger_One {
  println("MyLogger's constructor!")
}
trait TimeLogger_One extends Logger_One {
  println("TimeLogger's contructor!")
}
class Student_One extends Person_One with MyLogger_One with TimeLogger_One {
  println("Student's constructor!")
  }
object exe_one {
  def main(args: Array[String]): Unit = {
    val student = new Student_One
    //执行结果为:
//      Person's constructor!
//      Logger's constructor!
//      MyLogger's constructor!
//      TimeLogger's contructor!
//      Student's constructor!
  }
}
  • trait 继承 class
    1.在Scala中trait 也可以继承 class,此时这个 class 就会成为所有继承该 trait 的子类的超级父类

举例说明:

package cn.itcast.triat
class MyUtil {
  def printMsg(msg: String) = println(msg)
}
trait Logger_Two extends MyUtil {
  def log(msg: String) = this.printMsg("log: " + msg)
}
class Person_Three(val name: String) extends Logger_Two {
    def sayHello {
        this.log("Hi, I'm " + this.name)
        this.printMsg("Hello, I'm " + this.name)
  }
}
object Person_Three{
  def main(args: Array[String]) {
      val p=new Person_Three("Tom")
      p.sayHello
    //执行结果:
//      log: Hi, I'm Tom
//      Hello, I'm Tom
  }
}

6. 模式匹配
Scala有一个十分强大的模式匹配机制,可以应用到很多场合:如switch语句、类型检查等。
并且Scala还提供了样例类,对模式匹配进行了优化,可以快速进行匹配。

  • 匹配字符串
package cn.itcast.cases  
import scala.util.Random  
object CaseDemo01 extends App{
  val arr = Array("hadoop", "zookeeper", "spark")
  val name = arr(Random.nextInt(arr.length))
  name match {
    case "hadoop"    => println("大数据分布式存储和计算框架...")
    case "zookeeper" => println("大数据分布式协调服务框架...")
    case "spark" => println("大数据分布式内存计算框架...")
    case _ => println("我不认识你...")
  }
}  
  • 匹配类型
  package cn.itcast.cases
import scala.util.Random
object CaseDemo01 extends App{
  val arr = Array("hello", 1, 2.0, CaseDemo)
  val v = arr(Random.nextInt(4))
  println(v)
  v match {
    case x: Int => println("Int " + x)
    case y: Double if(y >= 0) => println("Double "+ y)
    case z: String => println("String " + z)
    case _ => throw new Exception("not match exception")
  }
}

注意: case y: Double if(y >=0) => ...
模式匹配的时候可以添加条件.若不符合条件,则case _

  • 匹配数组,元组
package cn.itcast.cases
object CaseDemo03 extends App{
  val arr = Array(1, 3, 5)
  arr match {
    case Array(1, x, y) => println(x + " " + y)
    case Array(0) => println("only 0")
    case Array(0, _*) => println("0 ...")
    case _ => println("something else")
  }
  val lst = List(3, -1)
  lst match {
    case 0 :: Nil => println("only 0")
    case x :: y :: Nil => println(s"x: $x y: $y")
    case 0 :: tail => println("0 ...")
    case _ => println("something else")
  }
  val tup = (2, 3, 7)
  tup match {
    case (1, x, y) => println(s"1, $x , $y")
    case (_, z, 5) => println(z)
    case  _ => println("else")
  }
}

注意:在Scala中列表要么为空(Nil表示空列表)要么是一个head元素加上一个tail列表。
9 :: List(5, 2) :: 操作符是将给定的头和尾创建一个新的列表
注意: :: 操作符是右结合的,如9 :: 5 :: 2 :: Nil相当于 9 :: (5 :: (2 :: Nil))

  • 样例类
    在Scala中样例类是一中特殊的类,可用于模式匹配。case class是多例的,后面要跟构造参数,case object是单例的.
package cn.itcast.cases
import scala.util.Random
case class SubmitTask(id: String, name: String)
case class HeartBeat(time: Long)
case object CheckTimeOutTask
object CaseDemo04 extends App{
  val arr = Array(CheckTimeOutTask, HeartBeat(12333), SubmitTask("0001", "task-0001"))
  arr(Random.nextInt(arr.length)) match {
    case SubmitTask(id, name) => {
      println(s"$id, $name")
    }
    case HeartBeat(time) => {
      println(time)
    }
    case CheckTimeOutTask => {
      println("check")
    }
  }
} 
  • Option类型
    在Scala中Option类型用样例类来表示可能存在或也可能不存在的值(Option的子类有Some和None)。Some包装了某个值,None表示没有值
package cn.itcast.cases
object OptionDemo {
  def main(args: Array[String]) {
    val map = Map("a" -> 1, "b" -> 2)
    val v = map.get("b") match {
      case Some(i) => i
      case None => 0
    }
    println(v)
    //更好的方式
    val v1 = map.getOrElse("c", 0)
    println(v1)
  }
}  
  • 偏函数
    被包在花括号内没有match的一组case语句是一个偏函数,它是PartialFunction[A, B]的一个实例,A代表参数类型,B代表返回类型,常用作输入模式匹配,偏函数最大的特点就是它只接受和处理其参数定义域的一个子集。
package cn.itcast.cases
object PartialFuncDemo  {
  def func1: PartialFunction[String, Int] = {
    case "one" => 1
    case "two" => 2
    case _ => -1
  }
  def func2(num: String) : Int = num match {
    case "one" => 1
    case "two" => 2
    case _ => -1
  }
  def main(args: Array[String]) {
    println(func1("one"))
    println(func2("one"))
  }
}  

7. Scala中的协变,逆变,非变

  • 协变,逆变,非变概念
    协变和逆变主要是用来解决参数化类型的泛化问题。Scala的协变与逆变是非常有特色的,完全解决了Java中泛型的一大缺憾;举例来说,Java中,如果有 A是 B的子类,但 Card[A] 却不是 Card[B] 的子类;而 Scala 中,只要灵活使用协变与逆变,就可以解决此类 Java 泛型问题;

由于参数化类型的参数(参数类型)是可变的,当两个参数化类型的参数是继承关系(可泛化),那被参数化的类型是否也可以泛化呢?Java中这种情况下是不可泛化的,然而Scala提供了三个选择,即协变(“+”)、逆变(“-”)和非变。
下面说一下三种情况的含义,首先假设有参数化特征Queue,那它可以有如下三种定义。
(1) trait Queue[T] {}
这是非变情况。这种情况下,当类型B是类型A的子类型,则Queue[B]不可认为是Queue[A]的子类型或父类型,这种情况是和Java一样的。
(2) trait Queue[+T] {}
这是协变情况。这种情况下,当类型B是类型A的子类型,则Queue[B]也可以认为是Queue[A}的子类型,即Queue[B]可以泛化为Queue[A]。也就是被参数化类型的泛化方向与参数类型的方向是一致的,所以称为协变。
(3) trait Queue[-T] {}
这是逆变情况。这种情况下,当类型B是类型A的子类型,则Queue[A]反过来可以认为是Queue[B]的子类型。也就是被参数化类型的泛化方向与参数类型的方向是相反的,所以称为逆变。

  • 协变、逆变、非变总结

    • C[+T]:如果A是B的子类,那么C[A]是C[B]的子类。
    • C[-T]:如果A是B的子类,那么C[B]是C[A]的子类。
    • C[T]: 无论A和B是什么关系,C[A]和C[B]没有从属关系。
  • 举例
package cn.itcast.scala.enhance.covariance
/**
  Created by lishas on 2016/8/10.
 */
class Super
class Sub extends Super
//协变
class Temp1[+A](title: String)
//逆变
class Temp2[-A](title: String)
//非变
class Temp3[A](title: String)
object Covariance_demo{
  def main(args: Array[String]) {
    //支持协变 Temp1[Sub]还是Temp1[Super]的子类  
    val t1: Temp1[Super] = new Temp1[Sub]("hello scala!!!")  
    //支持逆变 Temp1[Super]是Temp1[Sub]的子类  
    val t2: Temp2[Sub] = new Temp2[Super]("hello scala!!!")  
    //支持非变 Temp3[Super]与Temp3[Sub]没有从属关系,如下代码会报错
    //val t3: Temp3[Sub] = new Temp3[Super]("hello scala!!!")
    println(t1.toString)
    println(t2.toString)
  }
}

8. Scala中的上下界

  • 上界、下界介绍
    在指定泛型类型时,有时需要界定泛型类型的范围,而不是接收任意类型。比如,要求某个泛型类型,必须是某个类的子类,这样在程序中就可以放心的调用父类的方法,程序才能正常的使用与运行。此时,就可以使用上下边界Bounds的特性;

Scala的上下边界特性允许泛型类型是某个类的子类,或者是某个类的父类

- U >: T  

这是类型下界的定义,也就是U必须是类型T的父类(或本身,自己也可以认为是自己的父类)。

- S <: T  

这是类型上界的定义,也就是S必须是类型T的子类(或本身,自己也可以认为是自己的子类)。

网友评论

登录后评论
0/500
评论
得失与否
+ 关注