1. 程式人生 > >scala開發快速入門 | 第六篇 面向物件程式設計(上)

scala開發快速入門 | 第六篇 面向物件程式設計(上)

類的定義、變數的宣告初始化、private修飾變數、伴生類伴生物件

1)Class 關鍵字宣告一個類Person   

2)類成員變數的宣告的時候必須初始化   

3)編譯完後 通過位元組碼檔案發現 定義的變數都是private型別。   

*    val 宣告的成員變數 只有get方法 方法名為 變數名()   

*    var 宣告的成員變數 既有set方法也有get方法 set方法的名稱為 變數名_$eq(int x$1) get方法的名稱 變數名()   

4)private 修飾的變數編譯後 其set get方法 都是private型別的。   

*   被private修飾的欄位只能在本類以及伴生物件中。   

*  伴生物件和伴生類   

*  在同一個scala檔案中,如果有一個class 和object 的名字一樣,那麼稱class是object的伴生類,object是class的伴生物件 

/**
  * @author Andy
  *
  * 1)Class 關鍵字宣告一個類Person
  * 2)類成員變數的宣告的時候必須初始化
  * 3)編譯完後 通過位元組碼檔案發現 定義的變數都是private型別。
  * val 宣告的成員變數 只有get方法 方法名為 變數名()
  * var 宣告的成員變數 既有set方法也有get方法 set方法的名稱為 變數名_$eq(int x$1) get方法的名稱 變數名()
  * 4)private 修飾的變數編譯後 其set get方法 都是private型別的。
  * 被private修飾的欄位只能在本類以及伴生物件中。
  * 伴生物件和伴生類
  * 在同一個scala檔案中,如果有一個class 和object 的名字一樣,那麼稱class是object的伴生類,object是class的伴生物件
  *
  */
class Person {
  val name: String = "ysj"
  var age: Int = 23
  private var sex: String = "Man"

  //  為了在其他類中能夠訪問這個顯示指明的私有變數 給他定義set、get方法
  def setSex(sex: String): Unit = {
    this.sex = sex
  }
  def getSex(): String = {
    this.sex
  }
}
class Person2 {
  val person2 = new Person
  //  person2.sex Person類顯示宣告的私有變數無法訪問 但是可以訪問其他的非顯示私有變數
  println(person2.age) //Person類例項呼叫get方法訪問變數
  //  為了能在其他類中訪問某個類(Person)顯示宣告的私有變數
  //  可以在宣告這個變數是顯示私有變數的類中定義set、get方法
  println(person2.getSex())
}
object Person {
  def main(args: Array[String]): Unit = {
    val person = new Person
    println(person.sex) //顯示宣告的私有變數在伴生物件中使用
    val Person2=new Person2//例項化Person2 類預設呼叫無引數構造方法並執行構造器方法中的程式碼
  }
}

執行結果:

Man
23
Man

5)物件的例項化 

val person =new Person()

Person類的構造方法沒有引數,類的例項化可以定義如下

val person=new Person

6)類中的成員變數的訪問以及賦值

由於類的成員變數都是私有的,所以訪問的時候只能通過set、get方法進行訪問

person.name 其實是呼叫的get方法進行屬性訪問的。

person.name_=("yangshj") 通過set方法將變數賦值

person.name="yangshj" 直接給變數進行賦值,實際上底層還是呼叫的set方法    

類的構造器

Scala類的構造器包含1個主構造器和若干個(0個或多個)輔助構造器。

主構造器:

1)Scala的每個類都有主構造器。但是,Scala的主構造器和Java有著明顯的不同,Scala的主構造器是整個類體,需要在類名稱後面羅列出構造器所需的所有引數,這些引數被編譯成類的欄位屬性,欄位的值就是建立物件時傳入的引數的值。

主構造器的引數中,沒有被任何關鍵字(val 或者val)修飾的引數,會被標註為private[this] 這樣的引數只能在本類中訪問,其他地方無法訪問。

Note:不包含在任何方法中的程式碼,就是主構造器的程式碼。

輔助構造器:

2)輔助構造器的名稱為this,每個輔助構造器都第一行程式碼必須呼叫一個此前已經定義的輔助構造器或主構造器。

Note:建立物件時程式碼的執行順序一定是主構造器程式碼先執行。

package com.netcloud.scalalearning.day03.classconstruter

/*在主構造器引數值,沒有被任何關鍵字(var、val)修飾的引數,會被標註為private[this]
* class Person(var name: String, var age: Int,hobby:String) hobby這個欄位就會被標註為 private[this]
* 只能在本類訪問,其他的任何地方都不能訪問。
*
* */
class Person(var name: String, var age: Int) {

