8

Background

I am exposing the following interface as part of an API:

public interface Pasture {
    /**
     * @param t         The time of the visit (as measured from optimization starting point).
     * @param tLast     The time of the preceding visit (as measured from optimization starting point).
     * @return          The expected reward that will be reaped by visiting under the given conditions.
     */
    double yield(long t, long tLast);    
}

The client passes me models of "pastures" as objects implementing this interface. Each object represents one pasture.

On my side of the API, I keep track of "visits" to these objects at various times, then invoke pasture.yield(time, lastVisitTime) when I need to know how much the pasture would have produced in the mean time.

The problem arises that this interface can be implemented on the client side as a lambda expression, and apparently each lambda expression instantiation does not necessarily create a new object with a new identity, which is what I rely on in order to keep track of which pastures were visited at what times.

Question

Is there a way of preventing the interface from being implemented as a lambda expression, forcing instead that the client implements it as an anonymous class. Adding a dummy method to it would do the trick, of course, but that would be arbitrary and untidy in my opinion. Is there another way?

Community
  • 1
  • 1
Museful
  • 6,711
  • 5
  • 42
  • 68
  • 2
    Can you not detect that a duplicate Pasture was passed and handle it appropriately? Note: using an anonymous inner class does guarantee a new one is created each time either. – Peter Lawrey Aug 18 '15 at 08:30
  • 2
    ^ This: The difference between lambda and anonymous class is *mainly* syntactical in this regard. Maybe you need another approach at a higher level of abstraction (Maybe "Pasture" as an abstract class that can only be instantiated via a factory or so, but for concrete hints, details about the intented usage are missing...) – Marco13 Aug 18 '15 at 08:32
  • 3
    What will stop your client from saving a single instance of `Pasture` in a static/instance variable and reusing it? The exact same thing is done by the Java compiler when desugaring a lambda. – Marko Topolnik Aug 18 '15 at 08:34
  • @Marko Well, the documented contract of the API would be such that when you explicitly pass the same object you mean the same pasture. – Museful Aug 18 '15 at 08:37
  • 3
    Then your client will have to take care to respect the contract, whether by using a lambda expression or any other means to implement the interface. The point is that lambdas are not a special case for you. – Marko Topolnik Aug 18 '15 at 08:39
  • @Peter Did you mean "does *not* guarantee..."? If so, I didn't know that. – Museful Aug 18 '15 at 08:40
  • 1
    @tennenrishin sorry, yes, that is what I meant. You can assign a anonymous inner class to a field and use that field. – Peter Lawrey Aug 18 '15 at 08:42
  • @Marko That is why I said "explicitly". I would rather redesign the API if it relies on the client knowing that lambda expressions don't necessarily create new objects. – Museful Aug 18 '15 at 08:45
  • @Peter Ok, but at least an object with a new identity is guaranteed to be created every time `new Pasture(){...}` runs. Or not? – Museful Aug 18 '15 at 08:51
  • 2
    In Java it's very simple: you apply `new`, you get a new instance; you don't apply it --> no guarantee. Every Java dev should be aware of this. Lambdas are nothing special, they are just another expression. Does an average Java dev expect `(Integer) 3` to always return a new instance of `Integer`? I hope not, and surely the onus is on the dev to know this. – Marko Topolnik Aug 18 '15 at 08:51
  • @Marko I always thought of lambda expressions as shorthand for `new SingleMethodClass(){...}`. I'm afraid my client might make the same mistake. What you're telling me, essentially, is not to worry. I understand where you're coming from, idealistically, but given that IDEs even auto-suggest changing such anonymous classes to lambda expressions, I'd feel safer if I could prevent it. – Museful Aug 18 '15 at 09:05
  • 1
    @tennenrishin you can prevent it at runtime by checking the class of the object passed, but a better approach is to allow lambdas in the first place. – Peter Lawrey Aug 18 '15 at 09:08
  • 1
    No, I am not suggesting not to worry, but to clearly separate the responsibilities of your library and its users. Bending over backwards to cater for the uninformed programmer ends up as an annoyance in the long run. – Marko Topolnik Aug 18 '15 at 09:21
  • 2
    Can you add a few lines of code (maybe just somewhat sketchy pseudocode) of how you are using these `Pasture` instances internally (to see what you mean by "keeping track of visits"), and how they are supposed to be implemented by clients (maybe showing two cases: One that is "OK" and one that is "not OK")? One could probably make more focussed and helpful suggestions then... – Marco13 Aug 18 '15 at 09:55
  • 1
    From the code, it looks like the responsibilites are not clear. A line like `pasture.yield(accumulatedTime,pathInfo.timeOfLastVisitTo(pasture))` looks dubious, and as if some responsibilities could be "pulled out" to avoid the *dependency* on the actual `Pasture` instance. Maybe something like a `PastureYieldComputer` somewhere, but the overall goal is not clear, so this is just guesswork... – Marco13 Aug 18 '15 at 12:37
  • Yes, I'd need to paste a few classes to make the background clear. I don't think the sketchy code makes anything clearer than the explanation so I'm going to delete that code from the question again. – Museful Aug 18 '15 at 18:17

3 Answers3

5

It is not the lambda edge case that is your problem.

You are making an assumption that the same object represents the same Pasture and are therefore making your object do two things at once. That is a code smell and is the cause of your difficulties.

