8

I am not sure how I can be sure about equality/immutability of functional interface. I guess there might be no way to assure equality when I use this syntactic sugar in java 8, please let me know any hint if you have any.

I made a short code snippet for my question.

public interface Element {
    void doSomething(int a);
}

and I've tried to add instance of this interface in functional way

public class FunctionSet {

    public void doubleUp(int a) {
        System.out.println(a*2);
    }

    public void square(int a) {
        System.out.println(a*a);
    }

    public static void main(String[] args) {
        HashSet<Element> set = new HashSet<>();
        FunctionSet functionSet = new FunctionSet();

        set.add(functionSet::doubleUp);
        set.add(functionSet::square);

        System.out.println(set.add(functionSet::doubleUp));
    }

}

it prints true which means there were not any equality check and also I can't remove any instance from Set once I add it.

in case I use functional interface as an argument, Is there any way that I can compare those instance somehow?

will appreciate any help, thanks in advance!

Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
BinaryProbe
  • 273
  • 4
  • 12
  • 1
    Mutability is determined by state. When an object has no state (i.e., when it has no instance fields), then it can't be mutable. Stateless objects, such as most (all?) lambdas are therefore always immutable. Immutable objects can and should be singletons since their state can never change, but it is possible to have two otherwise identical immutable objects at different memory addresses ... which would yield two equivalent objects with separate identity. – scottb May 11 '15 at 04:39
  • 2
    Short answer: No, you can't test two lambdas from different code locations for equality. – Louis Wasserman May 11 '15 at 05:23
  • 2
    @Louis Wasserman: the code location doesn’t matter, the correct answer is that you can't test two lambdas for equality, even if instantiated at the same code location. While it might appear to work in some cases, this is implementation dependent behavior. – Holger May 11 '15 at 09:18

1 Answers1

3

You can store your method reference into a variable:

public static void main(String[] args) {
    HashSet<Element> set = new HashSet<>();
    FunctionSet functionSet = new FunctionSet();

    Element fn = functionSet::doubleUp;
    set.add(fn);
    set.add(functionSet::square);

    System.out.println(set.add(fn));
}

This way it returns false.

When you create the same labmda or method reference in different code locations, it's roughly the same as you would create a new anonymous class in both positions:

public static void main(String[] args) {
    HashSet<Element> set = new HashSet<>();
    FunctionSet functionSet = new FunctionSet();

    set.add(new Element() {
        @Override
        public void doSomething(int a) {
            functionSet.doubleUp(a);
        }
    });
    set.add(new Element() {
        @Override
        public void doSomething(int a) {
            functionSet.square(a);
        }
    });

    System.out.println(set.add(new Element() {
        @Override
        public void doSomething(int a) {
            functionSet.doubleUp(a);
        }
    }));
}

So every time it's a different object, though it may look the same. For every encountered method reference separate anonymous class is created at the runtime:

Element e1 = functionSet::doubleUp;
Element e2 = functionSet::doubleUp;

System.out.println(e1.getClass());
System.out.println(e2.getClass());

The output will be like this:

class FunctionSet$$Lambda$1/918221580
class FunctionSet$$Lambda$2/1554547125

So practically it's two distinct objects of two distinct classes. It would be quite difficult to conclude that they do the same thing without comparing their bytecode. Also note that they both capture the functionSet variable, so it should also be ensured that it wasn't changed between two method references.

The only workaround I can think up is to declare all the method references as constants in your code and later reference them instead of using method references directly:

public static final Element FN_DOUBLE_UP = new FunctionSet()::doubleUp; 
public static final Element FN_SQUARE = new FunctionSet()::square; 

public static void main(String[] args) {
    HashSet<Element> set = new HashSet<>();

    set.add(FN_DOUBLE_UP);
    set.add(FN_SQUARE);

    System.out.println(set.add(FN_DOUBLE_UP));
}
Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
  • "Different object every time"... Not nice, but okay, I guess. But why cannot `equals` be made to work properly? – Thilo May 11 '15 at 04:35
  • 1
    The default implementation of `equals()` is the method inherited from Object, which is the same as the `==` operator. If you have two separate instances of the same immutable object, `equals()` will be false because each has its own separate identity. If you are reusing a lambda, you should store it in a `static` field and access it through the field's name as Tagir has suggested. This will prevent multiple instantiations. – scottb May 11 '15 at 04:41
  • 1
    @Thilo: Added some more explanations and possible workaround, though I'm not sure this will make you happy – Tagir Valeev May 11 '15 at 04:45
  • @TagirValeev Thanks for your advice, it helps me a lot to understand more. – BinaryProbe May 11 '15 at 05:00
  • Another workaround would be to follow the lead of Function::identity and create static methods that return lambdas: public static final Element doubleUp() { return new FunctionSet()::doubleUp; }. As a nice bonus, unlike constants, the lambda in the static method is lazily initialized upon said method's first invocation. Also, CamelCase! /dbzanappa – srborlongan May 11 '15 at 05:56
  • 1
    It doesn’t look convincing to have the two static constants bound to arbitrary, otherwise unused `FunctionSet` instances. If these instances don’t matter, the methods should be declared `static`. Otherwise, if the instance(s) matter(s), you can’t create static constants this way. In either case, the code needs a cleanup… – Holger May 11 '15 at 09:24
  • 1
    @srborlongan: Since `Function.identity()` is implemented as `t->t`, there is no guaranty that it returns a unique instance. It does so due to the current implementation in Oracle’s JRE, but that’s off specification. Even worse, in your suggested work-around, `public static final Element doubleUp() { return new FunctionSet()::doubleUp; }`, you can be quite sure that it will return a new instance on each invocation, so the question remains, what problem this method ought to solve… – Holger May 11 '15 at 09:29