Как я могу параметризовать класс с типом объекта, чтобы получить его экземпляр в Scala?
Я хотел бы параметризовать класс с типом объекта, чтобы сделать мой код более универсальным. Делая это, мне не нужна реализация для всех объектов, которые расширяют определенную черту.
У меня есть следующий код, который демонстрирует мою цель:
abstract trait Foo {
def greet
}
object Coo extends Foo {
def greet = println("Coo!")
}
object Boo extends Foo {
def greet = println("Boo!")
}
class A[Fooer <: Foo] {
def moo = asInstanceOf[Fooer].greet()
}
object Main extends App {
new A[Coo.type].moo() // hopefully prints "Coo!"
}
Код вызывает исключение:
java.lang.ClassCastException: A cannot be cast to Foo
И я верю, что это из-за asInstanceOf
вызов, который, по-видимому, использует this
неявно.
По сути, мой вопрос заключается в следующем: как я могу получить экземпляр моего объекта по типу в классе с помощью параметризации?
1 ответ
И я верю, что это из-за
asInstanceOf
вызов, который, по-видимому, используетthis
неявно.
Каждый класс в Scala расширяется Any
, который наследует специальный метод asInstanceOf
кроме всего прочего. Я бы не сказал, что это происходит неявно- просто вы вызываете метод-член этого класса, который вызывает его сам по себе. Так же, как если бы вы написали def moo = greet
,
Кроме того, в любое время вы используете asInstanceOf
Вы просите, чтобы компилятор лгал самому себе и продолжал. Это чаще всего приводит к ClassCastException
, В этом случае это потому, что вы пытаетесь разыграть A
в какой-то неизвестный подтип Foo
, Это никогда не сработает. В очень конкретном случае вы пытаетесь передать одноэлементный тип Coo.type
к примеру A
а затем создать новый экземпляр Coo.type
, Хотя существует множество других причин, по которым это не сработает, одна из них заключается в том, что вы не можете создать второй экземпляр одноэлементного типа - это называется по определенной причине.
В более общем смысле, вы не знаете, что вы можете просто создать объект заданного типа. Тип не имеет конструктора, а класс имеет.
Это может быть побочным эффектом упрощенного примера, но вы заинтересованы в создании экземпляра A
с некоторым параметром типа без необходимости передавать ему фактический объект, но объекты, которые вас интересуют, уже существуют. Так почему бы не передать их?
class A[Fooer <: Foo](f: Fooer) {
def moo = f.greet
}
Единственный способ сделать это - предоставить доказательства того, что Fooer
может быть построен. Однако нет никаких ограничений типа для доказательства того, что что-то является конструируемым классом. Что вы можете сделать, это потребовать Class
мета-объект вместо:
class A[Fooer <: Foo](clazz: Class[Fooer]) {
def moo = clazz.newInstance.greet
}
Это грубо, однако. Во-первых, он не работает с вашими синглтон-типами выше (потому что они не могут быть созданы заново). А во-вторых, мы можем только позвонить newInstance
когда нет параметров конструктора для предоставления, и нет способа обеспечить это.
Так что пока это будет работать
scala> class Bar extends Foo { def greet = print("Bar") }
scala> new A(classOf[Bar]).moo
Bar
Следующего не будет:
scala> class Baz(i: Int) extends Foo { def greet = println("Baz") }
defined class Baz
scala> new A(classOf[Baz]).moo
java.lang.InstantiationException: Baz
scala> new A(classOf[Coo.type]).moo
<console>:14: error: class type required but Coo.type found
new A(classOf[Coo.type]).moo
^
В качестве альтернативы вы можете использовать класс типов, который позволяет вам последовательно создавать экземпляры Foo
, но это потребовало бы создания одного для каждого подкласса. например:
trait FooBuilder[A] {
def build(): A
}
implicit object BarBuilder extends FooBuilder[Bar] {
def build() = new Bar
}
class A[Fooer <: Foo](implicit fb: FooBuilder[Fooer]) {
def moo = fb.build().greet
}