object in Scala
object HelloWorld {
def main(args: Array[String]): Unit = {
println("Hello,World! Welcome to Scala!!")
}
}
In Scala, object is a keyword used to define a singleton object. A singleton object is an instance of a class that is created only once, meaning there can be only one instance of it in the program. This concept is used in Scala to encapsulate utility methods, constants, or as a replacement for static members (since Scala doesn’t have static methods like Java).
- Object is automatically instantiated. No need to use
newkeyword to instantiate. - We do not have a
statickeyword in Scala. - In
object, all the methods are by defaultstatic.
Key Features of object in Scala:
- Singleton: An
objectis created once and used globally in the program. It cannot be instantiated multiple times.- No
newKeyword: You don’t need to (and cannot) use thenewkeyword to create an instance of anobject.
- No
- No Constructor: Since an
objectis a singleton, it does not have a constructor like a class. - Static-Like Behavior: In Java, static methods and variables are associated with a class rather than an instance. Scala doesn’t have the
statickeyword, so you use anobjectto define methods or fields that should be globally accessible without requiring an instance of a class. - Companion Object: When an
objecthas the same name as a class and is defined in the same file, it is called a companion object. It can access the private members of the class and vice versa. Theobjectprovides methods and functionality that relate to the class but do not require an instance of the class. - Application Entry Point: In Scala, the entry point of a program is defined within an
objectusing themainmethod. Thismainmethod is executed when the program starts.
Differences Between object and class:
| Aspect | object | class |
|---|---|---|
| Instantiation | Only one instance (singleton). | Can have multiple instances. |
| Constructor | No constructor. | Can define constructors. |
| Usage | Used for static-like functionality, utilities, or entry points. | Defines a blueprint for creating objects. |
| Creation | Automatically instantiated. | Needs to be instantiated using new. |
Utility Methods: Like Java static utility classes.
object MathUtils {
def add(a: Int, b: Int): Int = a + b
}
println(MathUtils.add(2, 3)) // Output: 5
In short, an object in Scala is a singleton that is useful for defining methods, constants, and as an entry point for Scala applications. It helps achieve a static-like structure without requiring the static keyword like in Java.
object Internals – How object is mapped to Java class?
In Scala, an object is a singleton instance that provides a way to define methods and values without needing to create multiple instances. When Scala code is compiled to Java bytecode (for the JVM), Scala’s object gets mapped to Java in a specific way to accommodate the concept of singletons.
Here’s how a Scala object is represented in Java:
Mapping of Scala object to a Java Class
Scala compiles an object into a final class with a private constructor and a static instance of itself. This pattern ensures that only one instance of the class exists (similar to the Singleton Pattern in Java). Additionally, Scala generates a static method to access the singleton instance of the class.
Example:
Scala Code:
object HelloWorld {
def greet(): Unit = {
println("Hello, World!")
}
}
How it is translated to Java :
public final class HelloWorld {
// Private constructor to prevent instantiation
private HelloWorld() {}
// Static instance of the singleton object
public static final HelloWorld MODULE$ = new HelloWorld();
// Method in the singleton object
public void greet() {
System.out.println("Hello, World!");
}
}
In this Java equivalent:
- The Scala
object HelloWorldis mapped to a final class namedHelloWorld. - A static instance
MODULE$is created to represent the single instance of theobject. - The
greetmethod is accessible via the singleton instanceHelloWorld.MODULE$.greet().
Accessing the Singleton in Java
If you were to access this Scala object from Java, you would do it using the static MODULE$ field, which holds the single instance of the object:
HelloWorld.MODULE$.greet(); // Access the greet method of the singleton instance
In this case, MODULE$ provides access to the only instance of the HelloWorld object.
Static Methods in Objects
In Scala, an object can hold methods that look like static methods (from a Java perspective). In Java, methods in a Scala object are accessed via the MODULE$ instance, but conceptually they behave similarly to static methods.
Static Initialization in Scala object
In Java, static initialization blocks are used to execute code when the class is first loaded. In Scala, any code inside an object that is not part of a method is treated as a static initialization block, and it is executed when the object is first accessed.
Scala code :
object Singleton {
println("Singleton object created!")
}
Java translation :
public final class Singleton {
public static final Singleton MODULE$;
static {
MODULE$ = new Singleton();
System.out.println("Singleton object created!");
}
private Singleton() {}
}
- The
staticblock ensures that theprintlnstatement is executed when theMODULE$singleton instance is initialized.
Key Points
- Scala
objectis translated into a final Java class with a static instance (MODULE$). - The private constructor prevents instantiation from outside the class, ensuring singleton behavior.
- Methods defined in Scala
objectare like static methods in Java and are accessed via the singleton instanceMODULE$. - Companion objects are mapped to singleton classes in Java, with their instance stored in
MODULE$. - Any initialization code in the Scala
objectis executed when the singleton instance is first accessed, just like static initialization in Java.
Conclusion
In Scala, object provides a concise way to define singletons, and when compiled to Java bytecode, it is transformed into a class with a static instance (MODULE$) and a private constructor to enforce the singleton property. This allows Scala’s singleton model to integrate smoothly with the JVM.
What are companion classes & objects ?
In Scala, a companion class and a companion object are a pair of entities (a class and an object) that share the same name and are defined in the same source file. Despite having the same name, they serve different purposes:
- Companion Class: Represents a blueprint for creating instances (objects) with their own state and behavior.
- Companion Object: Holds static-like members (methods, values) that are shared among all instances of the class, similar to static methods and fields in Java. It is a singleton object, meaning only one instance of the object exists.
Characteristics
- Same Name: Both the class and the object must have the same name.
- Same File: They must be defined in the same source file.
- Access to Private Members: Companion objects and companion classes can access each other’s private members.
Use Cases
- Factory Methods: Companion objects are often used to create factory methods, which provide a convenient way to create instances of the class.
- Static Methods and Values: They can hold static methods or constants that are common across all instances of the companion class.
- Apply Method for Simplified Object Creation: The
applymethod in a companion object allows the class to be instantiated without explicitly using thenewkeyword. - Utility Methods: Companion objects can store utility methods that logically belong to the class but don’t require access to instance-specific data.
Companion Class and Object for Factory Method
class Person(val name: String, val age: Int) {
def display(): Unit = {
println(s"Name: $name, Age: $age")
}
}
object Person {
// Factory method to create a Person instance
// Apply method allows creating an instance without 'new'
def apply(name: String, age: Int): Person = new Person(name, age)
// Static-like method
def fromFullName(fullName: String): Person = {
val parts = fullName.split(" ")
new Person(parts(0), 0) // Default age of 0 for example
}
}
Usage :
val person1 = Person("Alice", 25) // Uses the apply method, no need for 'new'
val person2 = Person.fromFullName("Bob Smith") // Uses custom factory method
person1.display() // Output: Name: Alice, Age: 25
person2.display() // Output: Name: Bob, Age: 0
Explanation:
- Companion Class:
Personis the class that defines the structure (name and age) and behavior (displaymethod) of aPersonobject. - Companion Object:
Person(object) defines a factory methodapplyto createPersoninstances, so you don’t need to explicitly use thenewkeyword. It also defines a utility methodfromFullNameto create aPersonbased on a full name. - The
applymethod in the companion object makes object instantiation simpler. Instead of writingnew Person("Alice", 25), you can just writePerson("Alice", 25).
Companion Objects Holding Static Methods
Companion objects are ideal for holding static methods that don’t need access to the class instance.
class Circle(val radius: Double) {
def area: Double = Circle.pi * radius * radius
}
object Circle {
val pi: Double = 3.14159 // Static-like field
def circumference(radius: Double): Double = 2 * pi * radius // Static-like method
}
Usage :
val circle = new Circle(5)
println(s"Area: ${circle.area}") // Output: Area: 78.53975
println(s"Circumference: ${Circle.circumference(5)}") // Output: Circumference: 31.4159
Companion Object: Stores the constant value pi and the method circumference, which are shared across all Circle instances. The companion class Circle can reference the pi value directly in its instance method area.
Accessing Private Members Between Companion Class and Object
Companion objects and classes can access each other’s private members, which is a special feature of this relationship.
class BankAccount(private var balance: Double) {
// Method to show balance (for internal use only)
private def showBalance(): Unit = {
println(s"Current balance: $$balance")
}
}
object BankAccount {
def apply(initialBalance: Double): BankAccount = new BankAccount(initialBalance)
// Companion object can access private members
def printBalance(account: BankAccount): Unit = {
account.showBalance() // Accessing private method
}
}
Usage :
val account = BankAccount(1000)
BankAccount.printBalance(account) // Output: Current balance: $1000
- The companion object
BankAccountcan access the private methodshowBalancein the companion classBankAccount, even though this method is private. This demonstrates the special relationship between companion classes and objects.
Summary of Use Cases
- Factory Methods: Create instances without needing
new, providing a cleaner syntax. - Static Methods and Constants: Define methods or values that are common to all instances, such as mathematical constants or utility methods.
- Simplified Object Creation with Apply: The
applymethod can reduce the need for thenewkeyword, simplifying instantiation. - Access to Private Members: Companion objects can access private fields and methods of the companion class, and vice versa, allowing for encapsulation while still maintaining flexibility in initialization or utility functions.
By using companion objects effectively, you can define both instance-specific logic (in the class) and shared or utility logic (in the companion object) in a clean and organized manner.
Example code :
class CompanionDemo {
var x = 5
def getValue(): Unit ={
println(s"Value of x is: ${x} and value of y is: ${CompanionDemo.y}" )
}
}
object CompanionDemo {
var y = 2.2
def main(args: Array[String]): Unit = {
val objectForCompanionDemoClass = new CompanionDemo()
println(s"Value of x when retrieved from companion object is ${objectForCompanionDemoClass.x}")
println(s"Value of y when retrieved from companion object is ${y}")
objectForCompanionDemoClass.getValue
}
}
Case classes and case objects
In Scala, case classes and case objects are special types of classes and objects designed to make working with immutable data, pattern matching, and functional programming more convenient. Case classes and case objects come with several useful features by default, such as immutability, equality, and easy instance creation without new.
Case Class
A case class is a regular class that is immutable by default and comes with additional features automatically generated by the compiler.
Regular class (immutable) + lot of additional features (extra autogenerated code)
Features of Case Classes:
- Immutability: All fields are immutable (
val), though you can override this to make fields mutable withvar. - Automatic
equalsandhashCode: Case classes have structural equality, meaning two instances with the same values are considered equal. - **Pattern Matching: Case classes are particularly useful in pattern matching, making it easy to deconstruct instances.
- Automatically Generated Methods: The compiler automatically generates methods like
toString,equals,hashCode, andcopy. - No Need for
new: You can create instances of case classes without using thenewkeyword, thanks to the automatically generatedapplymethod.
Example – case class
case class Person(name: String, age: Int)
val person1 = Person("Alice", 25) // No need for 'new'
val person2 = Person("Bob", 30)
// Automatic toString, equals, and hashCode
println(person1) // Output: Person(Alice, 25)
// Equality comparison based on structure
println(person1 == Person("Alice", 25)) // Output: true
// Copy method to create a new instance with some modifications
val person3 = person1.copy(age = 26)
println(person3) // Output: Person(Alice, 26)
// Pattern matching
person2 match {
case Person(name, age) => println(s"Name: $name, Age: $age")
}
- The
Personcase class automatically gets methods likeequals,toString, andcopy. - You can create instances without
new. - Structural equality (
==) works based on the fields of the case class. - The
copymethod allows you to create new instances with some fields changed. - Case classes can be easily used in pattern matching.
Example :
case class Car(name: String, model: String) // define a case class
{
val carName = name
val carModel = model
def printDetails(): Unit ={
println(s"Car Name is: ${carName} and Car Model is: ${carModel}")
}
}
object CaseClassDemoA {
def main(args: Array[String]): Unit = {
val bmw = Car("BMW", "550") // 1. No need to write "new", since "apply" method is auto generated in case class
bmw.printDetails()
//bmw.name = "B.M.W" //2. constructor parameter is val by default. therefore mutator method is not autogenerated and hence you can not change the name. However, if you change the constructor parameter to var, mutator method will be auto generated and you will be able to modify the value in variable
//bmw.printDetails()
// 3. Case class autogenerate unapply method , used for pattern matching
bmw match { case Car(a,b) => println(a,b) } // (BMW, 550)
// 4. Autogenerates copy method
val mercedes = bmw.copy(name = "mercedes")
mercedes.printDetails() // Car Name is : mercedes and Car Model is : 550
// 5. equals and hashcode method
println(bmw == mercedes) // false
//6. toString method is auto-implemented
println(bmw) // Car(BMW, 550)
}
}
bmw match { ... }: This is a pattern matching expression. Thematchkeyword is similar to aswitchstatement in other languages, but it’s more powerful as it supports deconstructing objects and working with complex patterns.Here,bmwis the object or value being matched against the pattern defined inside the{ ... }.case Car(a, b): This is a case clause in pattern matching. It tries to matchbmwagainst a specific pattern, which in this case is an instance of aCarobject. TheCar(a, b)part is:- Car: A case class (or a class) called
Car. The case class typically has two fields (let’s assumemakeandmodelas an example). - (a, b): These are pattern variables that will hold the values of the fields inside the
Carobject if the match is successful. In this case,aandbwill represent the values of the fields ofbmw.
bmwis an instance ofCar("BMW", "X5"), the patternCar(a, b)will match, andawill be assigned"BMW"andbwill be assigned"X5".- Car: A case class (or a class) called
Pattern matching is powerful in Scala, enabling concise and expressive handling of different data types and object structures.
Use Cases for Case Classes
Immutable Data Models
Case classes are ideal for representing data models where immutability is desired, such as user data, configuration data, or event models.
case class Address(city: String, country: String)
case class User(name: String, address: Address)
val user = User("Alice", Address("New York", "USA"))
val updatedUser = user.copy(address = user.address.copy(city = "Los Angeles"))
println(updatedUser) // Output: User(Alice, Address(Los Angeles, USA))
Pattern Matching
Case classes make pattern matching concise and powerful, especially when working with algebraic data types or processing different types of data in functional programming.
case class Success(message: String)
case class Failure(reason: String)
def handleResponse(response: Any): Unit = response match {
case Success(message) => println(s"Success: $message")
case Failure(reason) => println(s"Failure: $reason")
}
handleResponse(Success("All good!"))
handleResponse(Failure("Something went wrong"))
Functional Programming
Case classes fit naturally into functional programming paradigms. They are used to define algebraic data types and simplify the process of working with immutable data and pattern matching.
Case Objects
A case object is a singleton object that has many of the same features as a case class. It is mainly used when you want a single instance of a class with pattern matching capabilities, and when you don’t need to maintain any state across different instances.
Features of Case Objects :
- Singleton: Only one instance exists.
- Pattern Matching: Like case classes, case objects can be used in pattern matching.
- No
applyorcopymethods: Since there is only one instance, there’s no need for anapplymethod (for instance creation) orcopy(for duplication with modified fields).
Example : case objects
case object Stop
case object Start
def handleCommand(command: Any): String = command match {
case Start => "Starting the process"
case Stop => "Stopping the process"
case _ => "Unknown command"
}
println(handleCommand(Start)) // Output: Starting the process
println(handleCommand(Stop)) // Output: Stopping the process
StartandStopare case objects used for pattern matching.- These case objects represent single values with predefined meanings (commands in this case).
- When using pattern matching, each case object is treated as a distinct command or state.
Use Cases for Case Objects
Singleton Values for Commands or States:
Case objects are useful when you need to represent fixed, singleton values that can be used in pattern matching. For example, you could use case objects to represent different states or commands in a finite state machine or actor model.
case object Idle
case object Running
case object Stopped
def checkState(state: Any): Unit = state match {
case Idle => println("System is idle.")
case Running => println("System is running.")
case Stopped => println("System is stopped.")
case _ => println("Unknown state.")
}
checkState(Idle) // Output: System is idle.
checkState(Running) // Output: System is running.
Enum-Like Behavior
When you need a set of fixed values, such as representing different days of the week or command statuses, case objects are an elegant solution in Scala (similar to enumerations in other languages).
case object Monday
case object Tuesday
case object Wednesday
def whichDay(day: Any): String = day match {
case Monday => "It's Monday."
case Tuesday => "It's Tuesday."
case Wednesday => "It's Wednesday."
case _ => "Unknown day."
}
println(whichDay(Monday)) // Output: It's Monday.
Case Objects are singleton instances that are useful for representing fixed values or commands in pattern matching and state transitions.
Key Differences Between Case Class and Case Object
| Feature | Case Class | Case Object |
|---|---|---|
| Instance Creation | Multiple instances can be created. | Only one instance (singleton). |
| Fields | Can have fields (parameters in constructor). | No constructor parameters (no fields). |
| Pattern Matching | Supports pattern matching. | Supports pattern matching. |
| Apply Method | Automatically generated apply method for instantiation. | No apply method needed. |
| Copy Method | Automatically generated copy method for creating modified instances. | No copy method. |
| Equality | Equality is structural, based on field values. | Only one instance, so equality is by reference. |