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?

16
  • 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. Commented Aug 18, 2015 at 8: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...) Commented Aug 18, 2015 at 8: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. Commented Aug 18, 2015 at 8:34
  • 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. Commented Aug 18, 2015 at 8:39
  • 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. Commented Aug 18, 2015 at 8:51

3 Answers 3

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> {
Sign up to request clarification or add additional context in comments.

9 Comments

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).
@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.
@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.
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.
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.
|
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.

Comments

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.