scala概览

第1章 控制结构和函数

相关知识点

  • if语句。
  • while语句。
  • for语句(包括守卫和for推导式)
  • 函数(默认参数、带名参数、变长参数定义以及使用)
  • 过程(返回为Unit的函数)
  • lazy
  • 异常机制

几乎一切语法结构都是表达式

表达式:表达式是可以作右值的语句,一般语句后面都会有一个代表语句最终都值
语句:执行动作的代码序列,没有最终的值,不可作为右值。

备注:
scala表达式贯彻的很彻底,几乎一切都是表达式,相比于kotlin更彻底。

kotlin: image

scala: image

if表达式

val a1 = if (true) 1
val a2 = if(true) 1 else ()

说明:
else分支如果不写默认认为是else (),相当于返回Unit。

for循环

for(num <- 1 to  100){
      println(num)
    }

跳出for循环:方法1(鼓励使用)

for(num <- 1 to  100){
 if(num > 10) return
 println(num)
}

跳出for循环:方法2(不鼓励使用)

import scala.util.control.Breaks._

object HelloWorld {
  def main(args: Array[String]): Unit = {
    breakable {
      for (num <- 1 to 100) {
        if (num > 10) break
          println(num)
      }
    }
  }
}

备注:方法2是通过breaks对象中的break方法来实现的,控制权的转移是通过抛出和捕获异常来完成的,如果对时间要求很高,不建议采用这种方式。

for循环可以有多个生成器,每个生成器还可以添加守卫

for (i <- 1 to 5; from = i * 2; j <- 1 to from if j > i) println(s"i:$i,j:$j")

for推导式:有点类似于kotlin map函数

val list:IndexedSeq[String] = for(i <- 1 to 5) yield s"$i"

函数

scala函数不需要return+返回值,最后表达式的值即为函数返回值,这一点也佐证来scala贯彻到底一切都是表达式的概念。

变长参数

def main(args: Array[String]): Unit = {
val argList = for (i <- 1 to 5) yield i.toString
varargsFun(argList:_*)
}

def varargsFun(args: String*): Unit = {
    args.foreach { arg => println(arg) }
  }

延迟初始化

lazy val a:String = {
 println("first call")
 "hello world"

}

异常处理:和kotlin一样,都是无unchecked异常

try {
  //Try block
}
catch {
  case e: Exception => {
    //process exception
  }
}
finally {
  //finally block
}

第2章 数组相关操作

相关知识点

  • 若数组长度固定采用Array,否则采用ArrayBuff
  • 提供初始值不要使用new
  • 用()访问元素
  • 遍历数组
  • map
  • 和java数组相互转换
  • 多维数组

静态数组

val a = new Array[Int](10)
val b = Array[Int](10) //new可以省略
val c = Array(1,2) //不要添加new
c(0); c(1) //采用()访问数组元素

动态数组

基本操作

val arr1 = new ArrayBuffer[String]()
val arr2 = ArrayBuffer[String]()
arr1 += ("1","2","3")
arr2 ++= arr1 //注意是两个+号
arr2.trimEnd(2) //删除后两个元素,注意,如果不足的话可能会抛异常
arr2.trimStart(2)//删除前两个元素,注意,如果不足的话可能会抛异常
arr2.insert(1,"4","5","6")
arr2.remove(2) //删除下标为2的元素
arr2.remove(2,3) //删除元素时,如果数量不足,会抛异常,谨慎使用
for(i <- 0 to arr2.length){
  arr2.remove(i) //remove不要滥用,每次remove操作,都会导致数组元素移动
}

遍历数组

//静态数组
val c = Array(1,2)
for(e <- c){ println(e) }
for(i <- c.indices) { println(c(i)) }

//动态数组
val d = ArrayBuffer[Int]()
d += (1,2,3)
for (e <- d) { println(e) }
for (i <- d.indices) { println(d(i)) }

数组转换

val c = Array(1,2)
val d = for (e <- c if e % 2 == 0) yield e * 2

多维数组

val del: Array[Array[Int]] = Array.ofDim[Int](3, 4)
del(1)(2) = 9
val arr = new Array[Array[Int]](10) //这里的new不要省略,本质上还是一唯数组
arr(0) = new Array[Int](3)

第3章 映射和元组

基本知识点

  • 获取映射中的值
  • 更新映射中的值(包括添加、删除)
  • 迭代映射
  • 拉链操作

基本操作

//元组
val aa = "hello" -> 1
val bb = ("hello", 1)
//不可变Map
val map1 = Map[String, Int](("hello", 1), ("world", 2))
val map2 = Map[String, Int]("hello" -> 1, "world" -> 2)
map1("hello") = 1//该操作非法,不可变Map存储的各元组无法改变
//可变Map
val map3 = mutable.Map[String, Int](("hello", 1), ("world", 2))
val element1 = map3("hello") //这种方式要求key一定存在,否则会抛异常,安全的方式可以采用get
val element2 = map3.get("hello1")
map3("hello") = 111
map3 += ("111" -> 2)
map3 -= "111"
val map4 = map3 + ("hhh" -> 3)
val map5 = map3 - "hhh" //减号右操作数不必要求一定是左操作数中的key

拉链操作

val arr1 = Array[String]("11","22")
val arr2 = Array[String]("aa","bb","cc")
val map1 = arr2.zip(arr1) //输出:(aa,11) (bb,22)
val map2 = arr1.zip(arr2) //输出:(11,aa) (22,bb)

第4章 类

基本知识点

  • 字段和属性
  • 取值器/改值器
  • 对象私有字段
  • Bean属性
  • 辅助构造器/主构造器
  • 嵌套类

原始类

class HelloWorld {
  var name:String = _
}

编译生成java类

➜  scala scalac HelloWorld.scala
➜  scala javap -private HelloWorld 
Compiled from "HelloWorld.scala"
public class HelloWorld {
  private java.lang.String name;
  public java.lang.String name();
  public void name_$eq(java.lang.String);
  public HelloWorld();
}

说明:

scala自带getter/setter(属性),字段永远都是private,通过属性访问或者修改字段值,setter属性为name_=,getter属性为name,属性默认是public

Bean属性

说明:scala采用统一访问原则, getter/setter并没有遵循JavaBean规范,如果想要使用JavaBean那一套getter/setter,可以加上注解@BeanProperty。 import scala.beans.BeanProperty

class HelloWorld {
  @BeanProperty
  var name: String = _
}

编译文件:

public class HelloWorld {
  private java.lang.String name;
  public java.lang.String name();
  public void name_$eq(java.lang.String);
  public java.lang.String getName();
  public void setName(java.lang.String);
  public HelloWorld();
}

构造器

class HelloWorld (val name:String,val age:Int){

  private var degree:String = _

  //辅助构造器
  def this(degree:String) {
    this(name,age) //调用主构造器
    this.degree = degree
  }

  //辅助构造器
  def this(degree:Int){
    this(degree.toString) //调用辅助构造器
  }

}

说明:

scala构造器和kotlin一样,也分为主构造器和辅助构造器,辅助构造器一定要直接或者间接调用主构造器,主构造器中的参数可以带var/val,也可以不要,如果不要,该参数有没有在其他函数中用到,则该参数仅仅只做参数,否则该参数升级为private字段。

嵌套类

import scala.collection.mutable.ArrayBuffer

class Outer {
  class Inner

  val members:ArrayBuffer[Inner] = new ArrayBuffer[Inner]()
  def addMember(member:Inner): Unit ={
    members += member
  }
}

object HelloWorld {
  def main(varargs: Array[String]): Unit = {
    val outer1 = new Outer
    val outer2 = new Outer
    val inner1 = new outer1.Inner
    val inner2 = new outer2.Inner
    outer1.addMember(inner1)
    outer1.addMember(inner2) //编译失败
  }
}

说明:

scala内部类属于外部类实例所独有,每个实例的内部类都是不同类型的。

第5章 对象

基本知识点

  • 单例对象/伴生对象
  • 扩展类或特质的对象
  • apply方法
  • 应用程序对象
  • 枚举

