Cris 的 Scala 筆記整理(七):面向對象

7. 面向對象(重點)

7.1 Scala 面向對象基礎

[修飾符] class 類名 {java

類體程序員

}編程

  1. scala語法中,類並不聲明爲public,全部這些類都具備公有可見性(即默認就是public)app

  2. 一個Scala源文件能夠包含多個類框架

定義一個最簡單的類函數式編程

object Demo {
  def main(args: Array[String]): Unit = {
    var man = new Man
    man.name = "cris"
    man.age = 12
    println(man.name + "----" + man.age) // cris----12
  }

}

class Man {
  var name = ""
  var age = 0
}
複製代碼

反編譯對應的 class 文件函數

屬性

屬性是類的一個組成部分,通常是值數據類型,也但是引用類型工具

def main(args: Array[String]): Unit = {
    val man = new Man()
    val pc = new PC
    man.pc = pc
    man.pc.brand = "惠普"
    // man.pc().brand()
    println(man.pc.brand) // 惠普
  }

class Man {
  var name = "" // 手動設置初始值,此時能夠省略成員屬性的數據類型聲明
  var age = 0
  var pc: PC = _ // _ 表示讓 Scala 自動賦默認值,此時聲明帶上成員屬性的數據類型,不然編譯器沒法肯定默認值
}

class PC {
  var brand: String = _
}
複製代碼

練習

  1. 針對 for(int i = 10;i>0;i--){System.out.println(i)} 翻譯成 Scala 代碼this

    object Practice {
      def main(args: Array[String]): Unit = {
        for (i <- 0.to(10).reverse) {
          print(i + "\t") // 10 9 8 7 6 5 4 3 2 1 0 
        }
      }
    }
    複製代碼
  2. 使用過程重寫上面的 Scala 代碼編碼

    def func(x: Int) {
      for (i <- 0 to x reverse) {
        print(i + "\t")
      }
    }
    複製代碼
  3. 編寫一個for循環,計算字符串中全部字母的Unicode代碼(toLong方法)的乘積。舉例來講,"Hello"中全部字符串的乘積爲9415087488L

    def cal(str:String): Unit ={
      var result = 1L
      for(x <- str){
        result*=x.toLong
      }
      print(result)
    }
    複製代碼
  4. 使用 StringOps 的 foreach 方法重寫上面的代碼

    var r2 = 1L
    // _ 能夠理解爲字符串的每個字符
    "Hello".foreach(r2 *= _.toLong)
    print(r2)
    複製代碼
  5. 使用遞歸解決上面求字符串每一個字符 Unicode 編碼乘積的問題

    def recursive(str: String): Long = {
      if (str.length == 1) str.charAt(0).toLong
      /*drop(n)從索引爲 1 開始切片到結尾*/
      else str.take(1).charAt(0).toLong * recursive(str.drop(1))
    }
    複製代碼
  6. 編寫函數計算 x^n,其中 n 是整數(負數,0,正數),請使用遞歸解決

    def pow(x: Int, n: Int): Double = {
      if (n == 0) 1
      else if (n < 0) {
        1.0 / x * pow(x, n + 1)
      } else {
        x * pow(x, n - 1)
      }
    }
    複製代碼

對象

val | var 對象名 [:類型] = new 類型()

  1. 若是咱們不但願改變對象的引用(即:內存地址), 應該聲明爲val 性質的,不然聲明爲var, scala設計者推薦使用val ,由於通常來講,在程序中,咱們只是改變對象屬性的值,而不是改變對象的引用

  2. scala在聲明對象變量時,能夠根據建立對象的類型自動推斷,因此類型聲明能夠省略,但當類型和後面new 對象類型有繼承關係即多態時,就必須寫

方法

Scala中的方法其實就是函數,只不過通常將對象中的函數稱之爲方法

def 方法名(參數列表) [:返回值類型] = {

​ 方法體

}

