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

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.

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

Java generic T extends Object

Given the following class:
public class TestMap<K,V> {
private HashMap<K,V> map;
public void put(K k, V v) {
map.put(k, v);
}
public V get(K k) {
return map.get(k);
}
}
and this function:
static<T extends Object> String getObj(TestMap<T, String> m, String e) {
return m.get(e);
}
Why does "get" show this error:
The method get(T) in the type MyHashMap<T,String> is not applicable for the arguments (String)
When getObj states T extends Object, and the map has been initialized with TestMap<T, String> m, why can't String be used as parameter? I cant wrap my head around why this doesnt work, as String is a subtype of Object ?
I tried extending T from a custom superclass and using a subclass instead of String.
get() requires as an argument a key, which is of type T, not String.
So either you need to change e to type T or change the type of the Map, e.g., to TestMap<String, T>.
Generics describe what an Object is, List<String> strings is a list of Strings. That gives us some convenience, we don't have to cast. String s = strings.get(0); and we get some compile-time protection. Integer i = 1; then strings.add(i); fails to compile.
The error you are getting is a compile time protection. The key you're providing is an Object, so it might be the correct type. In the case of a map, "why not check?" It is just going to hashcode and check for equality which is ok. In the case of a List though, strings.add((Object)i); needs to fail.
If the doesn't fail, then later something might call. for(String s: strings) and get a class cast exception.
To fix this you can change you V get(K k) method to a V get(Object k) the same as map uses. Your get method requires type K a map normally has a get method with a Object
As mentioned "T extends Object" is the same as just "T". You use the extends when you want to put a lower bound on something. public <T extends Number> int sum(List<T> numbers). In this case "T" can be any class that extends numbers. We can do Number n = numbers.get(0); because we know it is at least a number. But we could not do numbers.add(n); because while T is at least a number, it could be something else like an Integer, Double, Number, BigDecimal etc.

filtering a stream changes its wildcard bounds?

the below method compiles without problems:
static Stream<Optional<? extends Number>> getNumbers(Stream<Number> numbers) {
return numbers.map(Optional::of);
}
yet if I add a simple filtering to it like this:
static Stream<Optional<? extends Number>> getNumbers2(Stream<Number> numbers) {
return numbers.map(Optional::of).filter(number -> true);
}
it generates the following error:
incompatible types:
java.util.stream.Stream<java.util.Optional<java.lang.Number>> cannot be converted to
java.util.stream.Stream<java.util.Optional<? extends java.lang.Number>>
tested on openJdk-11 and openJdk-17.
I'd expect them both to do the same (either both compile ok or both generate the same compilation error), so I'm really puzzled by this: what is the general rule here that explains why the 1st method compiles ok yet the 2nd does not?
Thanks!
Compatibility with the return type Stream<Optional<? extends Number>> in the first case is not obtained by virtue of numbers.map(Optional::of) returning a Stream<Optional<? extends Number>> on its own; it's the compiler inferring the return type of numbers.map(...) due to it being a generic method:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
while Stream.filter() is not:
Stream<T> filter(Predicate<? super T> predicate);
Therefore, in the first case the compiler can take into account the return statement's context (getNumbers's type) when inferring type of numbers.map(...).
Compiler cannot do the same for numbers.map(...) in the second case, as there are subsequent chained calls, that may further change the type, so it would be very hard to guess what the right inferring should be at this stage. As a result, the most specific possible type is assumed for numbers.map(...) (Stream<Optional<Number>>) and further carried on by filter(...).
As a different example to illustrate that, please figure out why both of these compile (List.of() is the same code, after all):
static List<String> stringList() {
return List.of();
}
static List<Integer> intList() {
return List.of();
}
Now, why does this fail:
static List<String> stringList() {
return List.of().subList(0, 0);
}
That's because List.subList(...) does not infer the returned list's E type in context (i.e., the method is not generic), it carries the List instance's E type, which, with List.of() in that case gets defaulted to Object (yes, when you have return List.of();, return type inference kicks in, forcing the compiler to figure out that the intent is to make E match String, the type argument in the method's return type). Please note that this gets more complex than that, there are corners where inference doesn't work as wished/expected.
Short answer: return numbers.map(Optional::of) takes advantage of type inference as map() is generic, and filter() does not, expecting the E of Stream<E> to be carried. And with numbers.map(Optional::of), E is Optional<Number>, not Optional<? extends Number>, and filter carries that.

Nested generic types in static method

I was trying to develop a generic method that could convert a JSON to a generic object that would have another instantiable generic object inside so I could use it as a parser in several places in my application.
I had thought of the following solution, but it doesn't work:
public static <T, K> T<K> jsonToObjectType(String json, TypeReference<T<K>> type) {
// More code
}
Is there any way to be able to perform such a method?
public static <T, K> T<K>
Your T has no bounds, meaning, T can be anything. It could be String.
String has no generics, so how can T<K> make sense? It doesn't, hence, java doesn't let you compile this.
I guess you could conceive of the notion of: "T is some specific type, could be anything, as long as it has exactly 1 generics param", but java doesn't have this, and never will, because that is structural type and java doesn't do that.
However, note that a generics param can be any type, notably include types that are themselves parameterized. Here is a trival example:
public static <T> T coalesce(T a, T b) {
return a == null ? b : a;
}
This method can be used like so:
String a = null;
String b = "Hello";
coalesce(a, b).toLowerCase();
There is absolutely no problem feeding it 2 List<String>, at which point the expression coalesce(listA, listB) would be of type List<String>. And that's just with <T>, not with this <T, K> stuff.
I don't quite know what jsonToObjectType is supposed to do, but assuming that it is supposed to take a string that contains JSON + some super-type-token (you can search the web for that term), which I'm 99.9% certain you have, then just remove K from it all, and you get precisely what you wanted:
public static <T> T jsonToObjectType(String json, TypeReference<T> type) {
// code here
}
and you can call it like so:
String json = "[\"Hello\", \"World!\"]";
List<String> list = jsonToObjectType(json, new TypeReference<List<String>>() {});
and it'll compile without warnings or errors and works.

How generics really works as in parameters?

I'm little confused about how the generics works? I'm learning about function API in java and there I just test Function interface and got confused about compose method that how the generics is working in compose method.
Reading the generics on the java official tutorial website I realize that if we have any generic type in the method return or parameters we have to declare that type in the signature of method as explained below.
Here is the method I read in official docs tutorial.
public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
return p1.getKey().equals(p2.getKey()) &&
p1.getValue().equals(p2.getValue());
}
Above method have two types, K, V which are declared in the signature after the static keyword as but when I read java Function API there is one method called compose and the signature of the compose is as
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
1) The first question where is the T & R declared? which are being used in the return type and in the parameter. Or my understanding is wrong?
Then I read more in generics tutorials and then I try to understand the concept of super and extends in generics and read here then I test compose method more and then confused again about how the super and extends works in the compose method?
public static void main(String... args){
Function<Integer, String> one = (i) -> i.toString();
Function<String, Integer> two = (i) -> Integer.parseInt(i);
one.compose(two);
}
As above I have declared two Function with lamdas. One is having Integer input and String output the other one is reversed from it.
2) The second question is that how Integer and String are related to extends and super? There is no relation between String and Integer class no one is extending each other then how it is working?
I tried my best to explain my question/problem. Let me know what you didn't understand I will try again.
Where are T and R defined?
Remember, compose is declared in the Function interface. It can not only use generic parameters of its own, but also the type's generic parameters. R and T are declared in the interface declaration:
interface Function<T, R> {
...
}
What are ? extends and ? super?
? is wildcard. It means that the generic parameter can be anything. extends and super give constraints to the wildcard. ? super V means that whatever ? is, it must be a superclass of V or V itself. ? extends T means that whatever ? is, it must be a subclass of T or T itself.
Now let's look at this:
Function<Integer, String> one = (i) -> i.toString();
Function<String, Integer> two = (i) -> Integer.parseInt(i);
one.compose(two);
From this, we can deduce that T is Integer and R is String. What is V? V must be some type such that the constraints Function<? super V, ? extends T> is satisfied.
We can do this by substituting the argument we passed in - Function<String, Integer> - to get String super V and Integer extends Integer.
The second constraint is satisfied already while the first constraint now says that String must be a super class of V or String itself. String cannot have subclasses so V must be String.
Hence, you can write something like:
Function<String, String> f = one.compose(two);
but not
Function<Integer, String> f = one.compose(two);
When you compose a Function<Integer, String> and a Function<String, Integer> you cannot possibly get a Function<Integer, String>. If you try to do this, V is automatically inferred to be Integer. But String super Integer is not satisfied, so the compilation fails. See the use of the constraints now? It is to avoid programmers writing things that don't make sense. Another use of the constraints is to allow you to do something like this:
Function<A, B> one = ...
Function<C, SubclassOfB> two = ...
Function<SubclassOfC, B> f = one.compose(two);
There is no relationship between Integer and String in this case, it's all about V.
1) The compose function is part of Interface Function<T,R>. As you can see in documentation for this interface:
Type Parameters:
T - the type of the input to the function
R - the type of the result of the function
2) The super and extends constraints in questions aren't applied to T & R, they're applied to the generic type parameters of a function that you pass in as an argument to the compose function.
Basically this means that if you have:
Function<ClassA, ClassB> one;
Function<SomeSuperClassOfC, SomeSubclassOfA> two;
then it's valid to call
Function<ClassC, ClassB> three = one.compose(two)
I will try to explain from zero;
interface Function<T, R> - this is interface with one method, which must be implemented R apply (T);
in Java prior to 8 we must write:
Function<Integer, String> one = new Function<Integer, String>() {
#Override
public String apply(Integer i) {
return i.toString();
}
};
now you can use it:
String resultApply = one.apply(5);
now, I think, you get the idea.

How can I use both method and class type parameters in single constraint?

I'll try to illustrate my problem in the following simplified example:
public class DataHolder<T> {
private final T myValue;
public DataHolder(T value) {
myValue = value;
}
public T get() {
return myValue;
}
// Won't compile
public <R> DataHolder<R super T> firstNotNull(DataHolder<? extends R> other) {
return new DataHolder<R>(myValue != null ? myValue : other.myValue); }
public static <R> DataHolder<R> selectFirstNotNull(DataHolder<? extends R> first,
DataHolder<? extends R> second) {
return new DataHolder<R>(first.myValue != null ? first.myValue : second.myValue);
}
}
Here I want to write generic method firstNotNull that returns DataHolder parametrized by common supertype of type parameter T of the this and other argument, so later I could write e.g.
DataHolder<Number> r = new DataHolder<>(3).firstNotNull(new DataHolder<>(2.0));
or
DataHolder<Object> r = new DataHolder<>("foo").firstNotNull(new DataHolder<>(42));
The problem is that this definition of firstNotNull is rejected by compiler with message that super T part of type constraint is illegal (syntactically).
However without this constraint definition is also wrong (obviously), because in this case T and R are unrelated to each other.
Interestingly, definition of similar static method selectFirstNotNull is correct and the latter works as expected. Is it possible to achieve the same flexibility with non-static methods in Java type system at all?
It isn't possible to do this. The authors of Guava ran into the same issue with Optional.or. From that method's documentation:
Note about generics: The signature public T or(T defaultValue) is
overly restrictive. However, the ideal signature, public <S super T> S or(S), is not legal Java. As a result, some sensible operations
involving subtypes are compile errors:
Optional<Integer> optionalInt = getSomeOptionalInt();
Number value = optionalInt.or(0.5); // error
FluentIterable<? extends Number> numbers = getSomeNumbers();
Optional<? extends Number> first = numbers.first();
Number value = first.or(0.5); // error
As a workaround, it is always safe to cast an
Optional<? extends T> to Optional<T>. Casting either of the above
example Optional instances to Optional<Number> (where Number is the
desired output type) solves the problem:
Optional<Number> optionalInt = (Optional) getSomeOptionalInt();
Number value = optionalInt.or(0.5); // fine
FluentIterable<? extends Number> numbers = getSomeNumbers();
Optional<Number> first = (Optional) numbers.first();
Number value = first.or(0.5); // fine
Since DataHolder is immutable like Optional, the above workaround will work for you too.
See also: Rotsor's answer to Bounding generics with 'super' keyword
I don't think there is any easy and type-safe way to do this. I've tried a couple of approaches, but the only working approach that I found is to start with a super type generic instance, and make the method pretty simple like this:
public DataHolder<T> firstNotNull(DataHolder<? extends T> other) {
return new DataHolder<T>(myValue != null ? myValue : other.myValue);
}
Now you have to change your invocation to:
DataHolder<Number> r = new DataHolder<Number>(3).firstNotNull(new DataHolder<>(2.0));
You might argue that this doesn't really answer your question, but this is the simplest thing you're going to get, or better resort to a static method approach. You can surely come up with some highly convoluted (and type-unsafe) methods to do so, but readability should be of major concern here.
Try changing your method as follows:
public <R> DataHolder<R> firstNotNull(DataHolder<? super T> other) {
return new DataHolder<R>((R)(this.myValue != null ? myValue : other.myValue));
}
WARNING: This compiles and gives the appearance of being properly checked for the most part but is not perfect. It will restrict the input parameters, but not the output. This cannot be done perfectly. In some ways you might be better off doing this unchecked rather than giving the illusion of being checked. Here are some examples:
DataHolder<BigDecimal> a = new DataHolder<>(new BigDecimal(34.0));
DataHolder<Number> b = new DataHolder<>(new Integer(34));
DataHolder<String> c = new DataHolder<>("");
DataHolder<Number> p = a.firstNotNull(b); // WORKS (good)
DataHolder<BigDecimal> q = b.firstNotNull(a); // FAILS (good)
DataHolder<BigDecimal> r = b.firstNotNull(c); // FAILS (good)
DataHolder<String> s = a.firstNotNull(b); // WORKS (not good!!!)

Categories