Scala的物件導向程式設計

MXC肖某某發表於2020-07-01

一、類與物件

1,定義

[修飾符] class 類名 {
   類體
} 

1) scala語法中,類並不宣告為public,所有這些類都具有公有可見性(即預設就是public),[修飾符在後面再詳解].

2) 一個Scala原始檔可以包含多個類, 每個類預設都是public

2,屬性

1)屬性的定義語法同變數,示例:[訪問修飾符] var 屬性名稱 [:型別] = 屬性值
2)屬性的定義型別可以為任意型別,包含值型別或引用型別[案例演示]
3)Scala中宣告一個屬性,必須顯示的初始化,然後根據初始化資料的型別自動推斷,屬性型別可以省略(這點和Java不同)。
4)如果賦值為null,則一定要加型別,因為不加型別, 那麼該屬性的型別就是Null型別.
5)如果在定義屬性時,暫時不賦值,也可以使用符號_(下劃線),讓系統分配預設值, 這時要求屬性必須給定資料型別。

3,建立物件

val | var 物件名 [:型別]  = new 型別()
1)如果我們不希望改變物件的引用(即:記憶體地址), 應該宣告為val性質的,否則宣告為var, scala設計者推薦使用val ,因為一般來說,在程式中,我們只是改變物件屬性的值,而不是改變物件的引用。 2)scala在宣告物件變數時,可以根據建立物件的型別自動推斷,所以型別宣告可以省略,但當型別和後面new 物件型別有繼承關係即多型時,就必須寫了

4,方法(參考上一個章節函式)

 

二、構造器

1,簡介

1)和Java一樣,Scala構造物件也需要呼叫構造方法,並且可以有任意多個構造方法(即scala中構造器也支援過載)。
2)Scala類的構造器包括: 主構造器(一個) 和 輔助構造器(多個)

2,基本語法

class 類名(形參列表) {  // 主構造器
   // 類體
   def  this(形參列表) {  // 輔助構造器,所有的輔助構造器必須呼叫主構造器
   }
   def  this(形參列表) {  //輔助構造器可以有多個...
   }
}

3,注意事項

1)Scala構造器作用是完成對新物件的初始化,構造器沒有返回值2)主構造器的宣告直接放置於類名之後
3)主構造器會執行類定義中的所有語句(把類中寫的語句放入到主構造器),這裡可以體會到Scala的函數語言程式設計和麵向物件程式設計融合在一起,即:構造器也是方法(函式),傳遞引數和使用方法和前面的函式部分內容沒有區別。
4)如果主構造器無引數,小括號可省略,構建物件時呼叫的構造方法的小括號也可以省略
5)輔助構造器名稱為this(這個和Java是不一樣的),多個輔助構造器通過不同引數列表進行區分, 在底層就是構造器過載,輔助構造器無論是直接或間接,最終都一定要呼叫主構造器,執行主構造器的邏輯, 而且呼叫主構造器語句一定要放在輔助構造器的第一行6)如果想讓主構造器變成私有的,可以在()之前加上private,這樣使用者不能直接通過主構造器來構造物件了【反編譯】
7)輔助構造器的宣告不能和主構造器的宣告一致,會發生錯誤(即構造器名重複)

4,屬性

1)Scala類的主構造器的形參:未用任何修飾符修飾,則為區域性變數;val關鍵字修飾,為scala類的私有隻讀屬性;var關鍵字修飾,為可讀寫的成員變數2)給某個屬性加入@BeanPropetry註解後,會生成getXXX和setXXX的方法,並且對原來底層自動生成類似xxx(),xxx_$eq()方法,沒有衝突,二者可以共存

 

三、包

1,作用

1) 區分相同名字的類
2) 當類很多時,可以很好的管理類
3) 控制訪問範圍

2,命名

1)只能包含數字、字母、下劃線、小圓點.,但不能用數字開頭, 也不要使用關鍵字。
2)一般是小寫字母+小圓點一般是:com.公司名.專案名.業務模組名

3,scala會自動引包:java.lang;scala包;Predef包

4,包物件

//為了彌補包中不能直接定義函式/方法 和 變數的不足,scala提供包物件的概念來解決//在底層包物件(package object scala) 會生成兩個類  class package class package$
package object mypackage {
  var name = "aaa" //變數
  def hello(): Unit = { //方法
    println("package object scala hello()")
  }
}

 