練習

  1. 嵌套循環打印圖形

    def func1(): Unit ={
        for (i <- 1 to 4; j <- 1 to 3) {
            if (j == 3) println("*")
            else print("*\t")
        }
    }
    複製代碼

  2. 計算矩形的面積

    class Test {
      def area(): Double = {
        (this.width * this.length).formatted("%.2f").toDouble
      }
    
    
      var width: Double = _
      var length: Double = _
    複製代碼

構造器

java 的構造器回顧

[修飾符] 方法名(參數列表){

構造方法體

}

  1. 在Java中一個類能夠定義多個不一樣的構造方法,構造方法重載

  2. 若是程序員沒有定義構造方法,系統會自動給類生成一個默認無參構造方法(也叫默認構造器)

3)一旦定義了本身的構造方法,默認的構造方法就覆蓋了,就不能再使用默認的無參構造方法,除非顯示的定義一下,即: Person(){}

Scala 構造器

和Java同樣,Scala構造對象也須要調用構造方法,而且能夠有任意多個構造方法。

Scala類的構造器包括: 主構造器 和 輔助構造器

基礎語法

class 類名(形參列表) { // 主構造器

// 類體

def this(形參列表) { // 輔助構造器

}

def this(形參列表) { //輔助構造器能夠有多個...

}

}

簡單示例

abstract class Dog {
  var name = ""
  var age = 0
  val color: String

  def this(name: String, age: Int) {
    this()
    this.name = name
    this.age = age
  }

  def eat(): Unit = {
    println("吃狗糧")
  }

  def run()
}
複製代碼
class Cat(var name: String, val color: String) {
  println("constructor is processing")

  def describe: String = name + "--" + color
}
  def main(args: Array[String]): Unit = {
	var cat = new Cat("tom", "gray")
    println(cat.describe)
    var cat2 = new Cat("jack", "red")
    println(cat2.describe)
  }
複製代碼

細節

  1. Scala構造器做用是完成對新對象的初始化,構造器沒有返回值。

  2. 主構造器的聲明直接放置於類名以後 [反編譯]

  3. 主構造器會執行類定義中的全部語句,這裏能夠體會到Scala的函數式編程和麪向對象編程融合在一塊兒,即:構造器也是方法(函數),傳遞參數和使用方法和前面的函數部份內容沒有區別

  4. 若是主構造器無參數,小括號可省略,構建對象時調用的構造方法的小括號也能夠省略

  5. 輔助構造器名稱爲this(這個和Java是不同的),多個輔助構造器經過不一樣參數列表進行區分, 在底層就是java的構造器重載,輔助構造器第一行函數體必須爲 this.主構造器

abstract class Dog {
    var name = ""
    var age = 0
    val color: String

    def this(name: String, age: Int) {
        this()
        this.name = name
        this.age = age
    }

    def eat(): Unit = {
        println("吃狗糧")
    }

    def run()
}
複製代碼

6)) 若是想讓主構造器變成私有的,能夠在()以前加上private,這樣用戶只能經過輔助構造器來構造對象了,說明:由於Person3的主構造器是私有,所以就須要使用輔助構造器來建立對象

class Car private(){}
複製代碼
  1. 輔助構造器的聲明不能和主構造器的聲明一致,會發生錯誤

屬性高級

  1. Scala類的主構造器函數的形參未用任何修飾符修飾,那麼這個參數是局部變量

  2. 若是參數使用val關鍵字聲明,那麼Scala會將參數做爲類的私有的只讀屬性使用

  3. 若是參數使用var關鍵字聲明,那麼那麼Scala會將參數做爲類的成員屬性使用,並會提供屬性對應的xxx()[相似getter]/xxx_$eq()[相似setter]方法,即這時的成員屬性是私有的,可是可讀寫

class Counter {

  /*1. 有公開的 getter 和 setter 方法*/
  var count = 0
  /*2. 私有化 getter 和 setter,能夠手動提供 setter 和 getter*/
  private var number = 1
  /*3. 只能被訪問getter,沒法修改setter,final 修飾的 age 屬性*/
  val age = 12
  /*4. 對象級別的私有*/
  private[this] var length = 12

  def compare(other: Counter): Boolean = other.number > number

  // def compareLength(other: Counter): Boolean = length > other.length

  def increase(): Unit = {
    number += 1
  }

  /*無參方法能夠省略(),{}也能夠省略*/
  def current: Int = number
}

def main(args: Array[String]): Unit = {
    var c = new Counter()
    c.count = 3
    println(c.count) // 3

    c.increase()
    println(c.current) // 2

    println(c.age) // 12
}
複製代碼