You should force your Pasture objects to implements something like equals so that you can then check if the items are the same. Sadly there is no interface that does that, the nearest one is Comparable.

public interface Pasture extends Comparable<Pasture> {
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • 1
    Good point about design, but I would instead just specify the contract that distinct pastures must be unequal according to `equals` and `hashCode`. This will further allow OP to have a meaningful storage implementation (a `HashMap`). – Marko Topolnik Aug 18 '15 at 08:58
  • I don't think I'm getting the whole picture because to me it seems like you are making your object do two things at once too. Where I assume that pastureA==pastureB implies they represent the same pasture, you assume that pastureA.compareTo(pastureB)==0 implies they represent the same pasture. Would it be possible to elaborate on what the code smell is and how your proposed solution gets rid of it? – Museful Aug 18 '15 at 09:31
  • 1
    @MarkoTopolnik - Agreed - but merely specifying rather than enforcing may not be sufficient. `Camparable` would also allow for a `TreeMap` but that may not be what OP needs. – OldCurmudgeon Aug 18 '15 at 09:31
  • 2
    @tennenrishin - The code smell arises from your (mistaken) assumption that if two objects are **not** equal they are **different** objects. That is an unsafe assumption - in Java you can only assume that if two objects **are** equal (in the `==` sense) then they are the same object. By forcing the use of `equals` (as Marko suggests) or `compareTo` you are enforcing the contract rather than assuming it. – OldCurmudgeon Aug 18 '15 at 09:35
  • 1
    If pastures are only distinguishable but not comparable, then `equals` is exactly what this is about. The approach with `equals` also subsumes OP's current design where each instance is distinct. As for enforcing, well, `equals` is _always_ enforced, isn't it? A failure to provide the appropriate implementation can only be the client's fault. – Marko Topolnik Aug 18 '15 at 09:35
  • 2
    @MarkoTopolnik - Agreed - sadly there is no enforceable mechanism for ensuring that the object implements `equals` correctly. `Comparable` is not the perfect solution but it is better IMHO than merely documenting the requirement. – OldCurmudgeon Aug 18 '15 at 09:38
  • 2
    Well, don't get me started on broken `Comparable` implementations :) _Nothing_ can enforce correctness, in any programming language. `Comparable` just isn't a match for OP's problem if pastures are incomparable. – Marko Topolnik Aug 18 '15 at 09:39
  • @Marko So basically using `equals` rather than `==` on my side of the API allows me to leave the definition of equality up to the client. If the client mistakenly uses lambda-expressions where they meant to instantiate anonymous classes, I can tell them that this is inconsistent with their definition of equals (since they didn't override the default `==` implementation of equals). And if they *do* override it, they can't use a lambda expression anyway. Hmm.. – Museful Aug 18 '15 at 09:49
  • 4
    Exactly, that hits the nail. Talking about `equals` in the contract makes the dev acutely aware of the need to supply an appropriately implemented instance. It also makes it explicit that `Pasture` actually has _two_ concerns, even if it has just one custom method in the interface. You can even add `equals` to the interface and document it appropriately there. – Marko Topolnik Aug 18 '15 at 09:55
1

You can add some checked exception to method:

public interface Pasture {
   double yield(long t, long tLast) throws Exception;    
}

But it is strange approach to deny lambda.

sibnick
  • 3,995
  • 20
  • 20
0

"[...] apparently each lambda expression instantiation does not necessarily create a new object with a new identity, which is what I rely on in order to keep track of which pastures were visited at what times."

(Emphasis mine.)

When you're doing something, and it's causing you trouble, why not just stop doing it?

Instead, whenever a client passes you a "pasture", allocate a new internal ID for it and associate the pasture (and any metadata, such as the last visit time, that you may wish to store for it) with the ID.

That way, it doesn't matter if the client passes you the same pasture object several times — as far as your code is concerned, those pastures will have different IDs, and therefore represent distinct pastures with their own last visit times and other data, even if the client-side parts of them might be implemented by the same object.

(At least, that's true as long as the reused pasture objects don't maintain any mutable internal state; but your client should know better than to reuse objects with such state, and so does the Java 8 lambda implementation.)

Here's a quick sample implementation, using simple sequential integer IDs:

public class PastureManager {
    private int maxID = 0;
    private Map<Integer, Pasture> pastures = new HashMap<Integer, Pasture> ();
    private Map<Integer, Long> lastVisit = new HashMap<Integer, Long> ();
    long time = 0;

    public int addPasture (Pasture pasture) {
        maxID++;
        int id = maxID;
        pastures.put(id, pasture);
        lastVisit.put(id, time);
        return id;  // in case the client needs to re-identify the pasture
    }

    public double getYieldFor (int id) {
        Pasture pasture = pastures.get(id);
        if (pasture == null) {
            throw new IllegalArgumentException("Invalid pasture ID " + id);
        }
        long lastTime = lastVisit.put(id, time);
        return pasture.yield(time, lastTime);
    }

    // ...remaining code omitted...
}

The key feature here is that you never pass Pasture objects around after they've been added and assigned an ID, and you especially don't compare them or use them as map keys. Instead, any code that needs to refer to a specific pasture should use the pasture's ID; only the addPasture() and getYieldFor() methods (and possibly removePasture(), if you have such a method) need to access the actual Pasture objects stored in the pastures map.

Community
  • 1
  • 1
Ilmari Karonen
  • 49,047
  • 9
  • 93
  • 153