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

NO IMAGE

7. 面向對象(重點)

7.1 Scala 面向對象基礎

[修飾符] class 類名 {

類體

}

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

  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 文件

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

屬性

屬性是類的一個組成部分,一般是值數據類型,也可是引用類型

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

  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 = _
}

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

練習

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

    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")
    }
    }
    

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

  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. 示例代碼

    先在不同的包下建立同名的類

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

    如果想要在一個類中同時使用上面的兩個 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());
    }
    

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

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

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

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

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

  5. 基本語法

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

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

接著看看 Scala 是如何處理的

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

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

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 的打包路徑

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

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

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

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

Scala 的包

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

  1. 基本語法
    package 包名

  2. Scala包的三大作用(和Java一樣)

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

  4. 圖示

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

  5. 命名規範

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

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

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

Scala 打包細節(難點)

  • 常用的兩種打包形式

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

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

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

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

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

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

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

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

      進一步印證了 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層)

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

包對象

基本介紹:包可以包含類、對象和特質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()
}
}
}
}

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

使用反編譯工具打開瞧瞧

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

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

注意事項:

  1. 每個包都可以有一個包對象,但是需要在父包中定義它
  2. 包對象名稱需要和包名一致,一般用來對包(裡面的類)的功能做補充

包的可見性

在Java中,訪問權限分為: public,private,protected和默認。在Scala中,你可以通過類似的修飾符達到同樣的效果。但是使用上有區別

  1. 當屬性訪問權限為默認時,從底層看屬性是private的,但是因為提供了xxx_$eq()[類似setter]/xxx()[類似getter] 方法,因此從使用效果看是任何地方都可以訪問)

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

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

示例:

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

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

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

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

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

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

包的引入

細節說明

  1. 在Scala中,import語句可以出現在任何地方,並不僅限於文件頂部,import語句的作用一直延伸到包含該語句的塊末尾。這種語法的好處是:在需要時在引入包,縮小import 包的作用範圍,提高效率

    示例如下:

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

  2. Java中如果想要導入包中所有的類,可以通過通配符*,Scala中採用下 _

  3. 如果不想要某個包中全部的類,而是其中的幾個類,可以採用選取器(大括號)

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

  4. 如果引入的多個包中含有相同的類,那麼可以將不需要的類進行重命名進行區分,這個就是重命名

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

  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
    }
    

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

  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}")
}
}
}

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

相關文章

Cris的Scala筆記整理(十):隱式轉換

Cris的Scala筆記整理(九):面向對象高級

Cris的Scala筆記整理(八):面向對象中級繼承和多態

Cris的Scala筆記整理(八):面向對象中級封裝