How to say "both arguments must have the same type" in Java?

Viewed 143

In unit testing, you sometimes want a method like this one:

<V> void checkEq(V expectation, V value) {
  if (!expectation.equals(value))
    throw new IllegalArgumentException("not equal");
}

(assume for a second that we can't use junit for some reason) The idea is that the compiler should ensure both arguments have the same type. However the compiler doesn't complain about this invocation, where the types obviously differ:

checkEq(1, "");

I've tried some variations of this, like checkEq(V expectation, Supplier<V> value) and checkEq(1, () -> ""). It seems like the compiler is always happy to insert Object for V. Is there some way to tell the compiler not to use the trivial Object solution?

3 Answers

If you declare the type parameter as part of a wrapper class, then it's possible to infer the type and enforce that the expected and value follow it:

class CheckEq<V> {
    void checkEq(V expectation, V value) {        
        if (!expectation.equals(value))
            throw new IllegalArgumentException("not equal");
    }
}

But I feel this is an over-engineering practice, because you just want to do an equals() at the end (on two Objects), so type safety is secondary to that.

In this case, compile-time type safety is insufficient. The problem in this case is the developer, and a developer can make any invocation in such a scenario, by twisting the compiler if necessary.

If you, as the API developer, are so desperate to assist your callers make the correct invocation, you can add a third, Class-typed parameter that limits potential incorrect calls

<V> void checkEq(Class<V> cls, V expectation, V value) {
    if (!expectation.equals(value))
        throw new IllegalArgumentException("not equal");
}

I don't know what you would call such a parameter. But that code can't be called with either of these

checkEq(String.class, 1, "");
checkEq(Integer.class, 1, "");

However, the lazy kind will still be able to call it in this way, although it (supposedly) will be obvious to them that they're doing something wrong:

checkEq(Object.class, 1, "");

The only effective way to limit the problem is to make it fail at runtime. This way you force the developer to test.

if (expectation.getClass() != value.getClass() || !expectation.equals(value))
    throw new IllegalArgumentException("not equal");

Judging from the comments received so far, apparently that's just not how the Java compiler currently works. All answers and comments boil down to "just pass the explicit solution" in some form or other. Let's hope we get an improvement (some annotation or other compiler hint maybe) in the future to say "solution has to be better than X" (in particular X=Object).

Related