一、类和对象
类 是创建对象的模板或蓝图。它定义了一类事物共同的属性 (成员变量) 和行为 (成员方法)。
对象,也称为实例,是根据类这个蓝图创建出来的具体实体。
基本语法:
java
class ClassName {
// 成员变量 (字段)
// 成员方法
}
// 使用 new 关键字创建类的实例 (对象)
val objectName = new ClassName()
AI写代码scala
1234567
二、成员变量与成员方法
1. 定义和访问成员变量
在类中定义的变量或常量,称为成员变量或字段。
kotlin
class Person {
// 定义一个可变的成员变量 name
var name: String = "Unknown"
// 定义一个不可变的成员变量 (常量) age
val age: Int = 0
}
// 创建 Person 类的对象
val person1 = new Person()
// 访问和修改成员变量
println(person1.name) // 输出: Unknown
person1.name = "Alice"
println(person1.name) // 输出: Alice
// person1.age = 25 // 这行会编译错误,因为 age 是 val (常量)
AI写代码scala
123456789101112131415
2. 使用下划线 _
初始化成员变量
在 Scala 中,var
类型的成员变量必须被初始化。如果你暂时不想给它一个有意义的初始值,可以使用下划线 _
作为占位符,Scala 会为其赋予该类型的默认零值。
类型 | 默认零值 |
---|---|
数值类型 (Int, Double, etc.) | 0 |
Boolean |
false |
Char |
\u0000 |
所有引用类型 (AnyRef ) |
null |
代码案例:
kotlin
class Student {
var name: String = _ // 初始化为 null
var age: Int = _ // 初始化为 0
var 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
AI写代码scala
123456789
注意: 这种下划线初始化的方式在现代 Scala 编程中使用得越来越少,因为它容易引入 NullPointerException
。更推荐的做法是提供一个有意义的初始值,或者使用Option
类型来表示可能缺失的值。
3. 定义和访问成员方法
成员方法定义了对象的行为。
kotlin
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")
AI写代码scala
123456789101112
三、访问权限修饰符
Scala 通过访问权限修饰符来控制成员的可见性,以实现封装。
修饰符 | 描述 |
---|---|
(无修饰符) | 默认为 public ,在任何地方都可以访问。 |
private |
私有成员,只能在定义该成员的类或其伴生对象内部访问。 |
protected |
受保护成员,只能在定义该成员的类及其子类中访问。 |
private[this] |
对象私有 ,比 private 更严格。只能在当前对象实例中访问,即使是同一个类的其他对象也不能访问。 |
private[包名] |
包私有,成员的可见性被限定在指定的包及其子包中。 |
代码案例:
scala
class Animal {
private var privateName = "Secret"
protected var protectedAge = 2
def 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()}")
AI写代码scala
1234567891011121314151617181920212223
四、类的构造器
构造器是在创建对象时自动调用的特殊方法,用于初始化对象。
1. 主构造器
主构造器直接定义在类名之后的参数列表中。
主构造器会执行类定义中所有的语句。
如果主构造器的参数没有用
val
或var
声明,它将是一个私有的不可变字段,仅在类内部可见。如果用
val
或var
声明,该参数会成为一个公共的成员变量。
代码案例:
kotlin
// 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 // 可以修改
AI写代码scala
123456789101112
2. 辅助构造器
一个类可以有多个辅助构造器。
辅助构造器的名称必须是
this
。
关键规则:每个辅助构造器的第一行必须直接或间接地调用主构造器 (或另一个已定义的辅助构造器)。
代码案例:
kotlin
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}")
AI写代码scala
12345678910111213141516171819
五、单例对象、main方法与伴生对象
1. 单例对象
在 Scala 中,使用 object
关键字定义的不是类,而是一个单例对象------它是一个全局唯一的实例。
单例对象不能被 new
,它的所有成员都类似于 Java 中的静态成员。
代码案例:
kotlin
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.")
AI写代码scala
12345678910
2. main 方法
Scala 应用程序的入口点是一个名为 main
的方法,它必须定义在一个单例对象中。
两种实现方式:
- 标准
main
方法:
css
object MyApp {
def main(args: Array[String]): Unit = {
println("Hello from the main method!")
}
}
AI写代码scala
12345
- 继承
App
特质 (更简洁):
scala
object MyApp extends App {
// 这里的代码会直接作为 main 方法体执行
println("Hello from the App trait!")
// 命令行参数可以通过 args 变量访问
if (args.length > 0) {
println(s"First argument: ${args(0)}")
}
}
AI写代码scala
12345678
3. 伴生对象
当一个单例对象与一个类具有相同的名称,并且它们定义在同一个源文件中时,这个对象被称为该类的伴生对象 ,该类被称为该对象的伴生类。
核心特性:
伴生类和伴生对象可以互相访问对方的私有 (private
) 成员。
常见用途:
在伴生对象中放置类似于 Java 静态方法的工具方法。
在伴生对象中定义工厂方法 (特别是名为
apply
的方法),用于创建伴生类的实例,隐藏new
关键字。
代码案例:
scss
// 伴生类
class User private (val id: Int, val name: String) { // 主构造器设为 private
private 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") // 错误:构造器是私有的
AI写代码scala
1234567891011121314151617181920212223242526272829303132
六、综合案例
在 Scala 中,工具类 (包含纯粹的功能方法,不维护状态) 通常被实现为单例对象。
代码案例:一个简单的字符串工具对象
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 = null
println(s"'${str1}' is empty? ${StringUtils.isEmpty(str1)}")
println(s"'${str2}' is empty? ${StringUtils.isEmpty(str2)}")
println(s"Capitalized '${str1}': ${StringUtils.capitalize(str1)}")
}
AI写代码scala
123456789101112131415161718192021222324252627282930
练习题
题目一:简单类定义
定义一个 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
类,包含 x
和 y
两个坐标。再定义一个 isSameAs(other: Point)
方法,比较当前点是否与另一个点相同。然后,修改 x
和 y
为 private[this]
,并观察 isSameAs
方法是否还能正常编译。如果不能,解释原因。
题目十五:综合案例
创建一个 Counter
类,它有一个私有的、可变的 count
变量,初始值为0。类中提供 increment()
方法 (每次将count加1) 和 current()
方法 (返回当前count值)。为其创建一个伴生对象,提供一个 apply
方法,允许通过 Counter()
创建新实例。
答案与解析
答案一:
kotlin
class Book(val title: String, val author: String)
AI写代码scala
1
解析: 在主构造器参数前使用
val
是将参数直接定义为公共不可变成员变量的简洁语法。
答案二:
go
val myBook = new Book("Programming in Scala", "Martin Odersky")
println(myBook.title)
AI写代码scala
12
解析: 使用
new
关键字和类名来创建对象,通过.
操作符访问其成员。
答案三:
arduino
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())
AI写代码scala
1234567
解析:
def
用于在类中定义方法。s""
字符串插值器用于方便地格式化字符串。
答案四:
java
class Movie {
var director: String = _
}
val m = new Movie()
println(m.director) // 输出: null
AI写代码scala
12345
解析:
String
是引用类型 (AnyRef),其默认零值是null
。
答案五:
kotlin
class Laptop(val brand: String, var ramInGB: Int)
AI写代码scala
1
解析:
val
使brand
成为不可变成员,var
使ramInGB
成为可变成员。
答案六:
kotlin
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")
AI写代码scala
12345678
解析: 辅助构造器
def this(...)
必须在其第一行调用另一个构造器。
答案七:
kotlin
class BankAccount {
private var balance: Double = 0.0
def deposit(amount: Double): Unit = {
if (amount > 0) balance += amount
}
def getBalance(): Double = {
balance
}
}
AI写代码scala
1234567891011
解析:
private
关键字将balance
的访问权限限制在类内部,外部只能通过公共的deposit
和getBalance
方法进行交互,实现了封装。
答案八:
ini
object MathConstants {
val PI = 3.14159
val E = 2.71828
}
println(MathConstants.PI)
AI写代码scala
12345
解析:
object
关键字创建了一个全局唯一的单例对象。
答案九:
scala
object EntryPoint extends App {
println("Scala application started!")
}
AI写代码scala
123
解析: 继承
App
特质是创建可执行应用程序的最简洁方式,对象体内的代码会自动成为main
方法的内容。
答案十:
kotlin
class Circle private (val radius: Double)
object Circle {
def calculateArea(c: Circle): Double = {
// 可以访问 Circle 的私有成员 radius
Math.PI * c.radius * c.radius
}
}
AI写代码scala
12345678
解析: 伴生对象
Circle
可以访问伴生类Circle
的private
成员radius
。
答案十一:
scss
class Circle private (val radius: Double)
object Circle {
def apply(radius: Double): Circle = {
new Circle(radius)
}
// ... calculateArea 方法 ...
}
val myCircle = Circle(5.0) // 无需 new,直接调用 apply 方法
AI写代码scala
12345678910
解析:
apply
方法是一个特殊的语法糖,允许你像调用函数一样创建对象。
答案十二:
typescript
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)
AI写代码scala
12345678910111213141516171819
解析: 类定义体中、成员方法之外的代码都属于主构造器的一部分,会在对象创建时执行。
答案十四:
kotlin
class Point(private[this] val x: Int, private[this] val y: Int) {
def isSameAs(other: Point): Boolean = {
// this.x == other.x // 这行代码会编译错误
false // 仅为使代码完整
}
}
AI写代码scala
123456
解析: 代码无法正常编译 。因为
private[this]
是对象私有 的,意味着只有当前对象 (this
) 才能访问x
和y
。在isSameAs
方法中,other.x
尝试访问另一个Point
对象的x
字段,这是不被允许的。如果使用private
,则可以访问,因为private
允许同一类的不同实例之间互相访问私有成员。
答案十五:
scss
class Counter private {
private var count: Int = 0
def 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
AI写代码scala
123456789101112131415161718
解析: 这个例子结合了私有构造器、私有成员、公共方法和伴生对象的
apply
工厂方法,是一个典型的Scala封装模式。将构造器设为私有,强制用户通过伴生对象的工厂方法来创建实例。