伴生对象

1:scala没有静态字段或静态方法,这些都要通过伴生对象来实现。
2:类和伴生对象可以互相访问私有特性,但是他们必须在同一个源文件中。
3:类的伴生对象可以被访问,但是并不在作用域当中,举例来说,可以调用Account.hello(),不能直接调用hello()。
4:object可以继承(extend)类。

apply方法

class HelloWorld (private val fieldA:String,private val fieldB:Int) {
  println("main constructor")
}

object HelloWorld {
  def apply(fieldA:String,fieldB:Int): Unit = {
    println("this is object of HelloWorld")
  }
}

object Main {
  def main(args: Array[String]): Unit = {
    val hello1 = new HelloWorld("hello1",1) //output:main constructor
    val hello2 = HelloWorld("hello2",2) //output:this is object of HelloWorld
  }
}

说明:

不使用new时调用的时apply方法,使用new时调用的才是构造器。

应用程序对象

class Main extends App{
  println("hello world")
  for (arg <- args){
    println(arg)
  }
}

说明:

效果等同于在object写main函数,args为传递给main函数对参数。

枚举类

说明:

scala并没有提供枚举类,不过标准类库提供类一个实现枚举类的
助手类,具体实现如下。注意,EnumExample并不是枚举类对类型
,真实对类型是EnumExample.Value,内部类Value提供了两个参数
,ID和名称,可以都不传或根据需要传,ID默认累加,名称默认是字段名。

object EnumExample extends Enumeration {
//  val RED,YELLOW,BLUE = Value
  val RED = Value
  val YELLOW = Value(1)
  val BLUE = Value(2,"blue")
}

第6章 包和引入

基本知识点

  • 语法规则
  • 作用域规则
  • 串联式包语句
  • 文件顶部标记法
  • 包对象
  • 包可见性
  • 任何地方都可以声明引入
  • 重命名和隐藏方法
  • 隐式引入
  •  

语法规则

方法1:
package com.helios.scala
方法2:
package com {
  package helios {
    package Scala {
      object ScalaObject {
        def hello(): Unit = _
      }
    }
  }
}

作用域规则

这部分没必要太过关注,一般用处不大,在使用到的时候很多IDE都会自动import相关包。

串联包语句

package com.helios.scala {
  object HelloWorld
}

包对象

包可以包含对象和类,但是不能包含函数或者对象度定义, 不像kotlin可以定义top-level function或者top-level variable,但是scala包可以包含一个包对象。

package com.helios.scala {
  object HelloWorld{
    def main(args: Array[String]): Unit = {
      println(Scala.defaultName)
    }
  }

  package object Scala {
    val defaultName = "hello world"
  }
}

包可见性

package com.helios.scala {

  private[scala] object HelloWorld {
    def main(args: Array[String]): Unit = _
  }

  protected[helios] class HelloWorld

}

引入

说明:

1:任何地方都可以import包。
2:支持通配符_。
3:支持重命名和隐藏方法。
4:隐式引入,如果多个import引入都包有重名,则最后import引入会覆盖之前引入。

object China {
def main(args: Array[String]): Unit = {
import com.helios.scala._ // _表示通配符,相当于java中的*
import com.helios.scala.HelloWorld._ //import HelloWorld的字段和属性
import com.helios.scala.{HelloWorld} //使用选取器,固定引入
import com.helios.scala.{HelloWorld => NiHao} //import 重命名
import com.helios.scala. { HelloWorld => _} //隐藏scala包下HelloWorld

} }

第7章 继承

基本知识点

  • 扩展类
  • 重写方法
  • 类型检查和转换
  • 超类的构造
  • 重写字段
  • 匿名子类
  • 抽象类
  • 抽象字段
  • 构造顺序和提前定义
//重写方法,字段都是private,无法重写,不过可以采用别的方式重写字段,例如通过取值器或者改值器。

class Father(tempName: String) {
  var name: String = tempName

  def hi() = println(s"hi,$name")
}

class Child extends Father("child") {
  override def hi(): Unit = super.hi() //重写方法
}

类型检查和转化

isInstanceOf:用于类型判断。
asInstanceOf:用于类型转化,可能会抛异常。
classOf:相当于java中的.class。

object Main extends App {

  val father = new Child
  if(father.isInstanceOf[Child] || father == classOf[Child]){
    val child = father.asInstanceOf[Child]
  }
}

匿名子类

//匿名子类
val student = new Father("student") {
    println("I'm a student")
}

构造顺序和提前定义

class Father{
  val size:Int = 10
  val members:Array[Int] = new Array[Int](size)
}

class Child extends Father {
  override val size:Int = 3
}

object Main extends App {
  val child = new Child
  println(child.members.size) //output:0
}

说明:

1:child调用父类构造函数。
2:Father初始化size字段,size=10。
3:Father初始化member字段,调用子类size取值器,子类此时size=0。
4:最后members初始化数组长度即为0。

改进措施:提前初始化子类字段

class Father{
  val size:Int = 10
  val members:Array[Int] = new Array[Int](size)
}

class Child extends {
  override val size:Int = 3
} with Father

object Main extends App {
  val child = new Child
  println(child.members.size) //output:3
}

第8章 文件和正则表达式

相关知识点

  • 读取行。
  • 读取字符。
  • 读取词法单元和数字。
  • 从URL或其他源读取。
  • 序列化
  • 进程控制
  • 正则表达式

代码示例:

package com.helios.scala

import scala.io.Source

object Main extends App {
  val source = Source.fromFile("test.txt", "UTF-8")
  for (line <- source.getLines()) println(line) //读取行
  for (ch <- source) println(ch) //读取字符
  val tokens = source.mkString.split("\\S+") //读取词法单元
  val urlSource = Source.fromURL("www.baidu.com","UTF-8") //输入源是URL
  val strSource = Source.fromString("hello world") //输入源是String
  val stdInSource = Source.stdin //输入源是标准输入

}

序列化:

@SerialVersionUID(11L) class Person extends Serializable

进程控制:

package com.helios.scala

import java.io.File
import sys.process._

object Main extends App {
  "pwd" ! //output:/Users/chenak/IdeaProjects/scala-demo
  val a = "pwd" !! ; //这个分号要加,否则会认为下面是!!的参数
  println(s"a:${a}")
  "ls -al ." #| "grep src" ! ; //管道
  "ls -al ." #> new File("test.txt") !; //重定向,覆盖
  "ls -al ." #>>  new File("test.txt") !; //重定向,追加
  "grep src" #< new File("test.txt") ! ;

}

正则表达式[组]:

package com.helios.scala

object Main extends App {
  val str = "123 china"
  val pattern = "([0-9]+) ([a-zA-Z]+)".r //正则表达式组
  val pattern(digit,alpha) = str //提取器
  println(digit) //output:123
  println(alpha) //output:china

  val arr = "[0-9]+".r.findAllIn(str)
  for (a <- arr) println(a)
}

第9章 特质

1:特质被当作接口来使用。
2:特质中可以有抽象方法,也可以具体实现某个方法,不需要关键字修饰,不实现默认就是抽象方法。
3:特质可以被混入多个。

trait Trait1 {
  def fun1
  def fun2(): String
  def fun3() = println("I'm function 3 of Trait1")
}

trait Trait2 {
  def fun3() = println("I'm function 3 of Trait2")
}

class Test extends Trait1 with Trait2 {
  override def fun1: Unit = {}
  override def fun2(): String = ""
  override def fun3(): Unit = println("I'm function 3 of Test") //必须要实现,否则无法识别
}

带有特质的对象

trait Test{def test() = println("test")}
class HelloWorld 
  
val a = new HelloWorld with Test
a.test()

叠加在一起的特质

trait FatherTrait {
    def test:Unit = println("father trait")
  }

  trait Child1Trait extends FatherTrait {
    override def test: Unit = {
      println(classOf[Child1Trait].getSimpleName)
      super.test
    }
  }

  trait Child2Trait extends FatherTrait {
    override def test: Unit = {
      println(classOf[Child2Trait].getSimpleName)
      super.test
    }
  }

  class Child3 extends Child1Trait with Child2Trait {
    override def test: Unit = super.test
  }

  new Child3().test
  
  输出:
  Child2Trait
  Child1Trait
  father trait

