Scala学习总结
1、为什么学习Scala?
(1)Java++
【1】基于JVM,和JAVA完全兼容,可以跨平台,移植性好,方便垃圾回收
【2】比JAVA更面向对象
【3】函数式编程语言
(2)更适合大数据处理
【1】对集合类型数据处理有非常好的支持
【2】Spark的底层用Scala编写
2、Scala和Java的关系
3、语言特点
【1】面向对象函数编程
【2】和JAVA无缝对接
【3】更加简洁高效
4、Object
关键字,声明一个单例对象(伴生对象)类和对象的关系:
类是一个抽象概念,它的实例化就是对象
5、main方法
从为外部可以直接调用执行的方法
def 方法名称(参数名称: 参数类型): 返回值类型 = {方法体}
6、Scala的伴生类
(1)在JAVA中,如果定义的一个类中有全局属性(例如学校名字),是不需要变动且所有对象共有的,可以使用static对类的属性进行修饰,使用时也是使用类名进行操作,但这么做会导致操作的是类而不是对象,因此不够严谨。
(2)在Scala中,为了能够百分百面向对象,足够严谨,故删除了static,并引入了伴生对象来进行全局属性的操作,伴生对象有三个要求:
【1】使用Object定义
【2】名字和类要相同
【3】在同一个文件中
通过使用伴生对象,在定义的类中便只需要添加需要频繁更改的属性,而全局属性则可以在伴生对象中使用,且操作时使用伴生对象
(3)事实上static并没有消失,在文件中有两个类对象,一个是原类定义,代表入口类;另一个是类名+$定义的类,表示伴生对象,在该类中,会构造方法私有化,然后定义一个静态对象MODULE$,并用static定义一个新的对象实例,代表至此一份,最后将这个新对象赋值给MODULE$
7、代码规范
(1)使用一次 tab 操作,实现缩进,默认整体向右边移动,用 shift+tab 整体向左移
(2)或者使用 ctrl + alt + L 来进行格式化
(3)运算符两边习惯性各加一个空格。比如:2 + 4 5。
(4)一行最长不超过 80 个字符,超过的请使用换行展示,尽量保持格式优雅
(5)文档注释只有再一个方法或者类前使用/** /
8、变量和常量
var 变量名 [: 变量类型] = 初始值
val 常量名 [:常量类型] = 初始值
能用常量的地方不用变量
(1)声明变量时,类型可以省略,编译器自动推导,即类型推导
(2)类型确定后,就不能修改,说明 Scala 是强数据类型语言。
(3)变量声明时,必须要有初始值
(4)在声明/定义一个变量时,可以使用 var 或者 val 来修饰,var 修饰的变量可改变,val 修饰的变量不可改。
(5)var 修饰的对象引用可以改变,val 修饰的对象则不可改变,但对象的状态(值)却是可以改变的。(比如:自定义对象、数组、集合等等),对象不能变化,但对象内属性的值可以改
9、命名规则
(1)以字母或者下划线开头,后接字母、数字、下划线
(2)以操作符开头,且只包含操作符(+ - * / # !等)
(3)用反引号 包括的任意字符串,即使是 Scala 关键字(39 个)也可以
10、字符串输出
(1)字符串,通过+号连接
(2)printf 用法:字符串,通过%传值。
(3)字符串模板(插值字符串):通过$获取变量值,使用s". "实现,这种方式可以在输出字符串中加入变量
s"小明${age}岁了"
(4)如果需要对(3)中数值变量的输出设置规则,则需要改用f". ",并在${}%后面添加规则。如果使用
raw". ",则会无视规则直接连同变量一起输出
(5)如果输出的字符串比较长可以使用三引号""". """,stripMargin
11、输入
StdIn.readLine()、StdIn.readShort()、StdIn.readDouble()
12、数据类型
13、Unit、Null以及Nothing类型
(1)Unit:某个方法或者函数的返回值为空,表示空值
(2)Null:空引用
(3)Nothing:不考虑是空值还是空引用,只需要知道是什么都没有,用于一些方法或函数完全不会正常返回(抛出异常时),它是所有类型的子类
14、自动类型转换
在scala中同java一样,如果没有匹配到合适的类型,就会自动转换成更高精度的类型进行匹配。
byte(1) -> short(2) -> char(2) -> int(4)
附注:
(1)自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有数据转换成精度大的那种数据类型,然后再进行计算。
(2)把精度大的数值类型赋值给精度小的数值类型时,就会报错,反之就会进行自动类型转换。
(3)(byte,short)和 char 之间不会相互自动转换。
(4)byte,short,char 他们三者可以计算,在计算时首先转换为 int 类型。
15、强制类型转换
(1)将数据由高精度转换为低精度,就需要使用到强制转换
(2)强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级
16、函数式编程
解决问题时,将问题分解成一个一个的步骤,将每个步骤进行封装(函数),通过调用这些封装好的步骤,解决问题。
例如:请求->用户名、密码->连接 JDBC->读取数据库
Scala 语言是一个完全函数式编程语言。万物皆函数。
函数的本质:函数可以当做一个值进行传递
17、函数基本语法
18、函数和方法的区别
(1)核心概念
【1】为完成某一功能的程序语句的集合,称为函数。
【2】类中的函数称之方法。
(2)案例实操
【1】Scala 语言可以在任何的语法结构中声明任何的语法
【2】函数没有重载和重写的概念;方法可以进行重载和重写
【3】Scala 中函数可以嵌套定义
方法是对象编程中的一个概念,它是类中的一员;
函数是过程编程中的一个概念,是一个独立的代码块,用于实现某种功能。
19、函数参数
(1)可变参数
def f1(str: String*): Unit f1("aaa") f1("aaa","bbb","ccc")
可以传入多个参数,会作为集合传入
(2)如果参数列表中存在多个参数,那么可变参数一般放置在最后
def f2(str1: String,str2: String*)
(3)参数默认值,一般将有默认值的参数放置在参数列表的后面
def f3(name: String = "atguigu")
(4)带名参数
def f4(name:String,age: Int) f4(age = 23,name = "bbb")
20、函数至简原则
(1)至简原则细节
【1】return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
【2】如果函数体只有一行代码,可以省略花括号
【3】返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
【4】如果有 return,则不能省略返回值类型,必须指定
【5】如果函数明确声明 unit,那么即使函数体中使用 return 关键字也不起作用
【6】Scala 如果期望是无返回值类型,可以省略等号
【7】如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
【8】如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
【9】如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
def f9(name:String):Unit = {println(name)} val fun = (name:String) => {pringln(name)} fun("a")
21、匿名函数
(1)说明
没有名字的函数就是匿名函数。
(x:Int)=>{函数体} x:表示输入参数类型;Int:表示输入参数类型;函数体:表示具体代码逻辑
(2)化简案例实操
需求 1:传递的函数有一个参数传递匿名函数至简原则:
【1】参数的类型可以省略,会根据形参进行自动的推导
f((name) => {println(name)})
【2】类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参数超过 1 的永远不能省略圆括号。
f(name => {println(name)})
【3】匿名函数如果只有一行,则大括号也可以省略
f(name => println(name))
【4】如果参数只出现一次,则参数省略且后面参数可以用代替
f(println())
【5】如果可以推断出当前传入的println是一个函数体,而不是调用语句,可以直接省略下划线
f(println)
22、高级函数特点
(1)函数可以作为值进行传递,空一格加个下划线,代表一个函数整体
def f(n: Int) val f1 = f _
val f2: Int => Int = f
(2)函数可以作为参数传递
def dualEval(op: (Int, Int)=>Int,a: Int,b: Int): Int def add(a:Int, b:Int):Int dualEval(add,12,35) dualEval(+,12,35)
(3)函数可以作为函数的返回值返回
def f5(a: Int):Unit ={
def f6(a:Int):Unit =
println(",,,")
f6
}
println(f5()(25))
23、闭包
(1)说明
闭包:如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和他所处的环境,称为闭包
在stack中,每个方法都会单独划一份区域(栈针)用于存储,方法调用结束后释放。闭包就是会将已经调用
过的方法的参数打包存入需要使用但还未调用的方法中(heap堆内存中存放对象实例),这样就可以延长这些参数的生命周期
(2)函数柯里化:把一个参数列表的多个参数,变成多个参数列表。
24、惰性加载
当函数返回值被声明为 lazy 时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数我们称之为惰性函数。
25、包
(1)命名规则
com.公司名.项目名.业务模块名
(2)包语句
【1】用.来分隔表示层级关系
com.atguigu.scala
【2】嵌套风格表示层级关系
package com{
package atguigu{
package scala{
}
}
}
【3】包对象
在 Scala 中可以为每个包定义一个同名的包对象,定义在包对象中的成员,作为其对应包下所有 class 和
object 的共享变量,可以被直接访问。
需要注意的是定义的包对象只能够在所在包的位置进行引用,在其他包的位置是无法引用的包对象声明和包的定义必须在同一层级(同一包)中
【4】导包说明
通配符导入:import java.util._ //所有包
导入相同包的多个类:导入相同包的多个类:
import java.util.{HashSet, ArrayList}
26、类和对象
(1)Scala 语法中,类并不声明为 public,所有这些类都具有公有可见性(即默认就是 public),手动加上反而报错
(2)一个 Scala 源文件可以包含多个类
(3)@BeanProperty 能够自动生成java的get/set方法
27、访问权限调整
(1)protected 为受保护权限,Scala 中受保护权限比 Java 中更严格,同类、子类可以访问,同包无法访问。
(2)private[包名] 增加包访问权限,包名下的其他类也可以使用
(3)private 为私有权限,只在类的内部和伴生对象中可用。
28、构造器
(1)主构造器
class 类名(形参列表){
//主构造器,只有一个,必须有
(2)辅助构造器
def this(形参列表){
//辅助构造器,可有多个,要调用主构造器
说明:
【1】辅助构造器,函数的名称 this,可以有多个,编译器通过参数的个数及类型来区分。
【2】辅助构造方法不能直接构建对象,必须直接或者间接(嵌套)调用主构造方法。
【3】构造器调用其他另外的构造器,要求被调用构造器必须提前声明。
(3)主构造器传参
Scala 类的主构造器函数的形参包括三种类型:未用任何修饰、var 修饰、val 修饰
【1】未用任何修饰符修饰,这个参数就是一个局部变量
【2】var 修饰参数,作为类的成员属性使用,可以修改
【3】val 修饰参数,作为类只读属性使用,不能修改
29、继承
- 子类继承父类的属性和方法
- 继承的调用顺序:父类构造器->子类构造器
30、多态
一种接口有多种不同的实现方式 运行时才会绑定方法(动态绑定)
Scala 中属性和方法都是动态绑定,而 Java 中只有方法为动态绑定。(即scala属性内容也会随着继承发生变化,而java的属性不会)
在scala中属性和方法若和父类中名字相同要用overwrite进行重写
31、抽象属性和抽象方法
(1)定义抽象类:abstract class Person{}
//通过 abstract 关键字标记抽象类
(2)定义抽象属性:val|var name:String
//一个属性没有初始化,就是抽象属性
(3)定义抽象方法:def hello():String
//只声明而没有实现的方法,就是抽象方法
32、继承&重写
(1)如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明为抽象类
(2)重写非抽象方法需要用 override 修饰,重写抽象方法则可以不加 override。
(3)子类中调用父类的方法使用 super 关键字
(4)子类对抽象属性进行实现,父类抽象属性可以用 var 修饰;
子类对非抽象属性重写,父类非抽象属性只支持 val 类型,而不支持 var。因为 var 修饰的为可变变量,子类继承之后就可以直接使用,没有必要重写
33、单利对象(伴生对象)
Scala语言是完全面向对象的语言,所以并没有静态的操作(即在Scala中没有静态的概念)。但是为了能够和Java语言交互(因为Java中有静态概念),就产生了一种特殊的对象来模拟类对象,该对象为单例对象。若单例对象名与类名一致,则称该单例对象这个类的伴生对象,这个类的所有“静态”内容都可以放置在它的伴生对象中声明。
说明:
(1)单例对象采用 object 关键字声明
(2)单例对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。
(3)单例对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问。
(4)伴生类和伴生对象的名字必须完全一样且放在同一个文件内
apply方法:
(1)通过伴生对象的 apply 方法,实现不使用 new 方法创建对象。
(2)如果想让主构造器变成私有的,可以在()之前加上 private。
(3)apply 方法可以重载。
(4)Scala 中 obj(arg)的语句实际是在调用该对象的 apply 方法,即 obj.apply(arg)。用以统一面向对象编程和函数式编程的风格。
(5)当使用 new 关键字构建对象时,调用的其实是类的构造方法,当直接使用类名构建对象时,调用的其实时伴生对象的 apply 方法。
(6)调用伴生对象的apply方法时可以省去.apply直接写括号
34、特质(Trait)
Scala 语言中,采用特质 trait(特征)来代替接口的概念,也就是说,多个类具有相同的特质(特征)时,就可以将这个特质(特征)独立出来,采用关键字 trait 声明。
Scala 中的 trait 中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入(mixin)多个特质。这种感觉类似于 Java 中的抽象类。
Scala 引入 trait 特征,第一可以替代 Java 的接口,第二个也是对单继承机制的一种补充。
(1)基本语法
trait 特质名 { trait 主体
}
没有父类:class 类名 extends 特质 1 with 特质 2 with 特质 3 …
有父类:class 类名 extends 父类 with 特质 1 with 特质 2 with 特质 3…
(2)说明
【1】若特质中的属性和父类中的属性冲突,则需要进行重写
【2】类和特质的关系:使用继承的关系。
【3】当一个类去继承特质时,第一个连接词是 extends,后面是 with。
【4】如果一个类在同时继承特质和父类时,应当把父类写在 extends 后。
(3)动态混入
在对象需要使用的时候才会额外添加该特质,即在声明一个新对象实例时再with特质并重写方法
(4)特质叠加
当类、多个特质被继承时,若他们内部都有同名的方法,则在调用时会调用最后继承的特质(从后往前叠加),父类和叠加特质是平等的。
如果需要指定哪个特质进行调用,可使用.super[]进行调用
(5)特质和抽象类的区别
【1】优先使用特质。一个类扩展多个特质是很方便的,但却只能扩展一个抽象类。
【2】如果你需要构造函数参数,使用抽象类。因为抽象类可以定义带参数的构造函数,而特质不行(有无参构造)。
(6)特质自身类型
当一个特质想引用一个类的属性但又不想直接继承时,可以采用 _: 类名 => 的方式获取类中的属性进行操作
(7)类型的检查和转换
【1】obj.isInstanceOf[T]:判断 obj 是不是 T 类型。
【2】obj.asInstanceOf[T]:将 obj 强转成 T 类型
【3】classOf 获取对象的类名。
(8)枚举类和应用类
【1】枚举类:需要继承 Enumeration
写入的属性以键值对的形式出现
【2】应用类:需要继承 App
App类继承后自带main函数可直接执行
35、集合
(1)Scala 的集合有三大类:序列 Seq、集 Set、映射 Map,所有的集合都扩展自Iterable特质。
(2)对于几乎所有的集合类,Scala 都同时提供了可变和不可变的版本,分别位于以下两个包
【1】不可变集合:scala.collection.immutable
【2】可变集合: scala.collection.mutable
(3)Scala 不可变集合,就是指该集合对象不可修改,每次修改就会返回一个新对象,而不会对原对象进行修改。类似于 java 中的 String 对象
(4)可变集合,就是这个集合可以直接对原对象进行修改,而不会返回新的对象。类似于 java 中
StringBuilder 对象
建议:在操作集合的时候,不可变用符号,可变用方法
36、不可变数组
定义:val arr1 = new ArrayInt
(1)new 是关键字
(2)[Int]是指定可以存放的数据类型,如果希望存放任意数据类型,则指定 Any
(3)(10),表示数组的大小,确定后就不可以变化
(4)添加元素: val ints: Array[Int] = arr01 :+ 5
37、可变数组
定义:val arr01 = ArrayBufferAny
(1)[Any]存放任意数据类型
(2)(3, 2, 5)初始化好的三个元素
(3)ArrayBuffer 需要引入 scala.collection.mutable.ArrayBuffer
(4)可变数组由于调用的是IterableLike.toString方法,因此在打印输出时不会打印出地址。但是不可变数组调用的是java的toString,因此无法做到
(5)添加元素
【1】追加数据
arr01.+=(4)
使用:+需要将结果赋值给一个新的数组,地址不同
但是+=则不需要重新赋值,它赋值后的结果地址相同
【2】向数组最后追加数据,更推荐
arr01.append(5,6)
【3】向指定位置插入数据
arr01.insert(0,7,8)
(6)转换
arr1.toBuffer //不可变数组转可变数组
arr2.toArray //可变数组转不可变数组
【1】arr2.toArray 返回结果才是一个不可变数组,arr2 本身没有变化
【2】arr1.toBuffer 返回结果才是一个可变数组,arr1 本身没有变
38、多维数组
(1)定义
val arr = Array.ofDim [ Double ] (3,4)
说明:二维数组中有三个一维数组,每个一维数组中有四个元素
Array[Array[Int]] = Array.ofDim[Int] (2,3)
39、列表
(1)不可变列表(不可改变但可添加)
【1】创建
val list: List[Int] = List(1,2,3,4,3) val list5 = 1 :: 2 :: 3 :: 4 :: Nil
其中Nil代表空集合
【2】添加
::的运算规则从右向左
val list1 = 7 :: 6 :: 5 :: 2 :: Nil
添加到第一个元素位置
val list2 = list.+:(5)
【3】合并
val list4 = list3:::list1 val list4 = list3 ++ list1
(2)可变列表
【1】创建
val buffer = ListBuffer(1,2,3,4)
【2】添加
buffer.+=(5) 96 +=:list1 //冒号结尾的符号一般是从右到左添加 buffer.append(6) buffer.insert(1,2)
【3】修改
buffer.update(1,7)
【4】删除
buffer.-(5) buffer.-=(5) buffer.remove(5)
【5】合并同上
40、集合
(1)不可变
【1】创建
val set1 = Set(1,2,3,4,5,6)
【2】添加
set2 = set1.+(20)
【3】合并
set4 = set2 ++ set3
【4】删除
set5 = set - 13
(2)可变
【1】创建
val set = mutable.Set(1,2,3,4,5,6)
【2】添加
set += 8 set1.add(8)
【3】删除
set -= 11 set.remove(11)//返回布尔类型判断是否删除成功
【4】合并
set1 ++= set2
41、Map映射
(1)不可变
【1】创建
val map = Map( "a"->1, "b"->2, "c"->3 )
【2】访问数据
map.getOrElse("c",0)) map("a")
(2)可变
【1】创建
val map = mutable.Map( "a"->1, "b"->2, "c"->3 )
【2】访问数据
(maybeInt.getOrElse(0)
【3】添加数据
map.+=("d"->4) map += (("d",4)) map.put("d"->4)
【4】删除数据
map.-=("b", "c") map.remove("b")
【5】修改数据
map.update("d",5)
【6】合并
map1 ++= map2
42、元组
元组也是可以理解为一个容器,可以存放各种相同或不同类型的数据。说的简单点,就是将多个无关的数据封装为一个整体,称为元组。
注意:元组中最大只能有 22 个元素。
(1)声明形式
(元素 1,元素 2,元素 3)
val tuple: (Int, String, Boolean) = (40,"bobo",true)
(2)访问元组
【1】顺序访问
调用方式:_顺序号
tuple._1
【2】索引访问
(tuple.productElement(0)
【3】迭代器访问
for (elem <- tuple.productIterator) { println(elem) }
【4】嵌套元组
val mulTuple = (12, 0.3 , "hello" , (23,"scala"), 29 )
43、队列
【1】创建
val que = new mutable.Queue[String] ()
【2】入队
que.enqueue("a", "b", "c")
【3】出队
que.enqueue("a", "b", "c")
44、并行集合
Scala 为了充分使用多核 CPU,提供了并行集合(有别于前面的串行集合),用于多核环境的并行计算
val result2 = (0 to 100).par.map{case _ => Thread.currentThread.getName}
45、模式匹配
模式匹配语法中,采用 match 关键字声明,每个分支采用 case 关键字进行声明,当需要匹配时,会从第一个 case 分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。如果所有 case 都不匹配,那么会执行 case _分支,类似于 Java 中 default 语句。
var result = operator match { case '+' => a + b case '-' => a - b case '' => a b case '/' => a / b case _ => "illegal"//其他
(1)说明:
【1】如果所有 case 都不匹配,那么会执行 case 分支,类似于 Java 中 default 语句,若此时没有 case 分支,那么会抛出 MatchError。
【2】每个 case 中,不需要使用 break 语句,自动中断 case。
【3】match case 语句可以匹配任何类型,而不只是字面量。
【4】=> 后面的代码块,直到下一个 case 语句之前的代码是作为一个整体执行,可以使用{}括起来,也可以不括。
(2)模式守卫
如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫。
(3)一些重要的匹配类型
【1】匹配类型
List类型在匹配时只会判断集合类型,里边的类型会被泛型擦除,即不管List内元素类型都能匹配成功
但Array不同,Array不存在泛型擦除,因此如果Array类型不匹配则无法输出
【2】匹配数组
case Array(0) => "0" //匹配 Array(0) 这个数组 case Array(x, y) => x + "," + y //匹配有两个元素的数组,然后将将元素值赋给对应的 x,y case Array(x, 1, z) => x //匹配中间为1的三元素数组 case Array(0, _*) => "以 0 开头的数组" //匹配以 0 开头和数组
【3】匹配列表
方式一:
case List(0) => "0" //匹配 List(0) case List(x, y) => x + "," + y //匹配有两个元素的 List case List(0, _*) => "0 ..." case _ => "something else"
方式二:
case first :: second :: rest => println(first + "-" + second + "-" + rest) //分割成了三个列表first 和 second 还有 rest
【4】匹配元组
case (0, _) => "0 ..." //是第一个元素是 0 的元组 case (y, 0) => "" + y + "0" // 匹配后一个元素是 0 的对偶元组 case (a, b) => "" + a + " " + b case _ => "something else" //默认 //特殊:声明变量 val (x,y) = (10, "hello") val List(first,second, _*) = List(23,15,9,78) val fir :: sec :: rest = List(23,15,9,78)
【5】匹配对象
为了符合模式匹配规则,这里采用伴生对象来进行匹配,且需要使用unapply方法对伴生对象进行拆解获取内部的属性,从而能够拿属性进行比较
def appy(name: String, age: Int): Student = new Student(name, age) def unapply(user: User): Option[(String, Int)] //为了防止User为Nothing
简化方法:样例类
case class Student1 (name: String, age: Int) //加上后,主构造器中的所有参数都是默认成当前的一个属性,不需要再去定义,对应伴生对象、apply和 unapply方法全部自动生成
46、偏函数
偏函数也是函数的一种,通过偏函数我们可以方便的对输入参数做更精确的检查。例如偏函数的输入类型为
List[Int],而我们需要的是第一个元素是 0 的集合,这就是通过模式匹配实现的。
def abs(x: Int): Int = (positiveAbs or Else negativeAbs orElse zeroAbs)(x)
47、异常处理
try: 将可疑代码放入该区域
catch: 各类抛出异常所对应执行的语句,使用case 进行不同错误类型的划分划分
finally:最后一定执行的代码
Scala 的异常的工作机制和 Java 一样,但是 Scala 没有“checked(编译期)”异常,即 Scala 没有编译异常这个概念,异常都是在运行的时候捕获处理。
用 throw 关键字,抛出一个异常对象。所有异常都是 Throwable 的子类型。throw 表达式是有类型的,就是 Nothing,因为 Nothing 是所有类型的子类型,所以 throw 表达式可以用在需要类型的地方
48、隐式转换
当编译器第一次编译失败的时候,会在当前的环境中查找能让代码编译通过的方法,用于将类型进行转换,实现二次编译
(1)隐式函数
隐式转换可以在不需改任何代码的情况下,扩展某个类的功能。需求:通过隐式转化为 Int 类型增加方法。
当当前编译器发现代码通不过时就会寻找能够让当前数据类型转变的方法,若找到则进行转变并看代码能不能通过
(2)隐式参数
普通方法或者函数中的参数可以通过 implicit 关键字声明为隐式参数,调用该方法时,就可以传入该参数,编译器会在相应的作用域寻找符合条件的隐式值。注意,
【1】必须使用implicit 关键字在调用前进行声明赋值
【2】变量类型和隐式参数类型必须一样,和名称无关
【3】类似于柯里化,若隐式参数声明时没有提前加个空的小括号,且有函数进行隐式参数调用,那调用的函
数不能加括号
【4】一定范围内只不能有多个相同类型的隐式参数,但是可以用多个不同类型的
【5】省略写法
说明:
【1】同一个作用域中,相同类型的隐式值只能有一个
【2】编译器按照隐式参数的类型去寻找对应类型的隐式值,与隐式值的名称无关。
【3】隐式参数优先于默认参数
(3)隐式类
在 Scala2.10 后提供了隐式类,可以使用 implicit 声明类,隐式类的非常强大,同样可以扩展类的功能,在
集合中隐式类会发挥重要的作用。 直接把类的构造方法声明为了隐式
说明:
【1】其所带的构造参数有且只能有一个
【2】隐式类必须被定义在“类”或“伴生对象”或“包对象”里,即隐式类不能是顶级的。
【3】隐式参数值会覆盖默认值,优先级较高
49、泛型
一些类型(例如Int,String)由于相差过大因此需要强制转换,且有些类型可能报错。而在数组中,需要对某些元素的类型进行限定,且为了避免对每个元素都需要进行不同的类型定义,泛型便诞生了
(1)语法(括号内部可变化,一般大写单独字母)
class MyList[+T]{ //协变 } class MyList[-T]{ //逆变 } class MyList[T] //不变
(2)说明:
和继承关系有关,就是原本的类关系在变成数组后关系是否发生变化
协变:Son 是 Father 的子类,则 MyList[Son] 也作为 MyList[Father]的“子类”。逆变:Son 是 Father 的子类,则 MyList[Son]作为 MyList[Father]的“父类”。 不变:Son 是 Father 的子类,则 MyList[Father]与 MyList[Son]“无父子关系”。
(3)上下限
泛型的上下限的作用是对传入的泛型进行限定。
Class PersonList[T <: Person]{ //泛型上限 } Class PersonList[T >: Person]{ //泛型下限 }
(4)上下文限定
def fA : B = println(a) //等同于 def fA(implicit arg:B[A])=println(a)
上下文限定是将泛型和隐式转换的结合产物,以下两者功能相同,使用上下文限定[A : Ordering]之后,方法内无法使用隐式参数名调用隐式参数,需要通过 implicitly[Ordering[A]]获取隐式变量,如果此时无法查找到对应类型的隐式变量,会发生出错误。
implicit val x = 1 val y = implicitly[Int] val z = implicitly[Double]