Scala的面向對象編程

一、類與對象

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都會被搜索。

 

  

 

Tags: