Scala - 06 - 面向对象

1- 类

1.1- 简介:类、方法及对象

类是用来创建对象的蓝图。
Scala文件中包含的多个类之间,都是彼此可见的,不需要声明为public。
创建对象
定义好类以后,就可以使用new关键字来创建对象。
  • 字段默认为public,在类外部和内部都可以访问该字段。
  • 私有字段使用private关键字修饰,外界无法访问,只有在类内部可以访问该字段。
方法
  • 通过def关键字实现方法的定义
  • 方法的返回值:方法里面的最后一个表达式的值就是方法的返回值,不需要靠return语句。
  • 如果方法不返回任何值,返回值的类型就为Unit。
  • 如果包含方法具体操作语句的大括号里面只有一行语句,可以省略大括号。
package testscala

object TestScala {
  def main(args: Array[String]) {
    println("Testing, Scala!")

    val myTest1 = new myCounter
    myTest1.increment()
    // myTest1.increment  // 调用无参方法时,可以省略方法名后面的圆括号
    println(myTest1.current)

    val myTest2 = new myCounter2
    myTest2.increment(5)
    println(myTest2.current)
  }

}

class myCounter {
  private var value = 0
  def increment(): Unit = { value += 1 }
  // def increment(): Unit = value += 1  // 大括号里面只有一行语句,可以省略大括号
  def current(): Int = { value }
}

class myCounter2 {
  private var value = 0
  def increment(step: Int): Unit = { value += step }
  def current(): Int = { value }
}

1.2- 定义类似getter和setter的方法

Scala没有提供getter和setter方法,但可以通过定义类似getter和setter的方法,来访问私有value字段。
package testscala

object TestScala {
  def main(args: Array[String]) {
    println("Testing, Scala!")

    val myTest3 = new myCounter3
    println(myTest3.value)
    myTest3.value = 3
    println(myTest3.value)
    myTest3.increment(1)
    println(myTest3.current)

    val myTest4 = new myCounter4
    println(myTest4.value)
    myTest4.value = 3
    println(myTest4.value)
    myTest4.increment(1)
    println(myTest4.current)
  }
}

class myCounter3 {
  var value = 0 // 变量默认为public,对外部可见
  def increment(step: Int): Unit = { value += step }
  def current(): Int = { value }
}

class myCounter4 {
  private var privateValue = 0 // 变量为private,私有字段
  def value = privateValue
  def value_=(newValue: Int) {
    if (newValue > 0) privateValue = newValue // 新值为正数才可以修改
  }
  def increment(step: Int): Unit = { value += step }
  def current(): Int = { value }
}

1.3- 构造器

Scala构造器包含1个主构造器和若干个(0个或多个)辅助构造器。
主构造器
Scala的每个类都有主构造器。
Scala的主构造器是整个类体,需要在类名称后面罗列出构造器所需的所有参数,这些参数被编译成字段,字段的值就是创建对象时传入的参数的值。
package helloscala

object helloscala {
  def main(args: Array[String]) {
    println("Testing, Scala!")

    val myTest5 = new myCounter5("Timer", 2)
    myTest5.info
    myTest5.increment(1)
    printf("Current Value is: %d\n", myTest5.current)
  }
}

class myCounter5(val name: String, val mode: Int) {
  private var value = 0
  def increment(step: Int): Unit = { value += step }
  def current(): Int = { value }
  def info(): Unit = { printf("Name:%s and mode is %d \n", name, mode) }
}
辅助构造器
辅助构造器的名称为this。
每个辅助构造器都必须调用一个此前已经定义的辅助构造器或主构造器。
package helloscala

object helloscala {
  def main(args: Array[String]) {
    println("Testing, Scala!")

    val myTest6 = new myCounter6
    myTest6.info
    myTest6.increment(1)
    printf("Current Value is: %d\n", myTest6.current)

    val myTest62 = new myCounter6("Runner")
    myTest62.info
    myTest62.increment(2)
    printf("Current Value is: %d\n", myTest62.current)

    val myTest63 = new myCounter6("Timer", 2)
    myTest63.info
    myTest63.increment(3)
    printf("Current Value is: %d\n", myTest63.current)
  }
}