  println("這是主構造器的程式碼!")
  var sex: String = null
  var address: String = null

  /*定義第一個輔助構造器*/
  def this(name: String, age: Int, sex: String) {
    this(name, age) //直接呼叫主構造器
    this.sex = sex
    println(s"sex=$sex")

  }

  /*定義第二個輔助構造器*/
  def this(name: String, age: Int, sex: String, address: String) {
    this(name: String, age: Int, sex: String) //直接呼叫此前的輔助構造器
    this.address = address
    println(s"address=$address")

  }

  println(s"Person($name,$age,$sex,$address)")
}

object Person {
  def main(args: Array[String]): Unit = {
    //建立物件
    val person01 = new Person("yangshj", 25, "man", "北京")
  }

}

執行結果:

這是主構造器的程式碼!
Person(yangshj,25,null,null)
sex=man
address=北京

Object的介紹

在scala中被object關鍵字修飾的類具有如下特徵:

1)它是單例的
2)不需要通過關鍵字new建立物件 直接通過類名建立物件
3)沒有 有引數的構造器 但是有主構造程式碼塊(不包含任何方法中的程式碼 )
4)通常用來封裝一些常量、工具類、列舉、和隱式轉換函式
5)主構造器程式碼塊只執行一次,因為他是單例的。  

/*
* 1)它是單例的
* 2)不需要通過關鍵字new建立物件 直接通過類名建立物件
* 3)沒有 有引數的構造器 但是有主構造程式碼塊(不包含任何方法中的程式碼 )
* 4)通常用來封裝一些常量、工具類、列舉、和隱式轉換函式
* 5)主構造器程式碼塊只執行一次,因為他是單例的。
* */
object Person {
  var name: String = "ysj"
  println("Person 的主構造程式碼塊01")
  def sayHello() = {
    println("Hello World !")
  }
  println("Person 的主構造程式碼塊02")
}
object Person01 {
  def main(args: Array[String]): Unit = {
    val person = Person //object的例項不需要new  直接通過類名建立
    person.sayHello()
    println(person.name) //實際上呼叫的name的get方法 即 person.name()
  }
}

執行結果:

Person 的主構造程式碼塊01
Person 的主構造程式碼塊02
Hello World !
ysj

Object的apply方法

object Object_apply {
  def apply() = {
    println("hello")
  }
  def apply(nums: Int*): Array[Int] = {
    val arr = new Array[Int](nums.length)
    for (i <- 0 to nums.length - 1) {
      arr(i) = nums(i)
    }
    arr
  }
}
object Object_apply01 {
  def main(args: Array[String]): Unit = {
    // 建立 Object_apply物件 並且呼叫這個物件的無引數的apply方法
    val obj1 = Object_apply()
    // 不會再去建立物件了,因為上面一句程式碼已經建立了物件,這裡只是呼叫他的帶引數的apply方法
    val obj2 = Object_apply(1, 2, 3, 4, 5) //返回陣列物件
    obj2.foreach(println(_))
    // 顯示呼叫 apply方法
    Object_apply.apply(3, 4, 5, 6, 7)
    val tt=Array(1,2,3)// 這樣的方式建立的陣列 實際是呼叫的Array的apply方法 返回一個數組
  }
}

伴生類和伴生物件

伴生類和伴生物件一定是在同一個scala類檔案中,並且類名相同
伴生類和伴生物件的特點就是可以相互訪問被private修飾的關鍵欄位

/*伴生類*/
class MyArry {
  private var name: String = "ysj"
  //  伴生類中呼叫伴生物件的私有方法
  MyArry.name
}

/*伴生物件*/
object MyArry {
  private var name: String = "qin"
  //  伴生物件中呼叫伴生類的私有屬性
  new MyArry().name
  //  定義apply方法
  def apply(nums: Int*): Array[Int] = {
    var arr = new Array[Int](nums.length)
    for (i <- 0 to nums.length - 1) {
      arr(i) = nums(i)
    }
    arr
  }
}
object ObjectDemo {
  MyArry(5) //呼叫伴生物件的apply方法 返回一個數組
  //MyArry.name 無法訪問伴生物件的私有方法
}

Object 繼承App類

 object 類繼承App 類後 可以直接執行類中的列印語句以及呼叫其中的方法, 不需要再去寫main方法 App父類中就有main方法。

object ExtendsApp extends App{
  println("object 類繼承App 類後 可以直接執行類中的列印語句 不需要再去寫main方法")
}

Object 實現列舉功能

1)定義一個object繼承Enumeration

2)宣告列舉的方式

3)獲取列舉方式

/*object實現列舉功能
* 1)定義一個object繼承Enumeration
* 2)宣告列舉*/
object MyEnum extends Enumeration {
  /*宣告列舉*/
  //  方式一:宣告列舉的時候不需要指定下標值.
  //  val WOLF, DOG, CAT = Value
  //  方式二:宣告列舉的時候指定下標,同時指定值。
  val WOLF = Value(0, "wolf")
  val DOG = Value(1, "dog")
  val Cat = Value(2, "Cat")
}

object Main {
  def main(args: Array[String]): Unit = {
    println(MyEnum.WOLF)//wolf
   //通過列舉下標獲取列舉
    println(MyEnum(0))//wolf
    //通過列舉名稱獲取列舉
    println(MyEnum.withName("wolf"))//wolf
    //獲取所有的列舉值
    MyEnum.values.foreach(println(_))
  }

}

外部類、內部類、內部物件

內部類就是定義在物件或者類內部的類;內部物件就是定義在物件或者類內部的物件。

程式碼示例:

下面的程式碼中定義了一個伴生類(Student)和伴生物件(Student),在伴生類中定義了一個內部類Grade和一個內部物件Utils01,在伴生物件中則定義了一個內部類Printer 和內部物件Utils02.

1)建立內部類Grade的時候使用 外部類物件.內部類名稱(內部類構造方法引數)的方式建立內部類物件。可以看到訪問內部類就像是內部類是其成員變數一樣。

    外部類物件.內部物件的方式訪問內部物件。

2)伴生物件.內部類名稱(內部類構造引數)的方式建立伴生物件中的內部類物件。

     伴生物件.內部物件的方式訪問伴生物件中的內部物件。

/*伴生類Student*/
class Student(var name: String, var age: Int) {
  //  內部類
  class Grade(var name: String) {
  }
  //  內部物件
  object Utils01 {
    def prinnt(name: String) = {
      println(name)
    }
  }
}
/*伴生物件Student*/
object Student {

  //  伴生物件的內部類printer
  class printer {
    def print(name: String): Unit = {
    }
  }
  // 伴生物件的單例物件
  object Untils2 {
    def print(name: String) = {
      println(name)
    }
  }
}
object InnerClassAndInnerObject {
  def main(args: Array[String]): Unit = {
    //    建立伴生類物件
    val student = new Student("ysj", 25)
    //    建立伴生類中的內部類物件
    val grade = new student.Grade("qff")
    println("訪問伴生類物件中內部類中的屬性" + grade.name)
    //    呼叫伴生類內部物件的方法
    student.Utils01.prinnt("ysjloveqff")

    /*建立伴生物件的內部類物件*/
    val printer = new Student.printer
    printer.print("呼叫伴生物件的內部類方法!")
    Student.Untils2.print("呼叫伴生物件的內部物件方法!")
  }
}
class Person01(val name: String) {
  /*第一種方式
   * 在內部類通過【外部類.this.成員名稱】 訪問外部類成員*/
  class Student01(val name: String) {
    def getInfo(): Unit = {
      println("Outer Class name:" + Person01.this.name + " Inner Class name:" + name)
    }
  }

}
/*
  * 第二種方式
  * 在內部類通過【外部類別名】 訪問外部類成員*/
class Person02(val name: String) {
  outer =>
  class Student02(val name: String) {
    def getInfo(): Unit = {
      println("Outer Class name:" + outer.name + " Inner Class name:" + name)
    }

  }

}
object Main {
  def main(args: Array[String]): Unit = {
    println("第一種方式訪問")
    val outer01 = new Person01("ysj")
    val inner01 = new outer01.Student01("qff")
    inner01.getInfo()

    println("第二種方式訪問")
    val outer02 = new Person02("ysj")
    val inner02 = new outer02.Student02("qff")
    inner02.getInfo()
  }
}

樣例類(case class)

被關鍵字 case 修飾的類就是樣例類;

特點:其實樣例類和普通的類沒有什麼區別,唯一的區別就是樣例類的例項化不需要通過關鍵字new的方式。當然也可以通過new的方式例項化

定義一個樣例類 構造器的引數名可以不需要var 和val修飾  如果樣例類中沒有其他的方法可以省略 {}
case class CaseClass(var name:String){

}
object Main2{
  def main(args: Array[String]): Unit = {
    val caseclass=CaseClass("ysj")
    println(caseclass.name)

  }
}

抽象類和繼承

抽象類特點

1)與java相同都是被關鍵字abstract修飾

2)抽象類中可以定義抽象欄位,抽象方法,非抽象欄位,非抽象方法。

3)抽象欄位的宣告 其實就是普通欄位宣告的未初始化,抽象方法的宣告沒有方法體

