SAM Classes
Peter Levart
peter.levart at marand.si
Fri Jan 29 01:16:01 PST 2010
On Friday 29 January 2010 02:25:55 Joshua Bloch wrote:
> > I did a scan of the Java class libraries (those with java.* or
> > javax.*) - there's 100 interfaces and 220 classes which fall into this
> > definition. Quite a lot more than I expected :-) And I can see where
> > this would be useful, provided that we can deal with the ambiguous
> > cases (e.g. as proposed earlier, with an explicit cast).
> >
> > Alex
>
>
> If we're providing a nice new syntax for function objects, we should make it
> usable with as many existing function-object consumers as possible. It may
> not be possible to make it work for SAM-class instance consumers, but we
> shouldn't give up without a fight.
>
> Stephen Colebourne said this:
>
> I'm guessing that SAM classes are motivated by some specific use cases,
> > perhaps in the JDK. I'd strongly suggest that those use cases should have
> > API changes in parallel to lambdas instead of permitting SAM classes.
>
>
> This doesn't work for two reasons: It's not just the JDK, but the whole
> universe of Java libraries that would have to be changed. That won't happen
> overnight. Second, there were (sometimes) good reasons that interfaces were
> used in preference to classes. In the the case of TimerTask, the reason was
> performance: a hidden field is stashed in each TimerTask, without the need
> for an extra object to incorporate the TimerTask and the hidden field. In
> addition to object allocations, this saves on indirections.
I think that particularly nasty is the combination of supporting the conversion to SAM classes and the way this conversion is specified in Alex Buckley's specification draft. It specifies that the conversion can be performed with the syntax of a cast from function type to a SAM class. Beside the problems of possible exceptions thrown from SAM constructors the biggest problem with current specification I think is that each such cast creates new instance of SAM class that contains it's own state. This is a big surprise for a programmer that is used to the fact that casts maintain object identity (and therefore don't construct new state).
I will repeat the example with TimerTask here:
Timer timer = new Timer();
#void() task = #() { ....; ((TimerTask)this).cancel(); }
timer.schedule((TimerTask)task, 1000L, 1000L);
I deliberately inserted explicit casts where they would actually occur by current specification. Notice that one instance of timer task is passed to Timer.schedule() method, which asynchronously executes lambda's body which in turn cancels a different instance of timer task.
When an average Java programmer looks at the above code she doesn't notice anything wrong with it, because she is used to see the cast as something that just checks the type of the expression and makes compiler happy.
I assume Alex specified conversions to SAM types as he did because he wanted to leave the door open to the implementation of lambda's in terms of java.dyn.MethodHandle (or java.dyn.JavaMethodHandle ?) and he also wanted 'this' in the lambda's body to be of a function type, not of a type lambda expression was converted to, but allow conversion of 'this' to SAM type if needed.
I think that if conversion to SAM classes is to be supported, specification should ensure that only a single conversion is possible for each lambda or the conversion should use special syntax (not implicit or explicit casts).
If we want special syntax, we already have it (static methods/adapter classes), for example:
public class TimerTaskImpl extends TimerTask {
private final #void() task;
public TimerTaskImpl(#void() task) { this.task = task; }
public void run() { task.(); }
}
So when the above example has to be re-written as:
Timer timer = new Timer();
#void() task = #() { ....; new TimerTaskImpl(this).cancel(); }
timer.schedule(new TimerTaskImpl(task), 1000L, 1000L);
...the programmer immediately notices wrong semantics and corrects the code to something like this:
Timer timer = new Timer();
final TimerTask task = new TimerTaskImpl(#() { ....; task.cancel(); });
timer.schedule(task, 1000L, 1000L);
If we want implicit conversion (maybe with the help of a cast) it should be done only in conjunction with lambda expression evaluation and in that case, if it really must be, 'this' within lambda body should refer to the already converted SAM instance. In this case, above example should be written as:
Timer timer = new Timer();
TimerTask task = #() { ....; this.cancel(); };
timer.schedule(task, 1000L, 1000L);
or just:
Timer timer = new Timer();
timer.schedule(#() { ....; this.cancel(); }, 1000L, 1000L);
But in this case the following code is not possible (and we don't really need it):
#void() task = ...;
TimerTask tt = (TimerTask)task;
Regards, Peter
More information about the lambda-dev
mailing list