Re: T copy()
According to Aaron Fude <aaronfude@gmail.com>:
Suppose I want to have
public interface Element {
public Element copy();
}
and
public class MyClass implements Element {
public MyClass copy() {
return new MyClass();
}
}
This is invalid Java but can something like this be accomplished with
Java 5 features?
Actually it works out of the box with Java 5, just like you wrote it.
The conceptual term behind it is "covariance". In practice, it uses
bridge methods. It is worth looking at the produced bytecode for
MyClass. Namely, you will find that the MyClass.class file defines
_three_ methods for MyClass:
**** <init> () -> void
0: aload $0
1: invokespecial java/lang/Object.<init> {() -> void}
4: return
**** copy () -> MyClass
0: new MyClass
3: dup
4: invokespecial MyClass.<init> {() -> void}
7: areturn
**** copy () -> Element
0: aload $0
1: invokevirtual MyClass.copy {() -> MyClass}
4: areturn
The '<init>' method is the constructor (the implicit constructor without
argument, which the Java compiler generates when there is no explicit
constructor). What shall be noted here is that there are _two_ methods
named copy(), one which returns a 'MyClass' and one which returns an
'Element'. The former is the one which was defined in the source code.
The latter has been automatically added by the Java compiler; it is the
bridge method which does only one thing: it invokes the first copy()
method, forwarding the arguments and sending back the return value.
If you were to inspect MyClass through reflection, you would find the
second method to be marked as 'bridge' and 'synthetic' (as returned by
Method.isBridge() and Method.isSynthetic()).
Defining the methods with the same name ('copy') and the same argument
types (here, no argument at all) is forbidden at the source level.
That's because the Java compiler infers the method which is called based
on the method name, and the inferred types of the arguments. If two
methods have the same name and same argument types, then this lookup is
ambiguous. However, that lookup is entirely a compilation concept; it
has no relevance to the JVM. At the JVM level, a method invocation
opcode (e.g. 'invokevirtual') uses the complete method _descriptor_,
which includes the method name, the types of the arguments, _and_ the
type of the return value. Therefore, the JVM has no trouble with two
methods which differ only by the type of the returned value.
So here's the type of method I'd like to write in LinearOperator (fake
code):
public class LinearOperator {
Element argument;
public Sum distribute() {
if (argument.isASum()) {
Sum sum = (Sum) argument;
return new Sum(new LinearOperator(sum.a), new LinearOperator
(sum.b));
}
throw new RuntimeException();
}
}
However, rather than creating an instance of LinearOperator, I need to
create an instance of the derived class, such as TimeDerivative.
So is there a good way to keep this logic at the LinearOperator level?
I am not sure of the 'good' part. LinearOperator, as a class, has no
knowledge of classes which will extend it in some unspecified future.
Therefore, if LinearOperator is to create instances of such classes,
then it should be given an explicit access door. Which yields (with
equally fake code):
public abstract class LinearOperator {
Element argument;
protected abstract LinearOperator make(Element v);
public Sum distribute()
{
if (argument.isASum()) {
Sum sum = (Sum)argument;
return new Sum(make(sum.a), make(sum.b));
}
throw new RuntimeException();
}
}
public class TimeDerivative extends LinearOperator {
protected LinearOperator make(Element v)
{
return new TimeDerivative(v);
}
}
I.e. the TimeDerivative class which extends LinearOperator also
provides the make() method, which is used to create new instances of
TimeDerivative.
--Thomas Pornin