Scala学习笔记(详细)
第2章 变量
- val,var,声明变量必须初始化;变量类型确定后不可更改
- 数据类型:与java有相同的数据类型,在scala中数据类型都是对象
- 特殊类型:Unit:表示无值,只有一个实例值写出(),相当于void;Null:null;Nothing:null(表示非正常执行)
- 标识符命名规范:大于1个操作符可以作为标识符;反引号包含的任意字符,包含关键字
- scala关键字
第3章 运算符
- 没有java中的++,--,使用+=,-=替代
- 键盘输入:StdIn.readxxx
第4章 流程控制
- ifelse也有返回值,没有java中的三元运算符,where,do where没有返回值
- 没有switch
- for循环
- for(i <- xx)遍历集合
- for(i <- xx to xx)前后都闭合,for(i <- xx until xx)前闭后开
- 循环守卫:可以在for里面写if表达式
- 引入变量:for(i <- xx until xx; j = i + 1),没有关键字要加分号隔断逻辑
- 嵌套循环:for(i <- xx until xx; j <- xx until xx),要使用分号隔断逻辑
- 循环返回值:for(i <- xx until xx; j = i + 1) yield i,把循环结果放入vector中,使用yield关键字,后面也可以加代码块
- 注意:for循环的括号可以换成大括号,里面的逻辑可以分行写
- for控制步长:Range(start, end, step)或者使用循环守卫
- where和do where与java相同,没有break和continue
- 循环中断:使用break(),会抛出一个异常,最外层使用breakable()(高阶函数,也可以使用{})接收,continue可以使用循环守卫实现
第5章 函数式编程基础
- def 函数名 ([参数名: 参数类型], ...) [[: 返回值类型] = ] {语句 [return]}
- 使用def声明,()也可以没有
- 函数输入参数,可以有也可以没有
- 返回值类型,可以有也可以没有:可以定义返回值类型,也可以使用类型推断,也可以不返回
- return可以有也可以没有,如果没有以最后一行的结果作为返回值 ,如果有return必须写返回值类型
- 支持可变形参:args*
- 函数参数可以指定默认值,使用时传入的参数从左到右依次赋值,也可以使用=赋值给指定参数
- 递归函数不能使用类型推断,必须指定返回值类型
- 可变形参:只能放在参数列表的最后
- 没有返回值的函数也称为过程
- lazy修改的变量不会马上计算后面的值,lazy不能修饰var
- 异常捕获:try {} catch { case ex: xxxException => println() } finally {}
- 声明异常,在函数前@throws(classOf[xxxException])
第6章 面向对象编程(基础部分)
- 属性定义:默认私有,但底层是通过public方法使用的
- 必须赋初始值,或者下划线_表示默认的值,但是必须指明类型
- 对象向属性赋值时,底层是调用了对象的xxx_$eq()方法;同样,取值时底层也是调用了xxx()方法
- 不指定类型又赋值为null,那该属性为Null类型
- 在属性前指明@BeanProperty,则会在底层生成响应的getset方法,并且与原来的方法不冲突
- 默认权限可读可写,使用private修饰只可读(本类,底层为final修饰)
- class的定义:[修饰符] class {},public类型直接写class,写会报错;可以一个文件定义多个类,默认都是public的
- 构造器
- 主构造器:声明与类名之后,主构造器会执行类中除方法以为的所有语句,隐藏了对父构造器的调用
- 辅助构造器:名称为this,第一行必须调用主构造器(因为需要主构造器调用父类的构造器,而辅助构造器不允许调用父类的构造器),或者调用其他已经调用了主构造器的辅助构造器
- 如果在主构造器中形参定义为val,则该参数会成为该类的一个私有只读属性;如果定义为var,则该参数会成为该类的一个私有可读写属性,与定义的属性一样
- 对象创建的流程:1-加载类信息(属性,方法)到方法区,2-在内存中开辟空间,3-调用父类的主构造器和辅助构造器,4-调用主构造器初始化,5-调用辅助构造器,6-将开辟空间的地址赋值给引用对象
- 创建对象的方式:
- new、apply、匿名子类、动态混入
第7章 面向对象编程(中级部分)
- 包的管理
- scala包名和源码所在路径可以不一样,编译器会自动将生成的class文件放在和包名对应的目录下
- scala支持在一个文件中同时创建多个包,以及给多个包创建类、特质trait及object,package{ package{}}
- 作用域:大括号指定了作用域,子包内容可以直接访问父包内容,若有重名,则使用就近原则;父包若要使用子包内容则需要import引入,可以在任何地方import(相对路径,绝对路径(很少用,@__root__.xxx.xxx))
- 包可以包含类、特质、子包,但是不能包含方法和函数,这是JVM的局限,scala使用包对象来解决这个问题,一个包只能创建一个包对象,与包名一致,需要在父包内定义,一般用来对包的功能补充,package object xxx{},会生成两个class文件
- 包的可见性
- 伴生类:写非静态的内容,伴生对象:写静态的内容,scala中没有static,伴生类中的private属性可以在伴生对象中访问
- protected修饰的属性只能在子类中使用,在同包下的其他类中不可使用
- scala没有public修饰符
- 可以在修饰符后面加[包名],增加包对属性的访问,相当于扩大了private或protected修饰的属性的范围
- 包的引入
- 可以在任何地方import
- 使用_表示引入所有内容,等同于java的*
- 使用选择器引入包的部分内容.{xx,xxx},同时支持重命名.{xx => rename,xxx},隐藏某个类.{xx =>_, _}
- 继承
- 继承了所有属性,只是private无法访问到
- 重写方法需要关键字override,如果要调用父类方法需要使用super;
- classOf[]可以获取类型,isInstanceOf[]:判断类型,asInstanceOf[]:强制转换类型(多态,向下转型,向上转型直接赋值给父类类型的变量)
- 超类的构造:可以在extends时显式的指定调用超类的哪个构造器(先调用父类主构造器-辅助构造器--本类主辅构造器),不能再辅助构造器中调用超类的主构造器,只有主构造器才能调用父类的构造器
- 重写属性:java中重写属性只是隐藏了父类的属性,在使用父类的引入去取值时仍可以取到父类的属性,用子类的引用去取值则取到子类的属性,并不能像方法那样重写(方法都会调用重写后的方法);java动态绑定:如果是调用了方法,jvm会将对象和方法绑定;如果调用的是属性,没有动态绑定,在哪调用就返回哪值。在scala中override属性,使用父类的引用调用属性时返回的是子类的属性,因为底层是重新的方法,具有动态绑定机制
- val只能重写val,不能重写var,父类的var会有两个public方法(读和写),重写后val只有一个读,那么在赋值给子类的属性时就会逻辑混乱,读是用的子类的方法,写是父类的xx_$eq方法,因此会报错;val可以重写父类不带参数的def
- var不能重写var,只能重写或者实现抽象类的抽象属性
- 抽象类:abstract修饰,抽象属性,普通属性,抽象方法(不写方法体),不能使用private/final修饰
- 匿名子类
第8章 面向对象编程(高级部分)
- 静态属性和静态方法
- 伴生对象和伴生类:伴生对象的方法和属性可以直接调用,底层是通过一个MODULE$的静态对象调用方法实现的
- 在伴生对象中定义apply方法,可以实现类名直接创建对象
- 特质:等价于java中接口和抽象类,定义:trait 特质名 {}
- 使用:在scala中,java中的接口可以当做特质使用,使用extends,with;如果有父类,必须先extends父类,再with特质
- 底层原理:一个trait,如果只有抽象方法,对应底层的一个接口,继承这个特质的类实现了接口;如果有抽象方法和非抽象方法,对应底层一个接口和抽象类xxx$class.class,接口中有所有方法,抽象类中有非抽象方法,在继承trait是,抽象方法自己实现,非抽象方法直接调用抽象类中的非抽象方法
- 动态混入:创建对象时使用with xxx,动态混入trait中的方法;如果是抽象类要混入,with xxx {实现方法};可以混入多个trait,其创建对象的顺序是从左到右的,即从最左边开始执行父特质中的主构造语句;执行的顺序是从右向左,其中的super并不是其父类特质,而是向左查找,如果找不到,再去父特质中找;如果一定要调用父特质的方法,可以在trait中使用泛型,指定父特质,super[Data].xxx,Data必须是其直接超类类型;
- 特质中的super:在继承父特质的特质中如果使用super调用父特质的抽象方法,必须声明为abstract override,表示重写抽象方法,其中的super调用的方法与动态混入时的顺序有密切关系
- 富接口:特质中既有抽象方法,又有非抽象方法
- 动态混入中的抽象字段:特质中的字段在动态混入中是直接加入创建的对象中的,抽象字段必须实现
- 静态混入特质构造顺序(声明是加入,可继承):先调用父类的主构造器,在调用特质的主辅构造器,在调用自己的构造器;调用原则:先调用主构造器,在调用辅构造器,同样的父类,只调用一次构造器
- 动态混入特质构造顺序:先创建对象,调用创建对象过程中的所有主辅构造器,再调用动态混入特质中的主辅构造器
- 特质的扩展:特质可以继承类;继承特质的子类自动成为特质继承的那个父类的子类;因此,如果混入特质的类已经继承了类A,则要求类A也是特质的超类的子类,即A类和混入的特质有相同的父类,否则会出现多继承的现象,发生错误
- 自身类型:解决特质的循环依赖的问题,同时可以确保在不继承某个类的情况下依然可以做到限制混入该特质的类的类型(必须是自身类型的子类,使用this: Exception=>指明自身类型,相当于extends Exception)
- 嵌套类:
- java面试题:类共有5大成员:属性、方法、构造器、内部类、代码块;java内部类的分类:在成员位置上有两种:成员内部类,静态内部类,在局部位置上有两种:局部内部类(方法内定义),匿名内部类
- scala由于没有static关键字,则静态内部类要写在伴生对象中,与java的区别是,创建内部类对象时new写在外面(java使用外部类.new 内部类,内部类与外部类对象无关),因此,内部类实例是与外部类对象关联的;
- 内部类访问外部类的属性使用外部类.this.属性(java使用外部类.class.属性通过反射,也可以看出与scala的区别,scala内部类访问外部类属性时与外部类实例相关联的,即this为外部类的一个实例);推荐使用内部类别名(别名=>后面跟内部类定义),在访问外部类属性时就可以使用别名.属性访问
- 类型投影:内部类的方法参数是内部类时,会与创建该内部类的外部类对象关联,可以在方法参数(内部类类型)前加#,表示屏蔽外部类对象对内部类的影响(java中内部类是属于外部类的,不存在类型投影,只要是这个内部类都可以调用)
第9章 隐式转换和隐式参数
- 隐式转换:implicit定义隐式转换函数,与函数名无关,只与函数签名(参数类型和返回值类型)
- 要保证在作用域内唯一匹配,可以由多个,不可以在隐式转换函数内使用隐式转换(会形参死递归)
- 隐式转换丰富类库功能:满足OCP原则(开闭原则)下,给其他类增加功能,比如一个引隐式转换函数返回另一个类A的对象,那么这个类就有了类A的功能
- 隐式值:变量使用implicit修饰,在其他参数声明为implicit时,会自动使用implicit修饰的变量代入,类似java的默认值,但作用范围更广;
- 要保证在作用域内同一类型唯一,匹配时不能有二义性
- 使用函数时不加(),否则会调用函数声明没有定义为implicit的那个同名函数
- 优先级:传值 > 隐式值 > 默认值
- 隐式类:使用implicit修饰类,比隐式转换丰富类库功能更加方便
- 所带的构造参数有且只能有一个
- 必须定义在类、伴生对象、包对象中,即隐式类不能是顶级的
- 隐式类不能是样例类,case class
- 作用域内不能有与之相同的标识符
- 隐式转换的时机:
- 方法中的参数类型与目标类型不一致,会去找隐式转换函数转换
- 对象调用类中不存在的方法时,编译器会自动对对象进行隐式转换
第10章 数据结构(上) - 集合
- scala同时支持不可变(imutable)和可变集合(mutable),默认采用不可变集合,并发安全,效率高
- scala集合的三大类:序列Seq、集Set、映射Map
- Array,不可变,定长数组:类似java数组
- 创建:new Array[Int](10),声明泛型,大小;apply方式:Array(1,2)创建,可以直接初始化内容,使用类型推导
- 索引使用小括号
- ArrayBuffer,可变数组:类似java的ArrayList
- 创建:ArrayBuffer[](),声明泛型,()可以初始化内容
- ()访问,()修改值,append添加,remove()删除
- Array和ArrayBuffer相互转换:使用toArray或toBuffer方法,本身没有变化,返回的是改变的结果
- 多维数组:Array.ofDim[Int](3, 4)
- scala数组与javaList的转换:
- new ProcessBuider(arr)返回javaArr,调用javaArr.command(),要引入隐式函数bufferAsJavaList
- 使用mutable.Buffer[xxx]=arrList,直接接收,要引入隐式函数asScalaBuffer,scala.collection.JavaConversioins.xxx
- tuple:最多只有22个
- 访问:._1或者produceElement(0)
- 遍历:需要使用迭代器:produceIterator
- List:在scala中是不可变的,属于Seq,如果要可变,使用ListBuffer
- List(1,2),直接存放数据,就是一个Object
- 空集合Nil
- 追加数据:使用list:+或+:list,返回新list;::符号,:::符号,运算规则从右向左,:::要求其左右都是集合对象,把左边集合对象扁平化后放入右边集合对象
- ListBuffer:可变
- ListBuffer[](1,2),new ListBuffer[]
- 添加:+=,append;添加list:++= (展开为++);添加单个元素::+
- 删除remove()
- Queue
- new mutable.Queue[Int]
- 添加元素:+=,++=(扁平化集合对象后加入)
- 入队出队:enqueue()(可以多个元素),dequeue()
- 取出队头队尾:head,last,除队头之外的tail
- Map:底层都是Tuple2类型,不可变的Map是有序的
- 创建:mutable.Map(xx -> xx)(类型推导,或者使用二元组创建((key, value)..)),new mutable.HashMap[xxx, xxx]
- 查找:1> xxx(),key不存在抛异常,java返回null;2 > 判断是否存在:contains(key);3 > map.get(key).get,没有key也抛异常;4> getOrElse(xx, xx),存在返回,不存在返回默认值
- 添加:xxxMap(key)=value,有则更新,无则添加;+=
- 删除:-=
- 遍历:同时遍历kv((k,v) <- map),只遍历k(map.keys),只遍历v(map.values),遍历kv二元组(x <- map)
- Set:默认不可变
- 创建mutable.Set(1,2)
- 添加:add,+=
- 删除:remove,-=
第11章 数据结构(下) - 集合操作
- 函数赋值给变量不运行在函数后使用下划线 _
- 高阶函数
- 高阶函数映射map,集合类型都有,对集合中的每一个元素执行map函数传入的函数f
- 高阶函数扁平化flatmap,将集合中的每个元素的子元素映射到某个函数并返回新的集合
- 高阶函数过滤filter,对集合中的元素进行过滤,传入函数的返回值为Boolean类型
- 高阶函数化简reduce,reduceLeft,reduceRight,将二元函数引用于集合中的函数,它的泛型类型def reduceLeft [B >: A](op (B, A) => B): B,即传入的函数满足,输入B和A类型,返回B类型;那么reduceRight的应该是B和A类型返回A类型
- 高阶函数也可以传入匿名函数(_ + _)
- 高阶函数折叠fold,foldLeft,foldRight,提供初始的起点(其他list),使用函数的柯里化,reduce是简化版的fold;缩写形式(/:,:\),例如:list.foldLeft(2)(minus) 等价于(2 :/ list)(minus),list.foldRight(2)(minus) 等价于(list :\ 2)(minus)
- 高阶函数扫描scanLeft,scanRight,对某个集合中的所以元素做fold,但会把产生的中间结果保存到一个集合中保存
- 扩展
- 拉链zip,返回一个元组的list,使用:list1.zip(list2),分别出元素组成元组返回
- 迭代器iterator,使用list.iterator,可以使用while循环使用,有hasNext和next方法
- 流集合:可以存放无穷多个元素,末尾元素遵循lazy规则,def xx(n: Bigint) : Stream[Bigint] = n #:: xx(n+1),val stream = xx(2),查看头元素stream.head,查看尾元素.tail(会触发产生新的元素),不能使用last属性,会产生无限循环
- Stream的懒加载特性也可以对其他集合应用view方法得到类似的效果,1> view方法产生出一个总是被懒执行的集合,2>view不会缓存数据,每次都要重新计算
- 线程安全的集合:都以Synchronized打头,如SxxBuffer,Map,PriorityQueue,Queue,Set,Stack
- 并行集合:集合后使用.par可以进行并行计算
- 操作符:
- 使用保留字作为标识符,用反引号引住
- 中置操作符:A + B,也可以A.+(B)
- 后置操作符:A++,或者A.++,定义时参数为(),否则需要加()调用
- 前置操作符:!A,定义时需要在操作符前加unary:unary_!
第12章 模式匹配
- xxx match { case xx => 语句 },默认case _,都没有匹配会抛异常,所以在最后都要有默认匹配,默认匹配放第一个后的就匹配不到了
- case后有条件守卫if时,_表示不接收值
- 模式中的变量:case后写一个变量,会将匹配的值交给变量;match case有返回值,可以赋值给一个变量
- 类型匹配:case a: Int => xxx,可以直接匹配类型;case _: Int => xxx,类型匹配前_表示隐藏变量名
- 预先检测是否有可能的匹配,如果没有则报错;
- 匹配数组:Array(0)表示匹配只有一个元素且为0的数组;Array(x,y)匹配有两个元素的数组;Array(0, _*)匹配以0开头的数组
- 匹配列表:0 :: Nil匹配0;x :: y :: Nil匹配两个元素的;0 :: tail匹配以0开始的
- 匹配元组:(0, _)表示0开头,(y, 0)
- 匹配对象:case对象中的unapply方法(对象提取器,单个参数,多个参数:unapplySeq)返回Some集合则匹配成功,返回none则匹配失败
- 变量声明中的模式使用
- val (x, y) = (1,2);一次声明多个变量
- val (q, r) = BigInt(10) /% 3,分别把/和%的值给q和r
- val arr=Array(1,2,3,4) val Array(first, second, _*)=arr 取出arr的前两个元素
- for表达式中的模式
- for((k,v) <- map),分别赋值kv
- for((k,0) <- map),筛选v为0的
- for((k,v) <- map if v== 0),筛选v为0的
- 样例类:case class,会自动生成很多方法:apply,unapply,copy,toString
- case中的中置表达式:1+2就是一个中置表达式,如果unapply方法产出一个元组,可以在case语句中使用中置表示法
- 比如匹配一个序列:List(1,2,3,4), 它的unapply方法产出是一个元组
List(1,2,3,4) match {
case first :: second :: rest => println(first + " " + second + " " + rest.length)
case _ => println("匹配不到")
}
- 匹配嵌套结构:
- 1 - 如果我们进行对象匹配时,不想接收某些值,则使用_忽略即可,_*表示所有
- 2 - 通过@表示法将嵌套的值绑定到变量,case (_, _, art @ Book(_, _), res @ _*) => (art, res)
- 3 - 通过@表示法将嵌套的值绑定到变量,case (_, _, art @ Book(_, _), res) => (art, res)
abstract class Item // 项
case class Book(description: String, price: Double) extends Item
//Bundle 捆 , discount: Double 折扣 , item: Item* ,
case class Bundle(description: String, discount: Double, item: Item*) extends Item
//给出案例表示有一捆数,单本漫画(40-10) +文学作品(两本书)(80+30-20) = 30 + 90 = 120.0
val sale = Bundle("书籍", 10, Book("漫画", 40), Bundle("文学作品", 20, Book("《阳关》", 80), Book("《围城》", 30)))
def price(it, Item): Double = {
it match {
case Book(_, p) => p
case Bundle(_, disc, its @ _*) => its.map(price).sum - disc
}
}
- 密封类:如果想让case类的所有子类都必须在申明该类的相同的源文件中定义,可以将样例类的通用超类声明为sealed,这个超类称之为密封类。密封就是不能在其他文件中定义子类
第13章 函数式编程高级
- 偏函数:PartialFunction,对集合中的每个元素先判断isDefinedAt是否返回true,返回true调用apply,返回false则不调用apply,所有apply的返回值放在一个集合里,重写两个方法,isDefinedAt,apply;使用:collect(pf)
val list = List(1, 2, 3, 4, "abc")
val pf = new PartialFunction[Any, Int] = {
def isDefinedAt(any: Any) = any.isInstanceOf[Int]
def apply(any: Any) = any.asInstanceOf[Int] + 1
}
list.collect(pf) // list(2,3,4,5)
## 简化形式1, 直接定义新函数,类型为PartialFunction
def pf2: PartialFunction[Any, Int] = {
case i: Int => i + 1 // case语句可以自动转换为偏函数
}
val list2 = List(1, 2, 3, 4,"ABC").collect(f2)
## 简化形式2
val list2 = List(1, 2, 3, 4,"ABC").collect{
case i: Int => i + 1
}
- 作为参数的函数:函数的类型为<function1>,两个参数为<function2>,可以使用(f _)打印出来,这里_表示不执行函数;在list.map.(f(_))中_表示集合中的每一个元素
- 匿名函数:(xxx => xxx)
- 不需要写def
- 不需要写返回值类型,使用类型推断
- =变成=>
- 如果有多行使用{}
- 高阶函数:函数可以接收多个函数,也可以返回函数
- 参数(类型)推断:在某些情况下,参数的类型是可以推断出来的,比如list=(1,2,3),list.map(),map中的函数参数的类型是可以推断出来的,同时也可以进行简写:
- 参数类型是可以推断时,可以省略参数类型,map((x) => x + 1)
- 当传入的函数,只有单个参数时,可以省去括号,map(x => x + 1)
- 如果变量只在=>右边只出现一次,可以用_来代替,map( _ + 1),reduce( _ + _)(因为其中x,y都是出现一次)
- 闭包:闭包就是一个函数和与其相关的引用环境组合的一个整体,如def minusxy(y: Int) => x – y返回的是一个匿名函数 ,因为该函数引用到到函数外的x,那么 该函数和x整体形成一个闭包,如:这里 val f = minusxy(20) 的f函数就是闭包
- 返回函数是一个对象,而x就是该对象的一个字段,他们共同形成一个闭包
- 当多次调用f时(可以理解多次调用闭包),发现使用的是同一个x, 所以x不变
- 在使用闭包时,主要搞清楚返回函数引用了函数外的哪些变量,因为他们会组合成一个整体,形成一个闭包
- 函数柯力化:接收多个参数的函数都可以转化为单个参数的函数
- 抽象控制:定义一个函数f,接收没有参数也没有返回值的函数f1,def f(f1(): => Unit),在f中调用了f1,那么在之后调用f时,可以使用f{()=>代码块},代码块的内容为f中调用f1的地方
- 简化:def f(f1: => Unit),那么调用时f{代码块}就可以了,参考breakable{while{break()}}的写法
- 实践:实现类似while循环的函数until(){}
第14章 递归
第15章 项目
第16章 AKKA
- Akka是JAVA虚拟机JVM平台上构建高并发、分布式和容错应用的工具包和运行时,你可以理解成Akka是编写并发程序的框架
- Actor模型:
- ActorSystem负责创建并管理其创建的 Actor
- Actor之间通信:持有对方的Actor引用(代理)才能发送消息,首先发送给Dispatcher Message(线程池),然后Dispatcher Message发送给指定的Mailbox,Mailbox再推送给它的Actor
- 接收Actor中的receive方法用来接收Mailbox推送的消息(Mailbox调用receive方法),send方法可以得到发送消息的Actor的ActorRef,通过这个ActorRef,也可以回复消息
- Akka网络编程
- 初始化server引用:ActorSelection类型变量,重写preStart方法,在里面使用context.actorSelection(server地址)
- 创建引用:用ActorSystem的对象调用actorOf(Props[])
第17章 设计模式
- 简单工厂模式:属于创建型模式,定义了一个创建对象的工厂类,由这个类来封装实例化对象的行为
- 工厂方法模式:定义抽象类,将对象的实例化推迟到子类
- 抽象工厂模式:定义一个trait用于创建相关或有依赖关系的对象簇,可以将简单工厂模式和工厂方法模式进行整合,将工厂方法抽象为两层,抽象工厂和具体实现的工厂子类,这样可以根据创建对象的类型使用对应的工厂子类
- 单例模式:保证某个类只有一个实例
- 装饰者模式:动态的将新功能附加到对象上,就像打包一个快递
- 主体(compoment)
- 包装(Decorator)
- 具体的主体(ConcreteComponent),如果ConcreteComponent类很多,还可以设计一个缓冲层,将共有的部分提取出来,抽象层一个类
- 观察者模式:一个Subject(特质,有一系列抽象方法用于管理观察者,使用一个类A继承这个特质,实现抽象方法,类A的对象用来管理观察者),一个Observer(有一个update方法,等待其他观察者实现,在类A中调用所有观察者的update方法就可以实现通知所有观察者,因为所有的观察者都实现Observer特质),增加观察者只需要实现Observer特质,在类A的对象那里注册就可以了
- 代理模式:本地代理,远程代理(RMI),动态代理
第18章 泛型、上下界、视图界定 、上下文界定、协变逆变不变
- 泛型:在类和函数中都可以指定泛型[]
- 类型约束上下界:
- 上界:java中使用<T extends A><? extends A>,scala中使用[T <: A] [_ <: A]
- scala中有java类型到scala的隐式转换
- 下界:java中使用<T super A><? super A>,scala中使用[T >: A] [_ >: A]
- 如果T是A的父类可以传进去,如果是A的子类也可以传进去,返回类型是A,调用方法时如果子类重写了A类方法则调用子类方法;如果是与A无关的类传进去,则返回类型是Object
- 视图界定:[T <% A] [_ <% A],比上下界范围更广,支持隐式转换
- 上下文界定:配合隐式值使用
- 方式一:定义类时使用函数柯力化,使用隐式参数参数为(implicit xxx: xx)
- 方式二:将隐式参数放入方法内,def f1(implicit xxx: xx)
- 方式三:使用implicitly代替,val xxx = implicitly[xx]
- 协变逆变不变:带类型参数的类型,比如List[T]
- 如果对A及其子类型B,满足List[B]也是List[A]的子类型,那么称为协变,List[+T]
- 如果对A及其子类型B,满足List[A]是List[B]的子类型,那么称为逆变,List[-T]
- 如果一个类型支持协变或逆变,称为可变,否则称为不可变
---
本文来自博客园,作者:Bingmous,转载请注明原文链接:https://www.cnblogs.com/bingmous/p/15643703.html