В этой строке:
val animals = ArrayBuffer.empty[Any]
// ↑↑↑
Вы явно указываете Scala, что ваш animals
ArrayBuffer
может содержать Any
объект. В результате вы можете использовать только методы Any
, которых не так много .
Например, совершенно законно помещать Int
в animals
, поскольку вы сказали Scala, что содержимое animals
может быть любым. Тем не менее, Int
не имеет метода name
, и поэтому ваш код взорвется во время выполнения, если вы попытаетесь получить имя Int
. Чтобы предотвратить взрыв вашего кода во время выполнения, Scala скорее решает вообще не разрешать вам писать этот код.
Итак, вам нужно сообщить Scala , какие вещи вы хотите поместить в animals
. К сожалению, у нас есть только два явных номинальных типа: Dog
и Cat
. Однако, когда вы набираете animals
как ArrayBuffer[Dog]
, вы не можете поместить туда Салли, а если вы наберете ArrayBuffer[Cat]
, то вы не сможете ввести туда Гарри.
Итак, нам нужно набрать animals
как нечто, допускающее как Dog
s, так и Cat
s.
В Scala 3 вы можете использовать Union Type :
val animals = ArrayBuffer.empty[Dog | Cat]
Увы, Scala 3 все еще довольно далеко.
Другим способом было бы использовать тип соединения с уточнением структуры :
val animals = ArrayBuffer.empty[{val name: String}]
Это позволяет вашему коду работать, но может не обязательно делать то, что вы хотите: это позволяет любому объекту , который имеет val
с именем name
типа String
, а не только Dog
s и Cat
s. В частности, вы можете поместить в animals
что-то, что не является животным, если у него есть имя.
Лучшим способом, вероятно, было бы ввести абстрактный супертип (назовем его Pet
), который определяет абстрактный val name
, который переопределяется на Cat
и Dog
, а затем введите animals
как 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]