Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 lambda expression identity contract

The JavaDoc for the LambdaMetaFactory of Java 1.8 specifies that lambda capture "may involve allocation of a new function object, or may return an existing function object", but it doesn't specify when and under what circumstances it might choose one way or the other.

Looking at the actual implementation of the LambdaMetaFactory, on the other hand, it is clear that it happens if and only if the lambda expression captures no parameters.

What I'm wondering is, is this behavior actually specified somewhere (outside of the JavaDoc) and can be relied upon? It would be nice to know whether I can rely on a lambda expression's identity being constant or not.

like image 883
Dolda2000 Avatar asked Apr 01 '14 03:04

Dolda2000


2 Answers

There is essentially no contract that covers the identity of objects that result from evaluating a lambda expression. This is covered in the JLS section 15.27.4, Run-time Evaluation of Lambda Expressions. This section explicitly leaves unspecified the exact behavior of creation vs reuse of lambda objects. The rationale from that section explains this well:

These rules are meant to offer flexibility to implementations of the Java programming language, in that:

  • A new object need not be allocated on every evaluation.

  • Objects produced by different lambda expressions need not belong to different classes (if the bodies are identical, for example).

  • Every object produced by evaluation need not belong to the same class (captured local variables might be inlined, for example).

  • If an "existing instance" is available, it need not have been created at a previous lambda evaluation (it might have been allocated during the enclosing class's initialization, for example).

You can, of course, experiment with the implementation, call equals() or use == on lambda objects, put them into IdentityHashMaps, etc., but since these exact behaviors are unspecified, your program may change its behavior (i.e., break) when run on different versions of the JDK or on different implementations of Java SE.

I read the exchange in the comments below the question but I don't really have anything more to offer. Perhaps if you explain what you're trying to do, we could come up with some suggestions for alternatives to using lambdas as keys in a map.

like image 116
Stuart Marks Avatar answered Sep 22 '22 09:09

Stuart Marks


You should separate behavior from identity. Lambdas can be used to implement behavior while a single ordinary class can be used to implement identity by creating instances out of it. The following simplified example derived from your code should illustrate it:

import java.util.function.Function;

public class MeshBuf
{
  {
    // use case 1:
    LayerID<Col> idCol = new LayerID<>(mbuf -> mbuf.new Col());
    // use case 2:
    Attribute attrib=…;
    LayerID<Vec1Layer> idVec1 = new LayerID<>(mbuf->new Vec1Layer(attrib));
    // the expression new LayerID<>(…) is guaranteed to create new instances…
    LayerID<Vec1Layer> idVec2 = new LayerID<>(mbuf->new Vec1Layer(attrib));
    // therefore idVec1 != idVec2 even if referring to the same expression
  }

  // single class is enough for maintaining the identity
  public static final class LayerID<L> {
    private final Function<MeshBuf, L> cons;

    public LayerID(Function<MeshBuf, L> f) {
      cons = f;
    }

    public L cons(MeshBuf buf) {
      return cons.apply(buf);
    }
  }

  // the other classes are taken from your code, unchanged

  public class Col extends Layer<Color> {
    public VertexBuf.ColorArray build(Collection<Color> in) {
        FloatBuffer data = Utils.wfbuf(in.size() * 4);
        for(Color c : in) {
      data.put(c.getRed() / 255.0f);  data.put(c.getGreen() / 255.0f);
      data.put(c.getBlue() / 255.0f); data.put(c.getAlpha() / 255.0f);
        }
        return(new VertexBuf.ColorArray(data));
    }
  }

  public abstract class AttribLayer<T> extends Layer<T> {
    public final Attribute attrib;

    public AttribLayer(Attribute attrib) {
        this.attrib = attrib;
    }
  }

  public class Vec1Layer extends AttribLayer<Float> {
    public Vec1Layer(Attribute attrib) {super(attrib);}

    public VertexBuf.Vec1Array build(Collection<Float> in) {
        FloatBuffer data = Utils.wfbuf(in.size());
        for(Float d : in)
      data.put(d);
        return(new VertexBuf.Vec1Array(data, attrib));
    }
  }
}
like image 40
Holger Avatar answered Sep 24 '22 09:09

Holger