0

I read this statement:

By using User.getClass, you are referring to the class companion object that Scala by default creates for the case class, and not the case class itself.

To get the class object of the case class, use classOf[User].

Where could I come unstuck by using the class of the companion object? I would have thought - showing my ignorance here - they would be the same.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
thebluephantom
  • 16,458
  • 8
  • 40
  • 83
  • The companion object of a class has its own type and its own runtime class. They can't be the same. – Luis Miguel Mejía Suárez Jan 30 '23 at 17:09
  • @LuisMiguelMejíaSuárez I got that but cannot see the differences. An example? – thebluephantom Jan 30 '23 at 17:13
  • 1
    Write `case class Foo(int: Int = 0)`, save it as `Foo.scala`, then compile it with `scalac Foo.scala` and see the different methods in `javap Foo.class` and `javap Foo$.class`. Basically `classOf[Foo]` is more about instance methods while companion `classOf[Foo.type]` is more about "static" methods and factories. – Mateusz Kubuszok Jan 30 '23 at 17:15
  • @thebluephantom which difference you don't see? - Why the runtime classes are different? Because that is what the compiler implementation does, period. - Why the compile time types are different? Because that is the right thing to do, the companion object can _(and usually does)_ provide different methods than the class, and it is already a value. Of course, you can make the companion extend the class / trait _(`Random` is an example)_ in those cases, even if a different type, it is a subtype; just like with all values. – Luis Miguel Mejía Suárez Jan 30 '23 at 18:49
  • @LuisMiguelMejíaSuárez Some things remain a mystery. I looked at this https://docs.scala-lang.org/overviews/scala-book/companion-objects.html and understand that but not the stuff above in the question. – thebluephantom Jan 30 '23 at 19:08
  • Class and its companion are basically 2 distinct runtime types and you should treat them as such. They are sharing name "similarity" so that: 1) Scala compiler would be able to make companion object a singleton in runtime, 2) this singleton methods would be callable from Java through static methods (these 2 are true for any `object` though), 3) class and its companion object would have private access to each others members and 4) implicits defined within companion would be automatically in the scope when looking for the class implicits. – Mateusz Kubuszok Jan 30 '23 at 20:14
  • It would make no sense to make them the same type: `case class Foo(a: Int)` - if `classOf[Foo.type]`/`Foo.getClass` (this notation mean that you ask for type of `Foo` `val`/take `Foo` value and call `.getClass` on it, like on any other object, because Foo is an object) would be of the same type as `classOf[Foo]`, what would be the value of `Foo.a`? If they were of the same type then they would have the same methods and properties while they clearly have different use cases. – Mateusz Kubuszok Jan 30 '23 at 20:16
  • I would hve thought the attributes of the Classes would be similar. @MateuszKubuszok – thebluephantom Jan 30 '23 at 20:27
  • 1
    Then do the exercise that I described in 3rd comment. You'll find that class and its companion DO NOT share most of their methods - except all methods inherited from `java.lang.Object` and methods which are normal instance methods in companion class and have corresponding static methods in normal class' bytecode. Companion is basically a way of making static methods not-static, so that there wouldn't be a special cases, by putting them in a object-bag. But that cannot be achieved when you have only 1 type because how then would you distinct static from non-static? – Mateusz Kubuszok Jan 30 '23 at 20:44
  • I would make an answer, but I think the statement I took for the question from SO is a little vague for the lesser experienced. @MateuszKubuszok – thebluephantom Jan 30 '23 at 20:47

2 Answers2

1

Java has something called static methods

public class Foo {

  private int a:

  public Foo(a) { this.a = a; }

  public int getA() { return a; }

  static public String getB() { return "B"; }
}

it is like a method but not attached to an instance

var foo = new Foo(10);
foo.getA(); // method attached to foo, its value depends on foo

Foo.getB(); // method attached to class but not particular instance 

These static methods are used for storing globals (not recommended), or stateless functions not depending on object - like e.g. factories and other utilities. These methods can access private/protected members of the instances, and instances can access private/protected static methods - so you can limit visibility as if they were inside the object, even though they aren't (Java's reflection treats their methods as if they had null as this).

In Scala we didn't want to have the distinction for static and non-static methods so we decided to do the following:

  • create a separate object where such methods could be stored
  • make sure that this object could access instances members and vice-versa
  • have a similar name to distinct this object from other objects

That's how we arrived at companion object.

class Foo(val a: Int)
object Foo {
  val b = "B"
}
val foo = new Foo(10)
foo.a // this is normal method
foo.getClass // this would return Foo
class[Foo]   // same as above

Foo.getClass // this would return Foo$
classOf[Foo.type] // same as above

getClass is method that you can call on any object - since foo and Foo have different classes they would return different values for getClass.

Any value can describe their type with .type so foo.type would be the type of foo variable (Foo) and Foo.type would be the type of Foo object which is companion of Foo class (and would be called Foo$ in bytecode).

From the reason why companion object exist, it follows that Foo and its companion does not have the same (instance) methods, so they cannot have the same interface, and so they cannot be of the same type.

When it comes to case classes they just automatically create a companion object (if it doesn't exist) and generate some methods inside it based on their constructor: e.g. apply, unapply.

Mateusz Kubuszok
  • 24,995
  • 4
  • 42
  • 64
1

Generally, class A and its companion object A define different (unrelated) static types, namely A and A.type

class A
object A

implicitly[A <:< A.type] // doesn't compile
implicitly[A.type <:< A] // doesn't compile

val a: A.type = new A // doesn't compile, we can't assign a class instance to an object variable
val a1: A = A // doesn't compile, we can't assign an object to a class variable

Similarly, they have different runtime classes (different representations in bytecode), namely A and A$

classOf[A] == Class.forName("A") // true
classOf[A.type] == Class.forName("A$") // true

classOf[A].isAssignableFrom(classOf[A.type]) // false
classOf[A.type].isAssignableFrom(classOf[A]) // false

Sometimes, when there is a reason, companions can agree in types (although such design decision is made quite rarely). Example: https://github.com/apache/spark/blob/v3.3.1/sql/catalyst/src/main/scala/org/apache/spark/sql/types/LongType.scala#L56

class A
object A extends A

// implicitly[A <:< A.type] // doesn't compile
implicitly[A.type <:< A] // compiles

classOf[A].isAssignableFrom(classOf[A.type]) // true
classOf[A.type].isAssignableFrom(classOf[A]) // false

// val a: A.type = new A // doesn't compile
val a1: A = A // compiles

or

trait B
class A extends B
object A extends B

val b: B = new A
val b1: B = A 
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66