class myCounter6 {
  private var value = 0
  private var name = ""
  private var mode = 1
  def this(name: String) {
    this()
    this.name = name
  }
  def this(name: String, mode: Int) {
    this(name)
    this.mode = mode
  }
  def increment(step: Int): Unit = { value += step }
  def current(): Int = { value }
  def info(): Unit = { printf("Name:%s and mode is %d \n", name, mode) }
}

2- 面向对象-对象

2.1- 单例对象

Scala并没有提供Java那样的静态方法或静态字段。
但可以用object关键字定义单例对象,具备和Java静态方法同样的功能。
package helloscala

object helloscala {
  def main(args: Array[String]) {
    println("Testing, Scala!")
    printf("The first person id is %d.\n", Person.newPersonId())
    printf("The second person id is %d.\n", Person.newPersonId())
    printf("The third person id is %d.\n", Person.newPersonId())
  }
}

object Person {
  private var lastId = 0
  def newPersonId() = {
    lastId += 1
    lastId
  }
}

2.2- 伴生对象

在Scala中通过伴生对象来实现同时包含实例方法和静态方法的类。
当单例对象与某个类具有相同的名称时,它被称为这个类的“伴生对象”。
类和它的伴生对象必须存在于同一个文件中,而且可以相互访问私有成员(字段和方法)。
Scala源代码经过编译后都会变成JVM字节码,class和object在Java层面都会被合二为一。class里面的成员成了实例成员,object成员成了static成员。
伴生类中的成员和伴生对象中的成员都被合并到一起,并且伴生对象中的方法成为静态方法。
package testscala

object TestScala { // 单例对象与某个类具有相同的名称,称为这个类的伴生对象

  private var lastId = 0
  private def newPersonId() = {
    lastId += 1
    lastId
  }

  def main(args: Array[String]) {
    println("Testing, Scala!")
    val person1 = new TestScala("AAA")
    val person2 = new TestScala("BBB")
    person1.info()
    person2.info()
  }
}

class TestScala {
  private val id = TestScala.newPersonId() //调用伴生对象中的方法
  private var name = ""

  def this(name: String) {
    this()
    this.name = name
  }

  def info() { printf("The id of %s is %d.\n", name, id) }
}
 注意:
可以通过javap命令对.class文件进行反编译,来确认伴生类中的成员和伴生对象中的成员都被合并到一起,并且,伴生对象中的方法成为静态方法。
另外,进行反编译时,要把伴生对象中方法的private修饰符去掉。如果不去掉,作为伴生对象的私有方法,在javap反编译后,在执行结果中是看不到这个方法的。

2.3- 应用程序对象

每个Scala应用程序都必须从一个对象的main方法开始。
第一种方法:直接使用scala命令运行得到结果。
代码中没有定义类,就是一个单例对象,因此,可以不用编译,直接使用scala命令运行得到结果。
guowli@5CG450158J MINGW64 /d/Anliven-Running/Zen/ScalaProjets/temptest
$ ls -l
total 1
-rw-r--r-- 1 guowli 1049089 88 Nov 21 11:31 test.scala

guowli@5CG450158J MINGW64 /d/Anliven-Running/Zen/ScalaProjets/temptest
$ cat test.scala
object HelloWorld {
  def main(args: Array[String]){
    println("Hello, World!")
  }
}

guowli@5CG450158J MINGW64 /d/Anliven-Running/Zen/ScalaProjets/temptest
$ scala test.scala
Hello, World!

guowli@5CG450158J MINGW64 /d/Anliven-Running/Zen/ScalaProjets/temptest
$ ls -l
total 1
-rw-r--r-- 1 guowli 1049089 88 Nov 21 11:31 test.scala

guowli@5CG450158J MINGW64 /d/Anliven-Running/Zen/ScalaProjets/temptest
$

第二种方法:先编译再执行

