1

I have 1 question regarding case class of scala

scala> case class Person(name:String, age:Int)

scala> val person1 = Person("Posa",30)

scala> val person2 = new Person("Posa",30)

In what perspective both the objects (person1 and person2) differs ?

What difference does new keyword plays here?

Federico klez Culloca
  • 26,308
  • 17
  • 56
  • 95
Raks
  • 21
  • 4
  • 2
    there is no difference. – Dima May 06 '21 at 12:00
  • @Dima--In every document I read this as an advantage where in it is mentioned that we can create objects of the Case Class without using “new” keyword. – Raks May 06 '21 at 12:02
  • 3
    Well, it's "an advantage" in the sense that there's three fewer characters to type, yes. – Dima May 06 '21 at 12:05

2 Answers2

3

When you create a case class

case class Person(name: String, age: Int)

compiler automatically creates a companion object which has a factory apply method

object Person {
  def apply(name: String, age: Int): Person = new Person(name, age)
}

which is invoked when new is not used, so

val person1 = Person("Posa", 30) // function call creation syntax via companion's apply method

is equivalent to

val person1 = Person.apply("Posa", 30)

On the other hand instance creation expression is a constructor invocation on Person class (not its companion object)

val person2 = new Person("Posa", 30) // invokes constructor

Note starting Scala 3 even regular classes can be instantiated using function call creation syntax

Scala case classes generate apply methods, so that values of case classes can be created using simple function application, without needing to write new. Scala 3 generalizes this scheme to all concrete classes.

Mario Galic
  • 47,285
  • 6
  • 56
  • 98
1

Part of what the Scala compiler does (except in the case of an abstract case class) with a case class is that it generates a companion object for the class with an apply method with the same arguments as the case class's constructor and the case class as a result type.

When you write val person1 = Person("Posa", 30), that is exactly the same as writing

val person1 = Person.apply("Posa", 30)

The automatically generated apply method is effectively:

object Person {
  def apply(name: String, age: Int): Person =
    new Person(name, age)
}

Thus Person("Posa", 30) and new Person("Posa", 30) are the same thing.

For an abstract case class, the compiler will not generate an apply method in the companion object and will not generate a copy method in the case class, thus requiring an explicit new to construct an instance of a case class. This is not uncommonly used in an implementation pattern to ensure that:

  • only instances of a case class which satisfy domain constraints can be constructed
  • attempts to construct using apply will not throw an exception

For example, if we wanted to enforce that Persons must have a non-empty name and a non-negative age, we could

sealed abstract case class Person private[Person](name: String, age: Int) {
  def copy(name: String = name, age: Int = age): Option[Person] =
    Person(name, age)
}

object Person {
  def apply(name: String, age: Int): Option[Person] =
    if (name.nonEmpty && age >= 0) Some(unsafeApply(name, age))
    else None

  private[Person] def unsafeApply(name: String, age: Int) =
    new Person(name, age) {}
}

In this, we've effectively renamed the compiler-generated apply to unsafeApply and made it private to the Person class and companion object (where we presumably can know that we're not violating a domain constraint, e.g. we could have a method in Person

def afterNextBirthday: Person = Person.unsafeApply(name, age + 1)

(since we're not changing the already validated name, and if we had a valid age (ignoring overflow...), adding 1 will not invalidate the age)). Our apply method now expresses that not all combinations of name and age are valid.

Levi Ramsey
  • 18,884
  • 1
  • 16
  • 30