Scala - method not recognized in constructor
I'm trying to extend javax.swing.Timer, but it only has one constructor, which is
Timer(int delay, ActionListener listener)
I do not want my subclass in Scala to take a Java ActionListener
in its constructor. I read in a very old thread that "there is no way to call a superclass constructor directly; you have to pass by the primary constructor of your own class", so it looks like I'm stuck with the ActionListener
in the primary constructor. So I've added an auxiliary constructor thus:
case class TimerEvent (source: AnyRef) extends swing.event.Event
class ScalaTimer2 (delay: Int, listener: java.awt.event.ActionListener)
extends javax.swing.Timer(delay, listener) with swing.Publisher {
outer =>
def this(delay: Int) = {
this(delay, new java.awt.event.ActionListener {
def actionPerformed(e: java.awt.event.ActionEvent) {
publish(TimerEvent(outer)) // <-- publish not recogonized
开发者_JS百科 }
})
// publish(TimerEvent(outer)) // <-- publish recognized here
}
}
However I get a compile error error: not found: value publish
... why? And how to fix?
There are actually two problems here: both publish
and outer
will not be available until the constructor has completed. Scala seems to have a rule that this
instance cannot be referenced in an auxiliary constructor until the primary constructor has completed. For example, the following will fail to compile:
class Foo (x: Unit) { def this() { this(println(this)) } }
Rather than an auxiliary constructor, how about defining a factory method on the companion object?
object ScalaTimer2 {
def apply(delay: Int): ScalaTimer2 = {
lazy val ret: ScalaTimer2 = new ScalaTimer2(delay, new java.awt.event.ActionListener {
def actionPerformed(e: java.awt.event.ActionEvent) {
ret.publish(TimerEvent(ret))
}
})
ret
}
}
Note that the forward reference of ret
requires the use of a lazy val
instead of just val
. Presumably actionPerformed
is not called until the constructor returns, otherwise ret
will be invalid due to infinite recursion.
Here's a workaround I found in this particular case: send a dummy ActionListener then remove and replace with the real one.
class ScalaTimer2 private (delay: Int, listener: java.awt.event.ActionListener)
extends javax.swing.Timer(delay, listener) with swing.Publisher {
outer =>
def this(delay: Int) =
this(delay, new java.awt.event.ActionListener {
def actionPerformed(e: java.awt.event.ActionEvent) { } // dummy
})
removeActionListener(listener)
addActionListener(new java.awt.event.ActionListener {
def actionPerformed(e: java.awt.event.ActionEvent) {
publish(new TimerEvent(outer))
}
})
}
Edit: another trick: making the primary constructor private
so that there's no opportunity to mistakenly try constructing with your own ActionListener
.
Edit 2: or avoid the auxiliary constructor altogether by passing an anonymous ActionListener in the signature.
Edit 3 - solved! : I just read in the Javadoc that the ActionListener passed to the constructor can be null! So all we actually need is:
class ScalaTimer2 (delay: Int) extends Timer(delay, null) with Publisher {
outer =>
addActionListener(new ActionListener {
def actionPerformed(e: ActionEvent) {
publish(new TimerEvent(outer))
}
})
}
精彩评论