Scala Classes and Objects
Classes and objects are core concepts of object-oriented programming. This chapter will详细介绍(详细介绍 - 详细介绍)Scala中类的定义、对象的创建、继承、多态等面向对象编程的基本概念。
Class Definition
Basic Class Definition
scala
// Basic class definition
class Person {
// Class body
}
// Create object
val person = new Person()
// Class with parameters
class Student(name: String, age: Int) {
// Primary constructor parameters automatically become class fields
def introduce(): String = s"Hi, I'm $name and I'm $age years old"
}
val student = new Student("Alice", 20)
println(student.introduce())Constructor Parameters
scala
// val parameter: immutable field
class Person(val name: String, val age: Int) {
// name and age automatically become public immutable fields
}
val person = new Person("Bob", 25)
println(person.name) // Can access
println(person.age) // Can access
// person.age = 26 // Compile error: cannot modify
// var parameter: mutable field
class Counter(var count: Int) {
def increment(): Unit = count += 1
def decrement(): Unit = count -= 1
}
val counter = new Counter(0)
counter.increment()
println(counter.count) // 1
counter.count = 10 // Can directly modifyPrivate Parameters
scala
// Private parameter: does not generate field
class BankAccount(private var balance: Double) {
def deposit(amount: Double): Unit = {
if (amount > 0) balance += amount
}
def withdraw(amount: Double): Boolean = {
if (amount > 0 && amount <= balance) {
balance -= amount
true
} else {
false
}
}
def getBalance: Double = balance // Read-only access
}
val account = new BankAccount(1000.0)
account.deposit(500.0)
println(account.getBalance) // 1500.0
// println(account.balance) // Compile error: private fieldMethods and Fields
Instance Methods
scala
class Calculator {
def add(x: Int, y: Int): Int = x + y
def multiply(x: Int, y: Int): Int = x * y
// Methods can access class fields
private var history: List[String] = List()
def addWithHistory(x: Int, y: Int): Int = {
val result = x + y
history = s"$x + $y = $result" :: history
result
}
def getHistory: List[String] = history.reverse
}
val calc = new Calculator()
calc.addWithHistory(3, 4)
calc.addWithHistory(5, 6)
println(calc.getHistory) // List("3 + 4 = 7", "5 + 6 = 11")Field Initialization
scala
class Person(firstName: String, lastName: String) {
// Computed field
val fullName: String = s"$firstName $lastName"
// Lazy initialization field
lazy val initials: String = {
println("Computing initials...")
s"${firstName.head}.${lastName.head}."
}
// Mutable field
var nickname: String = firstName
// Private field
private var id: Int = Person.nextId()
def getId: Int = id
}
object Person {
private var currentId = 0
private def nextId(): Int = {
currentId += 1
currentId
}
}
val person = new Person("John", "Doe")
println(person.fullName) // "John Doe"
println(person.initials) // Computed now
println(person.getId) // 1Auxiliary Constructors
scala
class Person(val name: String, val age: Int) {
// Auxiliary constructor must call primary constructor or another auxiliary constructor
def this(name: String) = {
this(name, 0) // Call primary constructor
}
def this() = {
this("Unknown") // Call the auxiliary constructor above
}
override def toString: String = s"Person($name, $age)"
}
// Use different constructors
val person1 = new Person("Alice", 25)
val person2 = new Person("Bob")
val person3 = new Person()
println(person1) // Person(Alice, 25)
println(person2) // Person(Bob, 0)
println(person3) // Person(Unknown, 0)Objects (Object)
Singleton Object
scala
// Singleton object
object MathUtils {
val PI: Double = 3.14159
def square(x: Double): Double = x * x
def circleArea(radius: Double): Double = PI * square(radius)
private var callCount = 0
def getCallCount: Int = {
callCount += 1
callCount
}
}
// Use singleton object
println(MathUtils.PI)
println(MathUtils.circleArea(5.0))
println(MathUtils.getCallCount) // 1
println(MathUtils.getCallCount) // 2Companion Object
scala
// Class and its companion object can access each other's private members
class BankAccount private (private var balance: Double) {
def deposit(amount: Double): Unit = {
balance += amount
}
def getBalance: Double = balance
// Access companion object's private members
def getNextAccountNumber: String = BankAccount.nextAccountNumber()
}
object BankAccount {
private var accountCounter = 0
// Factory method
def apply(initialBalance: Double): BankAccount = {
new BankAccount(initialBalance)
}
def apply(): BankAccount = {
new BankAccount(0.0)
}
private def nextAccountNumber(): String = {
accountCounter += 1
f"ACC$accountCounter%06d"
}
// Access class's private members
def transfer(from: BankAccount, to: BankAccount, amount: Double): Boolean = {
if (from.balance >= amount) {
from.balance -= amount
to.balance += amount
true
} else {
false
}
}
}
// Use companion object
val account1 = BankAccount(1000.0) // Use apply method
val account2 = BankAccount() // Use no-argument apply method
account1.deposit(500.0)
BankAccount.transfer(account1, account2, 200.0)
println(account1.getBalance) // 1300.0
println(account2.getBalance) // 200.0Case Classes
Basic Case Classes
scala
// Case classes automatically generate many useful methods
case class Person(name: String, age: Int)
val person1 = Person("Alice", 25) // No need for new keyword
val person2 = Person("Alice", 25)
// Automatically generated methods
println(person1) // Person(Alice,25) - toString
println(person1 == person2) // true - equals
println(person1.hashCode) // Auto-generated hashCode
// Pattern matching support
person1 match {
case Person(name, age) => println(s"Name: $name, Age: $age")
}
// copy method
val person3 = person1.copy(age = 26)
println(person3) // Person(Alice,26)
// Field access
println(person1.name) // Alice
println(person1.age) // 25Advanced Case Class Features
scala
case class Address(street: String, city: String, zipCode: String)
case class Person(
name: String,
age: Int,
email: Option[String] = None,
address: Option[Address] = None
) {
// Can add custom methods
def isAdult: Boolean = age >= 18
def hasEmail: Boolean = email.isDefined
def fullAddress: String = address match {
case Some(addr) => s"${addr.street}, ${addr.city} ${addr.zipCode}"
case None => "No address"
}
}
val address = Address("123 Main St", "New York", "10001")
val person = Person(
name = "John Doe",
age = 30,
email = Some("john@example.com"),
address = Some(address)
)
println(person.isAdult) // true
println(person.hasEmail) // true
println(person.fullAddress) // 123 Main St, New York 10001
// Nested copy
val movedPerson = person.copy(
address = person.address.map(_.copy(city = "Boston"))
)Inheritance
Basic Inheritance
scala
// Base class
class Animal(val name: String) {
def speak(): String = "Some sound"
def move(): String = "Moving"
// Methods that can be overridden
def describe(): String = s"This is $name"
}
// Derived class
class Dog(name: String, val breed: String) extends Animal(name) {
// Override method
override def speak(): String = "Woof!"
override def describe(): String = s"This is $name, a $breed dog"
// New method
def wagTail(): String = s"$name is wagging tail"
}
class Cat(name: String) extends Animal(name) {
override def speak(): String = "Meow!"
def purr(): String = s"$name is purring"
}
// Use inheritance
val dog = new Dog("Buddy", "Golden Retriever")
val cat = new Cat("Whiskers")
println(dog.speak()) // Woof!
println(dog.describe()) // This is Buddy, a Golden Retriever dog
println(dog.wagTail()) // Buddy is wagging tail
println(cat.speak()) // Meow!
println(cat.purr()) // Whiskers is purringAbstract Classes
scala
// Abstract class
abstract class Shape {
// Abstract methods
def area(): Double
def perimeter(): Double
// Concrete methods
def describe(): String = s"A shape with area ${area()} and perimeter ${perimeter()}"
}
class Rectangle(val width: Double, val height: Double) extends Shape {
def area(): Double = width * height
def perimeter(): Double = 2 * (width + height)
}
class Circle(val radius: Double) extends Shape {
def area(): Double = math.Pi * radius * radius
def perimeter(): Double = 2 * math.Pi * radius
}
val rectangle = new Rectangle(5.0, 3.0)
val circle = new Circle(2.0)
println(rectangle.describe())
println(circle.describe())
// Polymorphism
val shapes: List[Shape] = List(rectangle, circle)
shapes.foreach(shape => println(s"Area: ${shape.area()}"))Traits
Basic Traits
scala
// Trait definition
trait Drawable {
def draw(): String
}
trait Movable {
def move(x: Int, y: Int): String
}
// Classes can mix in multiple traits
class Circle(val radius: Double) extends Drawable with Movable {
def draw(): String = s"Drawing a circle with radius $radius"
def move(x: Int, y: Int): String = s"Moving circle to ($x, $y)"
}
class Rectangle(val width: Double, val height: Double) extends Drawable with Movable {
def draw(): String = s"Drawing a rectangle ${width}x${height}"
def move(x: Int, y: Int): String = s"Moving rectangle to ($x, $y)"
}
val circle = new Circle(5.0)
val rectangle = new Rectangle(10.0, 8.0)
println(circle.draw())
println(rectangle.move(10, 20))Traits with Default Implementations
scala
trait Logger {
// Abstract method
def log(message: String): Unit
// Concrete methods
def info(message: String): Unit = log(s"INFO: $message")
def error(message: String): Unit = log(s"ERROR: $message")
def debug(message: String): Unit = log(s"DEBUG: $message")
}
class ConsoleLogger extends Logger {
def log(message: String): Unit = println(message)
}
class FileLogger(filename: String) extends Logger {
def log(message: String): Unit = {
// Simplified file writing
println(s"Writing to $filename: $message")
}
}
val consoleLogger = new ConsoleLogger()
val fileLogger = new FileLogger("app.log")
consoleLogger.info("Application started")
fileLogger.error("Database connection failed")Access Modifiers
Access Levels
scala
class AccessExample {
// Public field (default)
val publicField = "public"
// Private field
private val privateField = "private"
// Protected field
protected val protectedField = "protected"
// Package private
private[scala] val packagePrivateField = "package private"
def publicMethod(): String = "public method"
private def privateMethod(): String = "private method"
protected def protectedMethod(): String = "protected method"
// Private members can only be accessed within the class
def accessPrivate(): String = privateMethod()
}
class SubClass extends AccessExample {
// Can access protected members
def accessProtected(): String = protectedMethod()
// Cannot access private members
// def accessPrivate(): String = privateMethod() // Compile error
}Practical Application Examples
Bank Account System
scala
// Basic account class
abstract class Account(val accountNumber: String, protected var balance: Double) {
def getBalance: Double = balance
def deposit(amount: Double): Boolean = {
if (amount > 0) {
balance += amount
true
} else {
false
}
}
// Abstract method, implemented by subclasses
def withdraw(amount: Double): Boolean
def transfer(to: Account, amount: Double): Boolean = {
if (withdraw(amount)) {
to.deposit(amount)
true
} else {
false
}
}
}
// Savings account
class SavingsAccount(accountNumber: String, balance: Double, val interestRate: Double)
extends Account(accountNumber, balance) {
def withdraw(amount: Double): Boolean = {
if (amount > 0 && amount <= balance) {
balance -= amount
true
} else {
false
}
}
def addInterest(): Unit = {
balance += balance * interestRate
}
}
// Checking account
class CheckingAccount(accountNumber: String, balance: Double, val overdraftLimit: Double)
extends Account(accountNumber, balance) {
def withdraw(amount: Double): Boolean = {
if (amount > 0 && amount <= balance + overdraftLimit) {
balance -= amount
true
} else {
false
}
}
def getAvailableBalance: Double = balance + overdraftLimit
}
// Usage example
val savings = new SavingsAccount("SAV001", 1000.0, 0.02)
val checking = new CheckingAccount("CHK001", 500.0, 200.0)
savings.deposit(500.0)
savings.addInterest()
println(s"Savings balance: ${savings.getBalance}")
checking.withdraw(600.0) // Use overdraft
println(s"Checking balance: ${checking.getBalance}")
println(s"Available balance: ${checking.getAvailableBalance}")
// Transfer
savings.transfer(checking, 300.0)
println(s"After transfer - Savings: ${savings.getBalance}, Checking: ${checking.getBalance}")Graphics System
scala
// Trait definition
trait Drawable {
def draw(): String
}
trait Scalable {
def scale(factor: Double): Unit
}
trait Colorable {
var color: String
def setColor(newColor: String): Unit = color = newColor
}
// Abstract base class
abstract class Shape extends Drawable {
def area(): Double
def perimeter(): Double
}
// Concrete implementations
class Circle(var radius: Double, var color: String = "black")
extends Shape with Scalable with Colorable {
def area(): Double = math.Pi * radius * radius
def perimeter(): Double = 2 * math.Pi * radius
def draw(): String = s"Drawing a $color circle with radius $radius"
def scale(factor: Double): Unit = radius *= factor
}
class Rectangle(var width: Double, var height: Double, var color: String = "black")
extends Shape with Scalable with Colorable {
def area(): Double = width * height
def perimeter(): Double = 2 * (width + height)
def draw(): String = s"Drawing a $color rectangle ${width}x${height}"
def scale(factor: Double): Unit = {
width *= factor
height *= factor
}
}
// Usage example
val shapes: List[Shape with Scalable with Colorable] = List(
new Circle(5.0, "red"),
new Rectangle(10.0, 8.0, "blue")
)
shapes.foreach { shape =>
println(shape.draw())
println(s"Area: ${shape.area()}")
shape.scale(1.5)
shape.setColor("green")
println(s"After scaling and recoloring: ${shape.draw()}")
println(s"New area: ${shape.area()}")
println()
}Exercises
Exercise 1: Student Management System
scala
object StudentManagementSystem {
case class Course(code: String, name: String, credits: Int)
case class Grade(course: Course, score: Double) {
def letterGrade: String = score match {
case s if s >= 90 => "A"
case s if s >= 80 => "B"
case s if s >= 70 => "C"
case s if s >= 60 => "D"
case _ => "F"
}
def isPass: Boolean = score >= 60
}
class Student(val id: String, val name: String) {
private var grades: List[Grade] = List()
def addGrade(grade: Grade): Unit = {
grades = grade :: grades
}
def getGrades: List[Grade] = grades.reverse
def gpa: Double = {
if (grades.isEmpty) 0.0
else {
val totalPoints = grades.map(g => g.score * g.course.credits).sum
val totalCredits = grades.map(_.course.credits).sum
totalPoints / totalCredits
}
}
def totalCredits: Int = grades.filter(_.isPass).map(_.course.credits).sum
override def toString: String = s"Student($id, $name, GPA: ${gpa})"
}
def main(args: Array[String]): Unit = {
val math = Course("MATH101", "Calculus I", 4)
val cs = Course("CS101", "Introduction to Programming", 3)
val english = Course("ENG101", "English Composition", 3)
val student = new Student("S001", "Alice Johnson")
student.addGrade(Grade(math, 85.0))
student.addGrade(Grade(cs, 92.0))
student.addGrade(Grade(english, 78.0))
println(student)
println(s"Total Credits: ${student.totalCredits}")
student.getGrades.foreach { grade =>
println(s"${grade.course.name}: ${grade.score} (${grade.letterGrade})")
}
}
}Exercise 2: E-commerce System
scala
object ECommerceSystem {
trait Discountable {
def applyDiscount(percentage: Double): Unit
}
abstract class Product(val id: String, val name: String, protected var price: Double) {
def getPrice: Double = price
override def toString: String = s"$name ($$${price})"
}
class Book(id: String, name: String, price: Double, val author: String, val isbn: String)
extends Product(id, name, price) with Discountable {
def applyDiscount(percentage: Double): Unit = {
price = price * (1 - percentage / 100)
}
override def toString: String = s"Book: $name by $author ($$${price})"
}
class Electronics(id: String, name: String, price: Double, val brand: String, val warranty: Int)
extends Product(id, name, price) with Discountable {
def applyDiscount(percentage: Double): Unit = {
price = price * (1 - percentage / 100)
}
override def toString: String = s"Electronics: $name by $brand ($$${price}, ${warranty}y warranty)"
}
case class CartItem(product: Product, quantity: Int) {
def totalPrice: Double = product.getPrice * quantity
}
class ShoppingCart {
private var items: List[CartItem] = List()
def addItem(product: Product, quantity: Int): Unit = {
items = CartItem(product, quantity) :: items
}
def removeItem(productId: String): Unit = {
items = items.filterNot(_.product.id == productId)
}
def getItems: List[CartItem] = items.reverse
def totalAmount: Double = items.map(_.totalPrice).sum
def applyBulkDiscount(percentage: Double): Unit = {
items.foreach { item =>
item.product match {
case discountable: Discountable => discountable.applyDiscount(percentage)
case _ => // Products that don't support discounts
}
}
}
}
def main(args: Array[String]): Unit = {
val book = new Book("B001", "Scala Programming", 45.99, "Martin Odersky", "978-0981531687")
val laptop = new Electronics("E001", "MacBook Pro", 1299.99, "Apple", 1)
val cart = new ShoppingCart()
cart.addItem(book, 2)
cart.addItem(laptop, 1)
println("Shopping Cart:")
cart.getItems.foreach(item =>
println(s"${item.product} x ${item.quantity} = $$${item.totalPrice}")
)
println(s"Total: $$${cart.totalAmount}")
println("\nApplying 10% bulk discount...")
cart.applyBulkDiscount(10.0)
println("Updated Cart:")
cart.getItems.foreach(item =>
println(s"${item.product} x ${item.quantity} = $$${item.totalPrice}")
)
println(s"New Total: $$${cart.totalAmount}")
}
}Summary
This chapter introduced core concepts of Scala classes and objects:
- Class Definition: Constructor, field, method definitions
- Objects: Use of singleton objects and companion objects
- Case Classes: Special classes that auto-generate useful methods
- Inheritance: Class inheritance and method overriding
- Traits: Multiple inheritance and mixin mechanism
- Access Modifiers: Control member visibility
- Practical Applications: Bank system, graphics system, etc.
Mastering these object-oriented programming concepts is the foundation for building complex Scala applications. In the next chapter, we will learn Scala Traits to deeply understand Scala's unique trait system and mixin mechanisms.