注意:

super.method调用的顺序是最后一个混入的特质相对应的方法开始,如果该特质还有super.method方法,则调用倒数第二个特质的方法,而不是父特质的方法。‘

特质构造顺序:

1:首先调用超类构造器。
2:特质在超类构造器之后,子类构造器之前执行。
3:特质从左到右被构造。
4:每个特质中,父特质首先被构造。
5:如果多个特质共有一个父特质,而那个父特质已经被构造,则不会被重复构造。
6:所有特质构造完毕,子类开始被构造。

注意:
特质只有一个无参构造器。

第10章 操作符

apply方法和update方法

val map = new mutable.HashMap[Int,String]()
map(1) = "hello world" //调用update方法
map(1) //调用apply方法

unapply方法(提取器)

object Test {
    def unapply(arg: Test): Option[Int] = if(arg == null) Some(0) else Some(1)
  }

val Test(value) = Test //value = 1

第11章 高阶函数

相关知识点:

  • 作为值的函数
  • 匿名函数
  • 带函数参数的函数
  • 参数(类型)推断
  • 闭包
  • SAM转幻
  • 柯里化
  • 控制抽象

基本用法

val triple = (x: Double) => x * x * x   
def triple (a:Double) = a*a*a
val a = triple _ //_指示这是一个函数
def test(fn:(Double) => Double)= fn
test(a)
test(triple _ )

闭包

闭包由代码和代码用到的任何非局部变量组成,具体实现如下:

def mul(a:Int) = (b:Int) => a*b
mul(2)(3) //output:6

SAM

implicit def makeAction(fn:() => Unit) = new Test {
    override def test: Unit = fn()
} //控制SAM语法的开启,makeAction名称可以随意定

abstract class Test {
    def test
}

def fun(a:Test): Unit = a.test

fun(() => println("hello test")) //output:hello test
fun(new Test {
    override def test: Unit = println("inner test")
}) //output inner test

柯里化

柯里化是将原来带有两个参数的函数编程一个参数的函数的过程,新的函数接受原有函数第二个参数,示例如下:

def fun1(a: Int) = (b: Int) => a * b
def fun2(a: Int)(b: Int) = a * b

控制抽象

def test1(fn:() => Unit): Unit = fn()
test1{() =>} //你不得不用() =>
def test2(fn: =>Unit) = fn()
test2{} //无须使用() =>

第12章 集合

相关知识点

  • 主要集合特质
  • 可变与不可变集合
  • 序列
  • 列表
  • 化简、折叠和扫描
  • 拉链操作
  • 懒视图
  • 线性安全的集合
  • 并行集合
  •  

主要的集合特质

Iterable

Seq

Set

Map

IndexedSeq

SortedSet

SortedMap

说明:

Seq:一组先后次序的序列。
Set:一组没有先后次序的元素的集合。
Map:一组对偶集合。

统一创建原则

val seq = Array[String]("1","2","3")
val set = Set("1","2","3")
val map = Map[String,Int]("1" -> 1,"2" -> 2,"3" -> 3)

说明:

Scala对于每一种集合(这里的集合不是针对Set,而是Collection)特质,都有一个带有apply方法都伴随对象,用以构建该集合中都示例,这种统一构建集合的方法称之为“统一创建原则”。

可变和不可变集合

  1. 对于scala中都每一种集合特质,几乎都提供了可变和不可变两种类型。

序列

不可变序列

Seq

IndexedSeq

Vector

Range

List

Stack

Stream

Queue

可变序列

Seq

IndexedSeq

ArrayBuff

Stack

LinkedList

DoublleLinkedList

Queue

PriorityQueue

列表

class Element
class List {
  val head:Element = _
  val tail:List = _
}

