Scala语言学习

Scala语言学习

基础

  • 安装Scala
  • REPL(Read-Evaluate-Print-Loop shell)
  • 处理数据
    • 字面量:直接出现在源代码中的数据
    • valueval <identifier>[: <type>] = <data>
      • 不可变的、有类型的存储单元,支持类型推导
    • 变量variablevar <identifier>[: <type>] = <data>
      • 可变的、有类型的存储单元,支持类型推导
      • 可重新赋值,但是不可改变数据类型(支持类型转换的例外)
    • 命名:支持使用字母、数字和一些特殊的操作符字符
      • 例如可以使用*、:、+、\pi、\phi等,不能使用[].

      • 合法命名规则

        • 不以数字开头
          • 以数字开头的话,编译器最初会把解析到的第一个数字当成一个字面量数字,然后后面解析到其他非数字字符就会出错
        • 一个或多个除反引用号外的任意字符,这些字符必须包围在一对反引号之中
        val `a.b` = 4
        
  • 类型
    • 核心数值类型

      • ByteShortIntLongFloatDouble
      • toType方法手动完成类型间转换
      • JVM包装数值类型可以互操作
    • 字符串String

      • 建立在JavaString基础上

      • Java不同,==会检查字符串真正的想等性,而不是对象引用想等性

      • 三重引号创建多行String,类似Python """ your string """

      • 字符串内插

        • 在字符串的第一个双引号前面增加一个s前缀,使用美元符$指示插入数据的引用(某些容易区分的情况下,大括号{}可以省略,但是加上更容易区分)
        val approx = 355/113f
        println(s"Pi, using 355/133, is about ${approx}.")
        
      • 字符串内插的替代格式可以使用printf语法(java.util.Formatter

        • 在字符串前面添加f前缀
        scala> val item = "apple"
        scala> f"Enjoying this $item ${355/133.0}%.5f times today.")
        
      • 正则表达式(基于Javajava.util.regex.Pattern

        • 用正则表达式捕获值val <Regex value>(<identifier>) = <input string>)
        • val pattern = """.* apple ([\d.]+) times .*""".r
        • val pattern(amountText) = input
        • val amount = amountText.toDouble
    • Scala类型概述

      Scala类型概述

      • Nothing:所有类型的子类
      • Null:所有指示null值的AnyRef类型的子类
      • Unit:指示没有值(类似void
      • AnyScala中所有类型的根
      • AnyVal:所有值类型的根
      • AnyRef:所有引用(非值)类型的根
    • Scala不支持其他类型到Boolean类型的自动转换。非null字符串不会计算为true

    • 元组 (<value 1>,<value 2>[,<value 3>...])

      • 不同于列表和数组,没有办法迭代处理一个元组中的元素,元组的作用只是作为多个值的容器
      • 根据元素的索引访问元组中的元素(下划线加上索引,索引从1开始)
      • 创建一个大小为2的元组,也可以使用关系操作符(->),表示元组中的键值对

表达式和条件式

  • 表达式块(用{}结合多个表达式创建,最后一个表达式作为表达式块的返回值)

    • 可嵌套、跨行,多个表达式写在一行需要;分隔
  • 语句:不返回值的表达式

  • if...else表达式块

    • if (<Boolean expression>) <expression>
    • if (<Boolean expression>) <expression> else <expression>
  • 匹配表达式(类似switch

    • Scala匹配表达式支持匹配如值、类型、正则表达式、数值范围和数据结构内容
    <expression> match{
        case <pattern match> => <expression>
        [case ...]
    }
    
    • 可以使用模式替换式(pattern alternative),匹配多种内容
    • case <pattern 1> | <pattern 2> .. => <one or more expressions>
    • 增加通配模式,放止输入内容无法匹配的情况运行时异常
      • 值绑定模式case <identifier> => <one or more expressions>
        • 将输入绑定到一个特定的字面量上,如果不匹配就会进入这个绑定值的情况
      • 使用通配符 _:相当于匿名占位符,将在运行时替换为一个表达式的值
        • 与值绑定的不同在于不能再case表达式块中访问通配符的值
    • 用模式哨位匹配
      • 增加一个if语句,为匹配表达式增加条件逻辑
      • case <pattern> if <Boolean expression> => <one or more expression>
    • 用模式变量匹配类型
      • 匹配输入表达式的类型
      • case <identifier>: <type> => <one or more expressions>
  • 循环

    • Range(范围)数据结构

      • <starting integer> [to|until] <ending integer> [by increment]
    • 基本for循环迭代处理

      • for (<indentifier> <- <iterator>) [yield] [<expression>]
      • yield:如果指定,则调用的所有表达式的返回值将作为一个集合返回,如果没有指定,则调用表达式,但是不能访问它的返回值
      • for (x <- 1 to 7){println(s"Day $x:")}
    • 迭代器哨位(过滤器)

      • for (<indentifier> <- <iterator> if <Boolean expression>)...
      • threes = for(i <- 1 to 20 if i%3 == 0) yield i
    • 嵌套迭代器

      for{ x <- 1 to 2
           y <- 1 to 3 }
         { print(s"($x,$y) ")}
      
    • 值绑定

      • for (<indentifier> <- <iterator>; <identifier> = <expression>)...
      • for(i <- 0 to 8; pow = 1 <<i) yield pow
    • WhileDo/While循环

      • while(<Boolean expression>) statement

函数

  • 定义无输入的函数
    • def <identifier> = <expression>
    • def <identifier>() = <expression>
    • 如果定义函数时没加小括号,则调用时也不可以加小括号,而加了小括号的无输入函数调用时可以不加小括号
  • 定义函数时指定返回类型 def <identifier>: <type> = <expression>
  • 定义函数 def <identifier>(<identifier>: <type>[,...]): <type> = <expression>
  • 过程:没有返回值的函数
  • 使用表达式块调用函数
    • 当使用一个参数调用函数时,可以使用表达式块返回的结果发送参数
  • 递归函数
    • 递归函数可能会遇到“栈溢出”错误,Scala编译器可以使用尾递归优化一些递归函数
    • 利用尾递归优化的函数,递归调用不会创建新的空间,而是使用当前函数的栈空间
    • 只有最后一个语句是递归调用的函数才能由Scala编译器完成尾递归优化
    • 可以利用函数注解来标志一个函数将完成尾递归优化@annotation.tailrec
  • 嵌套函数
  • 用命名参数调用函数
    • 可以不按原先定义的顺序指定参数
  • 有默认值的参数
  • Vararg参数
    • 这是一个函数参数,可以匹配调用者的0个或多个实参
    • vararg参数后面不能跟非vararg参数,因为无法加以区分
    • vararg参数实现为一个集合,可以通过迭代器访问
    • 要标志一个参数匹配一个或者多个输入实参,在函数定义中需要该参数类型后面增加一个*
    • 例如def sum(items: Int*): Int = {...}
  • 参数组
    • 可以把参数表分解为参数组,每个参数组用小括号分割
    • 例如def max(x:Int)(y:Int) = if (x > y) x else y
  • 类型参数
    • 类型参数指示了值参数类型或返回值类型
    • 函数参数或返回值的类型不再固定,而是可以由函数调用者设置
    • def <function-name>[type-name](<parameter-name>:<type-name>): <type-name>...
    • 例如def identity[A](a: A): A = a
  • 方法和操作符
    • 中缀点记法:<class instance>.<method>[(<parameters>)]
    • Scala中使用的所有算术运算符都是方法
    • Scala的操作符记法允许使用空格分隔对象、操作符方法和方法的参数(只有一个)
    • 例如2 + 32.+(3)是一样的
    • 操作符记法:<object> <method> <parameter>
      • 如果要调用多个参数,则必须将多个参数包含在()

首类函数

  • “首类”表示函数不仅能得到声明和调用,还可以作为一个数据结构用在这个语言的任何地方
  • 高阶函数:接受其他函数作为参数,或者使用函数作为返回值
  • 声明式编程:要求使用高阶函数或其他机制声明要做的工作,而不手动实现
  • 命令式编程:总要明确指定操作的逻辑流
  • 函数类型和值
    • 函数的类型是其输入类型返回值类型的一个简单组合,用一个箭头从输入类型指向输出类型

    • ([<type>, ...]) => <type>

      def double(x: Int): Int = x*2
      val myDouble: (Int) => Int = double
      val myDoubleCopy = myDouble
      val myDouble2 = double _
      
      • myDouble就是一个值,只不过这个值可以调用
      • 一个函数值可以赋值给一个新值
      • myDouble必须有显示的类型,以区分它是一个函数值,而不是一个函数调用(如果函数只有单个参数可以省略小括号)
      • val <identifier> = <function name> _:使用通配符为函数赋值
  • 函数字面量
    • 创建一个没有名字的函数并把这个函数赋值一个新的函数值
    • val doubler = (x: Int) => x * 2
    • 函数字面量是所赋数据的一个字面量表达式
    • 其他名字:匿名函数Lambda表达式Lambdasfunction0,function1,function2,...(Scala编译器对字面量的叫法。根据输入参数的个数而定,单参数就叫<function1>)
    • 语法:([<identifier>: <type>, ...]) => <expression>
  • 占位符语法
    • 函数字面量的一种缩写形式,将命名参数替换为通配符(_

    • 使用条件

      • 函数的现实类型在字面量之外指定(明确好输入输出)
      • 输入参数最多只使用一次(因为只能按照位置匹配一次,多个占位符有着相同的符号,匹配之后就无法确定需要的参数)
    • 示例

      def combination(x: Int, y: Int, f: (Int,Int) => Int) = f(x,y)
      combination(23, 12, _*_)
      
      def tripleOp[A,B](a: A, b: A, c: A, f: (A, A, A) => B) => f(a, b, c)
      tripleOp[Int, Int](23, 92, 14, _ * _ + _)
      tripleOp[Int, Double](23, 92, 14, 1.0 * _ / _ / _)
      
  • 部分应用函数和柯里化
    • 功能:为函数保留一些重复使用的参数

    • 方法一:部分应用(partially apply)函数,使用通配符替代其他参数(函数按位置匹配)

      def factorOf(x: Int, y: Int) = y % x == 0
      val mutipleOf3 = factorOf(3, _: Int)
      val y = mutipleOf3(78)
      
    • 方法二:函数柯里化(curring,讨好的意思),使用参数组

      • 有多个参数表的函数可以认为是多个函数的一个链。单个参数表则认为是一个单独的函数调用

        def factorOf(x: Int)(y: Int) = y % x == 0
        val isEven = factorOf(2) _
        val z = isEven(32) // z = true
        
      • def factorOf(x: Int)(y: Int)的函数类型为 Int => Int => Boolean

  • 传名参数
    • 一种函数参数类型:可以取一个值,也可以取最终返回一个值的函数

    • 语法:<identifier>: => <type>

      scala> def doubles(x: => Int) = {
           |     println("Now doubling" + x)
           |     x * 2
           | }
      doubles: (x: => Int)Int
      
      scala> doubles(5)
      Now doubling 5
      res1: Int = 10
      
      scala> def f(i: Int) = { println(s"Hello from f($i)"); i }
      f: (i: Int)Int
      
      scala> doubles( f(8) )
      Hello from f(8)
      Now doubling 8
      Hello from f(8) //doubles调用两次x,所以f函数也调用两次
      res2: Int = 16    
      
  • 偏函数
    • 有些函数并不能支持满足输入类型的所有可能值。这种函数称为偏函数,因为它们只能部分应用于输入数据
  • 用函数字面量块调用高阶函数
    • 实际上就是输入的参数类型是函数,那个输入参数可以用表达式块或者函数字面量来代替
    • 好处
      • 将单独的代码块包为在工具函数中
      • 管理数据库事务,即高阶函数打开会话,调用函数参数,然后commit或者rollback结束事务
      • 重新尝试处理可能的错误,将函数参数调用指定的次数,直到不再产生错误
      • 根据局部、全局或者外部值有条件地调用函数参数

常用集合

  • 列表、集和映射
    • List:不可变的单链表
      • 访问列表中的单个元素,可以作为一个函数调用这个列表,并提供一个索引号(从0开始)
      • List是一个不可变的递归数据结构(链表),列表中的每一项都有自己的表头和越来越短的表尾
      • 所有列表都有一个Nil实例作为终结点,可以通过比较当前元素与Nil来检查是否到达列表表尾(Nil实际上是List[Nothing]的一个单例实例
    • Set:不可变的无序集合
    • Map:不可变的键值库,也称为散列映射、字典或关联数组
    • ListSetMap的根类型都是Iterable
      • foreach():取一个函数,对列表中的每一项,分别调用这个函数
      • map():取一个函数,将一个列表元素转换为另一个值或类型
      • reduce():取一个函数,将两个列表元素结合为一个元素
  • Cons操作符
    • 可以通过consconstruct的简写)操作符来构建列表

    • 使用Nil为基础,并使用右结合的cons操作符::绑定元素,就可以构建列表

      val numbersOld = List(1,2,3)
      val numbers = 1 :: 2 :: 3 :: Nil
      
    • ::List的一个方法,它取一个值,这会成为新列表的表头

      val first = Nil.::(1) // 1 -> Nil
      
  • 列表算术运算
    • 因为List是一个不可变的集合,所有“修改”是指“返回一个新列表,其中包含所请求的修改”
    • 谓词函数(predicate function):得到一个输入值后会相应地返回truefalse
    • :::为列表追加单个元素
    • ::::为列表追加另一个列表
    • ++:为列表追加另一个集合
    • ==:判断集合类型和元素内容是否相同
    • distinct:返回不包含重复的列表
    • drop:去除列表前n个元素
    • filter:输入一个谓词函数,过滤掉指定的元素
    • flatten:将一个包含列表的列表转换为元素列表
    • partition:输入一个谓词函数,将元素分组,由两个列表组成一个元组
    • reverse:逆转列表
    • slice:切片,输入切片的开头和结尾,输出不包括结尾
    • sortBy:根据指定函数排序
    • sorted:根据自然顺序排序
    • splitAt:根据指定索引将列表切分成两个
    • take:从列表抽取前n个元素
    • zip:两个列表合并为一个元组列表
  • 映射列表
    • collect:使用偏函数转换各个元素,保留可应用的元素
    • flatMap:给定一个函数转换各个元素,扁平化结果(消除列表中的列表,这种嵌套结构)
    • map:使用给定函数转换各个元素
    • Scala可以把Java数组转换为它自己的类型Array
  • 归约列表
    • 将列表收缩为单个值
    • Scala的集合支持:
      • 数学归约:maxminproductsum
      • 逻辑归约:containsendsWithexistsforallstartsWith
      • 通用的高阶操作,折叠,可以用来创建任何其他类型的列表归约算法
        • foldfoldLeftfoldRightreducereduceLeftreduceRightscanscanLeftscanRight
  • 转换集合
    • mkString:根据指定分隔符将一个集合转换为String
    • toBuffer:将一个不可变的集合转变为可变的集合
    • toList:将一个集合转变为List
    • toMap:将一个2元元组的集合转换为一个Map
    • toSet:将一个集合转换为一个Set
    • toString:将一个集合呈现为一个String,包括集合的类型
  • JavaScala集合兼容性
    • 默认情况下两者集合类型是不兼容的
    • 添加JavaConvertersimport collection.JavaConverters._
    • 集合转换:asJavaasScala
  • 使用集合的模式匹配
    • 匹配表达式、模式哨卫、值绑定
    • 模式匹配是Scala语言的一个核心特性

更多集合

  • 可变的集合类型 : 不可变的集合类型

    • collection.mutable.Buffer : collection.imutable.List
    • collection.mutable.Set : collection.imutable.Set
    • collection.mutable.Map : collection.imutable.Map
    • 使用toListtoSettoMap将可变集合类型转换回不可变的集合类型
  • 从不可变集合创建可变集合

    • 不可变集合ListMapSet都可以使用toBuffer方法转换为可变的collection.mutable.Buffer类型
  • 使用集合构建器

    • Builder:生成指定的集合类型,只支持追加操作
    • Set.newBuilder[Char]
    • 需要调用result方法得到最终结果
  • 数组

    • Array是一个大小固定的可变索引集合,实际只是Java数组类型的一个包装器,另外还提供了隐含类高级特性,使它可以项序列一样使用
    • val numbers = Array(1, 2, 3, 4)
  • Seq和序列

    • Seq是所有序列的根类型

      序列集合层次体系

    • Seq:所有序列的根类型,List()的快捷方式

    • IndexedSeq:索引序列的根类型,Vector()的快捷方式

    • Vector:这一类列表有一个后备Array实例,可以按索引访问

    • Range:整数范围。动态生成数据

    • LinearSeq:线性(链表)序列的根类型

    • List:元素的单链表

    • Queue:先进先出列表

    • Stack:后进先出列表

    • Stream:懒列表。访问元素时才增加相应元素

    • String:字符集合,扩展了Iterable

  • Stream

    • 懒集合,由一个或多个启示元素和一个递归函数生成。流可能是无界的,理论上是无限的集合,只是在访问元素时才回生成这个元素

    • 使用Stream.cons用表头表尾构建一个新的流,替代语法,可以使用#::操作符

    • 示例:def inc(head: Int): Stream[Int] = head #:: inc(head+1)

    • 创建一个有界的流

      def to(head: Char, end: Char): Stream[Char] = (head > end) match {
          case true => Stream.empty
          case false => head #:: to((head+1).toChar, end)
      }
      
  • 一元集合

    • 支持类似Iterable中的变换操作,但是包含的元素不能多于1个
  • Option集合

    • 扩展了Iterable的一个一元集合,Option类型表示一个值的存在或不存在
    • Option可以安全地替代null值,告诉用户这个值可能不存在,从而减少出发NullPointerException异常的可能性
    • 另一些开发人员把它看作是构建操作链的一种更为安全的方法,确保操作链中只包含有效的值
    • Option依赖两个子类型提供的具体实现:SomeNone
      • Some:一个类型参数化的单元素集合
      • None:一个空集合。None类型没有类型参数,因为它永远不包含任何内容
      • isDefinedisEmpty分别检查一个给定的OptionSome还是None
    • Option集合提供了一种安全的机制,而且还提供了一些操作来存储和变换可能存在也可能不存在的值,还提供了一些安全操作可以抽取可能存在的值
    • 警告:避免使用Option.get(),此方法不安全,对None调用get()将导致运行时错误
    • 安全的Option抽取操作
      • fold
      • getOrElse:为Some,则返回值;为None,则返回传名参数的结果
      • orElse:非空,返回这个Option,否则从给定的传名参数返回一个Option
      • 匹配表达式 match{ case Some(x) => x; case None => -1 }
  • Try集合

    • 将错误处理转变为集合管理。提供一种机制来捕获给定函数参数中发生的错误,并返回这个错误。
    • Scala抛出一个异常会中断程序流,并把控制交回给最近的处理器来处理这个特定的异常。未处理的异常会终止应用。
    • Scala支持try{}..catch{}块。
    • 推荐使用util.Try(),因为它提供了一种更安全、更有表述性,而且纯粹一元的方法来处理错误
    • util.Try类型没有具体实现,但是有两个已实现的子类型SuccessFailure
    • 使用Try的错误处理方法
      • flatMap:对于Success,调用一个同样返回util.Try的函数,从而将当前返回值映射到一个新的内嵌返回值

      • foreach:一旦Success,执行给定的函数,Failure则什么都不做

      • getOrElse:返回Success中的内嵌值,对于Failure则返回传名参数的值

      • orElse:与flatMap相反。对于Failure,调用一个同样返回util.Try的函数

      • toOption:将util.Try转换为Option,缺点是丢失内嵌的Exception

      • map:对于Success,调用一个函数,将内嵌值映射到一个新值

      • 匹配表达式 match { case util.Success(x) =>x; case util.Failure(error) => -1 }

      • 什么都不做:只需要允许异常在调用栈中向上传播,直到被捕获或者导致当前应用退出

        val input = " 123 "
        val result = util.Try(input.toInt) orElse util.Try(input.trim.toInt)
        result foreach { r => println(s"Parsed '$input' to $r!") }
        val x = result match {
            case util.Success(x) => Some(x)
            case util.Failure(ex) => {
                println(s"Couldn't parse input '$input'")
                None
            }
        }
        
  • Future集合

    • concurrent.Future,发起一个后台任务
    • future表示一个可能的值,并提供了安全操作来串链其他操作或者抽取值。与OptionTry不同,future的值不是立即可用,因为创建future时后台任务可能仍在工作。
    • 支持在并发线程中运行后台任务。
    • 调用future并提供一个函数会在一个单独的线程中执行该函数,而当前线程仍继续操作
    • 在创建future之前,必须指定当前会话或应用的“上下文”来并发运行函数,默认使用global上下文
      • import concurrent.ExecutionContext.Implicits.global
    • 可以设置回调函数或另外的future,当future任务执行完成时,执行这个回调或者future
    • 异步处理future
      • fallbackTo:将第二个future串链到第一个future,如果第一个future不成功,则调用第二个future
      • flatMap:如果第一个成功,则使用第一个的返回值调用第二个future
      • map:将给定的函数串链到future,如果future成功,则其返回值将用来调用这个函数
      • onCompletefuture任务完成后,将用一个util.Try调用指定函数,其中包含一个值(成功)或者一个异常(失败)
      • onFailure:如果future任务抛出一个异常,将使用这个异常来调用指定函数
      • onSuccess:如果future任务成功完成,将使用返回值来调用给定函数
      • Future.sequence:并发地运行给定序列中的future,返回一个新的future
    • 同步处理future
      • concurrent.Await.result(),取后台线程和一个最大等待时间
      • 等待时间:import concurrent.duration._
      • 等待10s:val maxTime = Duration(10, SECONDS)

  • 基础

    • Scala中,类参数在类名后指定,就像函数定义中函数参数跟在函数名后面一样。类参数可以用来初始化字段(类中的值和变量),或者用于传入函数,但是一旦类已经创建,这些参数就不再可用
    • 可以把某个字段声明为类参数,通过在类参数前增加val或者var,类参数就成为类中的一个字段
    • Scala中,一个类可以使用extends关键字扩展最多一个其他类,另外可以用override关键字覆盖所继承方法的行为
    • 类中的字段和方法用this关键字访问,父类中的字段和方法可以用super关键字访问
    • Scala多态指类能够采用其他兼容类的形式。“兼容”指一个子类的实例可以用于替代其父类的实例,但是反过来不行
  • 定义类

    class <identifier>  [type-parameters]
                        [([val|var] <identifier>: <type>[, ...])] //输入参数可以有默认值
                        [extends <identifier>[type-parameters](<input parameters>)] //继承父类可以有输入参数
                        [{ fields, methods, and classes }]
    
    • 实例就是一个内存分配,提供了类字段的存储空间。这个动作(即预留空间来分配一个类的内容)称为实例化
    • 访问类的字段和方法:标准中缀点记法、中缀操作符记法
  • 抽象类

    • 将由其他类扩展的一个类,而自己不能实例化
    • abstract关键字指定
    • 定义其子类必须有的核心字段和方法,但一般不提供具体实现(可以有默认实现)
    • 基于多态,如果一个值的类型为抽象类,它可以具体指向某个非抽象子类的实例,调用方法时实际上最后会在子类上调用
  • 重载方法

  • apply方法

    • 名为“apply”的方法有时是指它要作为一个默认方法或一个注入方法,可以直接调用而不需要方法名
    • 实际是一种快捷方式,可以使用小括号触发功能而不需要方法名,就如python__call__方法,以及pytorchforward
  • 懒值

    • 定义一个值时可以在val关键字前面加上关键字lazy来创建一个懒值
    • 类中使用的字段(值和变量)都是在类第一次实例化时创建的,而懒值是第一次调用时创建
    • 要确保时间或者性能敏感操作在类的生命周期中只执行一次,懒值则是一种很好的方法
    • 常用于存储基于文件的属性、打开的数据库链接,以及其他只有在确实必要时才初始化的不可变数据等信息。
  • 包装

    • Scala文件定义包:package <identifier>
    • 源文件要存储在与包匹配的目录中
    • 访问包装类:用点分隔的包路径或者导入包
    • Java不同,代码中的任何位置都可以使用import,与python类似
    • 支持使用_操作符导入一个包的全部内容
    • 导入组:import <package>.{<class 1>[,<class 2>...]}
    • 使用导入别名:import <package>.{<original name> => <alias>}
    • 包装语法:包作为一个块,用大括号包围它的类 package <identifier> { <class definitions> }
  • 私密性控制

    • 默认地,Scala不会增加私密性控制
    • protected:同一个类或者子类中的代码才能访问
    • private:仅定义这个字段或方法的类可以访问
  • 私密性访问修饰符

    • 包级保护
      • 另外,可以根据另一个类的亲密性覆盖这些策略
    • 实例级保护
  • 最终类和密封类

    • final类不能在子类中被覆盖,final关键字定义的值,变量或方法,可以确保所有子类都将使用这个实现
    • sealed密封类
      • 密封类会限制一个类的子类必须位于福勒所在的同一个文件中

对象、Case类和Trait

  • 对象object
    • 一种类类型,只能有不超过1个实例,也就是单例(singleton
    • Java和其他语言的某些字段和方法为“静态”或“全局”,对象可以提供类似的功能,不过将它们与可实例化的类解耦合
    • 对象不是用new关键字创建,只需要按名字直接访问,在首次访问时自动实例化(第一次访问前,不会实例化)
    • 定义对象用object关键字,没有任何输入参数,对象不能扩展,但是别的类可以继承它
    • 定义:object <identifier> [extends <identifier>] [{ fields, methods, and classes }]
    • 最适合object的函数是纯函数和处理外部I/O的函数
      • 纯函数:会返回完全由其输入计算得到的结果,而没有任何副作用
      • I/O函数:处理外部数据的函数,如处理文件、数据库和外部服务
    • apply方法和伴生对象
      • 工厂模式是一种很流行的方法,可以从伴生对象生成一个类的新实例。

      • 伴生对象是与类同名的一个对象,与类在同一个文件中定义

        class Multiplier(val x: Int) { def product(y: Int) = x * y }
        object Multiplier { def apply(x: Int) = new Multiplier(x) }
        
    • 使用对象的命令行应用
      • 使用对象中的一个main方法作为应用的入口点
      • def main(args: Array[String]) { ... }
    • Case
      case class <identifier> ([var] <identifier>: <type>[,...])
                               [extends <identifier>(<input-parameters>)]
                               [{ fields and methods }]
      
      • 不可实例化的类,包含多个自动生成的方法,包括一个自动生成的伴生对象
      • 适合数据传输对象,主要用于存储数据
      • val关键字可以用,但是case类默认将输入参数转换为值字段,所以没必要添加,如果需要一个变量字段,则使用var关键字
      • case类方法
        • apply:对象object中,一个工厂方法,用于实例化case
        • copy:类中,返回实例的一个副本
        • equals:类中
        • hashCode:类中
        • toString:类中
        • unapply:对象中,将实例抽取到一个字段元组,从而可以使用case类实例完成模式匹配
    • Trait
      • trait不能实例化,支持多重继承的类
      • trait不能有类参数,但可以有类型参数
      • 定义:trait <identifier> [extends <identifier>] [{ fields, methods, and classes }]
      • 用with关键字为类增加一个或多个trait,类没有扩展trait,而是被trait扩展,trait为现有的类增加新的功能或者配置,这个特性也称为依赖注入
        • Spring通过定制Java注解和初始化模块实现了类似的功能,但是Scalatrait不要求特定的注解或特殊的包来实现依赖注入
    • 导入实例成员
      • 与导入类和对象的import使用方法一样,可以导入单个成员,或者通过通配符_导入所有成员
      • 不会覆盖私密性控制,只能导入可以正常访问的字段和方法

高级类型

  • 暂略
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343