首先使用scalac编译命令对.scala文件进行编译,然后使用scala命令运行。
guowli@5CG450158J MINGW64 /d/Anliven-Running/Zen/ScalaProjets/temptest
$ scalac test.scala

guowli@5CG450158J MINGW64 /d/Anliven-Running/Zen/ScalaProjets/temptest
$ ls -l
total 3
-rw-r--r-- 1 guowli 1049089 665 Nov 21 11:33 'HelloWorld$.class'
-rw-r--r-- 1 guowli 1049089 602 Nov 21 11:33 HelloWorld.class
-rw-r--r-- 1 guowli 1049089  88 Nov 21 11:31 test.scala

guowli@5CG450158J MINGW64 /d/Anliven-Running/Zen/ScalaProjets/temptest
$ scala -classpath . HelloWorld
Hello, World!

guowli@5CG450158J MINGW64 /d/Anliven-Running/Zen/ScalaProjets/temptest
$

2.4- apply方法和update方法

apply方法
用括号传递给变量(对象)一个或多个参数时,Scala 会把它转换成对 apply方法的调用
类中定义apply方法:
package testscala

object TestScala {
  def main(args: Array[String]) {
    println("Testing, Scala!")
    val myObject = new TestApplyClass
    println(myObject("param1")) // 执行myObject("param1")时调用了apply方法
  }
}

class TestApplyClass {
  def apply(param: String): String = {
    println("apply method called, parameter is: " + param)
    "Hello World!" // apply方法的返回值
  }
}

单例对象中定义apply方法:

package testscala

object TestScala {

  def main(args: Array[String]) {
    println("Testing, Scala!")
    val group = TestScala("AAA", "BBB")  // 调用了apply方法,获得返回值并赋值给变量
    println(group)
  }

  def apply(param1: String, param2: String): String = {
    println("apply method called")
    param1 + " and " + param2  // 方法的返回值
  }

}

伴生类和伴生对象中的apply方法:

package testscala

object TestScala {
  def main(args: Array[String]) {
    val a = ApplyTest() // 调用伴生对象中的apply方法并返回该方法调用的值      
    a.greetingOfClass
    a() // 调用伴生类中的apply方法         
  }
}

object ApplyTest {
  def apply() = {
    println("apply method in object is called")
    new ApplyTest()  // 返回ApplyTest类的实例化对象
  }
}

class TestScala {
}

class ApplyTest {
  def apply() = println("apply method in class is called!")
  def greetingOfClass: Unit = {
    println("Greeting method in class is called.")
  }
}

示例:apply方法

 val myStrArr = Array("AAA", "BBB", "CCC")       //> myStrArr  : Array[String] = Array(AAA, BBB, CCC)
利用Scala中Array对象已定义的apply方法可以直接给对象传递参数。
也就是调用Array类的伴生对象Array的apply方法,完成数组的初始化。
理解:将伴生对象作为工厂使用,就使用关键字new创建实例化对象
package testscala

object TestScala {
  def main(args: Array[String]) {
    val mycar = Car("BMW") // 调用伴生对象中的apply方法创建一个Car类的实例化对象
    mycar.info()
  }
}

class Car(name: String) {
  def info() { println("Car name is " + name) }
}

object Car {
  def apply(name: String) = new Car(name) // 调用伴生类Car的构造方法创建一个Car类的实例化对象
}
update方法
当对带有括号并包括一到若干参数的对象进行赋值时,编译器将 调用对象的update方法,把括号里的参数和等号右边的对象一起作为update方法的输入参数来执行调用。
  val myStrArr = new Array[String](3)             //> myStrArr  : Array[String] = Array(null, null, null)
  myStrArr(0) = "AAA" //实际调用了伴生类Array中的update方法,执行myStrArr.update(0,"AAA")
  myStrArr(1) = "BBB" //实际调用了伴生类Array中的update方法,执行myStrArr.update(1,"BBB")
  myStrArr.update(2, "CCC") // 执行myStrArr.update(2,"CCC")
  for (x <- myStrArr) println(x)                  //> AAA
                                                  //| BBB
                                                  //| CCC

