美文网首页
scala基础(一)

scala基础(一)

作者: 我是嘻哈大哥 | 来源:发表于2018-12-10 20:53 被阅读4次

    Scala与Java的关系
    Scala与Java的关系是非常紧密的!!
    因为Scala是基于Java虚拟机,也就是JVM的一门编程语言。所有Scala的代码,都需要经过编译为字节码,然后交由Java虚拟机来运行。
    所以Scala和Java是可以无缝互操作的。Scala可以任意调用Java的代码。所以Scala与Java的关系是非常非常紧密的。
    安装Scala
    ·从Scala官方网站下载,http://www.scala-lang.org/download/,windows版本的安装包是scala-2.11.7.msi。
    ·使用下载下来的安装包安装Scala。
    ·在PATH环境变量中,配置$SCALA_HOME/bin目录。
    ·在windows命令行内即可直接键入scala,打开scala命令行,进行scala编程。
    Scala解释器的使用
    REPL:Read(取值)-> Evaluation(求值)-> Print(打印)-> Loop(循环)。scala解释器也被称为REPL,会快速编译scala代码为字节码,然后交给JVM来执行。
    计算表达式:在scala>命令行内,键入scala代码,解释器会直接返回结果给你。如果你没有指定变量来存放这个值,那么值默认的名称为res,而且会显示结果的数据类型,比如Int、Double、String等等。

    例如,输入1 + 1,会看到res0: Int = 2
    

    内置变量:在后面可以继续使用res这个变量,以及它存放的值。

    例如,2.0 * res0,返回res1: Double = 4.0
    例如,"Hi, " + res0,返回res2: String = Hi, 2
    

    自动补全:在scala>命令行内,可以使用Tab键进行自动补全。

    ·例如,输入res2.to,敲击Tab键,解释器会显示出以下选项,toCharArray,toLowerCase,toString,toUpperCase。因为此时无法判定你需要补全的是哪一个,因此会提供给你所有的选项。
    ·例如,输入res2.toU,敲击Tab键,直接会给你补全为res2.toUpperCase。

    声明变量
    声明val变量:可以声明val变量来存放表达式的计算结果。

    例如,val result = 1 + 1
    

    后续这些常量是可以继续使用的

    例如,2 * result
    

    但是常量声明后,是无法改变它的值的,例如,result = 1,会返回error: reassignment to val的错误信息。

    声明var变量:如果要声明值可以改变的引用,可以使用var变量。

    例如,val myresult = 1,myresult = 2
    

    但是在scala程序中,通常建议使用val,也就是常量,因此比如类似于spark的大型复杂系统中,需要大量的网络传输数据,如果使用var,可能会担心值被错误的更改。

    在Java的大型复杂系统的设计和开发中,也使用了类似的特性,我们通常会将传递给其他模块 / 组件 / 服务的对象,设计成不可变类(Immutable Class)。在里面也会使用java的常量定义,比如final,阻止变量的值被改变。从而提高系统的健壮性(robust,鲁棒性),和安全性。

    指定类型:无论声明val变量,还是声明var变量,都可以手动指定其类型,如果不指定的话,scala会自动根据值,进行类型的推断。

    例如,val name: String = null
    例如,val name: Any = "leo"
    

    声明多个变量:可以将多个变量放在一起进行声明。

    例如,val name1, name2:String = null
    例如,val num1, num2 = 100
    

    数据类型与操作符
    基本数据类型:

    Byte、Char、Short、Int、Long、Float、Double、Boolean
    

    乍一看与Java的基本数据类型的包装类型相同,但是scala没有基本数据类型与包装类型的概念,统一都是类。scala自己会负责基本数据类型和引用类型的转换操作。
    使用以上类型,直接就可以调用大量的函数

    例如,1.toString(),1.to(10)
    

    类型的加强版类型:scala使用很多加强类给数据类型增加了上百种增强的功能或函数。
    例如,String类通过StringOps类增强了大量的函数,"Hello".intersect(" World")
    例如,Scala还提供了RichInt、RichDouble、RichChar等类型,RichInt就提供了to函数,1.to(10),此处Int先隐式转换为RichInt,然后再调用其to函数

    基本操作符:scala的算术操作符与java的算术操作符也没有什么区别,比如+、-、*、/、%等,以及&、|、^、>>、<<等。
    但是,在scala中,这些操作符其实是数据类型的函数比如1 + 1,可以写做1.+(1)例如,1.to(10),又可以写做1 to 10

    scala中没有提供++、--操作符,我们只能使用+和-,比如counter = 1,counter++是错误的,必须写做counter += 1.

    函数调用与apply()函数
    函数调用方式:在scala中,函数调用也很简单。

    例如,import scala.math._,sqrt(2),pow(2, 4),min(3, Pi)。
    

    ·不同的一点是,如果调用函数时,不需要传递参数,则scala允许调用函数时省略括号的,例如,"Hello World".distinct

    apply函数
    Scala中的apply函数是非常特殊的一种函数,在Scala的object中,可以声明apply函数。而使用“类名()”(严格来讲应该是“对象名()”)的形式,其实就是“类名.apply()”(严格来讲应该是“对象名.apply()”)的一种缩写。通常使用这种方式来构造类的对象,而不是使用“new 类名()”的方式。

    例如,"Hello World"(6),因为在StringOps类中有def apply(n: Int): Char的函数定义,所以"Hello World"(6),实际上是"Hello World".apply(6)的缩写。
    例如,Array(1, 2, 3, 4),实际上是用Array object的apply()函数来创建Array类的实例,也就是一个数组。
    if表达式

    if表达式的定义:在Scala中,if表达式是有值的,就是if或者else中最后一行语句返回的值。
    例如,val age = 30; if (age > 18) 1 else 0可以将if表达式赋予一个变量,
    例如,val isAdult = if (age > 18) 1 else 0
    另外一种写法,var isAdult = -1; if(age > 18) isAdult = 1 else isAdult = 0,但是通常使用上一种写法
    if表达式的类型推断:由于if表达式是有值的,而if和else子句的值类型可能不同,此时if表达式的值是什么类型呢?Scala会自动进行推断,取两个类型的公共父类型。
    例如,if(age > 18) 1 else 0,表达式的类型是Int,因为1和0都是Int
    例如,if(age > 18) "adult" else 0,此时if和else的值分别是String和Int,则表达式的值是Any,Any是String和Int的公共父类型
    如果if后面没有跟else,则默认else的值是Unit,也用()表示,类似于java中的void或者null。例如,val age = 12; if(age > 18) "adult"。此时就相当于if(age > 18) "adult" else ()。

    将if语句放在多行中:默认情况下,REPL只能解释一行语句,但是if表达式通常需要放在多行。
    可以使用{}的方式,比如以下方式,或者使用:paste和ctrl+D的方式。

    if(age > 18) { "adult"
    } else if(age > 12) "teenager" else "children"
    

    语句终结符、块表达式
    默认情况下,scala不需要语句终结符,默认将每一行作为一个语句
    一行放多条语句:如果一行要放多条语句,则必须使用语句终结符

    例如,使用分号作为语句终结符,var a, b, c = 0; if(a < 10) { b = b + 1; c = c + 1 }
    通常来说,对于多行语句,还是会使用花括号的方式

    if(a < 10) {
        b = b + 1
        c = c + 1
    }
    

    块表达式:块表达式,指的就是{}中的值,其中可以包含多条语句,最后一个语句的值就是块表达式的返回值。

    例如,var d = if(a < 10) { b = b + 1; c + 1 }
    

    输入和输出
    print和println:print打印时不会加换行符,而println打印时会加一个换行符。

    例如,print("Hello World"); println("Hello World")
    

    printf:printf可以用于进行格式化

    例如,printf("Hi, my name is %s, I'm %d years old.\n", "Leo", 30)
    

    **readLine: ** readLine允许我们从控制台读取用户输入的数据,类似于java中的System.in和Scanner的作用。

    综合案例:游戏厅门禁

    val name = readLine("Welcome to Game House. Please tell me your name: ")
    print("Thanks. Then please tell me your age: ")
    val age = readInt()
    if(age > 18) {
      printf("Hi, %s, you are %d years old, so you are legel to come here!", name, age)
    } else {
      printf("Sorry, boy, %s, you are only %d years old. you are illegal to come here!", name, age)
    }
    

    循环
    while do循环:**Scala有while do循环,基本语义与Java相同。

    var n = 10
    while(n > 0) {
      println(n)
      n -= 1
    }
    

    Scala没有for循环,只能使用while替代for循环,或者使用简易版的for语句

    简易版for语句:var n = 10; for(i <- 1 to n) println(i)
    或者使用until,表式不达到上限:for(i <- 1 until n) println(i)
    也可以对字符串进行遍历,类似于java的增强for循环,for(c <- "Hello World") print(c)

    跳出循环语句
    scala没有提供类似于java的break语句。
    但是可以使用boolean类型变量、return或者Breaks的break函数来替代使用。

    import scala.util.control.Breaks._
    breakable {
        var n = 10
        for(c <- "Hello World") {
            if(n == 5) break;
            print(c)
            n -= 1
        }
    }
    

    高级for循环
    多重for循环:九九乘法表

    for(i <- 1 to 9; j <- 1 to 9) {
      if(j == 9) {
        println(i * j)
      } else {
        print(i * j + " ")
      }
    }
    

    if守卫:取偶数

    for(i <- 1 to 100 if i % 2 == 0) println(i)
    

    for推导式:构造集合**

    for(i <- 1 to 10) yield i
    

    函数的定义与调用
    在Scala中定义函数时,需要定义函数的函数名、参数、函数体。
    我们的第一个函数如下所示:

    def sayHello(name: String, age: Int) = {
      if (age > 18) { printf("hi %s, you are a big boy\n", name); age }
      else { printf("hi %s, you are a little boy\n", name); age
    }
    sayHello("leo", 30)
    

    Scala要求必须给出所有参数的类型,但是不一定给出函数返回值的类型,只要右侧的函数体中不包含递归的语句,Scala就可以自己根据右侧的表达式推断出返回类型。

    在代码块中定义包含多行语句的函数体
    单行的函数:

    def sayHello(name: String) = print("Hello, " + name)
    

    如果函数体中有多行代码,则可以使用代码块的方式包裹多行代码,代码块中最后一行的返回值就是整个函数的返回值。与Java中不同,不是使用return返回值的。
    比如如下的函数,实现累加的功能:

    def sum(n: Int) = {
      var sum = 0;
      for(i <- 1 to n) sum += i
      sum
    }
    

    递归函数与返回类型
    如果在函数体内递归调用函数自身,则必须手动给出函数的返回类型。
    例如,实现经典的斐波那契数列:

    9 + 8; 8 + 7 + 7 + 6; 7 + 6 + 6 + 5 + 6 + 5 + 5 + 4; ....
    def fab(n: Int): Int = {
      if(n <= 1) 1
      else fab(n - 1) + fab(n - 2)
    }
    

    默认参数
    在Scala中,有时我们调用某些函数时,不希望给出参数的具体值,而希望使用参数自身默认的值,此时就定义在定义函数时使用默认参数。

    def sayHello(firstName: String, middleName: String = "William", lastName: String = "Croft") = {
    firstName + " " + middleName + " " + lastName
    }
    

    如果给出的参数不够,则会从左往右依次应用默认参数。

    def sayHello(name: String, age: Int = 20) {
      print("Hello, " + name + ", your age is " + age)
    }
    sayHello("leo")
    

    函数调用时带名参数
    在调用函数时,也可以不按照函数定义的参数顺序来传递参数,而是使用带名参数的方式来传递。

    sayHello(firstName = "Mick", lastName = "Nina", middleName = "Jack")
    

    还可以混合使用未命名参数和带名参数,但是未命名参数必须排在带名参数前面。

    sayHello("Mick", lastName = "Nina", middleName = "Jack")
    

    变长参数
    在Scala中,有时我们需要将函数定义为参数个数可变的形式,则此时可以使用变长参数定义函数。

    def sum(nums: Int*) = {
      var res = 0
      for (num <- nums) res += num
      res
    }
    sum(1, 2, 3, 4, 5)
    

    序列作为变长参数
    在如果想要将一个已有的序列直接调用变长参数函数,是不对的。比如val s = sum(1 to 5)。此时需要使用Scala特殊的语法将参数定义为序列,让Scala解释器能够识别。这种语法非常有用!一定要好好主意,在spark的源码中大量地使用到了。

    val s = sum(1 to 5: _*)
    

    案例:使用递归函数实现累加

    def sum2(nums: Int*): Int = {
      if (nums.length == 0) 0
      else nums.head + sum2(nums.tail: _*)
    }
    

    过程
    在Scala中,定义函数时,如果函数体直接包裹在了花括号里面,而没有使用=连接,则函数的返回值类型就是Unit。这样的函数就被称之为过程,即过程就是没有返回值的函数。
    过程还有一种写法,就是将函数的返回值类型定义为Unit。

    def sayHello(name: String) = "Hello, " + name//函数
    def sayHello(name: String) { print("Hello, " + name); "Hello, " + name }//有值,但未使用=号,还是过程
    def sayHello(name: String): Unit = "Hello, " + name//有值,有=号,但强制返回类型为空,则还是过程
    

    lazy值
    在Scala中,提供了lazy值的特性,也就是说,如果将一个变量声明为lazy,则只有在第一次使用该变量时,变量对应的表达式才会发生计算。这种特性对于特别耗时的计算操作特别有用,比如打开文件进行IO,进行网络IO等。

    import scala.io.Source._
    lazy val lines = fromFile("C://Users//Administrator//Desktop//test.txt").mkString
    

    即使文件不存在,也不会报错,只有第一个使用变量时会报错,证明了表达式计算的lazy特性。

    val lines = fromFile("C://Users//Administrator//Desktop//test.txt").mkString
    lazy val lines = fromFile("C://Users//Administrator//Desktop//test.txt").mkString
    

    相当于定义了一个方法,只有在调用该方法时才会去执行方法体:

    def lines = fromFile("C://Users//Administrator//Desktop//test.txt").mkString
    

    异常

    在Scala中,异常处理和捕获机制与Java是非常相似的。

    try {
      throw new IllegalArgumentException("x should not be negative")
    } catch {
      case _: IllegalArgumentException => println("Illegal Argument!")
    } finally {
      print("release resources!")
    }
    
    Import java.io._
    try {
      throw new IOException(“io exception!!!")
    } catch {
      case _: IllegalArgumentException => println("illegal argument")
    }
    
    try {
      throw new IOException("user defined exception")
    } catch {
      case e1: IllegalArgumentException => println("illegal argument")
      case e2: IOException => println("io exception")
    }
    

    Array
    在Scala中,Array代表的含义与Java中类似,也是长度不可改变的数组。此外,由于Scala与Java都是运行在JVM中,双方可以互相调用,因此Scala数组的底层实际上是Java数组。例如字符串数组在底层就是Java的String[],整数数组在底层就是Java的Int[]。

    // 数组初始化后,长度就固定下来了,而且元素全部根据其类型初始化
    val a = new Array[Int](10)
    a(0)
    a(0) = 1
    val a = new Array[String](10)
    // 可以直接使用Array()创建数组,元素类型自动推断
    val a = Array("hello", "world")
    a(0) = "hi"
    val a = Array("leo", 30)
    

    ArrayBuffer
    在Scala中,如果需要类似于Java中的ArrayList这种长度可变的集合类,则可以使用ArrayBuffer。
    // 如果不想每次都使用全限定名,则可以预先导入ArrayBuffer类

    import scala.collection.mutable.ArrayBuffer
    // 使用ArrayBuffer()的方式可以创建一个空的ArrayBuffer
    val b = ArrayBuffer[Int]()
    // 使用+=操作符,可以添加一个元素,或者多个元素
    // 这个语法必须要谨记在心!因为spark源码里大量使用了这种集合操作语法!
    b += 1
    b += (2, 3, 4, 5)
    // 使用++=操作符,可以添加其他集合中的所有元素
    b ++= Array(6, 7, 8, 9, 10)
    // 使用trimEnd()函数,可以从尾部截断指定个数的元素
    b.trimEnd(5)
    // 使用insert()函数可以在指定位置插入元素
    // 但是这种操作效率很低,因为需要移动指定位置后的所有元素
    b.insert(5, 6)
    b.insert(6, 7, 8, 9, 10)
    // 使用remove()函数可以移除指定位置的元素
    b.remove(1)
    b.remove(1, 3)
    // Array与ArrayBuffer可以互相进行转换
    b.toArray
    a.toBuffer
    

    遍历Array和ArrayBuffer

    // 使用for循环和until遍历Array / ArrayBuffer
    // 使until是RichInt提供的函数
    for (i <- 0 until b.length)
      println(b(i))
    // 跳跃遍历Array / ArrayBuffer
    for(i <- 0 until (b.length, 2))
      println(b(i))
    // 从尾部遍历Array / ArrayBuffer
    for(i <- (0 until b.length).reverse)
      println(b(i))
    // 使用“增强for循环”遍历Array / ArrayBuffer
    for (e <- b)
      println(e)
    

    数组常见操作

    // 数组元素求和
    val a = Array(1, 2, 3, 4, 5)
    val sum = a.sum
    // 获取数组最大值
    val max = a.max
    // 对数组进行排序
    scala.util.Sorting.quickSort(a)
    // 获取数组中所有元素内容
    a.mkString
    a.mkString(", ")
    a.mkString("<", ",", ">")
    // toString函数
    a.toString
    b.toString
    

    使用yield和函数式编程转换数组

    // 对Array进行转换,获取的还是Array
    val a = Array(1, 2, 3, 4, 5)
    val a2 = for (ele <- a) yield ele * ele
    // 对ArrayBuffer进行转换,获取的还是ArrayBuffer
    val b = ArrayBuffer[Int]()
    b += (1, 2, 3, 4, 5)
    val b2 = for (ele <- b) yield ele * ele
    // 结合if守卫,仅转换需要的元素
    val a3 = for (ele <- if ele % 2 == 0) yield ele * ele
    // 使用函数式编程转换数组(通常使用第一种方式)
    a.filter(_ % 2 == 0).map(2 * _)
    a.filter { _ % 2 == 0 } map { 2 * _ }
    

    算法案例:移除第一个负数之后的所有负数

    // 构建数组
    val a = ArrayBuffer[Int]()
    a += (1, 2, 3, 4, 5, -1, -3, -5, -9)
    // 每发现一个第一个负数之后的负数,就进行移除,性能较差,多次移动数组
    var foundFirstNegative = false
    var arrayLength = a.length
    var index = 0
    while (index < arrayLength) {
      if (a(index) >= 0) {
        index += 1
      } else {
        if (!foundFirstNegative) { foundFirstNegative = true; index += 1 }
        else { a.remove(index); arrayLength -= 1 }
      }
    }
    

    算法案例:移除第一个负数之后的所有负数(改良版)

    // 重新构建数组
    val a = ArrayBuffer[Int]()
    a += (1, 2, 3, 4, 5, -1, -3, -5, -9)
    // 每记录所有不需要移除的元素的索引,稍后一次性移除所有需要移除的元素
    // 性能较高,数组内的元素迁移只要执行一次即可
    var foundFirstNegative = false
    val keepIndexes = for (i <- 0 until a.length if !foundFirstNegative || a(i) >= 0) yield {
      if (a(i) < 0) foundFirstNegative = true
      i
    }
    for (i <- 0 until keepIndexes.length) { a(i) = a(keepIndexes(i)) }
    a.trimEnd(a.length - keepIndexes.length)
    

    创建Map

    // 创建一个不可变的Map
    val ages = Map("Leo" -> 30, "Jen" -> 25, "Jack" -> 23)
    ages("Leo") = 31
    // 创建一个可变的Map
    val ages = scala.collection.mutable.Map("Leo" -> 30, "Jen" -> 25, "Jack" -> 23)
    ages("Leo") = 31
    // 使用另外一种方式定义Map元素
    val ages = Map(("Leo", 30), ("Jen", 25), ("Jack", 23))
    // 创建一个空的HashMap
    val ages = new scala.collection.mutable.HashMap[String, Int]
    

    访问Map的元素

    // 获取指定key对应的value,如果key不存在,会报错
    val leoAge = ages("Leo")
    val leoAge = ages("leo")
    // 使用contains函数检查key是否存在
    val leoAge = if (ages.contains("leo")) ages("leo") else 0
    // getOrElse函数
    val leoAge = ages.getOrElse("leo", 0)
    

    修改Map的元素

    // 更新Map的元素
    ages("Leo") = 31
    // 增加多个元素
    ages += ("Mike" -> 35, "Tom" -> 40)
    // 移除元素
    ages -= "Mike"
    // 更新不可变的map
    val ages2 = ages + ("Mike" -> 36, "Tom" -> 40)
    // 移除不可变map的元素
    val ages3 = ages - "Tom"
    

    遍历Map

    // 遍历map的entrySet
    for ((key, value) <- ages) println(key + " " + value)
    // 遍历map的key
    for (key <- ages.keySet) println(key)
    // 遍历map的value
    for (value <- ages.values) println(value)
    // 生成新map,反转key和value
    for ((key, value) <- ages) yield (value, key)
    SortedMap和LinkedHashMap
    // SortedMap可以自动对Map的key的排序
    val ages = scala.collection.immutable.SortedMap("leo" -> 30, "alice" -> 15, "jen" -> 25)
    // LinkedHashMap可以记住插入entry的顺序
    val ages = new scala.collection.mutable.LinkedHashMap[String, Int]
    ages("leo") = 30
    ages("alice") = 15
    ages("jen") = 25
    

    Map的元素类型—Tuple

    // 简单Tuple
    val t = ("leo", 30)
    // 访问Tuple
    t._1
    // zip操作
    val names = Array("leo", "jack", "mike")
    val ages = Array(30, 24, 26)
    val nameAges = names.zip(ages)
    for ((name, age) <- nameAges) println(name + ": " + age)
    

    定义一个简单的类

    // 定义类,包含field以及方法
    class HelloWorld {
      private var name = "leo"
      def sayHello() { print("Hello, " + name) } 
      def getName = name
    }
    
    // 创建类的对象,并调用其方法
    val helloWorld = new HelloWorld
    helloWorld.sayHello()
    print(helloWorld.getName) // 也可以不加括号,如果定义方法时不带括号,则调用方法时也不能带括号
    

    getter与setter

    1、定义不带private的 var field,此时scala生成class时,会自动生成一个private[this]的成员字段(名称与field不同),并还生成一对getter和setter方法,分别叫做field和 field_=,并且getter和setter方法的访问修饰符与field定义相同

    2、而如果使用private修饰field,则只生成的getter和setter,且访问修饰也是private的

    3、如果定义val field,则只会生成getter方法

    4、 如果不希望生成setter和getter方法,则将field声明为private[this]

    class Student {
      var name = "leo"
    }
    

    // 调用getter和setter方法,分别叫做name和name_=

    val leo = new Student
    print(leo.name)
    leo.name = "leo1" //实际上会调用 leo.name_=("leo1")方法
    

    自定义getter与setter
    // 如果只是希望拥有简单的getter和setter方法,那么就按照scala提供的语法规则,根据需求为field选择合适的修饰符就好:var、val、private、private[this]
    // 但是如果希望能够自己对getter与setter进行控制,则可以自定义getter与setter方法
    // 自定义setter方法的时候一定要注意scala的语法限制,签名、=、参数间不能有空格

    class Student {
      private var myName = "leo" //默认会生成一对private getter(myName)、 
      setter(myName _=)方法
      def name = "your name is " + myName //自定义myName 成员变量getter方法
      def name_=(newValue: String)  {//自定义myName 成员变量的setter方法
      print("you cannot edit your name!!!")
      }
    }
    val leo = new Student
    print(leo.name)
    leo.name = "leo1" //会去调用 name_+ 自定义setter 方法
    

    仅暴露field的getter方法
    // 如果你不希望field有setter方法,则可以定义为val,但是此时就再也不能更改field的值了
    // 但是如果希望能够仅仅暴露出一个getter方法,并且还能通过某些方法更改field的值,那么需要综合使用private以及自定义getter方法。此时,由于field是private的,所以setter和getter都是private,对外界没有暴露;自己可以实现修改field值的方法;自己可以覆盖getter方法

    class Student {
      private var myName = "leo"
      def updateName(newName: String) { //更改field的其他方法(命名约束的不满足setter方法)
        if(newName == "leo1") myName = newName
        else print("not accept this new name!!!")
      }
      def name = "your name is" + myName  //覆盖自动生成的私有getter方法
    }
    

    private[this]的使用
    // 如果将field使用private来修饰,那么代表这个field是类私有的,在类的方法中,可以直接访问类的其他对象的private field
    // 这种情况下,如果不希望field被其他对象访问到,那么可以使用private[this],意味着对象私有的field,只有本对象内可以访问到(子类对象中也是不可以访问的,因为私有的是不能被继承的)

    class Student {
      private var myAge = 0 //试着修改成private[this]
      def age_=(newValue: Int) {
      if (newValue > 0) myAge = newValue
      else print("illegal age!")
      }
      def age = myAge
      def older(s: Student) = {
      myAge > s.myAge //修改成private[this]后,就会报错
      }
    }
    

    private[this]还可以用为修改方法

    Java风格的getter和setter方法
    // Scala的getter和setter方法的命名与java是不同的,是field和field_=的方式
    // 如果要让scala自动生成java风格的getter和setter方法,只要给field添加@BeanProperty注解即可
    // 此时会生成4个方法,name: String、name_=(newValue: String): Unit、getName(): String、setName(newValue: String): Unit

    import scala.reflect.BeanProperty
    class Student {
      @BeanProperty var name: String = _
    }
    class Student(@BeanProperty var name: String)
    val s = new Student
    s.setName("leo")
    s.getName()
    

    辅助constructor
    // Scala中,可以给类定义多个辅助constructor,类似于java中的构造函数重载
    // 辅助constructor之间可以互相调用,而且必须第一行调用主constructor或其他辅构造器

    class Student {
      private var name = ""
      private var age = 0
      def this(name: String) {
      this()
     this.name = name
      }
    def this(name: String, age: Int) {
        this(name)
        this.age = age
      }
    }
    

    主constructor

    // Scala中,主constructor是与类名放在一起的,有且只有一个,与java不同
    // 而且类中,没有定义在任何方法中的代码(包括成员字段),都属于主constructor的代码,且执行的顺序与代码书写的顺序一致。这其实与Java是一样的,在Java中方法之外的代码(成员以及代码块)会在构造器调用之前最先执行,姑且将这些代码看作也是放到了一个主构造器中进行执行的,只不过这种主构造器不能带构造参数
    //主构造器与类定义是在一起的,如果有参数,则在类名后面跟括号即可:

    class Student(val name: String, val age: Int) {
      println("your name is " + name + ", your age is " + age)
    }
    

    当然没有参数的主构造器也可以带括号:

    class Student() {}
    // 主constructor中还可以通过使用默认参数,来给参数默认的值
    class Student(val name: String = "leo", val age: Int = 30) {
      println("your name is " + name + ", your age is " + age)
    }
    

    // 如果主constrcutor传入的参数什么修饰都没有,比如name: String,那么如果类内部除主constrcutor方法外其它方法也使用到了,则会自动将该参数修饰为private[this] name以便其它方法使用:

    class Student(name: String) {
             def f(){print(name)}
             def f(s:Student){print(s.name)}//编译出错
    }
    
    class Student(val name: String) {
             def f(){print(name)}
             def f(s:Student){print(s.name)}//编译正确,证明没有使用var或val修饰时,且在除主构造器中使用外,则使用private[this]来修饰
    }
    

    类中没有定义的在任何方法中的代码都属于主构造器,并且执行顺序与书写顺序一致:
    内部类
    // Scala中,同样可以在类中定义内部类;但是与java不同的是,每个外部类的对象的内部类,都属于不同的类

    import scala.collection.mutable.ArrayBuffer
    class Class {
      class Student(val name: String) {}
      val students = new ArrayBuffer[Student]
      def getStudent(name: String) =  {
      new Student(name)
      }
    }
    
    val c1 = new Class
    val s1 = c1.getStudent("leo")
    c1.students += s1
    val c2 = new Class
    val s2 = c2.getStudent("leo")
    c1.students += s2   //异常
    

    object
    1、object,相当于class的单个实例(但与从class实例化出来的对象的内容决不一样),通常在里面放一些class层面上共享的内容,如Java中的静态field或者method即在定义在object中(注:Scala中没有Java的静态概念,所以延伸出了object这个东东)
    2、你可以将object看作是一个类class,只是这个类在内存中只有一个单例,且定义的object名就是实例名,不需我们自己实例化,运行时JVM已帮我们new出来了
    3、第一次调用object的方法时,就会执行object的constructor,也就是object内部不在method中的代码;但是object不能定义接受参数的constructor
    4、注意,object的constructor只会在其第一次被调用时执行一次,以后再次调用就不会再次执行constructor了
    5、object通常用于作为单例模式的实现,或者放class的静态成员,比如工具方法

    object Person {
      private var eyeNum = 2
      println("this Person object!")
      def getEyeNum = eyeNum
    }
    

    伴生对象
    // 如果有一个class,还有一个与class同名的object,那么就称这个object是class的伴生对象,class是object的伴生类
    // 伴生类和伴生对象必须存放在一个.scala文件之中
    // 伴生类和伴生对象,最大的特点就在于,互相可以访问private field

    object Person {
      private val eyeNum = 2
      def getEyeNum = eyeNum
    }
    class Person(val name: String, val age: Int) {
    def sayHello = println("Hi, " + name + ", I guess you are " + age + " years old!" + ", and usually you must have " + Person.eyeNum + " eyes.")
    }
    

    让object继承抽象类
    // object的功能其实和class类似,除了不能定义接受参数的constructor之外
    // object也可以继承抽象类,并覆盖抽象类中的方法

    abstract class Hello(var message: String) {
      def sayHello(name: String): Unit
    }
    object HelloImpl extends Hello("hello") {
      override def sayHello(name: String) = {
        println(message + ", " + name)
      }
    }
    

    apply方法
    // object中非常重要的一个特殊方法,就是apply方法
    // 通常在伴生对象中实现apply方法,并在其中实现构造伴生类的对象的功能,一般用作工厂方法
    // 而创建伴生类的对象时,通常不会使用new Class的方式,而是使用Class()的方式,隐式地调用伴生对象得apply方法,这样会让对象创建更加简洁
    // 比如,Array类的伴生对象的apply方法就实现了接收可变数量的参数,并创建一个Array对象的功能

    val a = Array(1, 2, 3, 4, 5)
    

    // 比如,定义自己的伴生类和伴生对象

    class Person(val name: String)
    object Person {
      def apply(name: String) = new Person(name)
    }
    

    另外,如果直接在一个对象后面接小括号,则会去调用这个对象所对应类中相应的apply方法:

    class Person {
      def apply(name: String) = println(name)
    }
    scala> val p = new Person
    scala> p("Persion")
    Persion
    

    main方法
    // 就如同java中,如果要运行一个程序,必须编写一个包含main方法类一样;在scala中,如果要运行一个应用程序,那么必须有一个main方法,作为入口
    // scala中的main方法定义为def main(args: Array[String]),而且必须定义在object中

    object HelloWorld {
      def main(args: Array[String]) {
        println("Hello World!!!")
      }
    }
    

    // 除了自己实现main方法之外,还可以继承App Trait,然后将需要在main方法中运行的代码,直接作为object的constructor代码;而且用args可以接受传入的参数

    object HelloWorld extends App {
      if (args.length > 0) println("hello, " + args(0))
      else println("Hello World!!!")
    }
    

    // 如果要运行上述代码,需要将其放入.scala文件,然后先使用scalac编译,再用scala执行

    scalac HelloWorld.scala
    scala -Dscala.time HelloWorld
    

    // App Trait的工作原理为:App Trait继承自DelayedInit Trait,scalac命令进行编译时,会把继承App Trait的object的constructor代码都放到DelayedInit Trait的delayedInit方法中,然后由App Trait的main方法去调用执行

    用object来实现枚举功能
    // Scala没有直接提供类似于Java中的Enum这样的枚举特性,如果要实现枚举,则需要用object继承Enumeration类,并且调用Value方法来初始化枚举值

    object Season extends Enumeration {
      val SPRING, SUMMER, AUTUMN, WINTER = Value
    }
    

    // 还可以通过Value传入枚举值的id和name,通过id和toString可以获取; 还可以通过id和name来查找枚举值

    object Season extends Enumeration {
      val SPRING = Value(0, "spring")
      val SUMMER = Value(1, "summer")
      val AUTUMN = Value(2, "autumn")
      val WINTER = Value(3, "winter")
    }
    
    Season(0)  // spring
    Season.withName("spring") // spring,根据名称找
    // 使用枚举object.values可以遍历枚举值
    for (ele <- Season.values) println(ele)
    

    extends

    // Scala中,让子类继承父类,与Java一样,也是使用extends关键字
    // 继承就代表,子类可以从父类继承父类的field和method;然后子类可以在自己内部放入父类所没有,子类特有的field和method;使用继承可以有效复用代码
    // 子类可以覆盖父类的field和method;但要注意的是final类是不能被继承的,而且final类型的field和method是无法被覆盖的

    class Person {
      private var name = "leo"
      def getName = name
    }
    
    class Student extends Person {
      private var score = "A"
      def getScore = score
    }
    

    override和super
    // Scala中,如果子类要覆盖一个父类中的非抽象方法,则必须使用override关键字;如果是抽象的方法,则可以省略
    // override关键字可以帮助我们尽早地发现代码里的错误,比如:override修饰的父类方法的方法名我们拼写错了;比如要覆盖的父类方法的参数我们写错了;等等
    // 此外,在子类覆盖父类方法之后,如果我们在子类中就是要调用父类的被覆盖的方法呢?那就可以使用super关键字,显式地指定要调用父类的方法

    class Person {
      private var name = "leo"
      def getName = name
    }
    
    class Student extends Person {
      private var score = "A"
      def getScore = score
      override def getName = "Hi, I'm " + super.getName
    }
    

    重写时需要override关键字,如果是实现则可以省略override关键字
    override field
    子类可以覆盖父类的同名的非private成员
    // Scala中,子类可以覆盖父类的val field,而且子类的val field还可以覆盖父类的val field的getter方法;只要在子类中使用override关键字即可

    class Person {
      /*private*/ val name: String = "Person"
    }
    class Student extends Person {
    override val name: String = "leo" // 重写一定要带上override关键字
    }
    

    只有val变量才能被重写,var变量是不能被重写的:

    class A{
             var f = "a"
    }
    class B extends A{
             override var f = "b" // error: overriding variable f in class A of type String;
              //variable f cannot override a mutable variable
    }
    

    下面也是不行的:

    class A{
             var f:String = "a"
    }
    class B extends A{
             override def f:String = "b"
             override def f_=(x:String) = println(x) // error: overriding variable f in class A of type String;method f_= cannot override a mutable variable
    }
    

    var变量只能被实现,如果将上面换成抽象的var字段,则是可以的:

    abstract class A{
             var f:String
    }
    class B extends A{
             /* override */ def f:String = "b" //由于是实现,所以可以省略override
             override def f_=(x:String) = println(x) //也可以不省略override
    }
    或者:
    abstract class A{
             var f:String
    }
    
    class B extends A{
             var f:String = ""
    }
    

    val变量只能被val实现,不能被def实现:

    abstract class A{
             val f:String
    }
    
    class B extends A{
             def f:String = ""  // error: overriding value f in class A of type String;
                                       //method f needs to be a stable, immutable value
    
    }
    

    但可以这样:

    abstract class A{
             val f:String
    }
    
    class B extends A{
             val f:String = ""
    }
    

    val、var override/实现 def

    abstract class Person {
        def id: Int  
    }
    class Student extends Person{
        override var id = 9527  //Error: method id_= overrides nothing
    }
    

    在scala中定义了一个var变量,会自动生成getter和setter方法。由于父类中只定义了一个方法def id: Int,而子类中var变量会自动生成getter(id)与setter方法(id_),但是父类并没有这个setter方法,所以是无法重写的。如下修改即可:

    abstract class Person {
        def id: Int 
        def id_=(value: Int) //父类必须有set方法
    }
    class Student extends Person{
        override var id = 9527 //为var变量自动生成get和set方法
    }
    

    或者子类定义成val变量:

    abstract class Person {
        def id: Int 
    }
    class Student extends Person{
        override val id = 9527
    }
    

    上面是val或var来实现def,下面是val或var来重写def:

    class Person {
        def id: Int = 1
    }
    class Student extends Person{
        override val id = 9527
    }
    或
    class Person {
        def id: Int = 1
        def id_=(value: Int) =println(value)
    }
    class Student extends Person{
        override var id = 9527
    }
    

    但是不能使用def重写val或var:

    class Person {
      val sex: String
    }
    
    class Student extends Person {
      override def sex:String = "" //error: overriding value sex in class Person of type String;
    //method sex needs to be a stable, immutable value
    }
    class Person {
      var sex: String = "X"
    }
    class Student extends Person {
      override def sex:String = ""
      override def sex_=(x:String) = println(x)
    }
    

    也不能使用def实现val:

    abstract class Person {
      val sex: String
    }
    class Student extends Person {
      def sex:String = "" //error: overriding value sex in class Person of type String;
                                  //method sex needs to be a stable, immutable value
    }
    

    但可以使用def实现var:

    abstract class Person {
      var sex: String
    }
    class Student extends Person {
      def sex:String = ""
      def sex_=(x:String) = println(x)
    }
    

    成员变量与方法之间重写与实现结论:可以使用val或var来重写或实现def,也可以使用def实现var;但不能使用def重写val或var,也不能使用def实现val
    isInstanceOf和asInstanceOf
    // 如果我们创建了子类的对象,但是又将其赋予了父类类型的变量。则在后续的程序中,我们又需要将父类类型的变量转换为子类类型的变量,应该如何做?
    // 首先,需要使用isInstanceOf判断对象是否是指定类的对象,如果是的话,则可以使用asInstanceOf将对象转换为指定类型
    // 注意,如果对象是null,则isInstanceOf一定返回false,asInstanceOf一定返回null
    // 注意,如果没有用isInstanceOf先判断对象是否为指定类的实例,就直接用asInstanceOf转换,则可能会抛出异常

    class Person
    class Student extends Person
    val p: Person =  new Student
    var s: Student = null
    if (p.isInstanceOf[Student]) s = p.asInstanceOf[Student]
    scala> p.isInstanceOf[Student]
    res7: Boolean = true
    scala> p.isInstanceOf[Person]
    res8: Boolean = true
    

    getClass和classOf
    // isInstanceOf只能判断出对象是否是给定类或其子类的实例对象,而不能精确判断出对象就是给定类的实例对象
    // 如果要求精确地判断对象就是指定类的对象,那么就只能使用getClass和classOf了
    // 对象.getClass可以精确获取对象所属的类class,classOf[类]可以精确获取类,然后使用==操作符即可判断

    class Person
    class Student extends Person
    val p: Person = new Student
    p.isInstanceOf[Person]
    p.getClass == classOf[Person]
    p.getClass == classOf[Student]
    

    使用模式匹配进行类型判断
    // 但是在实际开发中,比如spark的源码中,大量的地方都是使用了模式匹配的方式来进行类型的判断,这种方式更加地简洁明了,而且代码得可维护性和可扩展性也非常的高
    // 使用模式匹配,功能性上来说,与isInstanceOf一样,也是判断主要是该类以及该类的子类的对象即可,也不是精准判断的

    class Person
    class Student extends Person
    val p: Person = new Student
    p match {
      case per: Person => println("it's Person's object")
      case _  => println("unknown type")
    }
    

    protected
    // 跟java一样,scala中同样可以使用protected关键字来修饰field和method,这样子类就可以继承这些成员或方法
    // 还可以使用protected[this],则只能在当前子类对象中访问父类的使用protected[this]修饰的field和method,无法通过其他子类对象访问父类中的这些字段与方法

    class Person {
      protected var name: String = "leo"
      protected[this] var hobby: String = "game"
    }
    class Student extends Person {
      def sayHello = println("Hello, " + name)
      def makeFriends(s: Student) {
       println("my hobby is " + hobby + ", your hobby is " + s.hobby) //此处编译出错
      }
    }
    

    protected[this]修饰的字段只能在本对象或其子对象中使用,不能在其他对象中使用:

    class Person {
      protected var name: String = "leo"
      protected[this] var hobby: String = "game"
      def makeFriends(s: Person ){
      println("my hobby is " + hobby + ", your hobby is " + s.hobby) //此处编译还是出错
      }
    }
    

    与private[this]一样,protected[this]也可以修饰方法
    调用父类的constructor
    // Scala中,每个类可以有一个主constructor和任意多个辅助constructor,而每个辅助constructor的第一行都必须是调用其他辅助constructor或者是主constructor;因此子类的辅助constructor是一定不可能直接调用父类的constructor的
    // 只能在子类的主constructor中调用父类的constructor,以下这种语法,就是通过子类的主构造函数来调用父类的构造函数(即在extends后面指定需要调用父类哪个构造器)
    // 注意!如果是父类中接收的参数,比如name和age,子类中接收时,就不要用任何val或var来修饰了(或者带上修饰了,但将参数名命成不一样也可),否则会认为是子类要覆盖父类的field

    class Person(val name: String, val age: Int)
    class Student(name: String, age: Int, var score: Double) extends Person(name, age) /*调用父类的辅助构造器*/{
        def this(name: String) {
        this(name, 0, 0) //调用主构造器
      }
      def this(age: Int) {
        this("leo", age, 0) //调用主构造器
      }
    }
    

    调用父类的主构造器:

    class Person(val name: String, val age: Int){
             def this(){
                       this("11",11)
             }
    }
    class Student(name: String, age: Int, var score: Double) extends Person/*或 Person()*/ {
      def this(name: String) {
      this(name, 0, 0) //调用主构造器
      }
      def this(age: Int) {
      this("leo", age, 0) //调用主构造器
      }
    }
    

    相关文章

      网友评论

          本文标题:scala基础(一)

          本文链接:https://www.haomeiwen.com/subject/azhdhqtx.html