What are classes ?
In Scala, a class is a blueprint for creating objects (instances). It can contain fields (variables), methods (functions), and constructors. Scala is both object-oriented and functional, so classes are a key part of its object-oriented aspect.
Defining a class
A class in Scala is defined using the class keyword followed by the class name. The primary constructor is defined in the class definition itself, while additional methods and fields can be defined within the class body.
In Scala, fields (also known as instance variables) are given public visibility by default, meaning they can be accessed from outside the class.
Default Visibility in Scala
- When you declare a field inside a class using
valorvar, the field is public by default. - If you want to restrict access to the field, you need to explicitly mark it as
private.
class Car {
var topClassExtraCost = 0
private var roadTax = 100
def cost(basicCost: Int): Int = basicCost + topClassExtraCost + roadTax
}
Explanation:
- Class Definition:
class Car: This defines a class namedCar.
- Fields:
var topClassExtraCost = 0: This is a public mutable field. By default, it is public, so it can be accessed and modified from outside the class. Its initial value is set to0. This field might represent the extra cost for a higher-end model of the car.private var roadTax = 100: This is a private mutable field. It is initialized with a value of100, and because it is markedprivate, it cannot be accessed or modified directly from outside the class. This might represent the car’s road tax.
- Method – function defined inside class:
def cost(basicCost: Int): Int: This is a method namedcostthat takes a parameterbasicCostof typeIntand returns anInt.- The method calculates the total cost of the car by adding the
basicCost,topClassExtraCost, androadTax.basicCost: The basic price of the car.topClassExtraCost: The additional cost for higher-end models (this value can be modified externally).roadTax: The road tax, which is private and cannot be accessed directly from outside but is used internally in the calculation.
Usage :
val bmw= new Car
bmw.topClassExtraCost = 5000 // Modifying the public field
// Calculating the total cost of the car
val totalCost = bmw.cost(20000)
println(totalCost) // Output: 25100 (20000 basic cost + 5000 extra cost + 100 road tax)
Key Points:
- Public Field (
topClassExtraCost): This field is accessible and modifiable from outside the class. In the example, it was set to5000. - Private Field (
roadTax): This field is private, so it cannot be accessed or modified from outside the class. It’s set to100by default, and it is used internally in the cost calculation. - Cost Calculation: The method
costadds up thebasicCost,topClassExtraCost, and the privateroadTaxto calculate the total price of the car.
This structure allows some fields to be modified by external code (topClassExtraCost), while keeping others (roadTax) hidden and controlled internally.
Code :
// Simple class and Object demo
class Car {
var topClassExtraCost = 0
private var roadTax = 100
def cost(basicCost: Int): Int = basicCost + topClassExtraCost + roadTax
}
object HelloWorld {
def main(args: Array[String]): Unit = {
var bmw = new Car
bmw.topClassExtraCost = 5000
var result = bmw.cost(20000)
println("Total cost of car : " + result) // 25100
}
}
Access Levels in Scala
In Scala, access levels (also called access modifiers) control the visibility of classes, traits, objects, methods, and fields. Scala provides three main access levels: public, private, and protected. It also allows more granular control with additional qualifiers.
1. Public (Default)
In Scala, members (fields, methods, etc.) of a class are public by default, meaning they are accessible from anywhere unless explicitly marked otherwise.
- If no access modifier is specified, the member is public.
- Public members can be accessed from any other code.
Example:
class Person {
var name: String = "John" // Public by default
}
val person = new Person
println(person.name) // Accessible, since it's public by default
2. Private
The private access modifier restricts access to members to the containing class or object. These members cannot be accessed from outside the class or object in which they are defined.
Example:
class Person {
private var age: Int = 30 // Private field
def getAge: Int = age // Public method to access private field
}
val person = new Person
// println(person.age) // Error: Cannot access private member 'age'
println(person.getAge) // Output: 30 (Accessed via a public method)
Private at Object Level:
- You can restrict access to specific objects or classes by defining members as private within those objects or classes.
object Test {
private var counter = 0 // Accessible only within 'Test' object
def incrementCounter(): Unit = {
counter += 1
}
}
// println(Test.counter) // Error: 'counter' is private
Test.incrementCounter() // Works because incrementCounter is public
3. Protected
The protected access modifier allows members to be accessed only from within the same class or subclasses of the class. Unlike Java, in Scala, protected members are not accessible from other classes in the same package.
Example:
class Animal {
protected var species: String = "Mammal"
}
class Dog extends Animal {
def getSpecies: String = species // Accessible in subclass
}
val dog = new Dog
println(dog.getSpecies) // Output: Mammal
// println(dog.species) // Error: Cannot access 'protected' member
Summary of Access Levels
| Modifier | Description |
|---|---|
| public (default) | Accessible from anywhere. No keyword needed. |
| private | Accessible only within the defining class/object. |
| protected | Accessible within the class and its subclasses. |
Scala’s access modifiers (public, private, protected) give you flexibility to control access to members in a class.
More on public and private access specifiers
In Scala, fields (also known as instance variables) are not private by default. Instead, they are given public visibility by default, meaning they can be accessed from outside the class.
Default Visibility in Scala
- When you declare a field inside a class using
valorvar, the field is public by default. - If you want to restrict access to the field, you need to explicitly mark it as
private.
Example of Public Fields (Default Behavior)
By default, fields are accessible outside the class. Here’s an example:
class Person {
var name: String = "John Doe" // Public field
val age: Int = 30 // Public field
}
val person = new Person
println(person.name) // Output: John Doe
println(person.age) // Output: 30
In this example:
- The field
nameis mutable (var) and public by default. - The field
ageis immutable (val) and public by default. - Both
nameandageare accessible from outside thePersonclass.
Private Fields in Scala
To make a field private and prevent it from being accessed outside the class, you can explicitly declare it with the private keyword.
Example of Private Fields
class Person {
private var name: String = "John Doe" // Private field
private val age: Int = 30 // Private field
// Method to access the private field
def getName: String = name
}
val person = new Person
// println(person.name) // Error: Cannot access private field 'name'
println(person.getName) // Output: John Doe
In this example:
- The
nameandagefields are declared asprivate, so they are not accessible directly from outside thePersonclass. - You can access the private field
nameusing thegetNamemethod.
Key Points
- Public by Default: In Scala, fields are public by default unless you specify otherwise.
- Private Fields: To make a field private, you explicitly declare it with the
privatekeyword. - Encapsulation: You can control the visibility of fields using access modifiers (
private,protected,public).
Visibility Modifiers in Scala
private: Restricts access to within the class.protected: Accessible within the class and subclasses.- No Modifier (default): Fields are public and can be accessed from anywhere.
By using these access modifiers, you can control how fields are accessed and ensure better encapsulation.
Method with side effects
In Scala, a method with side effects is one that, in addition to returning a value, modifies some state or interacts with the outside world (e.g., by changing a variable, writing to a file, printing to the console, or sending data over the network).
Side effects are not part of the method’s return value but happen as a consequence of calling the method.
Key Characteristics of Methods with Side Effects:
- Modifies mutable state: Changes the state of a variable or data structure.
- I/O operations: Writes to or reads from external systems like files, databases, or consoles.
- Non-deterministic behavior: Can produce different results on subsequent calls due to the change in state or interactions with the environment.
In functional programming, side effects are generally discouraged because they make reasoning about code harder. However, they are necessary for certain tasks like I/O or state management.
Example 1: Method that Modifies Mutable State
class Counter {
private var count: Int = 0 // Mutable state
// Method with side effect: modifies 'count'
def increment(): Unit = {
count += 1
}
def getCount: Int = count // Returns the current count
}
val counter = new Counter
counter.increment() // Modifies the state (side effect)
println(counter.getCount) // Output: 1
- Side effect: The method
incrementmodifies thecountvariable inside theCounterclass. Even though it returnsUnit, it has an observable impact by changing the internal state of theCounterobject.
Example 2: Method with I/O Side Effects
class Logger {
// Method with side effect: prints to the console
def log(message: String): Unit = {
println(s"Log: $message")
}
}
val logger = new Logger
logger.log("Application started") // Output: Log: Application started
- Side effect: The method
logwrites output to the console, which is a side effect because it interacts with the outside world.
Example 3: Method Writing to a File (I/O Side Effect)
import java.io._
class FileWriter {
// Method with side effect: writes to a file
def writeToFile(filename: String, content: String): Unit = {
val writer = new PrintWriter(new File(filename))
writer.write(content)
writer.close()
}
}
val fileWriter = new FileWriter
fileWriter.writeToFile("output.txt", "Hello, Scala!") // Side effect: writing to a file
- Side effect: The
writeToFilemethod writes the given content to a file, which is an external side effect (I/O operation).
Example 4: Method with Randomness (Non-deterministic Side Effect)
import scala.util.Random
class RandomGenerator {
// Method with side effect: produces different results on each call
def getRandomNumber(): Int = {
Random.nextInt(100) // Returns a random number between 0 and 99
}
}
val generator = new RandomGenerator
println(generator.getRandomNumber()) // Output: Varies each time
- Side effect: Although the method
getRandomNumberreturns a value, it has a non-deterministic side effect (producing different results each time it’s called).
Key Points about Methods with Side Effects
- State changes: If a method modifies a mutable field or variable, it has a side effect.
- I/O interactions: Printing to the console, writing to a file, or interacting with external systems is considered a side effect.
- Randomness and Time: If a method produces non-deterministic results (e.g., generating a random number, reading the current time), it has a side effect.
- Unit return type: Methods with significant side effects often return
Unit, indicating that their main purpose is performing an action rather than returning a meaningful result.
In functional programming, methods without side effects (also known as pure functions) are often preferred because they make code easier to reason about and test. However, side effects are necessary when dealing with real-world systems, so they must be managed carefully.