4)抽象欄位不能被private修飾,這樣子類繼承父類後就無法訪問父類的這個欄位,也就無法實現這個抽象欄位。

子類繼承父類

1)子類繼承抽象父類,必須實現父類中的抽象欄位和抽象方法,子類中的override關鍵字可以省略

2)子類繼承抽象父類,對於非抽象欄位和方法,可以進行重寫,但是這個欄位不能被var宣告,否則異常。

      同時override關鍵字不能省略。

abstract class FatherClass {
  //  定義一個抽象欄位
  var name: String
  //  定義一個具體的欄位
  val age: Int = 23

  //  定義一個抽象方法
  def sayHello()

  //定義具體的方法
  def say(): Unit = {
    println("hello Father !")
  }

}

class SonClass extends FatherClass {
  //  子類繼承抽象父類,必須實現父類的抽象欄位和抽象方法 override 可以省略
  override var name: String = "son"

  override def sayHello(): Unit = {
    println("子類實現父類的抽象方法!")
  }

  //  對於父類中的非抽象欄位和方法 子類可以進行重寫
  /*Note:
     1)欄位不能被var宣告否則執行報錯
     2)override不能省略,否則編譯報錯*/
  override val age = 25

  override def say(): Unit = {
    super.say() //呼叫父類的say方法
    println("hello Son !")

  }

}

object Application {
  def main(args: Array[String]): Unit = {
    val son = new SonClass
    println(son.age)
    son.say()

  }
}

isInstanceOf 和asInstanceOf方法的使用

class Animal {

  def say(): Unit = {
    println("say multi way")
  }
}
class Dog extends Animal {
  override def say(): Unit = {
    println("say wang wang wang!")
  }
}
class Cat extends Animal {
  override def say(): Unit = {
    println("say miao miao miao!")
  }
}
object Application {
  def main(args: Array[String]): Unit = {
    val dog: Any = new Dog
    //用來判斷例項物件是否是指定的類型別
    if (dog.isInstanceOf[Animal]) {
      //將物件轉換成指定的型別 在用asInstanceOf之前,應該使用isInstanceOf進行判斷
      val d = dog.asInstanceOf[Animal]
      d.say()
    }
  }
}

getClass和classOf方法的使用

用來判斷例項物件到底是哪個類創建出來的

class Person

class Student03 extends Person
class Teacher03 extends Person

object GetClassAndclassOf {
  def main(args: Array[String]): Unit = {
    //用isInstanceOf 不能做精確判斷 如果做精確判斷使用getClass和classOf
    val student=new Student03
    println(student.getClass==classOf[Student03])//true
    println(student.getClass==classOf[Person])//false

  }

子類呼叫父類的構造方法

子類繼承父類後,當例項化子類的例項的時候,一定是先執行父類的建構函式,再去執行自己的建構函式。

Note:如果子類的建構函式和父類的建構函式接受的引數名稱一樣,那麼子類相同的引數名字就不能新增任何關鍵字就行修飾,否則scala會認為子類要重寫父類的欄位。

class FatherConstrutor(var name: String, var age: Int) {
  println("這是父類的主構造程式碼塊!")
  println("name=" + name + " " + "age=" + age)
}
/*如果子類的建構函式和父類的建構函式接受的引數名稱一樣,那麼子類相同的引數名字就不能新增任何關鍵字就行修飾,
否則scala會認為子類要重寫父類的欄位*/
class SonConstrutor(name: String, age: Int, var sex: String) extends FatherConstrutor("ysj", 25) {
  println("這是子類的構造程式碼塊!")
  println("name=" + name + " " + "age=" + age + " " + "sex=" + sex)
}
object ApplicationMain {
  def main(args: Array[String]): Unit = {
    new SonConstrutor("xiaoming", 24, "man")
  }
}

執行結果:

這是父類的主構造程式碼塊!
name=ysj age=25
這是子類的構造程式碼塊!
name=xiaoming age=24 sex=man

匿名類

匿名類顧名思義就是沒有名字的類,當某個類在程式碼中僅使用一次的時候,可以考慮使用匿名類。

/*匿名類*/

abstract  class NiMingClass {
  def say()
}
class Animal04(var name:String){
  def say(): Unit ={
    println("大家好 我是"+name)
  }
}
object Application05 {
  def main(args: Array[String]): Unit = {
    /*這裡的new NiMingClass 並不是例項化抽象類而是例項化匿名類*/
    val s1 = new NiMingClass() {
      override def say(): Unit = {
        println("我說抽象類的匿名類!")

      }
    }
    s1.say()
  }
  val cat=new Animal04("Cat"){

    override def say(): Unit = {
      super.say()
      println("我是Animal的匿名類")
    }

  }
  cat.say()
}