How to detect class difference at compile time using generics (java)

Viewed 125

I am using java 8 and I would like to detect subtle class differences at compile time modifying withProperty() header. This code is working but I would like to force a compilation error in main() function because this::getInteger returns an Integer and the second argument is a String.

import java.util.function.Function;

public class MatcherProperty<T, K> {
    public static <T, K> MatcherProperty<T, K> withProperty(
            Function<T, K> mapper,
            K expected
    ){
        return new MatcherProperty<>();
    }

    private Integer getInteger(Object object) {
        return 1;
    }

    public void main() {
        withProperty(this::getInteger, "Random string");
    }
}

I would like to avoid (if possible) a third argument in withProperty() function specifying the class type or something like this. Maybe K is translated to Object, the superclass of Integer an String. What is actually happening under the hoods? Is it possible to force a compilation error in this case?

Thanks in advance.

1 Answers

There is no compile error in your current code because the result of the call to withProperty is ignored.

If you would try to assign the result like this:

MatcherProperty<Object, Integer> mp = withProperty(this::getInteger, "Random string");

then you'd get a compilation error because the String argument doesn't match type K which is Integer in the result.

If you would try to assign the result like this:

MatcherProperty<Object, String> mp = withProperty(this::getInteger, "Random string");

then you'd get a compilation error because the Integer result of the function given as first argument doesn't match type K which is String in the result.

You can only make the assignment compile by using a common super type such as Object or Serializable:

MatcherProperty<Object, Serializable> mp = withProperty(this::getInteger, "Random string");

You can't force people to assign the result of course. You can add a Class<K> parameter to make them choose a class (such as Integer.class or String.class) but even then, they can just pass Serializable.class or Object.class instead:

public static <T, K> MatcherProperty<T, K> withProperty(
                Class<K> clazz,
                Function<T, K> mapper,
                K expected
        )


withProperty(String.class, this::getInteger, "Random string"); // doesn't compile

withProperty(Integer.class, this::getInteger, "Random string"); // doesn't compile

withProperty(Serializable.class, this::getInteger, "Random string"); // compiles

If you don't tell the compiler somehow what type K is (using class argument or assignment of the return value which is of type K) then it will infer the common type, Serializable in this case.

Related