四、物件導向

1,抽象

  我們在前面去定義一個類時候(oo),實際上就是把一類事物的共有的屬性和行為提取出來,形成一個物理模型(模板)這種研究問題的方法稱為抽象

  class用abstract修飾,方法就是沒有方法體:def aaa()

2,封裝、繼承和多型

  方法重寫用override關鍵字override def clone(): AnyRef = super.clone()

3,動態繫結(待補充)

4,isInstanceOf、asInstanceOf、classOf 

val t = new MyTest
val bool: Boolean = t.isInstanceOf[MyTest]
if (bool) {
  val test: MyTest = t.asInstanceOf[MyTest]
}
//得到class物件類似於java的類名.class
val clazz: Class[MyTest] = classOf[MyTest]

5,伴生類和伴生物件

1)Scala中伴生物件採用object關鍵字宣告,伴生物件中宣告的全是 "靜態"內容,可以通過伴生物件名稱直接呼叫2)伴生物件對應的類稱之為伴生類,伴生物件的名稱應該和伴生類名一致。伴生類中的變數可當做成員變數,而伴生物件中的變數可看作靜態變數。
3)伴生物件中的屬性和方法都可以通過伴生物件名直接呼叫訪問
4)從語法角度來講,所謂的伴生物件其實就是類的靜態方法和靜態變數的集合
5)從技術角度來講,scala還是沒有生成靜態的內容,只不過是將伴生物件生成了一個新的類,實現屬性和方法的呼叫。
6)從底層原理看,伴生物件實現靜態特性是依賴於 public static final  MODULE$ 實現的。
7)伴生物件的宣告應該和伴生類的宣告在同一個原始碼檔案中(如果不在同一個檔案中會執行錯誤!),但是如果沒有伴生類,也就沒有所謂的伴生物件了,所以放在哪裡就無所謂了。
8)如果 class A 獨立存在,那麼A就是一個類, 如果 object A 獨立存在,那麼A就是一個"靜態"性質的物件[即類物件], 在 object A中宣告的屬性和方法可以通過 A.屬性 和 A.方法 來實現呼叫
9)在伴生物件中定義apply方法,可以實現: 類名(引數) 方式來建立物件例項. def apply(cAge: Int): Cat = new Cat(cAge)還是呼叫class Cat的構造方法,apply方法可以過載

6,特質(trait)

  a)宣告

trait 特質名 {
    trait體
}

  b)使用

#沒有父類
class  類名   extends   特質1   with    特質2   with   特質3 ..
#有父類
class  類名   extends   父類   with  特質1   with   特質2   with 特質3

  c)動態混入

//建立ClassDemo物件,同時動態混入TraitDemo特質
val myClass= new ClassDemo with TraitDemo {
     override def traitMethod(): Unit = {
     println("traitMethod")
  }
}

  d)疊加特質

1)特質宣告順序從左到右2)Scala在執行疊加物件的方法時,會首先從後面的特質(從右向左)開始執行
3)Scala中特質中如果呼叫super,並不是表示呼叫父特質的方法,而是向前面(左邊)繼續查詢特質如果找不到,才會去父特質查詢
4)如果想要呼叫具體特質的方法,可以指定:super[特質].xxx(…).其中的泛型必須是該特質的直接超類型別
object MuTraitDemo {
  def main(args: Array[String]): Unit = {
    //1. Operate4...
    //2. Data4
    //3. DB4
    //4. File4
    val mysql = new MySQL4 with File4 with DB4
    //分析問題1: 動態混入時,構建物件例項的順序是什麼?
    //構建例項時,順序是從左到右 -》

    //分析問題2:動態混入建立物件,在執行方法時,順序
    //是 從右到左執行
    // 1. 向資料庫
    // 2. 向檔案
    // 3. 插入資料 = 100
    println("-----------------------")
    mysql.insert(100)
  }
}

trait Operate4 {
  println("Operate4...")

  def insert(id: Int)
}

trait Data4 extends Operate4 {
  println("Data4")

  override def insert(id: Int): Unit = {
    println("插入資料 = " + id)
  }
}

trait DB4 extends Data4 {
  println("DB4")

  override def insert(id: Int): Unit = {
    println("向資料庫")
    super.insert(id)
  }
}

trait File4 extends Data4 {
  println("File4")