若是在主構造器中爲屬性設置了默認值,那麼就沒必要在函數體內再去聲明屬性以及賦值了,大大簡化代碼的書寫

def main(args: Array[String]): Unit = {
	val dog = new Dog()
    println(dog.name) // cris
    println(dog.age)  // 10
  }
}

class Dog(var name :String= "cris",var age:Int = 10){
    
}
複製代碼

JavaBean 註解

JavaBeans規範定義了Java的屬性是像getXxx()和setXxx()的方法。許多Java工具(框架)都依賴這個命名習慣。爲了Java的互操做性。將Scala字段加@BeanProperty時,這樣會自動生成規範的 setXxx/getXxx 方法。這時可使用 對象.setXxx() 和 對象.getXxx() 來調用屬性

給某個屬性加入@BeanPropetry註解後,會生成getXXX和setXXX的方法

而且對原來底層自動生成相似xxx(),xxx_$eq()方法,沒有衝突,兩者能夠共存

對象建立流程分析

請針對如下代碼簡述對象建立流程

class Bike {
  var brand = ""
  var color = ""

  def this(brand: String, color: String) {
    this
    this.brand = brand
    this.color = color
  }
}

def main(args: Array[String]): Unit = {
   var bike = new Bike("ofo", "黃色")   
}
複製代碼
  1. 加載類信息(屬性信息,方法信息)

  2. 在堆中,給對象開闢空間

  3. 調用主構造器對屬性進行初始化

  4. 使用輔助構造器對屬性進行初始化

  5. 把對象空間的地址,返回給 bike 引用

7.2 面向對象進階

包(難點)

回顧 Java 的包知識

  1. 做用

    1. 區分相同名字的類

    2. 當類不少時,能夠很好的管理

    3. 控制訪問範圍

  2. 打包基本語法

    package com.cris;

  3. 打包的本質分析

    實際上就是建立不一樣的文件夾保存類文件

  4. 示例代碼

    先在不一樣的包下創建同名的類

    若是想要在一個類中同時使用上面的兩個 Pig,Java 的解決方式以下:

    public static void main(String[] args) {
            Pig pig1 = new Pig();
            cris.package2.Pig pig2 = new cris.package2.Pig();
    // pig1.getClass() = class cris.package1.Pig
            System.out.println("pig1.getClass() = " + pig1.getClass());
    // pig2.getClass() = class cris.package2.Pig
            System.out.println("pig2.getClass() = " + pig2.getClass());
        }
    複製代碼

    再來看看咱們的源碼所在路徑和字節碼文件所在路徑,都是一一對應的

    Java 要求源碼所在路徑和字節碼文件所在路徑必須保持一致,若是咱們此時去修改源碼的打包路徑

  5. 基本語法

    import java.awt.* or import java.util.List

  6. 注意事項:java中包名和源碼所在的系統文件目錄結構要一致,而且編譯後的字節碼文件路徑也和包名保持一致

接着看看 Scala 是如何處理的

咱們使用 Scala 重寫上面的 Java 包案例

def main(args: Array[String]): Unit = {
  var b1 = new cris.package1.Bird1
  var b2 = new cris.package2.Bird2
  // class cris.package1.Bird1
  println(b1.getClass)
  // class cris.package2.Bird2
  println(b2.getClass)
}
複製代碼

此時咱們若是修改了 Bird1 的打包路徑

再看看源代碼和字節碼文件所在的路徑

Scala 的包

和Java同樣,Scala中管理項目可使用包,但Scala中的包的功能更增強大,使用也相對複雜些

  1. 基本語法 package 包名

  2. Scala包的三大做用(和Java同樣)

    1. 區分相同名字的類
    2. 當類不少時,能夠很好的管理類
    3. 控制訪問範圍
  3. Scala中包名和源碼所在的系統文件目錄結構要能夠不一致,可是編譯後的字節碼文件路徑包名會保持一致(這個工做由編譯器完成)

  4. 圖示

  5. 命名規範

    只能包含數字、字母、下劃線、小圓點.,但不能用數字開頭, 也不要使用關鍵字

    通常是小寫字母+小圓點通常是 com.公司名.項目名.業務模塊名

  6. Scala 自動 import 的包有:java.lang.*,scala,Predef 包