说明:

  1. 列表都数据结构类似于上述代码。(笔者用以解释简单缩写,如有需要请查阅源码)
  2. 列表有head和tail两个元素,列表是有head元素和tail列表组成。举例来说: List(1,2,3) => 1::List(2,3)

集合

  1. 一般情况下,集合是无序的概念,也并不会记录元素插入的顺序。
  2. 集合是以哈希集实现的,其元素根据hashCode组织。
  3. 如果想要记录集合插入顺序,可以使用LinkedHashSet,该特质维护一个链表来实现。
  4. 集合也可以存储为有序,可以采用SortedSet,该特质采用红黑树实现。
  5. |:并集运算符(++)。
  6. &:交集运算符。
  7. &~:差集运算符(--)。
  8.  

用以添加或是去除元素的运算符

image

折叠

image说明:

  1. map函数只是将一元函数应用到集合所有元素,而折叠操作则是将二元函数应用到集合所有元素。
  2. reduceLeft/reduceRight。
  3. foldLeft/foldRight(init)(op)。
val list = List(1, 2, 3, 4, 5)
val aa = list.reduceLeft { (a, b) => a - b } //等价于:1-2-3-4-5
val bb = list.foldLeft(15)(_-_) //等价于:15-1-2-3-4-5
val cc = (15 /: list)(_-_) //bb等另一种写法

拉链

val list1 = List(1, 2, 3, 4, 5)
val list2 = List("11", "22", "33")
val zip1: List[(Int, String)] = list1 zip list2 //相当于:List(1 -> "11", 2 -> "22", 3 -> "33")
val zip2 = list1 zipAll(list2,"44","55") //相当于:List(1 -> "11", 2 -> "22", 3 -> "33",4 -> "44",5 -> "55")
val zip3 = list1 zipAll(list2,"44") //元素个数不足以拉链时编译就会报错的
val zip4 = list2.zipWithIndex //该集合和索引集拉链,相当于:List("11" -> 0,"22" -> 1,"33" -> 2)

迭代器

说明:

scala提供了很多方法用来对集合元素进行遍历,不过对于数据量较多的集合来说,
对于不需要将所有元素一下子加载到内存的场景来说,有时候也需要用到迭代器,
迭代器的操作和java、kotlin类似,在此不做赘述。

说明:

1:迭代器针对于集合来说是一种懒的计算,只有在需要元素的时候才会加载该元素,然后迭代器并不是不可变的,在遍历过程中,需要不停的修改迭代器的指向。
2:相对于迭代器而言,流提供一个不可变的替代品,只对第一个元素做计算,其他都是懒计算。

  def fn(n: Int): Stream[Int] = {
    n #:: fn(n + 1)
  }

  val n10 = fn(10) //等价于:Stream(10,?)
  val list = n10.take(3).force //取前三个计算,输出等价于Stream(10,11,12),切记直接force会OOM

懒视图

说明:

1:map运算会产生一个新的集合,可以采用懒试图,提供一种相对于流更懒的一种方式。

val list = List(1, 2, 3).view.map { e => e * e } //并没有对元素做平方运算,该方式是一种懒的计算方式,可以用force强制运算

线程安全的集合

scala提供几个特质用来提供集合线性安全特性。

  • SyncronizedMap
  • SyncronizedBuffer
  • SyncronizedPriorityQueue
  • SyncronizedQueue
  • SyncronizedSet
  • SyncronizedStack

备注:不要滥用。

并行集合

scala提供对集合的并行计算

for (e <- (1 to 10).par) {
    print(e + " ") //output:3 8 6 4 5 7 1 2 9 10 
  }

备注:不要滥用。

第13章 模式匹配和样式类

说明:

  1. 模式匹配可以设置守卫。
  2. 模式匹配中case可以直接跟变量。
  3. 可以对类型进行模式匹配。
  4. 可以匹配列表、数组和元祖。
  5. 模式匹配的本质是偏函数(一组case语句),如果匹配不上会跑异常。
