可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Consider the following base and derived classes in Scala:
abstract class Base( val x : String )
final class Derived( x : String ) extends Base( "Base's " + x )
{
override def toString = x
}
Here, the identifier 'x' of the Derived class parameter overrides the field of the Base class, so invoking toString like this:
println( new Derived( "string" ).toString )
returns the Derived value and gives the result "string".
So a reference to the 'x' parameter prompts the compiler to automatically generate a field on Derived, which is served up in the call to toString. This is very convenient usually, but leads to a replication of the field (I'm now storing the field on both Base and Derived), which may be undesirable. To avoid this replication, I can rename the Derived class parameter from 'x' to something else, like '_x':
abstract class Base( val x : String )
final class Derived( _x : String ) extends Base( "Base's " + _x )
{
override def toString = x
}
Now a call to toString returns "Base's string", which is what I want. Unfortunately, the code now looks somewhat ugly, and using named parameters to initialize the class also becomes less elegant:
new Derived( _x = "string" )
There is also a risk of forgetting to give the derived classes' initialization parameters different names and inadvertently referring to the wrong field (undesirable since the Base class might actually hold a different value).
Is there a better way?
Edit 1: To clarify, I really only want the Base values; the Derived ones just appear to be necessary for initializing the fields of the base class. The example only references them to illustrate the ensuing issues.
Edit 2: Actually, the example would have been clearer if I had used vars instead of vals, since that highlights the problem with values getting changed later on in the base class:
class Base( var x : Int ) { def increment() { x = x + 1 } }
class Derived( x : Int ) extends Base( x ) { override def toString = x.toString }
val derived = new Derived( 1 )
println( derived.toString ) // yields '1', as expected
derived.increment()
println( derived.toString ) // still '1', probably unexpected
Edit 3: It might be nice to have a way to suppress automatic field generation if the derived class would otherwise end up hiding a base class field. It would appear that the Scala compiler could actually have been designed to do this for you, but of course this contradicts the more general rule of "nearer" identifiers (the Derived class' 'x') hiding more remote ones (the Base class' 'x'). It seems like a reasonably nice solution would be a modifier like 'noval', maybe like this:
class Base( var x : Int ) { def increment() { x = x + 1 } }
class Derived( noval x : Int ) extends Base( x ) { override def toString = x.toString }
val derived = new Derived( 1 )
println( derived.toString ) // yields '1', as expected
derived.increment()
println( derived.toString ) // still '2', as expected
回答1:
The idiomatic way to avoid duplicating the field would be to write
abstract class Base { val x: String }
final class Derived(val x: String) extends Base {
def toString = x
}
However, in your version it looks like you actually want a second field, since you have two distinct values. As you correctly point out, giving these fields the same name is likely to lead to confusion.
Since you don't actually need the constructor argument outside of the constructor, you could use this approach (a private constructor with a companion module that acts as a factory):
abstract class Base { val x: String }
final class Derived private (val x: String) extends Base {
def toString = x
}
object Derived {
def apply(x: String) = new Derived("Base " + x)
}
回答2:
As the base class is abstract, it doesn't look as though you really want a val
(or var
) in the Base
class, with the associated backing field. Instead, you're simply looking to make a guarantee that such a thing will be available in concrete subclasses.
In Java, you'd use an accessor method such as getX
to achieve this.
In Scala, we can go one better, vals, vars and defs occupy the same namespace, so a val
can be used to implement an abstract def
(or to override a concrete def
, if that's what floats your boat). More formally, this is known as the "Uniform Access Principle"
abstract class Base{ def x: String }
class Derived(val x: String) extends Base {
override def toString = x
}
If you need for x
to be settable via a reference to Base
, you also need to declare the "setter" (which can then be implemented with a var
):
abstract class Base {
def x: String
def x_=(s: String): Unit
}
class Derived(var x: String) extends Base {
override def toString = x
}
(not that I would ever encourage mutability in any design; unless there was an especially compelling justification for it. There are too many good reasons for favouring immutability by default)
UPDATE
The benefits of this approach are:
x
could be an entirely synthetic value, implemented entirely in terms of other values
(i.e. the area of a circle for which you already know the radius)
x
can be implemented at any arbitrary depth in the type hierarchy, and doesn't have to
be explicitly passed through each intervening constructor
(having a different name each time)
- There's only a single backing field required, so no memory is wasted
- As it now doesn't need a constructor,
Base
could be implemented as a trait;
if you so desire
回答3:
You can try this:
abstract class Base( val x : String )
final class Derived( _x : String ) extends Base( _x ) {
override val x = "Base's " + _x
override def toString = x
}
Then
println(new Derived("string").toString)
prints exactly what you want
回答4:
you already provided the answer that works
abstract class Base( val x : String )
final class Derived( _x : String ) extends Base( "Base's " + _x )
{
override def toString = x
}
If the problem is that _x
is not a nice name, then you should use a meaningful one.
Alternatively, you could declare your classes as follows
abstract class Base( val _x : String )
final class Derived( x : String ) extends Base( "Base's " + x )
{
override def toString = _x
}
And now you would have the "nice" syntax for initializing Derived
instances.
If scala was to allow
a way to suppress automatic field generation if the derived class would otherwise end up hiding a base class field.
This to me seems a very low-level detail that you don't want to deal with in the code. If this can be done safely, the compiler should do it for you.
回答5:
As suggested by @jsuereth I created an enhancement ticked for Scala, just for the record, that I hope correctly summarizes the content of the discussions here. Thanks for all of your input! The ticket can be found here, the content below: https://issues.scala-lang.org/browse/SI-4762
Inadvertent shadowing of base class fields in derived classes, Warning desirable
Issue arises whenever (a) a class parameter in a derived class uses the same
symbol as a field or function in a base class and (b) that base class symbol
is subsequently accessed in the derived class. The derived class parameter
causes the compiler to auto-generate a field of the same name that shadows
the base class symbol. Any reference to that symbol in the Derived class,
intended to refer to the base class field, then inadvertently (a) causes
duplicate definition of the field and (b) unexpectedly (but correctly) refers
to the auto-generated field in the derived class.
Code example:
class Base( val x : String )
class Derived( x : String ) extends Base( x ) { override def toString = x }
Since the 'Base' class has 'val' to generate the field and the derived class
does not, the developer clearly intends to use the 'Derived' 'x' only for
pass-through to Base, and therefore expects the reference in 'toString' to
yield the Base value. Instead, the reference in 'toString' causes the compiler
to automatically generate a field 'Derived.x' that shadows the 'Base.x' field,
resulting in an error. The compiler behaves correctly, but the result is a
programming error.
Usage scenario (with var fields for clearer illustration of the risks):
class Base( var x : Int ) { def increment() { x = x + 1 } }
class Derived( x : Int ) extends Base( x ) { override def toString = x.toString }
val derived = new Derived( 1 )
println( derived.toString ) // yields '1', as expected
derived.increment()
println( derived.toString ) // still '1', probably unexpected
Since this issue arises whenever anyone uses the default way to initialize
base class fields from a derived class in Scala, the scenario would appear to
be extremely common and result in lots of programming errors (easily fixed,
but still) for newer users.
An easy work-around for this issue exists (use differing names for the derived
class parameter, like '_x', 'theX', 'initialX', etc.), but this introduces
unwanted extra symbols.
Solution A (Minimal): issue a warning whenever the compiler infers that a
class parameter requires an auto-generated field in a derived class that
would shadow a symbol already defined in a base class.
Solution B: the work-around, still required with Solution A, is to come up
with a new symbol name each time one initializes a base class field. This
scenario comes up all the time and polluting the namespace with workaround
field names like '_x' and 'theX' seems undesirable. Instead, it might be nice
to have a way to suppress automatic field generation if the developer
determines that the derived class symbols would otherwise end up hiding a base
class symbol (e.g., following the warning of Solution A). Maybe a useful
addition to Scala would be a modifier like 'noval' (or 'passthrough' or
'temp', or whatever - in addition to 'val' and 'var'), like this:
class Base( var x : Int ) { def increment() { x = x + 1 } }
class Derived( noval x : Int ) extends Base( x ) { override def toString = x.toString }
val derived = new Derived( 1 )
println( derived.toString ) // yields '1', as expected
derived.increment()
println( derived.toString ) // still '2', as expected
回答6:
Ok, first of all I would like to point out that the answer from @Yuriy Zubarev is probably what you really want to have. Secondly, I think the problem might lie in your design. Check this out. This is the part of your code:
extends Base( "Base's " + _x )
So some value x
comes into your derived class and gets modified with the information (in this case "Base's " + ...
). Do you see the problem? Why does your derived type knows something that your base type actually should have known? Here is the solution I propose.
abstract class Base {
// this works especially well if you have a var
// which is what you wanna have as you pointed out later.
var x: String
x = "Base's " + x
}
final class Derived(override var x: String ) extends Base{
override def toString = x
}
This might sound harsh, but if this solution helps you out it automatically means that you had bad design. If on the other hand it does not help, than I probably don't understand you problem correctly and therefore apologize right away.
回答7:
@Gregor Scheidt
your code does not work if I move toString() down to Derived, as the following:
object Test {
abstract class Base ( val x: String)
final class Derived(x: String) extends Base(x + " base") {
override def toString() = x
}
def main(args: Array[String]): Unit = {
val d = new Derived( "hello")
println( d) // hello
}
}
A post from the official site said,
A parameter such as class Foo(x : Int) is turned into a field if it is
referenced in one or more methods
And Martin's reply confirms its truth:
That's all true, but it should be treated as an implementation
technique. That's why the spec is silent about it.
since there is no way to prevent the compiler's action, my option is that a more robust way to reference base class field is to use a different name, for example using a underscore "_" as prefix, as the following:
object Test {
abstract class Base ( val x: String)
final class Derived(_x: String) extends Base(_x + " base") {
override def toString() = x
}
def main(args: Array[String]): Unit = {
val d = new Derived( "hello")
println( d) // hello
}
}