Scala 打包細節(難點)

  • 經常使用的兩種打包形式

    • 源代碼的路徑和字節碼文件路徑保持一致

    • 源代碼的路徑和字節碼文件路徑不一致

    • 上面的演示中已經很清楚的展現了 Scala 包的這一特色,咱們繼續用下面代碼演示 Scala 包的嵌套

      咱們在 Detail 類文件中寫入以上很是奇怪的代碼,編譯運行後再查看源代碼和字節碼文件的位置

      進一步印證了 Scala 中源文件和字節碼文件路徑能夠不一致

  • 包也能夠像嵌套類那樣嵌套使用(包中有包), 見上面圖示。好處是:程序員能夠在同一個文件中,將類(class / object)、trait 建立在不一樣的包中,很是靈活

  • 做用域原則:能夠直接向上訪問。即: Scala中子包中直接訪問父包中的內容, 大括號體現做用域。(提示:Java中子包使用父包的類,須要import)。在子包和父包 類重名時,默認採用就近原則,若是但願指定使用某個類,則帶上包名便可

    示例代碼

    package com.cris {
    
      class Apple {
    
      }
      package scala {
        
        class Apple {
    
        }
    
        object Boy {
          def main(args: Array[String]): Unit = {
            /*1. Scala 中子包能夠直接訪問父包的內容;2. 子包和父包的類重名,默認採起就近原則;3. 能夠帶上類的路徑名指定使用該類*/
            val apple = new Apple
            val apple2 = new com.cris.Apple
            // class com.cris.scala.Apple
            println(apple.getClass)
            // class com.cris.Apple
            println(apple2.getClass)
          }
        }
      }
    }
    複製代碼
  • 父包要訪問子包的內容時,須要import對應的類

    package com.cris {
    	
      import com.cris.scala.Apple
    
      object Apple{
        def main(args: Array[String]): Unit = {
            // 推薦只在使用的時候再引用,控制做用域
    	  import com.cris.scala.Apple
          val apple = new Apple()
    // class com.cris.scala.Apple
          println(apple.getClass)
        }
      }
    
      package scala {
    
        class Apple {
    
        }
      }
    }-
    複製代碼
  • 能夠在同一個.scala文件中,聲明多個並列的package(建議嵌套的pakage不要超過3層)

包對象

基本介紹:包能夠包含類、對象和特質trait,但不能包含函數或變量的定義。這是Java虛擬機的侷限。爲了彌補這一點不足,scala提供了包對象的概念來解決這個問

參見以下代碼

package com.cris {

  // 不能直接在 package 中定義函數和變量
  // var name = "cris"

  /** * 包對象的名字須要和包名一致 * package object emp 會在 com.cris.emp 包下生成&emsp;package.class 和&emsp;package$.class */
  package object emp {
    def eat(): Unit = {
      println("eat")
    }

    val salary = 1000.0
  }

  package emp {

    object test {
      def main(args: Array[String]): Unit = {
        eat() // eat=》等價於使用了&emsp;package$.class 中的&emsp;MODULE$.eat()
        println(salary) // 1000.0=>&emsp;等價於使用了&emsp;package$.class 中的 MODULE$.salary()
      }
    }
  }
}
複製代碼

使用反編譯工具打開瞧瞧

具體的執行流程第二章節已經解釋過,這裏再也不贅述

注意事項:

  1. 每一個包均可以有一個包對象,可是須要在父包中定義它
  2. 包對象名稱須要和包名一致,通常用來對包(裏面的類)的功能作補充

包的可見性

在Java中,訪問權限分爲: public,private,protected和默認。在Scala中,你能夠經過相似的修飾符達到一樣的效果。可是使用上有區別

  1. 當屬性訪問權限爲默認時,從底層看屬性是private的,可是由於提供了xxx_$eq()[相似setter]/xxx()[相似getter] 方法,所以從使用效果看是任何地方均可以訪問)

  2. 當方法訪問權限爲默認時,默認爲public訪問權限

  3. private爲私有權限,只在類的內部和伴生對象中可用

