allstar
allstar

Reputation: 1205

Why is an array of a value classes compiled to an array of objects?

As I understand, if you create an array of a value class, you're actually creating an array of objects rather than the wrapped primitive. What's the reason behind this?

source:

class Wrapper(val underlying: Int) extends AnyVal

class Main {
  val i: Int = 1
  val w: Wrapper = new Wrapper(1)
  val wrappers = Array[Wrapper](new Wrapper(1), new Wrapper(2))
  val ints = Array[Int](1, 2)
}

javap output:

public class Main {
  public int i();
  public int w();
  public Wrapper[] wrappers(); // <----why can't this be int[] as well
  public int[] ints();
  public Main();
}

Upvotes: 3

Views: 96

Answers (2)

Jasper-M
Jasper-M

Reputation: 15086

One of the constraints of value classes is that x.isInstanceOf[ValueClass] should still work correctly. Where correctly means: transparently, without the programmer having to be aware when values may or may not be boxed.

If an Array[Meter] would be represented as an Array[Int] at runtime the following code would not work as expected, because the information that the ints in the array are actually meters is lost.

class Meter(val value: Int) extends AnyVal

def centimeters[A](as: Array[A]) = as.collect{ case m: Meter => m.value * 100 }

Note that if you have val m = new Meter(42); m.isInstanceOf[Meter] then the compiler knows that m is a Meter even though it's an Int at runtime and he can inline the isInstanceOf call to true.

Also note that this wouldn't work for arrays. If you would box the values in the array on demand you'd have to create a new array, which wouldn't be transparent to the programmer because arrays are mutable and use reference equality. It would also be a disaster for performance with large arrays.

Upvotes: 6

Markus Appel
Markus Appel

Reputation: 3238

According to https://docs.scala-lang.org/overviews/core/value-classes.html:

Allocation Summary

A value class is actually instantiated when:

  1. a value class is treated as another type.
  2. a value class is assigned to an array.
  3. doing runtime type tests, such as pattern matching.

[...]

Another situation where an allocation is necessary is when assigning to an array, even if it is an array of that value class. For example,

val m = Meter(5.0)
val array = Array[Meter](m)

The array here contains actual Meter instances and not just the underlying double primitives.

This has probably something to do with type erasure, or simply because you're creating an array of a specific type, which doesn't allow one type to be treated as another. In any case, it's a technical limitation.

Upvotes: 3

Related Questions