val a = 10L

  a match {
    case 1 => 1
    case b if b > 10 => b - 10
  }

  val arr = Array[Int](1,2)
  arr match  {
    case Array(_) => 1 //匹配一个元素
    case Array(aa,bb) => aa + bb //匹配两个元素的数组,并赋值
  }
  
  val list = List(1,2,3)
  list match {
    case 1 :: _ => "start with 1"
    case 1 :: 2 :: _ => "1 and 2 and others"
  }

提取器

提取器是apply的逆向操作,scala提供unapply和unapplySeq两种提取器。 unapply用来提取固定数量的对象,而unaplySeq则用来提取一个序列。

第14章 类型参数

知识要点:

1:泛型类。
2:泛型函数。
3:类型变量界定。
4:视图界定。
5:上下文界定。
6:多重界定。
7:类型约束。
8:逆变和协变。
9:对象不能泛型。
10:类型通配符。


  class Pair[T] (val first:T,val second:T) //泛型类
  def fn[T](a:T): Unit = {} //泛型函数
  class Upper
  class Lower extends Upper
  class Test1[T >: Lower <: Upper] //类型变量界定,多重界定
  class Test2[T <% Comparable[T]] //视图界定
  class Order[T]
  class Test3[T : Order] //上下文界定
  class Test4[T] {
    def fn1(a:T)(implicit ev: T =:= Upper): Unit = {} //类型约束,T是否等于Upper
    def fn2(b:T)(implicit ev: T <:< Upper):Unit = {} //类型约束,T是否是Upper的子类型
  }

  class Test5[+T] //协变
  class Test6[-T] //逆变
  class Test7[ _ >: Lower] //类型通配符

第16章 隐士转换和隐士参数

相关知识点:

  • 隐式转换
  • 隐式转换规则
  • 隐式参数
  • 上下文界定
  • 视图界定
  • 类型证明

隐士转换函数

所谓隐士转换函数指的是那些以implicit关键字声明的带有单个参
数的函数,将值从一种类型自动转换成另一种类型。

object Test extends App {

  class TestFraction(val left: Int, val right: Int)

  implicit def fraction2Double(fraction: TestFraction): Double = {
    if (fraction.right == 0) 0
    else fraction.left.toDouble / fraction.right
  }


  val result = 3 * new TestFraction(2, 3) //value:2.0

  class Hello {
    def hello = {
      println("hello")
    }
  }

  implicit def str2Hello(string: String): Hello = {
    new Hello
  }

  "hello".hello //String类型直接调用hello方法
}

隐士转换规则

1:如果不需要隐士转换即可通过编译,不调用隐士转换。
2:不支持多次转换,例如convert1(convert2(arg))。
3:转换存在二义性(同一个值可以转换不同类型)。

隐士参数

函数或方法可以implicit关键字修饰其参数,被implicit修饰的参
数当调用的时候未传入该参数值,则寻找默认值(隐士参数)。

/**
  * 隐士参数
  */
object TestArgs extends App {

  class Hello(val name: String)

  implicit def hello = new Hello("hello world")

  def test(implicit hello: Hello) = {
    println(hello.name)
  }

  test //output:hello world

  implicit def test1Fn: () => Hello = {
    () => new Hello("test1 function")
  }

  def test1(implicit fn: () => Hello) = {
    println(fn().name)
  }

  test1 //output: test1 function
}

上下文界定

形式:T:M,要求在当前作用域内存在一个M[T]类型的隐士值。

/**
  * 上下文界定
  */
object TestContext extends App {

  class TestContext[T](val member: T)
  class Test[T: TestContext](val field: T)

  implicit val testContext = new TestContext(1)
  new Test(1) //编译通过
//  new Test(1.0) //编译失败,上下文不存在类型为TextContext[Double]的字段
  println(implicitly[TestContext[Int]].member) //打印出隐士值member字段 output:1

}

类型证明

说明:

类型证明本质上采用的也是隐士类型转换。

@implicitNotFound注解

image说明:

@implicitNotFound 注解告诉编译器在不能构造出带有该注解的类型的参数时给出错误提示。

CanBuildFrom解读

说明:

感兴趣可以深入研究一下。

第17章 定界延续

略。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值