Skip to content

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 modify

Private 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 field

Methods 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)      // 1

Auxiliary 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)  // 2

Companion 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.0

Case 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)   // 25

Advanced 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 purring

Abstract 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.

Content is for learning and research only.