Scala学习笔记
2022-04-03 21:14:50 33 举报
AI智能生成
Scala是一种功能强大的编程语言,它集成了面向对象编程和函数式编程的特性。Scala的设计目标是提高程序员的生产力,让代码更简洁、更易读。学习Scala需要掌握一些基本概念,如变量、数据类型、函数、类和对象等。此外,还需要了解Scala的一些特性,如模式匹配、高阶函数、闭包和隐式转换等。在学习过程中,可以通过阅读官方文档、参加在线课程或参考相关书籍来提高自己的技能。总之,Scala是一门值得学习的编程语言,它可以帮助你更好地解决实际问题并提高编程效率。
作者其他创作
大纲/内容
变量声明的格式:var | val 变量名 [: 变量类型] = 变量值
var表示变量是可变的,可以重新赋值
val表示变量不可变
推荐使用val,没有线程安全问题,效率更高
val类型的变量底层是使用了final修饰
idea代码示例
同时在idea中,如果使用var修饰变量,但是后面没有重新给变量赋值,var会变成灰色
和Java类似,Scala也是值传递,对于引用数据类型,例如对象,只是地址不变,但是对象的属性是可以改变的
var和val
变量类型可以省略,由编译器自行推导,推荐省略,代码更简洁
Settings -> Editor -> Inlay Hints -> Scala -> Type hints -> Show type hints for
在idea中配置Scala数据类型灰色提示
变量声明
在Scala中没有基本数据类型,都是引用数据类型。这点和Java不一样
Scala数据类型分为两大类AnyVal(值类型)和AnyRef(引用类型)。不管是AnyVal和AnyRef都是对象
属于基本类型,被映射成scala.Symbol2.当两个Symbol值相等时,指向同一个实例3.Symbol类型存在的意义:作为不可变的字符串,同时不必重复地为相同对象创建实例,节省资源4.定义变量val s=‘my_symbol时,s值即为 s:Symbol = ’my_symbol。 而s.name为Sting类型,值为my_symbol
Symbol
数据类型一览
1.Scala的数据类型与Java的基本一样
2.在Scala中有一个根类型Any,它是是所有类的父类
3.Scala中一切皆为对象,分为两大类AnyVal(值类型)和AnyRef(引用类型)
4.Null类型是Scala的特别类型,它只有一个值null,它是bottom class,是所有AnyRef类型的子类
5.Nothing类型也是bottom class,它是所有类的子类,在开发过程中可以将Nothing类型的值返回给任意变量或者函数,这里抛出异常使用很多
6.在Scala中仍然遵守,低精度的值向高精度的值自动转换(implictit conversion)隐式转换
小结1.在Scala中,所有引用类型的类都是AnyRef的子类,类似于Java的object2.AnyVal和AnyRef都扩展自Any类。Any类是根节点3.Any中定义了isInstanceOf、asInstanceOf方法,以及哈希方法等4.Null类型的唯一实例就是null对象,可以将null赋值给任何引用,但不能赋值给值类型的变量5.Nothing类型没有实例。它对于泛型结构是有用处的,举例:空列表nuil的类型时List[Nothing],它是List[T]的子类型,T可以时任何类
数据类型说明
数据类型
和Java一样
标识符(变量名)
和Java基本一样
Scala不存在i++或者++i这样的写法,只能使用i = i + 1
var num2 = if (5 > 6) \"大于\" else \"小于\"
Scala不支持三目运算符,在Scala中使用if - else的方式实现
运算符
和Java完全一样
if() ... else if(...) else
分支控制
1. 基本案例for (i <- 1 to 3) { println(i)}1 to 3左闭右闭i表示循环中的变量,并且i是val修饰的,不能改变值1 until 3表示左闭右开
2. 循环守卫 for(i <- 1 to 3 if i!=2){ println(i) }循环守卫,即循环保护式(也称条件判断式),保护式为true则进入循环体内部,为false则跳过,类似于continue
3. 引入变量for (i <- 1 to 10; j = 1 + i) { println(\"i = \" + i + \
4. 循环嵌套for (i <- 1 to 3; j <- 1 to 3) { println(\"i = \" + i + \
5. 循环返回值val rest = for (i <- 1 to 10 ) yield { if (i % 2 == 0) { i } else { \"不是偶数\" } }案例说明 将遍历过程中处理的结果返回到一个新的Vector集合中,使用yield关键字,yield后可以接收一段代码块
for循环
和Java完全一致
//1. 计算1-100的和 var i = 1 var sum = 0 while (i < 101) { sum += i i += 1 } println(sum)
while循环
DROPPED: DO-WHILE
高版本已经废弃
do while循环
//continue。通过循环守卫实现 for (i <- 1 to 10 if i % 2 == 0) { println(i) } //直接在代码中进行if判断 for (i <- 1 to 10) { if (i % 2 == 0) { println(i) } }
continue
//2. 使用循环守卫实现break println(\"*******************\") var loop = true var sum = 0 for (i <- 1 to 100 if loop) { sum += i if (sum > 20) { loop = false } println(\"i = \" + i) } println(\"sum = \" + sum)
break
类似于for循环的循环守卫,定义一个变量
//breakable()函数 //说明 //1. breakable是一个高阶函数: 可以接收函数的函数就是高阶函数 import scala.util.control.Breaks._ var n = 10 breakable { while (n <= 20) { n += 1 println(n) if (n == 18) { break() } } }
while和do while循环
实现Java的continue和break关键字的效果
推荐使用for循环,while循环和do while循环不能返回值,变量必须定义在外部,且必须定义成var
循环控制
Scala提供了一种新的机制来根据数据生成字符串:字符串插值。字符串插值允许使用者将变量引用直接插入处理过的字面字符中
val name=\"James\"println(s\
Scala官网文档字符串插值
字符串插值
Scala语法基础(重要)
返回值形式1::返回值的类型 =
返回值形式2:只有一个等号=,表示返回值类型不确定,由编译器自行推导
返回值形式3:什么都没有,表示没有返回值,等价于:Unit=
函数式编程介绍
如果函数没有形参,在进行函数调用的时候可以不写(),建议不要省略
Scala中的函数可以根据函数体最后一行代码自行推断出函数的返回类型,那么在这种情况下,return关键字可以省略因为Scala可以自行推断,所以函数声明处的返回值的类型也可以省略
如果函数明确使用return关键字,那么函数的返回类型就不能自行推断了,这时就要写成:返回值类型 =,如果不写,即使有return,返回值为()
如果函数需要声明无返回值(Unit),那么函数体中有return关键字,也不会有返回值
Scala语法中任何语法结构都可以嵌套其他语法结构,即:函数中可以在声明定义函数,类中可再声明类,方法中可再声明方法
在Scala函数中形参默认是val,因此不能在函数中进行修改
函数的注意事项和细节
和Java不同的是,Scala可以给函数的形参设定默认值
Scala函数的形参,在声明参数时,直接赋初始值(默认值),这时调用函数,如果没有指定实参,则会使用默认值。如果指定了实参,则实参覆盖默认值。
如果函数存在多个参数,每一参数都可以设定默认值。在进行函数调用的时候,参数到底是覆盖默认值,还是赋值给没有默认值的参数,这就不确定了
这种情况下,可以采用命名参数 def getSum(n1:Int=1){} getSum(n1=2)
默认形参和命名参数
将函数的返回类型为Unit的函数称为过程,如果没有明确函数没有返回值,那么等号可以去掉。
简单来说:没有返回值的函数称为过程。
过程
一.惰性函数使用的场景 惰性计算(尽可能延迟表达式计算)是许多函数式编程语言的特性。惰性集合在需要时提供其元素,无需预先计算它们,这带来一些好处。首先,可以将耗时计算推迟到绝对需要的时候。其次,可以创造无限个集合,只要它们继续收到请求,就会继续提供元素。函数的惰性使用可以得到更高效的代码。Java并没有为惰性提供原生支持,Scala提供了lazy关键字来支持惰性函数
二.惰性函数介绍 当函数返回值被声明为lazy时,函数执行将会被推迟,直到我没首次对此取值,该函数才会执行。这种函数称之为惰性函数
三.注意事项和细节 1.lazy不能修饰var类型的变量,只能修饰val类型的变量 2.不但是在调用函数时,加了lazy,会导致函数的执行被推迟,我们在声明一个变量时,如果声明了lazy,那么变量值的分配也会被推出 例如:lazy val i =10
//main方法开始执行//sum方法之后//sum函数执行了。。。。。。。。//3object LazyDemo01 { def main(args: Array[String]): Unit = { println(\"main方法开始执行\
代码示例
惰性函数、惰性变量(lazy关键字)
Scala的异常处理机制和Java基本一致。使用try...catch...finally的形式。catch的时候使用模式匹配,而不是多个从小到大的catch代码块。Scala没有编译期异常的概念,都是运行时异常。如果在代码中想要抛出异常,直接使用throw new XXXException即可和Java一样。
同时如果想要和Java一样,提醒代码可能会报错,可以使用throws关键字,但是方法调用处也不需要显示处理
try { var i = 1 / 0 } catch { case ex: Exception => ex.printStackTrace() throw new RuntimeException(\"测试手动抛出运行时异常\") } finally { println(\"finally最终执行了\") }
exception
Scala函数式编程(重要)
创建对象 基本语法 val | var 对象名[:类型] = new 类型() 1.如果我们不希望改变对象的引用(即:内存地址),应该声明val,否则声明var。Scala设计者推荐使用val,因为一般来说,我们只改变对象属性值,而不是改变对象的引用 2.Scala在声明对象变量时,可以根据创建对象的类型自动判断,所以类型声明可以省略,但是当类型和后面new对象类型有继承和多态时,就必须写了
面向对象基础
成员变量定义格式:var | val 属性名[: 数据类型] = 属性值定义在类的内部
和Java类似成员变量可以是任意数据类型值类型和引用类型
Scala中没有静态成员变量,这点和Java不同
属性默认都是private的,但是提供了对应public的XXX()和XXX_eq()方法
Bean属性,使用@BeanProperty注解会在底层自动生成getXxx()和setXxx()方法 \t@BeanProperty var name =\"张三\"
成员变量|属性
和函数使用类似
调用对象的方法为对象名.方法名(形参列表)
成员方法默认都是public的
成员方法可以访问成员变量,使用this.成员变量也可以给成员变量重新赋值
成员方法使用和Java类似
Scala没有静态方法,这点和Java不一样
成员方法
Scala构造器的基本语法 class 类名(形参列表){//主构造器 //类体 def this(形参列表){//辅助构造器 } def this(形参列表){//辅助构造器 }说明:主构造器只有一个,辅助构造器函数名称为this,可以有多个。编译器通过不同参数来区分多个构造器
基本说明
1.Scala构造器作用是完成对对象的初始化,构造器没有返回值,和Java一样2.主构造器的声明直接放置于类名之后3.主构造器会执行类定义中的所有语句,这里可以体会到Scala的函数式编程和面向对象编程融合在一起。即:构造器也是方法(函数),传递方法以及使用方法和前面函数部分内容没有区别4.如果主构造器无参数,小括号可以省略,构造对象时调用构造方法的小括号也可以省略,建议不要省略5.辅助构造器的名称为this(这个和Java是不一样的),多个辅助构造器通过不同参数列表进行区分,在底层就是构造器重载。6.辅助构造器一定要先调用主构造器,执行主构造器中的逻辑,并且需要在第一行调用7.Java里辅助构造器可以直接super去调用父类的主构造器,但是在Scala中只允许主构造器去调用父类的主构造器,使用辅助构造器先要调用主构造器,主构造器会默认先执行父类中的构造器8.如果想让主构造器变成私有的,可以在()之前加上private,这样用户只能通过辅助构造器来创建对象9.辅助构造器的声明和主构造器的声明一致,否则会发生错误
构造器使用注意事项和细节
1.Scala类主构造器的形参未用任何修饰符,那么这个参数就是局部变量。可以理解主构造方法就是一个普通方法\tclass B(name:String){},在类中的任意地方都可以使用,且不允许重新赋值2.如果参数使用val关键字声明,那么Scala会将参数作为类的私有的只读属性\tclass A(val name:String){}3. 如果参数使用var关键字声明,那么Scala会将参数作为类的成员属性使用,\tclass A(var name:String){}
属性高级(结合构造器)
构造器
区分同名类
将类按照文件夹进行管理
控制访问范围
对类的功能进行扩展
作用
只能包含数字、字母、下划线,不能使用数字开头,也不能使用关键字
命名规则
小写字母+小圆点
常见如com.公司名.项目名.业务模块
命名规范
java.lang.*
scala包
Predef包
Scala自动导入的包
1.在Scala中,import语句可以出现在任何地方,并不和Java一样限于文件顶部,import语句的作用一致延申到包含该语句的块末尾,这样做的好处是:在需要时引入包,缩小import包的作用范围,提高效率 2.Java中如果想要导入包的所有类,可以通过通配符*,Scala采用_ 3.如果不想要某个包中的全部类,而是其中的几个类,可以采用选取器(大括号) 4.font color=\"#ff0000\
import详解
import 导入包
代码实现
Scala包介绍
Java的package本质上就是文件夹,不能定义类、成员变量、方法这是Java虚拟机的局限,为了弥补这一点不足,Scala提供包对象来解决这个问题
基本介绍
1.每个包中只能有一个包对象
2.包对象名称需要与包名一致,一般用来对包的功能补充
包对象使用注意事项
因为包对象中的所有方法和属性,包下面的所有类都能访问,类似于抽取的作用。之前使用工具类,现在可以使用包对象
因此在包对象中一般定义隐式值、隐式转换函数、隐式类和通用方法等详情可以见org.apache.flink.streaming.api.scala包对象,里面定义了隐式转换函数
DROPPED: PACKAGE OBJECTS
Scala包对象
包
1.成员变量访问权限为默认时,从底层看属性时private,但是提供了xxx_$eq()【类似setter】/xxx()【类似getter】方法,因此从效果上看时任何地方都可以访问 2.当方法访问权限问默认时,默认为public访问权限 3.private时私有权限,只能在类的内部和伴生对象中使用 4.protected为受保护权限,在Scala中受保护权限比Java更严格,只能子类访问,同包无法访问 5.在Scala中没有public关键字,即不能用public显式的修饰属性和方法
访问权限
Scala面向对象初级
和Java一样,Scala也支持继承,也只支持单继承。
基本语法格式:class 子类名 extends 父类名 { 类体 }
访问父类的成员变量和方法使用super.的方式
子类继承了父类的所有属性和方法,但是父类中私有的属性和方法不能访问
class Person { var name : String = \"jack\" private val sal : Double = 9999.9 protected var age = 10 var job : String = \"大数据工程师\" def showInfo(): Unit = { println(\"name = \" + name + \
子类重写父类的非抽象方法需要使用override关键字修饰
重写父类方法
如果类之间存在继承关系那么在声明子类时必须要给父类的主构造器传值可以简单地理解为子类主构造器必须显示调用父类的主构造器
不存在super(...)调用父类的构造器的方式
class Student100(studentName: String) extends Person100(studentName) { println(s\"Student100住构造器被调用了。。。。传入的参数$studentName\
显示调用父类构造器
class AAA { val age : Int = 20}class BBB extends AAA{ override val age : Int = 20}
在Scala中支持重写子类重写父类的字段,复写的字段需要使用override关键字修饰
class AAAA { //这里运行报错,编译不报错// var age : Int = 10 val age : Int = 20}class BBBB extends AAAA { override val age: Int = 20}
覆写字段的注意事项和细节 1.def只能重写另一个def(即方法只能重写另一个方法) 2.val只能重写另一个val属性 或不带参数的def 3.var只能重写另一个抽象的var属性
重写父类字段
继承
抽象字段就是没有初始化的字段
抽象方法就是没有方法体的方法
Scala中可以使用abstract关键字用来标识不能实例化的类抽象类中的可以有抽象字段、实例字段、抽象方法以及普通方法
声明未初始化的变量就是抽象的属性,抽象属性在抽象类
①一个属性没有初始化,那么这个属性就是抽象属性\t②抽象属性在编译成字节码文件时,属性并不会声明,但是会自动生成抽象方法,所有类必须声明为抽象类\t③如果时覆写一个父类的抽象属性,那么override关键字可以省略(原因:父类的抽象属性生成的时抽象方法,因此就不会涉及到方法重写的概念,因此override可以省略)
var重写抽象的var属性小结\t
抽象属性
abstract class Animal {// 抽象字段,只会生成抽象的name()方法 val name : String// 抽象字段,会生成抽象的age()和age_$eq()方法 var age : Int// 非抽象字段,普通属性,生成color()和color_$eq()方法 var color : String = \"黄色\"// 非抽象字段,不可变属性,生成sex()方法 val sex : String = \"公\
val animal: Animal = new Animal { override val name: String = \"\" override var age: Int = _ override def eat(): Unit = {} }
想要实例化一个抽象类,必须要要重写所有的抽象字段和抽象方法
子类继承自抽象类,必须要重写所有的抽象字段和抽象方法,否则自己也声明成抽象类
抽象类
Scala语言完全是面向对象的(万物皆对象)的语言,所以并没静态的操作(即在Scala中没有静态的概念)。
但是为了和Java语言交互(因为Java中有静态概念),就产生了一种特殊的对象来模拟类对象,我们称之为伴生对象。
这个类的所有静态内容都可以放置在它的伴生对象中声明和调用
静态概念介绍
1.scala中伴生对象用object关键字声明,伴生对象中的内容全部是静态 2.伴生对象对应的是伴生类,伴生对象的名称和伴生类的名称要一致 3.伴生对象中的属性和方法可以直接通过伴生对象名称调用 4.从语法上来看,所谓的伴生对象就是静态方法和静态属性的集合 5.从技术上来看,Scala还是没有生成静态的内容,只不过是将伴生对象生成了一个新的类,实现属性和方法的调用 6.从底层原理上看,伴生对象静态特性是依赖于public static final MODULE$实现的 7.伴生对象和伴生类的声明必须在同一个源码文件中,但是没有伴生类,就没有所谓的伴生对象了,所有放哪里无所谓,但是底层还是生成了一个空的伴生类 8.如果class A独立存在,那么A就是一个类;如果object A独立存在,那么A就是一个“静态”性质的对象(类对象) 9.当一个文件中同时存在伴生对象和伴生类,那么文件的图标会发生变化10.伴生对象是一种特殊的类,可以继承类、也可以实现trait等,但是只有一个实例
伴生对象小结
伴生对象apply方法 1.在伴生对象中定义apply方法,可以实现类名()的方式创建对象实例
apply方法
伴生类和伴生对象简单使用
伴生对象apply方法
伴生对象和伴生类
一.特质的声明 trait 特质名{ trait体 } trait的命名一般首字母大写
二.Scala中特质的使用 一个类中具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所有在使用时也是用extends关键字,如果有多个特征存在父类,那么需要用with关键字连接 1.没有父类 class 类名 extends 特质1 with 特质2 with 特质3 2.有父类 class 类名 extends 父类 with 特质1 with 特质2
三.快速入门 1.可以把特质看作是对继承的一种补充。Scala的继承是单继承,也就是一个类最多只能有一个父类,这就对子类功能的扩展有一定的影响。所以我们认为:Scala引入trait特质,第一可以替代Java的接口,第二也是对单继承的一种补充
四.特质的再说明 1.trait可以同时拥有抽象方法和具体方法,一个类可以实现或者继承多个特质 2.Java中的接口可以直接当成Scala特质使用
object TraitDemo01 { def main(args: Array[String]): Unit = { new Demo01().sayHello() new Demo01().sayHi(\"张三\") Demo01.sayHello() }}trait Trait01 {// var age : Int def sayHi(name: String): Unit = { println(\"Hi \" + name) } def sayHello()}class Demo01 extends Trait01 { override def sayHello(): Unit = { println(\"Hello\") }}object Demo01 extends Trait01 { override def sayHello(): Unit = { println(\"Hello\") }}
特质基本介绍与快速入门
1.从面向对象来看,接口并不属于面向对象的范畴,Scala是纯面向对象的语言,在Scala中没有接口 2.Scala语言中采用特质trait(特征)来代替接口的概念,多个类具有相同的特征时,就可以将这个特征独立出来,采用关键字trait声明。简单理解为 trait等价于 interface+abstract class
Scala用特质代替接口
动态混入是Scala特有的方式(Java没有动态混入),可在不修改类声明/定义的情况下,扩展类的功能,非常的灵活,耦合性低 。动态混入可以在不影响原有的继承关系的基础上,给指定的类扩展功能。
在不修改类的定义基础,让他们可以使用trait中的方法除了可以在类声明时继承特质以外,还可以在构建对象时混入特质,扩展目标类的功能此种方式也可以应用于对抽象类功能进行扩展
object MixInDemo01 { def main(args: Array[String]): Unit = { val oracleDB = new OracleDB with Operate3 //OCP原则。开闭原则 oracleDB.insert(100) // val mySQL = new MySQL3 with Operate3 mySQL.insert(200) //如果一个抽象类有抽象方法,如何动态混入特质 val mySql_ = new MySQL3_ with Operate3 { override def say(): Unit = { println(\"say\") } } mySql_.insert(999) mySql_.say() }}trait Operate3 { //特质 def insert(id: Int): Unit = { //方法(实现) println(\"插入数据 = \" + id) }}class OracleDB { //空}abstract class MySQL3 { //空}abstract class MySQL3_ { //空 def say()}
动态混入
一.叠加特质的基本介绍 构建对象的同时如果混入多个特质,称之为叠加特质 那么特质声明顺序从左到右,方法执行顺序从左到右
二.叠加特质的注意事项和细节 1.特质声明顺序从左到右 2.Scala在执行叠加对象方法时,会首先从最右边的特质开始执行,类似于数据结构栈 3.Scala特质中如果调用super方法,并不是表示调用父特质中的方法,而是向左边继续查找特质,如果左边没有特质,此时就会调用父特质 4.如果想要调用具体特质的方法,可以指定super[特质名].方法,其中泛型必须是该类的直接超类类型
三.在特质中重写抽象方法 trait A{ def insert() //抽象方法}trait B extends A{ override def insert(): Unit = { println(\"将数据保存到文件中!\") super.insert() //思考这里super如何调用父特质中的抽象方法 }} 以上代码会出错,原因:是没有完全实现insert方法,同时还要声明abstract override 解决方法:①去掉super.insert()\t②调用父特质的抽象方法,那么在实际使用中,没有方法的具体实现,无法通过编译,为了避免这种情况的发生。可以重写抽象方法,这样在使用过程中必须考虑动态混入的顺序问题
叠加特质
一.富接口的概念 当一个特质中既有抽象方法,又有非抽象方法,这种特质我们称之为富接口
二.特质中的具体字段 特质中可以定义具体字段,如果初始化了就是具体字段,如果没有初始化就是抽象字段。混入该特质的类就具有了该字段,字段不是继承而是直接加入类,成为自己的字段
三.特质构造顺序 1.特质也是有构造器的,构造器中的内容由“字段初始化”和一些其他语句构成。具体实现请参考“叠加特质”第一种特质构造顺序(声明类的同时混入特质【静态混入】) ①调用当前类的超类构造器 ②第一个特质的父类构造器 ③第一个特质构造器 ④第二个特质构造器的父特质构造器,如果已经执行,就不会在执行 ⑤第二个特质构造器 ⑥重复4,5步骤(如果有第3,4个特质) ⑦最后执行本类的构造 第二种特质构造顺序(构建类的同时混入特质【动态混入】) ①调用当前类的超类构造器,然后执行本类的构造 ②第一个特质的父类构造器 ③第一个特质构造器 ④第二个特质构造器的父特质构造器,如果已经执行,就不会在执行 ⑤第二个特质构造器 ⑥重复4,5步骤(如果有第3,4个特质) 分析两种方式对构造顺序的影响 第1种方式实际是构建类对象,在混入特质时,该对象还没有创建 第2种方式实际是构造匿名子类,可以理解成在混入特质时,该对象已经创建
特质扩展
四.扩展类的特质 1.特质可以继承类,以用来扩展该特质的一些功能 2.所有混入该特质的类,会自动成为那个特质所继承的超类的子类 3.如果混入该特质的类,已经继承了另一个类(A类),则要求A类是特质超类的子类,否则就出现了多继承,就会报错。
五.自身类型 说明:自身类型主要是为了解决特质的循环依赖问题,同时可以确保特质在不扩展某个类的情况下,依然可以做到限制混入该特质的类的类型
特质(trait)
嵌套类
①obj.isInstanceOf[T] 就如同Java中的obj isInstanceOf T 判断obj是不是T类型\t②obj.asInstanceOf[T] 就如同Java的(T).obj 将obj强转成T类型\t③classOf[String] 就如同Java的 String.class 输出方法的类名
Scala中推荐使用匹配代替强制转换
Scala类型检查和转换
Scala面向对象高级
一.隐式转换的实际需要 指定某些类型的相互转换 比如 Double <=> Int
二.隐式转换基本介绍 隐式转换函数是以implicit关键字声明的带有单个参数的函数。这种函数将会自动应用,将值从一种类型转换成另一种类型
三.快速入门 implicit def f1(d:Double):Int={ //将double类型转换成int类型 d.toInt }
四.隐式转换注意事项和细节 1.隐式转换函数的函数名可以是任意的,隐式转换于函数名称无关,只与函数签名(函数参数类型和返回值类型)有关 2.隐式函数可以有多个(隐式函数列表),但是要保证当前环境下只有一个隐式函数能被识别
五.隐式转换丰富类库的功能 1.如果需要为一个类增加一个方法,可以通隐式转换来实现。(动态增加功能),比如想为MySQL类增加一个delete方法 2.当前程序中,如果想要给MySQL类增加功能是非常简单的,但是在实际项目中,如果想要增加新的功能就要修改源代码,这是很难接受的。而且违背了软件开发的OCP开发原则(闭合原则:open close priceple),这种情况下,可以通过隐式转换函数给类添加功能
隐式转换函数(隐式方法)
一.隐式类的基本介绍 在Scala2.10后提供了隐式类,可以使用implicit声明类,隐式类非常强大,同时可以扩展类的功能,比前面使用的隐式转换(函数)丰富类库功能更加方便,在集合中隐式类会发挥重要作用
二.隐式类的特点 1.其他带有构造参数有且只能有一个 2.隐式类必须定义在“类“、”伴生对象”、“包对象”中,既即隐式类不能是顶级的(top-class objects) 3.隐式类不能是case class样例类 4.作用域内不能有与之相同名称的标识符(名称)
三.创建方式implicit class BBB(i: Int){ def add(j: Int) = i + j }}
隐式类
隐式值也叫隐式变量,将变量标记使用implicit修饰。隐式值只要配合隐式参数
隐式值(隐式变量)
指方法或者函数的形参使用了implict关键字修饰
隐式参数(隐式形参)
1.编译器会在方法省略隐式参数的情况下,去搜索作用域中的隐式变量作为缺省参数(默认值) 2.当同时有implicit值和默认值时,会调用implicit值,因为implicit值的优先级高 3.当同时有implicit值和默认值时,但是两则类型不匹配,则会调用默认值 4.当同时有implicit值和没有默认值时,但是implicit值类型与形参类型不匹配,此时会报错
隐式值和隐式参数的关系
在于可以给方法的形参设置默认值,但是优先级高于参数的默认值
在方法调用时,省略形参的赋值,让代码看上去更加简洁
一般隐式参数使用科里化的方式,而且也是最后一个形参
隐式值和隐式参数的作用
object ImplicitValDemo01 { def main(args: Array[String]): Unit = { //隐式值或者隐式变量 //可以使用import关键字导入相应的隐式值 implicit val string: String = \"Jack\
代码示例1
object ImplicitValDemo02 { def main(args: Array[String]): Unit = { // 隐式变量(值) //implicit val name2: String = \"Scala\" implicit val name1: String = \"World\" //隐式参数 def hello(implicit content: String): Unit = { println(\"Hello \
代码示例2
隐式参数和隐式值的代码示例
总结:\t①在程序中,同时有 隐式值 默认值 传值 时\t②优先级为 传值 > 隐式值 > 默认值\t③在隐式值匹配时,不能有二义性\t④如果隐式值 默认值 传值都不存在,则程序会报错
使用隐式参数常见问题
隐式参数(隐式形参)、隐式值(隐式变量)
使用implicit关键字修饰object
上面的代码中的S2隐式对象,可以看成特质S1的一个默认实现。在调用时,就能够省略这个默认实现
隐式对象
一.隐式转换时机 1.当方法中的参数的类型与目标类型不一致时 2.当对象调用所在类中不存在的方法和成员时,编译器会自动将对象进行隐式转换
二.隐式解析机制 即:编译器是如何查找到缺失信息的,解析具有以下两种规则 1.首先会在当前代码作用域下查找隐式实体(隐式方法,隐式类,隐式对象),这是一般情况 2.如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域查找。类型的作用域是指与该类型相关联的全部伴生模块,一个隐式实体的类型T它的查找范围如下(这种情况复杂并且使用范围广,应当避免出现)
三.隐式转换的规则 1.隐式转换不能有二义性 2.隐式操作不能嵌套使用
隐式转换细节
Setting -> Languages & Frameworks -> Scala -> Hignligting
效果1
效果2
idea配置和显示下划线显示隐式转换
隐式转换(重要)
一.Scala集合基本介绍 1.Scala同时支持不可变集合和可变集合,不可变集合可以安全的并发访问 2.两个主要的包\t不可变集合:scala.collection.immutable\t可变集合:scala.collection.mutable 3.Scala默认采用不可变集合,对于几乎所有的集合类,Scala都同时提供了可变(mutable)和不可变(immutable)的版本, 4.Scala的集合有三大类:序列Seq、集Set、映射Map,所有集合都扩展自Iterable特质,在Scala中集合有可变(mutable)和不可变(immutable)两种类型
二.可变集合和不可变集合 1.不可变集合:Scala不可变集合,就时这个集合本身不能动态变化。类似于Java的数组,是不可以动态增长的 2.可变集合:就是这个集合本身可以动态变化,是可以动态增长的
三.集合的种类 1.数组Array 2.元组Tuple 3.列表List 4.队列Queue 5.映射Map 6.集Set
集合的基本介绍
第一种方式定义数组(动态创建) 这里的数组等同于Java中的数组,中括号的类型就是数组的类型,小括号中的数字代表数组的长度 var arr1 = new Array[int](10)
注意:如果赋值元素全部为Int,则该数组的泛型就是Int;如果有各种类型的元素,则该数组的泛型为Any
定长数组
一.变长数组的基本使用 val array = new ArrayBuffer[Int]() arrar.append(7) //追加元素 array.remove(0) //删除元素 ,索引位置 array(0) = 7 //重新赋值
二.变长数组分析小结 1.ArrayBuffer是变长数组,类似于Java的ArrayList 2.val arr = ArrayBuffer[Int]() 3.def append(elems:A*) {append(elems)}接受的是可变参数 4.每append一次,arr在底层会重新分配空间,进行扩容,arr的内存地址会发生变化,也就成为新的ArrayBuffer
变长数组
三.定长数组和变长数组的转换 arr1.toBuffer //定长数组转变长数组 arr2.toArray //变长数组转可变数组 ①arr2.toArray 返回的结果才是一个定长数组,arr2本身没有发生变化 ②arr1.toBuffer返回结果才是一个可变数组,arr1本身没有发生变化
定长数组与变长数组相互转换
二.多维数组的遍历 for (item <- arr){ for (item2 <- item){ print(item) } }
多维数组
数组(Array)
一.tuple介绍 元组可理解成一个容器,可以存放各种相同和不同的数据。简单来说,就是将多个无关的数据封装成一个整体,称为元组。注意:元组内最多只能有22个数据
三.小结 1.tuple的类型取决于tuple中有多少个元素,比如有 6个元素,则类型就是tuple6 2.元组中最大只能有22个元素
四.元组的访问 访问元组中的数据,可以采用顺序序号(_顺序号),也可以通过索引(productElement)访问 tuple1._1 //1代表顺序号 tuple1.productElement(0)\t//0代表索引
五.元组的遍历 1.元组的遍历需要用到迭代器 2.即 for(item <- tuple1.productIterator){ \tprint(item) }
元组(Tuple)
一.列表的基本介绍 Scala中list和Java List本不一样,Java中的List是一个接口,真正存放数据的是ArrayList,而Scala的list可以直接存放数据,就是一个object,默认情况下,Scala的list是不可变的,list属于序列Seq
二.说明 1.在默认情况下list是Scala.collection.immutable.list,即list是不可变的 2.在Scala中,list就是不可变的,如果需要使用可变的list,则使用listBuffer 3.Nil代表空集合
三.小结 1.list默认为不可变的集合 2.list在Scala包对象声明的,因此不需要引入其他包也可以使用 3.val List = scala.collection.immutable.List 4.list中可以存放任何数据类型,比如arr1的类型为list[Any] 5.如果希望得到一个空列表,可以使用Nil对象,在Scala包对象声明的,因此不需要引入其他包也可以使用 val Nil = scala.collection.immuteable.Nil
七.列表的其他操作 list.head //返回列表的第一个元素 list.tail //返回列表除第一个元素外的元素 list.isEmpty //判断列表是否为空,空true,非空false list.last //返回列表最后一个元素 list.init //返回列表除最后一个元素外的元素
列表(List)
一.队列的基本介绍 1.队列是一个有序列表,再底层可以用数组或链表来实现 2.其输出和输入要遵循先入先出的原则。即:先存入队列的数据先取出,后存入的数据后取出 3.在Scala中,由设计者直接给我们提供队列类型使用 4.在Scala中,由scala.collection.mutable.Queue 和 scala.collect.immutable.Queue 我们使用最多的是可变的队列
二.队列的创建 val queue = new mutable.Queue[Int] //Int为队列的泛型
四.返回队列中的元素 1.返回队列第一个元素 queue.head 2.返回队列最后一个元素 queue.last 3.返回队列尾部元素,返回的是一个队列,所以可以级联使用该方法(除了第一个元素,其他元素都返回)\tqueue.tail\tqueue.tail.tail
队列(Queue)
一.回顾:Java中的Map的基本介绍 HashMap 是一个散列表(数组+链表),它存储的内容是键值对映射(key-value),key不能重复,Java中的Hash是无序的
二.Scala中的Map 1.Scala中的Map和Java类似,也是一个散列表,它存储的内容也是键值对(key-value)映射,Scala中不可变的Map是有序的,可变的Map是无序的 2.Scala中,有可变Map:scala.collection.mutable.Map\t不可变Map:scala.collection.immutable.Map
三.构建不可变的Map //Map默认是不可变的val map1 = Map(\"zhangsan\
四.构建可变映射Map val map2 = mutable.Map(\"name\" -> \"zhangsan\
Map的创建
val map = mutable.Map(\"name\"<- \"张三\
五.更新map中的元素 val map2 = mutable.Map(\"name\"-> \"张三\
六.增加map中的元素 map2 += (\"D\" -> 2) //添加单个元素,如果key已经存在,则会更新 map2 += (\"C\
Map的操作
映射(Map)
一.集的基本介绍 集是不重复元素的集合。集不保留顺序,默认是以哈希集实现二.回顾:Java中Set Java中,Hash是实现Set<E>接口的一个实现类,数据是以哈希表的顺序存放的,里面不能包含重复数据。Set接口是一个不包含重复数据的collection,HashSet中的数据也是没有顺序的三.Scala中的Set说明 1.默认情况下,Scala使用的是不可变的集合,如果想使用可变的集合,需要引入scala.collection.mutable.Set包。 2.set(1) 可以判断元素 1 在集合中是否存在,存在返回true,不存在返回false
五.可变集合元素的添加 set.add(4) //只有可变集才能添加元素,如果添加的元素重复也不会报错,但是不会添加 set += 8
六.可变集合元素的删除 set.remove(4) //集Set中可以根据值来删除,因为Set中的元素不重复 set -= 8 //如果删除的元素不存在,则不会报错。
集(Set)
集合
map映射操作
flatmap映射:flat 即压扁flatmap:就是集合中每个元素的子元素映射到某个函数并返回新的集合 val names = ListBuffer(\"Python\
flatmap扁平化操作
filter:将符合要求的数据(筛选)放置到新的集合中应用案例: /* 将 val names = List(\"Alice\
filter过滤
reduce化简
fold折叠
scan扫描
groupBy分组
zip拉链
常用高阶函数
iterator迭代器
一.基本说明 stream是一个集合。它可以存放无穷多个元素,但是这无穷多个元素并不会一次性生产出来,而是需要多大的区间,就会动态的产生,末尾元素遵循lazy规则(即:需要是,才计算) //创建一个流 def numForm(n:BigInt):Stream[BigInt]={n #:: numForm(n+1)} val stream = numForm(1) println(stream) //取出第一个元素 println(stream.head) println(stream.tail)说明:1.stream集合中存放的数据类型是BigInt2.numForm是一个函数名,由程序员指定3.创建的集合的第一个元素是n,后续生成的规则是n+14.后续元素生成的规则可以自行指定5.使用tail方法就可以产生一个新的元素
stream流
一.基本介绍 stream的懒加载特性,也可以对其他集合应用view方法来得到类似的效果,具有如下特点: 1.view方法产生出一个总是被懒加载执行的集合 2.view不会缓存数据,每次都要重新计算,比如遍历view时。 val view = 1 to 100 val res = view.filter(eq) println(res) def eq(n:Int):Boolean={ n.toString.equals(n.toString.reverse) }
view视图
一.所有线程安全的集合都是以synchronized开头的集合 SynchronizedBuffer SynchronizedMap SynchronizedPriorityQueue SynchronizedQueue SynchronizeSet SynchronizeStack二.并行集合 1.Scala为了充分使用多核CPU,提供了并行集合(有别于前面的串行集合),用于多核环境的并行计算 2.主要用到的算法有:Divied and conquer(分治算法),Scala通过splitters,combiners等抽象层来实现,主要原理是将计算工作分解成很多任务,分发给一些处理器去完成,并将它们处理的结果合并返回 3.Work stealin算法【学数学】,主要用于任务调度负载均衡(load-balancing),通俗点完成自己的所有任务之后,发现其他人还有活没干完,主动(或被安排)帮他人一起干,这样达到尽早干完的目的
三.使用并行模块 Scala 2.13之后,并行模块变成了外部库,和XML、Swing、parser-combinators等一样需要在maven项目的pom.xml中手动导入:<dependency> <groupId>org.scala-lang.modules</groupId> <artifactId>scala-parallel-collections_2.13</artifactId> <version>0.2.0</version> </dependency>然后在代码文件中导入如下的包就行了: import scala.collection.parallel.CollectionConverters._ 参考链接:https://stackoverflow.com/questions/57287607/answer/submit[code=html]四.基本案例 1.输出1-5 (1 to 5).foreach(println(_)) //单行操作 println() (1 to 5).par.foreach(println(_)) //并行操作 2.查看并行集合中元素访问的线程 val result1 = (0 to 100).map{case _ => Thread.currentThread.getName} val result2 = (0 to 100).par.map{case _ => Thread.currentThread.getName} println(result1) println(result2)
线程安全
一.操作符扩展 1.如果想在变量名,类名等定义中使用语法关键字(保留字),可以配合反引号`` val`val`=42 2.中置操作符:A操作符B (等同于) A.操作符(B)
操作符
集合基本操作
Scala数据结构
Scala中的模式匹配类似于Java中的switch语法,但是更加强大
模式匹配语法中,采用match关键字声明,每个分支采用case关键字进行声明,当需要匹配时,会从第一个case分支开始,如果执行成功,就会执行相应的逻辑代码,同时终止;如果不匹配,则继续执行下一个分支case。如果所有的case都不匹配,那么会执行case_分支,类似于Java中default
二.代码案例val oper = '#'val n1 = 20val n2 = 10var res = 0oper match {/*1.match类似于Java的switch,2.如果匹配成功,则会执行=> 后面的代码块3.匹配顺序是从上往下4.=>后面的代码块不需要写break,因为match会自动退出5.如果所有的case都不匹配,则会执行case_后的代码块6.如果所有的case都不匹配,并且没有case_,则会抛出异常MatchError4.可以再match中使用其他类型,不仅仅是Char*/case '+' => res = n1 + n2case '-' => res = n1 - n2case '*' => res = n1 * n2case '/' => res = n1 / n2case _ => println(\"oper error\")}println(\"res=\" + res)
模式匹配初体验
一.基本介绍 如果想要表达匹配某个范围的数据,就需要再模式匹配中增加条件守卫 for (ch <- \"+-3!\") { var sign = 0 var digit = 0 ch match { case '+' => sign = 1 case '-' => sign = -1 // 说明case _ if表示条件守卫,这里的下划线 _ 表示不接收ch case _ if ch.toString.equals(\"3\") => digit = 3 case _ => sign = 2 } println(ch + \" \" + sign + \" \" + digit) }
条件守卫
二.模式中的变量 如果在case关键字后跟变量名,那么match前表达式的值会赋给那个变量 val ch = 'V'ch match {case '+' => println(\"ok~\")case mychar => println(\"ok~\" + mychar)case _ => println (\"ok~~\") }
模式匹配中的变量
模式匹配扩展
声明变量中的匹配
一.基本介绍 for循环中也可以进行模式匹配 val map = Map(\"A\
for循环中的模式匹配
匹配数组
匹配列表
匹配元组
一.基本介绍 1.样例类仍然是类 2.样例类用case关键字进行声明 3.样例类是为模式匹配而优化的类 4.构造器中的每一参数都成为了val,除非显示的声明var (不推荐) 5.在样例类对应的伴生对象中存在apply方法,可以是在实例化时不使用new关键字,就可以直接构造出对象 6.也提供unapply方法,让模式匹配可以正常的工作 7.将自动生成toString,equal,hashCode和copy方法(有点类似模板类,直接生成使用) 8.除了上述外,样例类和其他类完全一样,可以添加字段和方法
样例类
一.基本介绍 1.case中对象的unapply方法(提取器)返回Some集合则为匹配成功 2.返回None则为匹配失败
二.基本案例object Square{ //对象提取器,它会提取出创建对象时传入的参数 //简单来说,unapply就是逆向拆解对象 def unapply(z:Double): Option[Double] = { println(\"unapply方法被调用,z= \
三.小结 1.构建对象时,apply方法方法会被调用,比如 val n = Square(6) 2.当Square(n)写再case后面时,即【case Square(n) => xxx】,会默认调用unapply方法(对象提取器) 3.number会被传递给def unapply(z:Double) 的形参 z 4.如果返回unapply方法返回的时Some,则会将结果传递给Square(n)中的 n 5.case中对象的unapply方法(提取器)返回的时Some则匹配成功,为None则匹配失败
匹配样例类
一.基本介绍 操作原理类似于正则表达式三.最佳实践案例-商品捆绑打折出售 现在有一些商品,请使用Scala设计相关的样例类,完成商品捆绑打折出售。要求商品捆绑可以是单个商品,也可以是多个商品。打折时按照折扣x元进行设计.能够统计出所有捆绑商品打折后的最终价格
匹配嵌套结构
一.基本介绍 1.如果想让case类的所有子类都必须在申明该类的相同源文件中定义,可以将样例类通过超类声明为sealed,这个超类就成为了密封类 2.密封类就是不能在其他文件中定义子类
密封类
Scala模式匹配(重要)
偏函数的引出
一.基本介绍 1.在对某个集合进行处理时,其中有些元素不符合处理条件,这时就可以使用偏函数 2.将函数中封装了一组case语句的函数,叫做偏函数。它只会对作用于指定类型的参数或者指定范围的参数实施计算,超出范围的值会另外处理 3.偏函数在Scala中是一个特质PartialFunction
偏函数介绍
偏函数的简化形式二 这种方式不能指定传入值的类型和返回值的类型,只能使用默认的 //偏函数的简化形式二 val res2 = list.collect{ case n:Int => n+1 case n:Double => n*2 } println(res2)
偏函数的化简形式
做为参数的函数
偏函数
一.基本介绍 没有名字的函数就是匿名函数,可以通过函数表达式来设置匿名函数二.代码演示 val res = (n:Int) => n+3 println(res(2))三.小结 1.不需要写def函数名 2.不需要写返回值类型,因为默认使用类型推导 3.如果代码块有多行,就使用{}
匿名函数的简写(参数类型推断)
匿名函数
一.基本介绍 可以传入函数的函数就作高阶函数,在某些情况下也可以返回一个函数
高级函数
一.基本介绍 闭包(closure)就是一个函数和与其相关的引用环境组合的一个整体(实体)。
三.代码小结 1.test函数返回的是 (m:Int) => n-m 匿名函数,因为该函数引用到了函数外的变量 n ,那么该函数和 n 整体形成了一个闭包。val f1 = test(2)这里的f1就是一个闭包 2.可以这样理解,返回函数是一个对象,而n就是该对象的一个字段,它们共同形成了一个闭包 3.当多次调用f1时(可以理解多次调用闭包),发现使用的是同一个n ,所以 n 不变 4.当使用闭包时,主要搞清楚返回函数引用了函数外的哪些变量,因为它们组合成了一个整体(实体),形成了一个闭包
四.添加后缀名的案例object clusureDemon { def main(args: Array[String]): Unit = { val f = makeSuffix(\".jpg\") println(f(\"scala\")) //scala.jpg println(f(\"python.jpg\")) //python.jpg } /* 传入一个文件名,如果这个文件名带有后缀名,那么直接返回; 如果这个文件名没有后缀名,则为它添加一个后缀名并返回 */ def makeSuffix(suffix:String) ={ (fileName:String) => { if (fileName.endsWith(suffix)) fileName else fileName+suffix } }}五.闭包的好处 1.返回的匿名函数和 test(n:Int) 的 n 变量组合成了一个闭包,因为返回的函数要引用 n 这个变量 2.可以反复引用上一次传入的值
闭包
一.基本介绍(curry) 1.函数编程中,接收多个参数的函数都可转换成接收一个参数的函数,这个转换过程就是函数柯里化 2.柯里化就是证明了函数只需要一个参数而已。在前面的学习中也用到了函数柯里化 3.柯里化是面向函数思想的必要产生结果
函数柯里化
一.基本介绍 控制抽象是这样的函数 1.参数是函数 2.函数参数没有输入值也没有返回值
二.代码演示 //像这样,参数是函数,函数没有输入值也没有返回值的函数,就是控制抽象 def myRun(f:()=>Unit): Unit ={ new Thread{ override def run(): Unit = { f() } }.start() } myRun { //这里就是调用myRun这个函数 () => //这里表示没有传入参数也没有返回值 println(\"开始干活!\") Thread.sleep(5000) println(\"干完了\") }三.案例 /* 利用控制抽象写出until函数,实现一下代码 var n = 10 while (n > 0){ n -= 1 println(n) } */ var x = 10 until(x > 0){ x -= 1 println(x) } //创建一个控制抽象函数,这里也用到了函数柯里化 def until(f1: => Boolean)(f2: => Unit): Unit = { if (f1){ f2 until(f1)(f2) }
控制抽象
Scala函数式编程高级
一.基本介绍 1.如果我们要求函数的参数可以接受任意泛型。可以使用泛型,这个类型可以代表任意的数据类型。 2.例如List,在创建List时,可以传入整型,字符串,浮点数等等任意类型。那是因为List在类定义时引用了泛型。
泛型
在Java泛型里表示某个类型是A类型的子类型,使用extend关键字,这种形式叫upper bounds(上线或上界)语法如下:\t<T extend A>\t//使用通配符的形式\t<? extends A>
Java上界
Scala上界 在Scala里表示某个类型是A类型的子类型,也称上限或者上界。使用<:关键字。语法如下\t[T <: A]\t//使用通配符\t[_ <: A]
//java中上界//在 Java 泛型里表示某个类型是 A 类型的子类型,使用 extends 关键字,这种形式叫 upper bounds(上限或上界),语法如下: <T extends A>//或用通配符的形式: <? extends A>object UpperBoundsDemo01 { def main(args: Array[String]): Unit = { //1. 第一种方法 println(\"传统方法: \
泛型上界
在Java中泛型里表示某个类型是A类型的父类型,使用super关键字<T super A>//使用通配符<? super A>
Java下界
Scala下界 在Scala中下界,使用 >: 关键字\t[T >: A]\t//表示T的下界是A\t//使用通配符\t[_ >: A]
Scala下界使用小结 ①对于下界,可以传入任意类型 ②传入和A直系的,是A父类就按照A父类处理,如果是A的子类,则按照A处理 ③如果传入的对象与A无关,一律按照Object处理 ④也就是说下界可以随便传,只是处理方式不一样 ⑤不能使用上界的思路来类推下界的含义
泛型下界
一.基本介绍 1.Scala的协变(+),逆变(-),协变covariant、逆变contravariant、不可变invariant 2.对于一个带类型参数的类型,比如 List[T],如果对A及其子类型B,满足 List[B]也符合List[A]的子类型,那么就称为covariance(协变) ,如果 List[A]是 List[B]的子类型,即与原来的父子关系正相反,则称为contravariance(逆变)。如果一个类型支持协变或逆变,则称这个类型为variance(翻译为可变的或变型),否则称为invariance(不可变的) 3在Java里,泛型类型都是invariant,比如 List<String> 并不是 List<Object> 的子类型。而Scala支持,可以在定义类型时声明(用加号表示为协变,减号表示逆变),如: trait List[+T] // 在类型定义时声明为协变这样会把List[String]作为List[Any]的子类型。
二.应用实例 在这里引入关于这个符号的说明,在声明Scala的泛型类型时,“+”表示协变,而“-”表示逆变 C[+T]:如果A是B的子类,那么C[A]是C[B]的子类,称为协变 C[-T]:如果A是B的子类,那么C[B]是C[A]的子类,称为逆变 C[T]:无论A和B是什么关系,C[A]和C[B]没有从属关系。称为不变.val t: Temp[Super] = new Temp[Sub](\"hello world1\")class Temp3[A](title: String) { //Temp3[+A] //Temp[-A] override def toString: String = { title }}//支持协变class Superclass Sub extends Super
//协变、逆变、不变//Scala的协变(+),逆变(-),协变covariant、逆变contravariant、不可变invariant//对于一个带类型参数的类型,比如 List[T],如果对A及其子类型B,满足 List[B]也符合List[A]的子类型,那么就称为covariance(协变),如果 List[A]是 List[B]的子类型,即与原来的父子关系正相反,则称为contravariance(逆变)。如果一个类型支持协变或逆变,则称这个类型为variance(翻译为可变的或变型),否则称为invariance(不可变的)//在Java里,泛型类型都是invariant,比如 List<String> 并不是 List<Object> 的子类型。而scala支持,可以在定义类型时声明(用加号表示为协变,减号表示逆变),如:traitList[+T]//在类型定义时声明为协变这样会把List[String]作为List[Any]的子类型。//在这里引入关于这个符号的说明,在声明Scala的泛型类型时,“+”表示协变,而“-”表示逆变//C[+T]:如果A是B的子类,那么C[A]是C[B]的子类,称为协变//C[-T]:如果A是B的子类,那么C[B]是C[A]的子类,称为逆变//C[T]:无论A和B是什么关系,C[A]和C[B]没有从属关系。称为不变.object Demo01 { def main(args: Array[String]): Unit = { //不变 val t1: Temp3[Sub] = new Temp3[Sub](\"abc\") //ok // val t2: Temp3[Sub] = new Temp3[Super](\"abc\") //error// val t3: Temp3[Super] = new Temp3[Sub](\"abc\") //error val t4: Temp3[Sub] = new Temp3[Sub](\"abc\") //ok //协变 val t5: Temp4[Super] = new Temp4[Sub](\"abc\") //ok val t6: Temp4[Sub] = new Temp4[Sub](\"abc\") //ok //逆变 val t7: Temp5[Sub] = new Temp5[Sub](\"abc\") //ok val t8: Temp5[Sub] = new Temp5[Super](\"abc\") //ok// val t9: Temp5[Super] = new Temp5[Sub](\"abc\") //error }}//协变class Temp4[+A](title: String) { override def toString = title}//逆变class Temp5[-A](title: String) { override def toString = title}//不变class Temp3[A](title: String) { override def toString = title}class Superclass Sub extends Super
逆变、协变和不可变
视图界定(View Bounds)<% 的意思是“view bounds”(视界),它比<:适用的范围更广,除了所有的子类型,还允许隐式转换类型。def method[A <% B](argList): R = ... 等价于:def method[A](argList)(implicit viewAB: A => B): R = ...并且在调用的地方必须存在相应的隐式转换 A => B或等价于: implicit def conver(a:A): B = …<% 除了方法使用之外,class 声明类型参数时也可使用:class A[T <% Int]
隐式视图
高版本的Scala已经废弃
视图界定
上下文界定
泛型、泛型上下界、视图界定、上下文界定、逆变|协变|不可变(重要)
Scala中常用特殊符号
Scala中常用特殊符号(重要)
Scala学习笔记
0 条评论
回复 删除
下一页