  override def insert(id: Int): Unit = {
    println("向檔案")
    //理論上應該是呼叫其父特質的insert
    //這裡的 super 含義: Scala中特質中如果呼叫super,並不是表示呼叫父特質的方法,而是向前面(左邊)繼續查詢特質,如果找不到,才會去父特質查詢
    super.insert(id)
    //如果我們就是希望去讓super指向自己的直接父特質,可以如下操作
    //這裡的Data4必須是File4 直接父特質
    //super[Data4].insert(id)
  }
}

//普通的類
class MySQL4 {}

 

五、隱式轉換、隱式函式和隱式值

1,隱式函式

  a)定義

  隱式轉換函式是以implicit關鍵字宣告的帶有單個引數的函式。這種函式將會自動應用,將值從一種型別轉換為另一種型別

  b)案例

//使用隱式函式
implicit def f1(d:Double): Int = { //底層會生成一個方法 f1$1
  d.toInt
}
val n1: Int = 3.4 //=> val n1: Int = f1$1(3.4)
val n2: Int = 5.6
println(n1 + "...." + n2 )

  c)注意事項

1)隱式轉換函式的函式名可以是任意的,隱式轉換與函式名稱無關,只與函式簽名(函式引數型別和返回值型別)有關2)隱式函式可以有多個(即:隱式函式列表),但是需要保證在當前環境下,只有一個隱式函式能被識別

2,隱式值

  a)定義

  隱式值也叫隱式變數,將某個形參變數標記為implicit,所以編譯器會在方法省略隱式引數的情況下去搜尋作用域內的隱式值作為預設引數

  b)案例

//這裡的 str1就是隱式值
implicit val str1: String = "scala"
//1.implicit name: String 就是一個隱式引數
//2.當呼叫hello的時候,沒有傳入實參,則編譯器會自動的將隱式值關聯到name上
def hello(implicit name: String): Unit = {
  println(name + " hello")
}
hello //用到隱式值 底層  hello$1(str1)
//輸出: scala hello

3,隱式類

  a)特點

1)其所帶的構造引數有且只能有一個
2)隱式類必須被定義在“類”或“伴生物件”或“包物件”裡,即隱式類不能是頂級的(top-level  objects)
3)隱式類不能是case class樣例類)
4)作用域內不能有與之相同名稱的識別符號

  b)案例

object ImplicitClass {
  def main(args: Array[String]): Unit = {
    //一個隱式類, 可以返回一個隱式類的例項,然後就可以呼叫隱式類的方法
    implicit class DB1(val m: MySQL1) {
      def addSuffix(): String = { //方法
        m + " scala"
      }
      def sayHi(): Unit = {
        println("sayHi..")
      }
      def sayHello(): Unit = {
        println("hello")
        m.sayOk()
      }
    }
    val mySQL = new MySQL1
    mySQL.sayOk() //
    // 1.底層 DB1$1(mySQL).addSuffix()
    // 2. DB1$1(mySQL) 返回的是 :ImplicitClass$DB1$2 例項
    // 3. 通過返回的  ImplicitClass$DB1$2例項.addSuffix()
    println(mySQL.addSuffix()) //DB1$1(mySQL).addSuffix()
    mySQL.sayHi()
    mySQL.sayHello()
  }
}
class MySQL1 { //普通類
  def sayOk(): Unit = {
    println("sayOk")
  }
}

4,隱式解析機制

1)首先會在當前程式碼作用域下查詢隱式實體(隱式方法、隱式類、隱式物件)。(一般是這種情況)
2)如果第一條規則查詢隱式實體失敗,會繼續在隱式引數的型別的作用域裡查詢。型別的作用域是指與該型別相關聯的全部伴生模組,一個隱式實體的型別T它的查詢範圍如下(這種情況範圍廣且複雜在使用時,應當儘量避免出現):
    a)  如果T被定義為T with A with B with C,那麼A,B,C都是T的部分,在T的隱式解析過程中,它們的伴生物件都會被搜尋。
    b)  如果T是引數化型別,那麼型別引數和與型別引數相關聯的部分都算作T的部分,比如List[String]的隱式搜尋會搜尋List的伴生物件和String的伴生物件。
    c)  如果T是一個單例型別p.T,即T是屬於某個p物件內,那麼這個p物件也會被搜尋。
    d)  如果T是個型別注入S#T,那麼S和T都會被搜尋。

 

  

 

相關文章