• Scala Important Tips For Newbie => Scala入门小纸条(2)


    101. 只有主构造器可以调用超类的主构造器。主构造器调用超类构造器,辅助构造器调用主构造器,不能跨越。scala也用super来调用超类的方法

    102. scala重写非抽象方法必须用override;除了方法,scala也能重写字段

    103. 特质是将java接口变为更加通用的scala概念

    104. 判断是某个类的对象,而不是子类,可用如p.getClass==classOf[Employee],其中classOf方法在scala.Predef被定义,会被自动引入 

    105. 与类型检查和转换相比,模式匹配是更好的选择

    106. scala也有protected修饰符,但与java不同,它对于类所属的包是不可见的,可以用包修饰符来获取可见性

    107. protected[this]、private[this]、protected[类名/包名]、private[类名/包名]都是常见的修饰符

    108. scala类/特质/对象可以继承和扩展java类

    109. scala的final在修饰类时,和java一样,都表示不能被扩展。在修饰方法时,也和java一样,都表示不能被重写。但在修饰字段时,稍有区别,在java中表示不可变,同时不能被重写;但在scala中只表示不能被重写,要想不可变,要加上val,否则var就是可变的

    110. val也可以重写抽象的def;注意,def修饰字段不带参数,这与修饰函数不同;还有一种理解,def就是修饰方法,只不过这里用字段(指val)来重写方法(指def)

    111. def只能重写另一个def;val只能重写另一个val或不带参数的def;var只能重写抽象的var 

    112. 用var不如用def,特别是需要在子类中扩展它时,因为var没得选,所有子类只能被动接受

    113. scala的抽象方法不需要用abstract修饰,只需要省去方法体即可;子类重写超类的抽象方法,不需要override关键字;抽象字段是指没有初始值的字段;和方法一样,子类中重写超类的抽象字段,也不需要override关键字

    114. 提前定义语法,让你在超类的构造器执行之前初始化子类的val字段,具体类似class  Ant  extends {override val range=2} with Creature,注意,提前定义只能引用之前已经定义的字段或方法 

    115. 可以用-xcheckpoint编译器标志来调试构造顺序的问题。构造顺序问题的根本原因在于,jvm语言的一个设计决定,即允许超类的构造方法调用子类的方法

    116. scala继承层级中,AnyVal和AnyRef都继承Any;Any是根节点,而Any又扩展了Nothing特质,Any类定义了isInstanceof和asInstanceof方法

    117. AnyVal是所有基本类型和Unit的根节点,而AnyRef是所有其他类的父类,AnyRef等价于java中的Object;AnyRef添加了带参数的synchronized方法,该方法等价于java中的同步块,当然它也包括Object类的wait、notify/notifyAll

    118. Nothing是所有类型的子类型,而Null是所有引用类型的子类型

    119. Null类型的唯一实例是null;可以将null赋值给任何引用,但不能给值类型,比如将Int设为null是错误的

    120. Nothing类型没有实例,它在泛型结构时被用到;Nothing和java中的void是两个概念,scala中void用Unit表示,且Unit只有一个值,就是(),且Unit不是任何其他类型的超类型(Nothing除外)

    121. 所有scala类都实现ScalaObject这个特质或标记接口,且ScalaObject接口没有任何方法;注意:有些书上说ScalaObject扩展自AnyRef,而有的书认为二者独立(我倾向这种)

    122. 在我安装的scala中AnyRef的源码是  val AnyRef = new Specializable { override def toString = "object AnyRef" },和书上说的AnyRef是一个类似Object的类有出入。明白了,其实AnyRef有两层含义,另一个作为类的实现(如T <: AnyRef中)的确如书所述。另外,我无法找到ScalaObject在scala源码的位置,猜测也是在jvm内部实现的,可以在评论区留言讨论

    123. AnyRef的eq方法用来判断两个引用是否指向同一个对象,其中equals方法调用eq

    124. 读取文件可以用scala.io.Source,它有fromFile方法,再调用getLines方法,得到Iterator,然后可以利用for逐条处理,或直接对迭代器执行toArray或toBuffer;用完Source对象后要关闭

    125. 把Source对象当做迭代器,可以读取单个字符;执行Source对象的buffered方法可以查看但不处理,类似peek功能;Source对象的mkString方法将整个文件读取为一个字符串

    126. source.mkString.split("\\s+")将读取文件并以空格分割,其中source为Source对象

    127. Predef.scala定义了readInt / readDouble / readLong等方法用于从控制台读取数字,但是目前已经迁移到scala.io.StdIn了

    128. 除了fromFile,Source的其他常用方法有fromURL / fromString / stdin等

    129. scala没有提供读取二进制文件的方法,可以使用java类库,如使用File和FileInputStream来读取文件;scala同样没有内建的写入文件的支持,可以使用java的PrintWriter写入文件

    130. scala要访问目录的所有文件,可以利用递归+函数式的方式遍历,具体可以用java的File和Directory类,结合自身的filter和flatMap方法。也可以用java nio的Files类的walkFileTree方法

    131. scala使用Serializable特质来实现序列化;scala集合类都是可序列化的;如果能接受缺省的id,可以略去@SerialVersionUID 

    132. scala.sys.process包提供了与shell交互的工具;"ls -al .."!中的!本质上是调用了ProcessBuilder对象,它的返回结果是执行程序的返回结果,成功为0,否则就是非0;如果使用!!而不是!,那么会以字符串形式返回

    133. #|是管道,#>是重定向,#>>是追加,#<是把文件或url作为输入

    134. p#&&q表示p成功则执行q,p#||q表示p不成功则执行q 

    135. scala只是给标准的shell操作加上了#前缀,因此优先级是相同的

    136. scala.util.matching.Regex是正则表达式类,有findAllIn、findFirstIn、findPrefixof、replaceFirstIn、replaceAllIn等方法

    137. 特质允许给出方法的特定实现;类可以使用任意数量的特质;多个特质叠加的时候顺序很重要,方法先被执行的特质排在后面;scala和java一样不允许多重继承;scala只有特质,没有接口;特质中的抽象方法不需要加abstract,因为默认就是抽象的;在重写特质的抽象方法时也不需要写override(如果没有用super.xxx来调用特质的抽象方法,因此方法最终有具体实现)

    138. 比起接口,特质和类更相似,因为都使用extends,而不是实现;扩展多个特质,除了第一个用extends,后面都用with;所有java接口都可以作为特质使用

    139. 解读时,Logger with xx with yy 是一个整体,然后才是extends;问题来了,怎么区分第一个是类还是特质,比如这里的Logger?答案是无法区分

    140. 让特质拥有具体行为有一个弊端,就是特质改变时,所有混入该特质的类,都必须重新编译

    141. 可以在构造对象阶段混入特质,具体如new SavingAccount with ConsoleLogger;而另一个对象可混入不同特质;思考:这种语法有什么优缺点?

    142. 特质能形成任意形态的图或树,而不仅仅是链 

    143. new SavingAccount with aa with bb,会先执行bb的log方法,再执行aa的log方法,也就是说,特质总是从最后一个开始处理;如果bb通过super.log调用了aa的log方法(根据类的线性化或叫继承层级),那么看上去就是aa的log先执行

    144. 如果想控制哪个特质的方法被调用,可以使用super[xxLogger].log这种形式,注意这里给出的必须是直接超类型,而无法使用继承层级中更远的特质或类(不太理解,可以留言讨论)

    145. 重写特质的抽象方法,如果用了super.xxx调用特质的抽象方法,需要加上abstract override,如果不加会报错,因为它最终还是抽象方法

    146. 特质可以当做富接口使用,例如Iterator就利用抽象的next和hasnext定义了几十个方法

    147. 如果特质中有具体字段,那么混入该特质的类自动获得该字段,但不能看成是继承的,只是简单的被加入子类,说白了,原因在于,scala也是单继承,但可以实现多个特质,因此为了避免歧义,特质的字段不能看成是继承的

    148. 特质中的抽象字段在具体子类中必须被重写,但不用写override 

    149. 与接口不同,与类相同,特质也可以有构造器,是直接混在主体中的常规语句。这些语句在混入该特质的对象的初始化时会被执行 

    150. 构造器的执行顺序规则如下,首先是超类的构造器,再是特质构造器,最后是本身的类构造器,其中特质构造器是从左到右被构造,每个特质又是父特质先被构造,如果多个特质有共同的父特质,该父特质被构造后不再重复构造(整个过程,理解起来很自然)

    151. 简单来说,构造器的顺序是类的线性化的反向!!!(线性化指的是在包含特质和类的继承关系中,super被解析的顺序;线性化是描述某个类型的所有超类型的一种技术规格)

    152. 特质不能有构造器参数,这是和类的唯一技术区别 

    153. 没有构造器参数有时会是问题,人们想到用抽象字段,但存在陷阱,问题在构造顺序上,解决办法是用提前定义,即在new后面加提前定义块,或者在子类extends后面加提前定义块,还有一种方法是使用lazy的变量,会在首次使用时初始化,但是懒值的每次检查会导致没那么高效

    154. 特质可以继承类,但不常见,该类会自动成为混入该特质的对象或类的超类

    155. 当特质以this:类型=>开始定义时,便只能被混入指定类型的子类,这叫做自身类型,即self type

    156. 带自身类型的特质和带超类的特质很相似,都能保证混入该特质的类具有特定类型的特性

    157. 自身类型更灵活,且能解决特质的循环依赖问题,即两个特质彼此需要

    158. 自身类型也可以处理结构类型,即this后面给出类必须拥有的方法,而不是类名,即这个特质可以被混入任何带有该方法的类

    159. 特质的jvm层原理,只有抽象方法的特质会变为java接口,如果特质有具体方法,会创建出一个伴生类,用静态方法存放具体方法,所谓伴生类,就是和接口同名的java类

    160. 反引号能用来定义特殊的标识符,比如val `val`=42

    161. to是中置操作符,表示位于两个参数之间,->也是,实际上是方法调用

    162. 中置操作符是二元的,而一元操作符分为前置和后置,比如tostring是后置,而+ -!~是前置,其中前置会被转换成unary_操作符,比如-a就是a.unary_-

    163. 后置操作符优先级低于中置操作符 

    164. scala中大多数操作符都是左结合的,除了以冒号结尾的比如构造列表的::,以及赋值操作符,比如1::2::Nil等价于1::(2::Nil),同时注意2::Nil意思是Nil.::(2)

    165. apply和update方法很基础很重要,apply方法可被用于伴生对象,这样避免显式使用new,数组和映射的修改值本质上调用了update方法

    166. 提取器是带unapply方法的对象,它是apply的反操作,它接收对象然后提取值,而apply是给出值得到对象,unapply通常返回一个Option

    167. 每个样例类(即case class)都自动具备apply和unapply

    168. 提取器也可以只测试输入,而并不将值提取出来,此时需要返回一个boolean

    169. 可以定义unapplySeq方法来提取任意长度的值的序列,返回Option[Seq[A]],A可以是String等被提取的值的类型,然后可以结合模式匹配

    170. scala中函数是头等公民,就和数字一样

    171. 可以在变量中存放函数,如val fun=ceil _ ;技术上讲,_将ceil从方法转为了函数,因为在scala中,只能直接操纵函数,而不能直接操纵方法;这里的fun是一个包含函数的变量,而不是固定的函数

    172. scala有匿名函数,因为函数像数字一样,可以不用起名,可以传给map使用

    173. 函数命名或创建可以用val或def

    174. 在调用map时可以加或不加".",比如Array(, ,).map{}或者Array(, ,) map{},注意,这里使用花括号,也能使用圆括号

    175. 高阶函数是接受函数作为参数的函数,类型是(参数类型)=>(结果类型),比如((Double)=>Double)=>Double

    176. scala支持匿名函数的类型推断,即不需要写类型比如Double,可以直接写(x)=>3*x,甚至对于只有一个参数的情况,可以省略圆括号,即x=>3*x,甚至当参数在右侧只出现一次,可以写成3 * _,这是终极版本,注意,该写法只在类型已知或说能推断出的情况下有效

    177. 常用的高阶函数有map filter reduceLeft foreach sortWith等,比如(1 to 9).map (0.1 * _) 以及 (1 to 9).map("*" * _).foreach(println _);注意:foreach和map不同在于,它没有返回值

    178. 闭包是一个函数,它由代码和代码用到的任何非局部变量定义构成;函数可以在变量不再处于作用域时被调用,这是闭包的特征;scala的闭包实际上是以类的对象方式实现的,比如该类有实例变量即参数如factor,以及包含了函数体的apply方法

    179. java中的SAM是只含单个抽象方法的接口,其中SAM指single abstract method,因为java不支持函数,必须将动作方法放到实现某接口的类中;java的样本代码在scala中可以用隐式转换来代替,即implicit修饰的函数,只需将隐式函数与相关代码放在一起

    180. 柯里化是指将原来接受两个参数的函数变为接受一个参数的函数的过程,新函数返回以原有第二个参数为参数的函数

    181. 柯里化可以把某个函数参数单独拎出来,来提供更多用于类型推断的信息;一个稍复杂的柯里化例子是a.corresponds(b)(_.equalsIgnoreCase(_)),其中a b都是Array

    182. 换名调用表示法是在参数声明和调用函数参数的地方省略掉(),但保留=>,比如将原来函数参数从block: ()=>Unit变为block: =>Unit,并在调用时将() =>println("Hi")变为println("Hi")

    183. 可以利用柯里化实现换名调用和换值调用,区别是函数被调用时,参数表达式会不会被求值

    184. scala中一般不需要显式return,除非用于从匿名函数返回值给调用它的带名函数;另外要注意,要想在带名函数中使用return,必须给出返回类型,因为编译器无法推断它会返回比如Int

    185. scala所有集合都扩展自Iterable特质,包括Map、Set、Seq,后三者又分别被SortedMap、SortedSet、IndexedSeq扩展

    186. 集合(某种Iterable)调用iterator得到Iterator;而Iterator有hasNext和next等方法

    187. scala集合包括可变和不可变两类;scala优先采用不可变集合;Predef.Map其实就指向scala.collection.immutable.Map;可以基于不可变集合创建新集合,特别是在递归中

    188. Seq是有先后顺序的序列,子类包括Vector、Range、List、Stream、Stack、Queue、ArrayBuffer、LinkedList、DoubleLinkedList、PriorityQueue等,其中ArrayBuffer是Vector的可变版本;Vector是利用树形结构实现的;Range只存储起始值、结束值、增值,可用to、until构造

    189. scala的列表(即List)要么是Nil(即空表),要么是一个head元素加上一个tail,而tail又是一个列表,比如val d=List(4,2)中d.head是4,而d.tail是List(2),进一步d.tail.head是2,d.tail.tail是Nil

    190. LinkedList是List的可变版本,可以对elem引用赋值来修改头部,对next引用赋值来修改尾部,此时不再有head和tail;DoubleLinkedList比LinkedList多了一个prev引用 

    191. 如果要将列表某个元素设为最后一个节点,不能将next设为Nil或null,而应设为LinkedList .empty 

    192. scala的Set以哈希集实现,以hashcode进行组织,Set中查找元素比列表和数组快的多;Set没有先后顺序,但SortedSet的元素以某种排过序的顺序访问,有可变和不可变两种版本;LinkedHashSet是链式哈希集,可以记住元素被插入的顺序,只有可变版本

    193. 位集(bit set)是集的一种实现,以一个字位序列的方式存放非负整数,如果集中有i,则第i个字位是1,scala提供了可变和不可变的两种BitSet实现

    194. Set的操作有union(或|、++)、intersect(或&)、diff(或&~、--)、subsetOf、contains等

    195. 集合中的添加或删除元素操作中,+用于将元素添加到无先后次序的集合,而+:和:+将元素添加到有先后次序的集合的开头或结尾,比如4+: ArrayBuffer(1, 3, 2):+2、4+:List(1,3,2):+2、4+:Vector(1,3,2):+2 以及Set(2,3,1,0,1)+1、Map(1->'a',2->'b')+(3->'c')

    196. 与+、+:、:+关注有序无序不同,+=关注可变性,即它一般只适用于可变集合,用于修改左侧变量;比如ArrayBuffer(1,2,3)+=4,但是对于不可变集合,当配合var时,也有可以使用的可能性,比如var s=Set(2,3)再s+=4是可以的,以及var v=Vector(1,2)再v:+=3也是可以的

    197. 对于列表优先使用::和:::,比如77::List(1,2,3):::List(88,99),注意一个特殊情况,模式匹配不认可+:操作符,如case h::t

    198. 集合的改值操作有+=、++=、-=、--=

    199. 尽量不用++:、+=:和++=:,如List(1,2,3)++:List(99)、1+=:ArrayBuffer(9,10)以及ArrayBuffer(1)++=:ArrayBuffer(9,10)

    200. 每个scala集合的特质或类都有一个带apply方法的伴生对象,可用来构建该集合的实例,不用new,叫做统一创建原则

  • 相关阅读:
    【飞桨PaddleSpeech语音技术课程】— 声纹检索系统与实践
    【网络安全】——sql注入之云锁bypass
    伦敦金投资为什么要止损?
    快速排序全面详解
    聊一聊向上管理
    IJCAI2023【基于双曲空间探索的非独立同分布联邦学习】
    【PAT甲级】1010 Radix
    绘图软件 OmniGraffle mac中文版特点说明
    插值查找的简单理解
    Chrome浏览器关闭左上角搜索标签页的解决办法
  • 原文地址:https://blog.csdn.net/fearlesslpp/article/details/125467224