Search code examples
javareferencefunctional-interface

Java Member References placed in variables do not compare equal


Java 8 or Java 11 - I load a static functional interface through an enum constructor. Then I try to compare against the same member reference. Either the code won't compile, or the comparison against the same functional interface fails. Comparisons using .equals, and ==, and comparing .hashcode() all fail. I have created the following POJO Test that demonstrates the issue and points out the specific points in question. I want to code "If instance-member is equal to specific-static-functional-interface-method-x, then do ..."

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;

import java.util.function.Function;

import org.junit.jupiter.api.Test;

enum MethodReferenceEnum {
    One("One", MethodReferenceInterface::one),
    Two("Two", MethodReferenceInterface::two);

    public String name;
    public Function<String, Integer> method;

    private MethodReferenceEnum(String aName, Function<String, Integer> aMethod) {
        name = aName;
        method = aMethod;
    }
}

interface MethodReferenceInterface {
    public static Integer one(String aString) {
        return 1;
    }
    public static Integer two(String aString) {
        return 2;
    }
}

class MethodRererencesTest {

    @Test
    void LocalUseTest() {
        // This performs in JavaSE-8 (jdk-1.8.0_271) and JavaSE-11 (jdk-11.0.11).

        // These succeed in placing the member references in variables.
        Function<String, Integer> choice1a = MethodReferenceInterface::one;
        Function<String, Integer> choice1b = MethodReferenceEnum.One.method;
        Function<String, Integer> choice2a = MethodReferenceInterface::two;

        // These succeed in executing the member references from variables.
        assertEquals(1, choice1a.apply("A").intValue());
        assertEquals(1, choice1b.apply("B").intValue());
        assertEquals(2, choice2a.apply("2nd").intValue());

        // Why does the following fail to compile?
        //
        // assertEquals(choice1a, MethodReferenceInterface::one);
        //
        // ^ assertEquals fails to compile due to:
        // The method assertEquals(Object, Object) in the type Assert is not applicable
        // for the arguments (Function<String, Integer), MethodReferenceInterface::one)
        // ^ MethodReferenceInterface::one fails to compile due to:
        // The target type of the expression must be a functional interface

        // Compiles and succeeds; expected.
        assertEquals(choice1a, choice1a);

        // This compiles, and succeeds,
        // but why do they not compare equal?
        assertNotEquals(choice1a, choice1b);
    }
}

The last line and question sum up the issue. I ran the test above and it passed. The issue is that the variables that house the same member references are not comparing equal. This occurs in Java 8 and Java 11.


Solution

  • When you type:

    Function<String, Integer> choice1a  = MethodReferenceInterface::one
    

    the compiler converts it to:

    Function<String, Integer> choice1a = new Function<>() {
       String apply(Integer n) {
         return 1;
       }
    }
    

    It knows to convert it to Function interface and not anything else because of the type of the member choice1a.

    The function assertEquals is not a generic function and the input of it is object, object. Therefore the compiler does not know how to create MethodReferenceInterface::one.

    The first parameter of the function choice1a does not make the function generic equals (aka assertEquals(Function, Function)) but it makes it assertEquals(object, object).

    // This compiles, and succeeds, // but why do they not compare equal? assertNotEquals(choice1a, choice1b);

    They are not equal the same reason I specified at first. Each time you use :: short cut, you create a new instance behind the hood and their references are different.