示例:

  1. protected爲受保護權限,scala中受保護權限比Java中更嚴格,只能子類訪問,同包沒法訪問

  2. 在scala中沒有public關鍵字,即不能用public顯式的修飾屬性和方法。

包訪問權限(表示屬性有了限制。同時增長了包的訪問權限),這點和Java不同,體現出Scala包使用的靈活性

包的引入

細節說明

  1. 在Scala中,import語句能夠出如今任何地方,並不只限於文件頂部,import語句的做用一直延伸到包含該語句的塊末尾。這種語法的好處是:在須要時在引入包,縮小import 包的做用範圍,提升效率

    示例以下:

  2. Java中若是想要導入包中全部的類,能夠經過通配符*,Scala中採用下 _

  3. 若是不想要某個包中所有的類,而是其中的幾個類,能夠採用選取器(大括號)

  4. 若是引入的多個包中含有相同的類,那麼能夠將不須要的類進行重命名進行區分,這個就是重命名

  5. 或者使用 import java.util.{HashMap => _ } 對衝突的包進行隱藏

練習

  1. 編寫一個Time類,加入只讀屬性hours和minutes,和一個檢查某一時刻是否早於另外一時刻的方法before(other:Time):Boolean。Time對象應該以new Time(hrs,min)方式構建

    object Practice {
      def main(args: Array[String]): Unit = {
        val time1 = new Time(4, 12)
        val result = time1.before(new Time(4, 14))
        println(result)
      }
    }
    
    class Time(val hour: Int, val minute: Int) {
      
      def before(other: Time) = {
        if (this.hour < other.hour) true
        else if (this.hour > other.hour) false
        else if (this.hour == other.hour) {
          if (this.minute < other.minute) true
          else if (this.minute > other.minute) false
          else false
        }
      }
    }
    複製代碼
  2. 建立一個Student類,加入可讀寫的JavaBeans屬性name(類型爲String)和id(類型爲Long)。有哪些方法被生產?(用javap查看。)你能夠在Scala中調用JavaBeans的getter和setter方法嗎?

    object Practice {
      def main(args: Array[String]): Unit = {
        var s = new Student
        println(s.getName)
        println(s.age)
      }
    }
    
    class Student {
      @BeanProperty var name = "好學生"
      @BeanProperty var age = 0
    
    }
    複製代碼

  3. 編寫一段程序,將Java哈希映射中的全部元素拷貝到Scala哈希映射。用引入語句重命名這兩個類

    object Ex extends App {
    
      import java.util.{HashMap => JavaHashMap}
      import scala.collection.mutable.{HashMap => ScalaHashMap}
    
      var map1 = new JavaHashMap[Int, String]()
      map1.put(1, "cris")
      map1.put(2, "james")
      map1.put(3, "simida")
    
    
      var map2 = new ScalaHashMap[Int, String]()
      for (key <- map1.keySet().toArray()) { // key 的數據類型是 AnyRef
        // asInstanceOf 強制數據類型轉換
        map2 += (key.asInstanceOf[Int] -> map1.get(key))
      }
      println(map2.mkString("||")) // 2 -> james||1 -> cris||3 -> simida
    
    }
    複製代碼

抽象

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

示例代碼

object Demo extends App {

  var account = new Account("招行:888888", 200, "123456")
  account.query("123456")

  account.save("123456", 100)
  account.query("123456")

  account.withdraw("123456", 250)
  account.query("123456")

}

class Account(val no: String, var balance: Double, var pwd: String) {
  def query(pwd: String): Unit = {
    if (pwd != this.pwd) {
      println("密碼錯誤!")
    } else {
      println(s"卡號:${this.no},餘額還有:${this.balance}")
    }
  }

  def save(pwd: String, money: Double): Unit = {
    if (pwd != this.pwd) {
      println("密碼錯誤")
    } else {
      this.balance += money
      println(s"卡號:${this.no},存入:${money},餘額爲:${this.balance}")
    }
  }

  def withdraw(pwd: String, money: Double): Unit = {
    if (pwd != this.pwd) {
      println("密碼錯誤")
    } else if (money > this.balance) {
      println("餘額不足")
    } else {
      this.balance -= money
      println(s"卡號:${this.no},取出:${money},餘額爲:${this.balance}")
    }
  }
}
複製代碼

相關文章
相關標籤/搜索