3

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.

Rubén Morales
  • 332
  • 4
  • 6
  • 1
    Why do you want `test()` to be within `MatcherProperty` ? There seems no reason for it to be so, and if it wasn’t then you wouldn’t have this problem? If `test()` is intended as part of unit testing this class, then it should be in the test case class (so again would be moved out) – racraman Jun 24 '20 at 00:22
  • It's just an example in order to keep everything in one class. The question still makes sense with line `withProperty(this::getInteger, "Random string");` in another file. I renamed the function to main. – Rubén Morales Jun 24 '20 at 07:17
  • With that call in another file, how did you declare the instance of MatcherProperty (and could you edit the question to show this code) ? If you had, for example, `new MatcherProperty” then you should be getting the compiler error. – racraman Jun 24 '20 at 11:09
  • That's the point, I did not declare any instance of MatcherProperty. As I commented to herman, this code is used with something like `assertList(listVatCodes.getVatCodes(), containsItem( withProperty(VatCode::getId, "Random string")));`. I could attach all the files but I think that the code I listed in the question is the minimal example to reproduce what I am trying to reach. It is easier with the minimal code I guess (?) – Rubén Morales Jun 24 '20 at 12:52

1 Answers1

1

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.

herman
  • 11,740
  • 5
  • 47
  • 58
  • Thanks. This code actually is a simplification of something like this `assertList(listVatCodes.getVatCodes(), containsItem( withProperty(VatCode::getId, "Random string")) );` that's why I was asking to force a compilation error without the assignment, there is no assignment in the real code. Line `MatcherProperty mp = withProperty(this::getInteger, "Random string");` will compile even if we add a `Class` parameter, isn't it? Would it be checked at runtime (?) – Rubén Morales Jun 24 '20 at 12:43
  • @RubénMorales like I said it would compile if you supplied `Serializable.class` or `Object.class` but not `String.class` or `Integer.class`. See edited answer. – herman Jun 24 '20 at 13:32