注意:在进行元组赋值的时候,之所以没有采用Java中的方括号myStrArr[0],而是采用圆括号的形式,myStrArr(0),是因为存在上述的update方法的机制。

3- 面向对象-继承

Scala和Java一样,不允许类从多个超类继承。
Scala中的继承与Java有着显著的不同:
  1. 重写一个非抽象方法必须使用override修饰符。
  2. 只有主构造器可以调用超类的主构造器。
  3. 在子类中重写超类的抽象方法时,不需要使用override关键字。
  4. 可以重写超类中的字段。
抽象类
  • 使用关键字abstract定义抽象类
  • 抽象类不能直接被实例化,可以被其他类继承
  • 定义抽象类中的抽象方法不需要使用abstract关键字,方法体为空
  • 抽象类中定义的字段必须声明类型,否则编译会报错;如果字段没有初始化值,就表示是一个抽象字段
扩展类
  扩展类可以直接被实例化。
package testscala

object TestScala {
  def main(args: Array[String]) {
    val myCar1 = new BMWCar()
    val myCar2 = new BenzCar()
    myCar1.greeting()
    myCar1.info()
    myCar2.greeting()
    myCar2.info()
  }
}

abstract class Car { //抽象类
  val carBrand: String //抽象字段没有初始化值
  def info() //抽象方法,不需要使用abstract关键字,方法体为空
  def greeting() { println("Welcome to my car!") }  //非抽象方法,方法体内容非空
}

class BMWCar extends Car {  //扩展类
  override val carBrand = "BMW" //重写超类字段,需要使用override关键字,否则编译会报错
  def info() { printf("This is a %s car.\n", carBrand) } //重写超类的抽象方法时,可以不使用override关键字
  override def greeting() { println("Welcome to my BMW car!") } //重写超类的非抽象方法,必须使用override关键字
}

class BenzCar extends Car { //扩展类
  override val carBrand = "Benz"
  def info() { printf("This is a %s car.\n", carBrand) }
  override def greeting() { println("Welcome to my Benz car!") }
}

4- 面向对象-特质

Scala中没有接口的概念,而是提供了“特质(trait)”,不仅实现了接口的功能,还具备了很多其他的特性。
Scala中一个类只能继承自一个超类,却可以实现多个特质,通过重用特质中的方法和字段可以实现多重继承。
  • 使用关键字trait定义特质
  • 特质中没有方法体的方法,默认为抽象方法。抽象方法不需要使用abstract关键字。
  • 特质定义完成后,可以使用extends或with关键字把特质混入类中。
  • 特质可以包含具体实现,特质中的字段和方法不一定要是抽象的。
  • 如果特质只包含了抽象字段和抽象方法,相当于实现了类似Java接口的功能。
package helloscala

object helloscala {
  def main(args: Array[String]) {
    println("Testing, Scala!")
    val myCarId1 = new BenzCarId()
    val myCarId2 = new BMWCarId()
    printf("My first CarId is %d.\n", myCarId1.currentId)
    myCarId2.greeting("Welcome my second car.")
    printf("My second CarId is %d.\n", myCarId2.currentId)
  }
}

trait CarId { //使用关键字trait定义特质
  var id: Int //定义抽象字段
  def currentId(): Int //定义抽象方法,不需要使用abstract关键字
}

trait CarGreeting { //使用关键字trait定义特质 
  def greeting(msg: String) { println(msg) } //定义非抽象方法,
}

class BenzCarId extends CarId { //使用extends关键字把特质混入类中
  override var id = 10000 //BYD汽车编号从10000开始
  def currentId(): Int = { id += 1; id } //返回汽车编号
}

class BMWCarId extends CarId with CarGreeting { //使用extends关键字混入第1个特质,后面可以反复使用with关键字混入更多特质
  override var id = 20000
  def currentId(): Int = { id += 1; id }
} 

猜你喜欢

转载自www.cnblogs.com/anliven/p/10041852.html