Scala: Do classes that extend a trait always take the traits properties?
Given the following:
class TestClass extends TestTrait {
def doesSomething() = methodValue + intValue
}
trait TestTrait {
val intValue = 4
val unusedValue = 5
def methodValue = "method"
def unusedMethod = "unused method"
}
When the above code runs, will TestClass actually have memory allocated to unusedValue or unusedMethod? I've used javap and I know that there exists an unusedValue an开发者_StackOverflowd an unusedMethod, but I cannot determine if they are actually populated with any sort of state or memory allocation.
Basically, I'm trying to understand if a class ALWAYS gets all that a trait provides, or if the compiler is smart enough to only provide what the class actually uses from the trait?
If a trait always imposes itself on a class, it seems like it could be inefficient, since I expect many programmers will use traits as mixins and therefore wasting memory everywhere.
Thanks to all who read and help me get to the bottom of this!
Generally speaking, in languages like Scala and Java and C++, each class has a table of pointers to its instance methods. If your question is whether the Scala compiler will allocate slots in the method table for unusedMethod
then I would say yes it should.
I think your question is whether the Scala compiler will look at the body of TestClass
and say "whoa, I only see uses of methodValue
and intValue
, so being a good compiler I'm going to refrain from allocating space in TestClass
's method table for unusedMethod
. But it can't really do this in general. The reason is, TestClass
will be compiled into a class file TestClass.class
and this class may be used in a library by programmers that you don't even know.
And what will they want to do with your class? This:
var x = new TestClass();
print(x.unusedMethod)
See, the thing is the compiler can't predict who is going to use this class in the future, so it puts all methods into its method table, even the ones not called by other methods in the class. This applies to methods declared in the class or picked up via an implemented trait.
If you expect the compiler to do global system-wide static analysis and optimization over a fixed, closed system then I suppose in theory it could whittle away such things, but I suspect that would be a very expensive optimization and not really worth it. If you need this kind of memory savings you would be better off writing smaller traits on your own. :)
It may be easiest to think about how Scala implements traits at the JVM level:
- An interface is generated with the same name as the trait, containing all the trait's method signatures
- If the trait contains only abstract methods, then nothing more is needed
- If the trait contains any concrete methods, then the definition of these will be copied into any class that mixes in the trait
- Any vals/vars will also get copied verbatim
It's also worth noting how a hypothetical var bippy: Int
is implemented in equivalent java:
private int bippy; //backing field
public int bippy() { return this.bippy; } //getter
public void bippy_$eq(int x) { this.bippy = x; } //setter
For a val
, the backing field is final and no setter is generated
When mixing-in a trait, the compiler doesn't analyse usage. For one thing, this would break the contract made by the interface. It would also take an unacceptably long time to perform such an analysis. This means that you will always inherit the cost of the backing fields from any vals/vars that get mixed in.
As you already hinted, if this is a problem then the solution is just use def
s in your traits.
There are several other benefits to such an approach and, thanks to the uniform access principle, you can always override such a method with a val
further down in the inheritance hierarchy if you need to.
精彩评论