四、Scala深入面向對象:類、對象與伴生關系

在前幾節中,我們學習了 Scala 的基礎語法和流程控制。現在,我們將深入探索 Scala 作為一門純粹的面向對象語言的核心。在 Scala 中,萬物皆對象,沒有像 Java 那樣的原始類型和靜態成員的區分。本節將重點介紹如何定義對象的藍圖,以及如何使用 Scala 獨特的單例對象和伴生機制。

思維導圖

在這里插入圖片描述
在這里插入圖片描述

一、類和對象

創建對象模板或藍圖。它定義一類事物共同的屬性 (成員變量)行為 (成員方法)
對象,也稱為實例,是根據類這個藍圖創建出來具體實體

基本語法:

class ClassName {// 成員變量 (字段)// 成員方法
}// 使用 new 關鍵字創建類的實例 (對象)
val objectName = new ClassName()

二、成員變量與成員方法

1. 定義和訪問成員變量

在類中定義的變量或常量,稱為成員變量或字段

class Person {// 定義一個可變的成員變量 namevar name: String = "Unknown"// 定義一個不可變的成員變量 (常量) ageval age: Int = 0
}// 創建 Person 類的對象
val person1 = new Person()// 訪問和修改成員變量
println(person1.name) // 輸出: Unknown
person1.name = "Alice"
println(person1.name) // 輸出: Alice
// person1.age = 25 // 這行會編譯錯誤,因為 age 是 val (常量)

2. 使用下劃線 _ 初始化成員變量

在 Scala 中,var 類型的成員變量必須被初始化。如果你暫時不想給它一個有意義的初始值,可以使用下劃線 _ 作為占位符,Scala 會為其賦予該類型的默認零值

類型默認零值
數值類型 (Int, Double, etc.)0
Booleanfalse
Char\u0000
所有引用類型 (AnyRef)null

代碼案例:

class Student {var name: String = _ // 初始化為 nullvar age: Int = _     // 初始化為 0var isMale: Boolean = _ // 初始化為 false
}val student1 = new Student()
println(s"Name: ${student1.name}, Age: ${student1.age}, Is Male: ${student1.isMale}")
// 輸出: Name: null, Age: 0, Is Male: false

注意: 這種下劃線初始化的方式在現代 Scala 編程中使用得越來越少,因為它容易引入 NullPointerException。更推薦的做法是提供一個有意義的初始值,或者使用Option 類型來表示可能缺失的值。

3. 定義和訪問成員方法

成員方法定義了對象的行為

class Circle {val radius: Double = 5.0// 定義一個計算面積的方法def getArea(): Double = {Math.PI * radius * radius}
}val c1 = new Circle()
// 調用方法
val area = c1.getArea()
println(s"The area of the circle is: $area")

三、訪問權限修飾符

Scala 通過訪問權限修飾符控制成員的可見性,以實現封裝

修飾符描述
(無修飾符)默認為 public,在任何地方都可以訪問。
private私有成員,只能在定義該成員的類或其伴生對象內部訪問。
protected受保護成員,只能在定義該成員的類及其子類中訪問。
private[this]對象私有,比 private 更嚴格。只能在當前對象實例中訪問,即使是同一個類的其他對象不能訪問。
private[包名]包私有,成員的可見性限定在指定的及其子包中。

代碼案例:

class Animal {private var privateName = "Secret"protected var protectedAge = 2def printInfo(): Unit = {println(s"This is a private name: $privateName") // 類內部可以訪問 private}
}class Dog extends Animal {def getAge(): Int = {// println(privateName) // 錯誤:子類不能訪問父類的 private 成員protectedAge // 正確:子類可以訪問父類的 protected 成員}
}val animal = new Animal()
// println(animal.privateName) // 錯誤:外部不能訪問 private 成員
// println(animal.protectedAge) // 錯誤:外部不能訪問 protected 成員
animal.printInfo()val dog = new Dog()
println(s"Dog's age: ${dog.getAge()}")

四、類的構造器

構造器是在創建對象自動調用特殊方法,用于初始化對象

1. 主構造器

主構造器直接定義類名之后參數列表中。
主構造器會執行類定義所有語句
如果主構造器的參數沒有用 valvar 聲明,它將是一個私有不可變字段僅在類內部可見。
如果用 valvar 聲明,該參數會成為一個公共成員變量

代碼案例:

// name 和 age 是主構造器的參數,并成為公共的不可變/可變成員變量
class Employee(val name: String, var age: Int) {// 這部分代碼是主構造器的一部分,在 new Employee(...) 時執行println(s"New employee created: $name, age $age")// 一個普通的成員方法def work(): Unit = println(s"$name is working.")
}val emp1 = new Employee("Alice", 30)
println(emp1.name) // 可以訪問
emp1.age = 31 // 可以修改

2. 輔助構造器

一個類可以有多個輔助構造器。
輔助構造器的名稱必須是 this
關鍵規則:每個輔助構造器的第一行必須直接或間接地調用主構造器 (或另一個已定義的輔助構造器)。

代碼案例:

class Car(val brand: String, val year: Int) {var color: String = "White"// 輔助構造器一:提供品牌、年份和顏色def this(brand: String, year: Int, color: String) {this(brand, year) // 必須先調用主構造器this.color = color}// 輔助構造器二:只提供品牌def this(brand: String) {this(brand, 2024) // 調用主構造器,年份默認為 2024}
}val car1 = new Car("Toyota", 2023)
val car2 = new Car("BMW", 2024, "Black")
val car3 = new Car("Ford")
println(s"${car3.brand} color is ${car3.color} and year is ${car3.year}")

五、單例對象、main方法與伴生對象

1. 單例對象

在 Scala 中,使用 object 關鍵字定義的不是類,而是一個單例對象——它是一個全局唯一實例
單例對象不能被 new,它的所有成員都類似于 Java 中的靜態成員

代碼案例:

object Logger {var level: String = "INFO"def log(message: String): Unit = {println(s"[$level] $message")}
}// 直接通過對象名訪問成員
Logger.level = "DEBUG"
Logger.log("This is a debug message.")

2. main 方法

Scala 應用程序的入口點是一個名為 main方法,它必須定義在一個單例對象中。

兩種實現方式:

  1. 標準 main 方法:
object MyApp {def main(args: Array[String]): Unit = {println("Hello from the main method!")}
}
  1. 繼承 App 特質 (更簡潔):
object MyApp extends App {// 這里的代碼會直接作為 main 方法體執行println("Hello from the App trait!")// 命令行參數可以通過 args 變量訪問if (args.length > 0) {println(s"First argument: ${args(0)}")}
}

3. 伴生對象

當一個單例對象與一個具有相同的名稱,并且它們定義在同一個源文件中時,這個對象被稱為該類的伴生對象,該類被稱為該對象的伴生類

核心特性:
伴生類和伴生對象可以互相訪問對方的私有 (private) 成員
常見用途

在伴生對象中放置類似于 Java 靜態方法工具方法
在伴生對象中定義工廠方法 (特別是名為 apply 的方法),用于創建伴生類實例隱藏 new 關鍵字。

代碼案例:

// 伴生類
class User private (val id: Int, val name: String) { // 主構造器設為 privateprivate def secretMethod(): String = s"User $name has a secret."def greet(): Unit = {// 訪問伴生對象的私有成員println(User.defaultGreeting + ", " + name)}
}// 伴生對象
object User {private val defaultGreeting = "Welcome"// 工廠方法,可以訪問 User 類的私有構造器def apply(name: String): User = {val newId = scala.util.Random.nextInt(1000)new User(newId, name)}def printSecret(user: User): Unit = {// 訪問 User 實例的私有方法println(user.secretMethod())}
}// 使用伴生對象的 apply 工廠方法創建實例 (無需 new)
val user1 = User("Bob")
user1.greet()
User.printSecret(user1)// val user2 = new User(10, "Charlie") // 錯誤:構造器是私有的

六、綜合案例

在 Scala 中,工具類 (包含純粹的功能方法,不維護狀態) 通常被實現單例對象

代碼案例:一個簡單的字符串工具對象

object StringUtils {/*** 判斷字符串是否為空 (null 或 "")* @param s 待檢查的字符串* @return 如果為空則返回 true,否則返回 false*/def isEmpty(s: String): Boolean = {s == null || s.trim.isEmpty}/*** 將字符串首字母大寫* @param s 待轉換的字符串* @return 轉換后的字符串*/def capitalize(s: String): String = {if (isEmpty(s)) s else s.substring(0, 1).toUpperCase + s.substring(1)}
}// 在另一個對象中(例如主程序)使用工具類
object MainApp extends App {val str1 = "hello scala"val str2 = "  "val str3: String = nullprintln(s"'${str1}' is empty? ${StringUtils.isEmpty(str1)}")println(s"'${str2}' is empty? ${StringUtils.isEmpty(str2)}")println(s"Capitalized '${str1}': ${StringUtils.capitalize(str1)}")
}

練習題

題目一:簡單類定義
定義一個 Book 類,包含兩個不可變的成員變量:title (String) 和 author (String)。

題目二:創建和訪問對象
創建 Book 類的一個實例,title 為 “Programming in Scala”,author 為 “Martin Odersky”。然后打印出這本書的標題。

題目三:成員方法
Book 類添加一個名為 getInfo 的方法,該方法返回一個格式為 "Title by Author" 的字符串。

題目四:下劃線初始化
定義一個 Movie 類,包含一個可變的成員變量 director (String),使用下劃線 _ 進行默認初始化。創建實例后打印出 director 的初始值。

題目五:主構造器
定義一個 Laptop 類,其主構造器接收 brand (String, 不可變) 和 ramInGB (Int, 可變) 兩個參數,并將它們直接定義為公共成員變量

題目六:輔助構造器
Laptop 類添加一個輔助構造器,該構造器只接收 brand 參數,并默認將 ramInGB 設置為 8。

題目七:訪問修飾符
定義一個 BankAccount 類,其中 balance (Double) 是私有的。提供一個公共deposit(amount: Double) 方法和一個公共getBalance() 方法來訪問余額。

題目八:單例對象
創建一個名為 MathConstants單例對象,在其中定義兩個常量:PI (值為 3.14159) 和 E (值為 2.71828)。

題目九:main 方法
創建一個名為 EntryPoint 的單例對象,并繼承 App 特質,在其中打印 “Scala application started!”。

題目十:伴生對象與私有成員
定義一個 Circle 類,其主構造器接收一個私有radius (Double) 參數。然后,為其創建一個伴生對象,該對象有一個 calculateArea(c: Circle) 方法,可以計算并返回給定 Circle 實例的面積 (面積 = PI * r * r)。

題目十一:apply 工廠方法
Circle 的伴生對象中添加一個 apply 方法,該方法接收一個 radius 參數,并返回一個新的 Circle 實例。這樣就可以使用 Circle(5.0) 來創建對象。

題目十二:工具類方法
在之前的 StringUtils 單例對象中,添加一個名為 reverse 的方法,接收一個字符串并返回其反轉后的結果。

題目十三:主構造器代碼塊
修改 Employee 類的定義,在其主構造器代碼塊中添加一條邏輯:檢查傳入的 age 是否小于18,如果是,則打印一條警告信息 “Warning: Employee age is below 18.”。

題目十四:對象私有成員 private[this]
定義一個 Point 類,包含 xy 兩個坐標。再定義一個 isSameAs(other: Point) 方法,比較當前點是否與另一個點相同。然后,修改 xyprivate[this],并觀察 isSameAs 方法是否還能正常編譯。如果不能,解釋原因。

題目十五:綜合案例
創建一個 Counter 類,它有一個私有的、可變count 變量,初始值為0。類中提供 increment() 方法 (每次將count加1) 和 current() 方法 (返回當前count值)。為其創建一個伴生對象,提供一個 apply 方法,允許通過 Counter() 創建新實例。

答案與解析

答案一:

class Book(val title: String, val author: String)

解析: 在主構造器參數前使用 val 是將參數直接定義為公共不可變成員變量的簡潔語法。

答案二:

val myBook = new Book("Programming in Scala", "Martin Odersky")
println(myBook.title)

解析: 使用 new 關鍵字和類名來創建對象,通過 . 操作符訪問其成員。

答案三:

class Book(val title: String, val author: String) {def getInfo(): String = {s"$title by $author"}
}
val myBook = new Book("A Brief History of Time", "Stephen Hawking")
println(myBook.getInfo())

解析: def 用于在類中定義方法。s"" 字符串插值器用于方便地格式化字符串。

答案四:

class Movie {var director: String = _
}
val m = new Movie()
println(m.director) // 輸出: null

解析: String 是引用類型 (AnyRef),其默認零值是 null

答案五:

class Laptop(val brand: String, var ramInGB: Int)

解析: val 使 brand 成為不可變成員,var 使 ramInGB 成為可變成員。

答案六:

class Laptop(val brand: String, var ramInGB: Int) {// 輔助構造器def this(brand: String) {this(brand, 8) // 調用主構造器}
}
val defaultLaptop = new Laptop("Dell")
println(s"${defaultLaptop.brand} has ${defaultLaptop.ramInGB}GB RAM")

解析: 輔助構造器 def this(...) 必須在其第一行調用另一個構造器。

答案七:

class BankAccount {private var balance: Double = 0.0def deposit(amount: Double): Unit = {if (amount > 0) balance += amount}def getBalance(): Double = {balance}
}

解析: private 關鍵字將 balance 的訪問權限限制在類內部,外部只能通過公共的 depositgetBalance 方法進行交互,實現了封裝。

答案八:

object MathConstants {val PI = 3.14159val E = 2.71828
}
println(MathConstants.PI)

解析: object 關鍵字創建了一個全局唯一的單例對象。

答案九:

object EntryPoint extends App {println("Scala application started!")
}

解析: 繼承 App 特質是創建可執行應用程序的最簡潔方式,對象體內的代碼會自動成為 main 方法的內容。

答案十:

class Circle private (val radius: Double)object Circle {def calculateArea(c: Circle): Double = {// 可以訪問 Circle 的私有成員 radiusMath.PI * c.radius * c.radius}
}

解析: 伴生對象 Circle 可以訪問伴生類 Circleprivate 成員 radius

答案十一:

class Circle private (val radius: Double)object Circle {def apply(radius: Double): Circle = {new Circle(radius)}// ... calculateArea 方法 ...
}val myCircle = Circle(5.0) // 無需 new,直接調用 apply 方法

解析: apply 方法是一個特殊的語法糖,允許你像調用函數一樣創建對象。

答案十二:

object StringUtils {// ... isEmpty, capitalize 方法 ...def reverse(s: String): String = {if (s == null) s else s.reverse}
}
println(StringUtils.reverse("scala"))
```*   **解析:** `String` 類型自帶 `.reverse` 方法,可以直接使用。**答案十三:**
```scala
class Employee(val name: String, var age: Int) {if (age < 18) {println(s"Warning: Employee $name's age is below 18.")}def work(): Unit = println(s"$name is working.")
}
val youngEmployee = new Employee("Tom", 17)

解析: 類定義體中、成員方法之外的代碼都屬于主構造器的一部分,會在對象創建時執行。

答案十四:

class Point(private[this] val x: Int, private[this] val y: Int) {def isSameAs(other: Point): Boolean = {// this.x == other.x // 這行代碼會編譯錯誤false // 僅為使代碼完整}
}

解析: 代碼無法正常編譯。因為 private[this]對象私有的,意味著只有當前對象 (this) 才能訪問 xy。在 isSameAs 方法中,other.x 嘗試訪問另一個Point 對象的 x 字段,這是不被允許的。如果使用 private,則可以訪問,因為 private 允許同一類的不同實例之間互相訪問私有成員。

答案十五:

class Counter private {private var count: Int = 0def increment(): Unit = {count += 1}def current(): Int = {count}
}object Counter {def apply(): Counter = new Counter()
}val c1 = Counter()
c1.increment()
c1.increment()
println(c1.current()) // 輸出: 2

解析: 這個例子結合了私有構造器、私有成員、公共方法和伴生對象的 apply 工廠方法,是一個典型的Scala封裝模式。將構造器設為私有,強制用戶通過伴生對象的工廠方法來創建實例。

在這里插入圖片描述

日期:2025年9月14日
專欄:Scala教程

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/96599.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/96599.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/96599.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【大語言模型 58】分布式文件系統:訓練數據高效存儲

分布式文件系統&#xff1a;訓練數據高效存儲 關鍵詞&#xff1a;分布式文件系統、HDFS、Lustre、GlusterFS、數據本地性、I/O優化、存儲架構、大數據存儲、訓練數據管理、存儲性能調優 摘要&#xff1a;本文深入探討大語言模型訓練中的分布式文件系統技術&#xff0c;從存儲架…

【科研繪圖系列】R語言繪制散點圖以及線性回歸擬合曲線圖

禁止商業或二改轉載,僅供自學使用,侵權必究,如需截取部分內容請后臺聯系作者! 文章目錄 介紹 加載R包 數據下載 函數 導入數據 數據預處理 畫圖 總結 系統信息 介紹 數據導入 代碼的開始部分涉及多個數據集的導入,這些數據集涵蓋了不同類型的生態學數據,包括實驗室培養…

SQL 數據庫操作語言詳解

1. SQL 語言概述SQL&#xff08;Structured Query Language&#xff09;是用于管理關系型數據庫的標準語言&#xff0c;主要分為以下幾個子語言&#xff1a;- DQL&#xff08;數據查詢語言&#xff09;&#xff1a;SELECT - 用于數據查詢 - DML&#xff08;數據操作語言&#x…

積分變換的前世今生

積分變換常應用于解微分方程微分方程的解法&#xff1a;時域經典法&#xff1b;頻域變換法&#xff1b;“積分變換”最初并不是為了解微分方程&#xff0c;而是出于更“純粹”的數學動機——理解函數的結構、求解代數或幾何問題&#xff0c;以及簡化復雜的積分運算。微分方程的…

《Linux——gflags》

一、什么是gflags&#xff1f; gflags 是一個由 Google 開發的命令行參數解析庫&#xff0c;主要用于在 C&#xff08;也支持其他語言&#xff09;程序中便捷地處理命令行參數。它的核心作用是幫助開發者快速定義、解析和使用命令行選項&#xff0c;避免手動編寫繁瑣的參數解析…

編譯器的前端中端和后端

前面說的詞法分析和語法分析&#xff0c;確實是編譯器前端 (Front End) 最核心的兩個部分。但前端的工作還沒有結束。編譯器各階段劃分 一個完整的編譯器通常可以分為三個部分&#xff1a;前端、中端 (Middle End)、后端 (Back End)。 前端 (Front End) 核心職責: 理解源代碼。…

黑馬Java進階教程,全面剖析Java多線程編程,并發和并行,筆記02

黑馬Java進階教程&#xff0c;全面剖析Java多線程編程&#xff0c;并發和并行&#xff0c;筆記02 一、并發和并行 并發&#xff1a;在同一時刻&#xff0c;有多個指令在單個CPU上交替執行 并行&#xff1a;在同一時刻&#xff0c;有多個指令在多個CPU上同時執行 二、為什么有…

20250908 背包DP總結

引子 ~ 我們都有一個家&#xff0c;名字叫背包 ~ 背包DP 顧名思義&#xff0c;背包DP是用來解決背包最值問題的。題目會給出背包的容量&#xff0c;以及幾個物品的屬性&#xff0c;比如重量&#xff0c;價值&#xff0c;限額等等&#xff0c;具體是什么看題目。 01背包 01…

Redis持久化之RDB:快照機制原理、配置與最佳實踐

Redis持久化之RDB&#xff1a;快照機制原理、配置與最佳實踐 1. RDB持久化概述 1.1 什么是RDB RDB&#xff08;Redis Database&#xff09;是Redis的默認持久化方式&#xff0c;它在指定的時間間隔內生成數據集的快照&#xff08;snapshot&#xff09;&#xff0c;并將快照保…

daily notes[44]

文章目錄基礎references基礎 hello,world是幾乎所有編程語言的第一例子&#xff0c;rust也不例外。但和其它語言不一樣&#xff0c;Rust的源碼最好擁有自己的項目目錄。 $ mkdir ~/pro $ cd ~/pro $ mkdir helloWorld $ cd helloWorld源代碼文件名為main.rs&#xff0c;內容如…

JavaScript對象創建方式完全指南:從原始到現代的演進之路

前言 作為一名前端開發者&#xff0c;JavaScript中對象創建是很重要。在JavaScript這門基于原型的語言中&#xff0c;對象幾乎無處不在。今天&#xff0c;我將帶領大家回顧JavaScript對象創建的7種方式&#xff0c;從最原始的字面量到現代的ES6 class&#xff0c;每一步演進都解…

基于單片機的無線水塔監控系統設計(論文+源碼)

本設計為基于單片機的無線水塔監控系統設計&#xff0c;主要由以下幾部分組成&#xff1a;均采用STC89C52RC單片機為主控&#xff1b;主機&#xff1a;NRF24L01無線通訊模塊&#xff0c;1602LCD液晶顯示屏。從機&#xff1a;NRF24L01無線通訊模塊&#xff0c;水位傳感器&#x…

凌晨0-3點不睡,你熬的不是夜,是人生!

“熬夜”這個詞&#xff0c;早已成為現代生活的常態。有人為了工作加班到深夜&#xff0c;有人為了娛樂刷劇到天明&#xff0c;但你知道嗎&#xff1f;熬夜最“要命”的時間段&#xff0c;其實是凌晨0點到凌晨3點。別以為只是少睡幾個小時而已&#xff0c;這個時間段不睡&#…

大語言模型基石:Transformer

一、引言 如今火爆的 GPT、LLaMA、通義千問、ChatGLM 等大語言模型&#xff0c;背后都離不開一個核心架構——Transformer。 2017 年&#xff0c;Google 在論文《Attention Is All You Need》中首次提出 Transformer 模型&#xff0c;徹底改變了自然語言處理的發展方向。它摒…

【算法】【鏈表】160.相交鏈表--通俗講解

算法通俗講解推薦閱讀 【算法–鏈表】83.刪除排序鏈表中的重復元素–通俗講解 【算法–鏈表】刪除排序鏈表中的重復元素 II–通俗講解 【算法–鏈表】86.分割鏈表–通俗講解 【算法】92.翻轉鏈表Ⅱ–通俗講解 【算法–鏈表】109.有序鏈表轉換二叉搜索樹–通俗講解 【算法–鏈表…

MySQL——庫的操作

1、創建數據庫語法&#xff1a;CREATE DATABASE [IF NOT EXISTS] db_name [create_specification [, create_specification] ...] create_specification: [DEFAULT] CHARACTER SET charset_name [DEFAULT] COLLATE collation_name這里的CHARACTER SET表示指定數據庫采用的字符集…

Python ast模塊(Abstract Syntax Trees,抽象語法樹)介紹及使用

文章目錄 核心概念 基本使用流程 常用節點類型 示例代碼 實際應用場景 注意事項 `ast.literal_eval()` 功能說明 適用場景 使用示例 限制與安全特性 與 `eval()` 的對比 總結 Python 的 ast 模塊( Abstract Syntax Trees,抽象語法樹)允許你解析、分析和修改 Python 代碼的…

C++寬度優先搜索算法:隊列與優先級隊列

本期我們就來深入學習一下C算法中一個很重要的算法思想&#xff1a;寬度優先搜索算法 寬度優先算法是一個應用十分廣泛的算法思想&#xff0c;涉及的領域也十分繁多&#xff0c;因此本篇我們先只涉獵它的一部分算法題&#xff1a;隊列/優先級隊列&#xff0c;后續我們會進一步地…

類的property屬性

??Python 中的 property 特性詳解??property 是 Python 中用于??將方法轉換為屬性??的裝飾器&#xff0c;它允許開發者以訪問屬性的方式調用方法&#xff0c;同時可以添加邏輯控制&#xff08;如數據校驗、計算屬性等&#xff09;。以下是其核心用法和優勢&#xff1a;…