Reputation: 2296
I've just started learning scala. In classes primary constructor parameters with val
and var
are public whereas parameters without val
or var
are private values. So, when I'm trying to execute following code, everything should work fine.
import scala.collection.mutable.ArrayBuffer
class Cat(val name: String)
class Dog(val name: String)
val dog = new Dog("Harry")
val cat = new Cat("Sally")
val animals = ArrayBuffer.empty[Any]
animals.append(dog)
animals.append(cat)
animals.foreach(pet => println(pet.name))
But I'm getting following error:
ScalaFiddle.scala:12: error: value name is not a member of scala.this.Any
animals.foreach(pet => println(pet.name)) // Prints Harry Sally
Why is this happening ?
Upvotes: 3
Views: 1118
Reputation: 369430
In this line:
val animals = ArrayBuffer.empty[Any]
// ↑↑↑
You explicitly tell Scala that your animals
ArrayBuffer
can contain Any
object. As a result, you can only use methods of Any
which are not that many.
It is, for example, perfectly legal to put an Int
into animals
, since you told Scala that the contents of animals
can be anything. However, Int
doesn't have a name
method, and thus your code would blow up at runtime if you tried to get the name of an Int
. In order to prevent your code from blowing up at runtime, Scala rather decides to not allow you to write that code at all in the first place.
So, you need to tell Scala what kinds of things you want to put into the animals
. Unfortunately, we only have two explicit nominal types at our disposal here: Dog
and Cat
. However, when you type animals
as ArrayBuffer[Dog]
, then you can't put Sally in there, and if you type it as ArrayBuffer[Cat]
, then you can't put Harry in there.
So, we need to type animals
as something that allows both Dog
s and Cat
s.
In Scala 3, you could use a Union Type:
val animals = ArrayBuffer.empty[Dog | Cat]
Alas, Scala 3 is still quite far away.
Another way would be to use a Compound type with structural refinement:
val animals = ArrayBuffer.empty[{val name: String}]
This allows your code to work, but it may not necessarily do what you want: this allows any object that has a val
named name
of type String
, not just Dog
s and Cat
s. In particular, you could put something in animals
which is not an animal, as long as it has a name.
The best way would probably be to introduce an abstract supertype (let's call it Pet
) that defines an abstract val name
that gets overridden by Cat
and Dog
, and then type animals
as ArrayBuffer[Pet]
:
trait Pet {
val name: String
}
class Dog(val name: String) extends Pet
class Cat(val name: String) extends Pet
val animals = ArrayBuffer.empty[Pet]
Upvotes: 4
Reputation: 48400
ArrayBuffer.empty[Any]
specifies array buffer is storing Any
values, not Cat
s and Dog
s, and Any
does not know about name
. Try relating Cat
and Dog
via Animal
trait like so
trait Animal {
val name: String
}
class Cat(val name: String) extends Animal
class Dog(val name: String) extends Animal
and then specify the array buffer to store Animal
like so
val animals = ArrayBuffer.empty[Animal]
animals.append(dog)
animals.append(cat)
animals.foreach(pet => println(pet.name))
which outputs
Harry
Sally
Upvotes: 4