Classes
## Learning Objectives
- Master class definitions
- Work with constructors
- Understand companion objects
- Learn case classes
- Master basic OOP concepts in Scala
## Class Basics
### Defining a Class
```scala
class Person {
var name: String = ""
var age: Int = 0
def greet(): String = s"Hello, I'm $name"
}
val person = new Person()
person.name = "Alice"
person.age = 30
println(person.greet()) // Hello, I'm Alice
```
### Primary Constructor
The class parameters become the primary constructor:
```scala
class Person(val name: String, var age: Int) {
def greet(): String = s"Hello, I'm $name"
}
val alice = new Person("Alice", 30)
println(alice.name) // Alice
println(alice.age) // 30
alice.age = 31 // OK - var
// alice.name = "Bob" // Error - val
```
### Constructor with Default Values
```scala
class Person(val name: String = "Unknown", var age: Int = 0) {
def greet(): String = s"Hello, I'm $name"
}
val unknown = new Person() // name="Unknown", age=0
val bob = new Person("Bob") // name="Bob", age=0
val alice = new Person("Alice", 30) // name="Alice", age=30
```
## Auxiliary Constructors
```scala
class Person(val name: String, var age: Int) {
def this(name: String) = this(name, 0) // Auxiliary constructor
def this() = this("Unknown", 0) // Another auxiliary
override def toString = s"Person($name, $age)"
}
val p1 = new Person("Alice", 30)
val p2 = new Person("Bob")
val p3 = new Person()
```
## Visibility Modifiers
### Private Members
```scala
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 // Getter
}
val account = new BankAccount(100)
account.deposit(50)
account.withdraw(30)
println(account.getBalance) // 120
// account.balance // Error: balance is private
```
### getter and setter Generation
```scala
class Person {
var name: String = _ // Generates getter and setter
val id: Int = 0 // Generates getter only
private var balance: Double = 0 // No getter/setter generated
private[this] var secret: String = "" // Object-private
}
```
## Companion Objects
A companion object has the same name as the class:
```scala
class Person(val name: String, val age: Int) {
override def toString = s"Person($name, $age)"
}
object Person {
def apply(name: String, age: Int): Person = new Person(name, age)
def apply(name: String): Person = new Person(name, 0)
def createStudent(name: String, age: Int): Person = {
if (age < 0) throw new IllegalArgumentException("Invalid age")
new Person(name, age)
}
}
// Using companion
val p1 = Person("Alice", 30) // apply method
val p2 = Person("Bob") // apply with default age
val p3 = Person.createStudent("Charlie", 25)
```
## Case Classes
Case classes automatically generate:
- val parameters (immutable fields)
- apply, unapply, copy methods
- toString, equals, hashCode
```scala
case class Person(name: String, age: Int) {
def greet(): String = s"Hello, I'm $name"
}
val alice = Person("Alice", 30)
// No 'new' needed - apply is generated
val bob = Person("Bob", 25)
// Immutable - no var allowed by default
// alice.age = 31 // Error!
// Copy with modifications
val aliceOlder = alice.copy(age = 31)
// Pattern matching
alice match {
case Person(n, a) => s"$n is $a years old"
}
// Equality
val alice2 = Person("Alice", 30)
alice == alice2 // true (equals generated)
// Hash map usage
val people = Set(alice, alice2)
// Set contains only one Person("Alice", 30)
```
## Objects
Objects are singleton instances:
```scala
object Config {
val defaultTimeout = 30
val maxRetries = 3
def getSetting(key: String): String = {
// Load from config
key match {
case "timeout" => defaultTimeout.toString
case "retries" => maxRetries.toString
case _ => "unknown"
}
}
}
println(Config.defaultTimeout) // 30
println(Config.getSetting("timeout")) // 30
```
## Class vs Object vs Case Class
| Feature | Class | Object | Case Class |
|---------|-------|--------|------------|
| Instantiation | new | Single instance | new (or apply) |
| Parameters | var/val | N/A | val by default |
| Methods | Yes | Yes | Yes |
| extends | Yes | Yes | Yes |
| equals/hashCode | Reference | N/A | Generated |
| apply | Not generated | Yes | Generated |
| unapply | Not generated | Yes | Generated |
| copy | Not generated | N/A | Generated |
## Abstract Classes
```scala
abstract class Shape {
def area(): Double
def perimeter(): Double
}
class Circle(val radius: Double) extends Shape {
def area(): Double = Math.PI * radius * radius
def perimeter(): Double = 2 * Math.PI * radius
}
class Rectangle(val width: Double, val height: Double) extends Shape {
def area(): Double = width * height
def perimeter(): Double = 2 * (width + height)
}
```
## Nested Classes
```scala
class Outer {
class Inner {
def hello() = "Hello from Inner"
}
def createInner(): Inner = new Inner()
}
val outer = new Outer()
val inner = outer.createInner()
println(inner.hello())
```
## Enum-like Structures
```scala
object Color extends Enumeration {
val Red = Value
val Green = Value
val Blue = Value
val Yellow = Value("YELLOW")
}
import Color._
Red match {
case Red => "Red"
case Green => "Green"
case Blue => "Blue"
case _ => "Other"
}
```
## Summary
- Classes define blueprints; objects are singleton instances
- Primary constructor parameters can be val or var
- Private members hide implementation details
- Companion objects provide factory methods and static-like utilities
- Case classes automatically generate boilerplate for immutable data
- Case class copy creates modified copies
- Abstract classes define contracts for subclasses
- Use objects for utility functions and singleton state
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →