7

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.

Dolda2000
  • 25,216
  • 4
  • 51
  • 92
  • 1
    I'd hope not. That behavior should be able to change. If you depend on a lambda being constant, then store it. – Louis Wasserman Apr 01 '14 at 03:59
  • 1
    Well, to be honest, it's more important for me to be able to rely on a capture always returning a *different* object if it does take parameters, even if the captured parameters happen to be the same as one returned previously. The current specification leaves open the possibility for the metafactory to intern objects on captured parameters, however. – Dolda2000 Apr 01 '14 at 04:01
  • I'm not sure I follow why that would be relevant? – Louis Wasserman Apr 01 '14 at 04:02
  • I often tend to use object factories of various kinds as keys of a map to the objects they have created. – Dolda2000 Apr 01 '14 at 04:03
  • ...Why would you do that? Factories don't usually have any kind of useful identity. – Louis Wasserman Apr 01 '14 at 04:31
  • @LouisWasserman: Well, mine do. ;) One way I use them is by `static final` reference, so that they act as identity for lazily created data. – Dolda2000 Apr 01 '14 at 11:02
  • But what would you do with a map like that? It doesn't seem to have any practical use in any event. – Louis Wasserman Apr 01 '14 at 14:52
  • @LouisWasserman: This is veering rather steeply off-topic, but I commonly use it for instance when several independent parts of a program cooperate in building some composite data structure, or when building some data structure that can consist of several optional parts. In the latter case, an example could be building a 3D mesh that converts into an OpenGL vertex buffer, in which case I use them to add optional components of the "mesh builder" that may correspond to such things as texture coordinates, color arrays, or arbitrary shader `attribute` data. – Dolda2000 Apr 01 '14 at 15:17
  • For the latter example, you can see the code [here](https://github.com/dolda2000/salem-client/blob/master/src/haven/MeshBuf.java) (though it's completely uncommented). In this case, the `LayerID` interface is what I'd like to implement using Lambda expressions. Notice, in particular, the `public static final` variables `tex` and `col` that are used to fetch (and lazily initalize) layers. Similar layer IDs exist in other code modules. In this particular case it's probably not a problem, but in other cases I may want to have several layers that are built by the same captured parameters. – Dolda2000 Apr 01 '14 at 15:19

2 Answers2

8

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.

Community
  • 1
  • 1
Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
  • Thanks for locating the relevant part of the JLS. Don't worry about finding a solution; I'm already using anonymous classes, and that works. It would just have been nice to shorten them down to lambda expressions. – Dolda2000 Apr 01 '14 at 15:25
  • @StuartMarks: I'm interested in clarifications relevant for one concrete situation: Say that I create a listener with a lambda, save it in a field, and add it as a listener to some component. Can I use the reference in that field to remove the listener? (This assumes that listeners identity are used to removed them.) – Lii May 10 '16 at 16:01
  • @StuartMarks: A more abstract way to state (I think) the same problem: Given that the value of `a` is created with a lambda, does the following two properties always hold: 1) `a == a` 2) If `a == b`, then calling the method `a` or `b` will execute exactly equivalent code. – Lii May 10 '16 at 16:07
  • Property 1 implies that some listener will be removed when I try to remove one. Property 2 implies that even if the listener that is removed is created at a different lambda expression, then it will be equivalent to the one I wanted to remove. – Lii May 10 '16 at 20:33
  • @Lii An object reference resulting from a lambda expression has the usual reference identity semantics. References are `==` to themselves. If you copy a reference around and later compare them, they will be `==`, e.g., after `Runnable a = () -> { }; Runnable b = a;` then `a == b`. However if you write the "same" lambda, the references might be unequal, e.g., `Runnable a = () -> { }; Runnable b = () -> { };` then `a` and `b` might or might not be `==`. There is no sense of equality for lambdas along the lines of "equivalent code." There is only reference equality. – Stuart Marks May 12 '16 at 01:03
2

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));
    }
  }
}
Holger
  • 285,553
  • 42
  • 434
  • 765
  • Admittedly, that's a good point. I've noticed that Java 8 commonly inverts a lot of my previous assumptions, and I haven't yet completely gotten around to it. :) – Dolda2000 Apr 02 '14 at 12:36