Guava why is toStringFunction not a generic function?

Guava toStringFunction () has the following declaration:

public static Function<Object, String> toStringFunction() { ... } 

All non-primitive roots in Object, so the function works well.

but when I try to link it with another function, for example:

 Function<Integer, Double> f1 = Functions.compose(Functions.forMap(someMap), Functions.toStringFunction()); 

where the variable someMap is a mapping of <String, Double>, so I expect toStringFunction to convert Integer to String, and then forMap to convert String to Double. But I get a compiler error:

  Function<Integer, Double> f1 = Functions.compose(Functions.forMap(someMap), ^ required: Function<Integer,Double> found: Function<Object,Double> 2 errors 

My two questions are:

1.Tell a specific compiler that toStringFunction should be Function <Integer, String>? Simple execution will not work, and I am looking for the real composition of these two functions.

 Function< Integer, String> g1 = (Function< Integer, String>)Functions.toStringFunction(); // cause error 

2. Make the toStringFunction function as follows:

  @SuppressWarnings("unchecked") public static <E> Function<E, String> toStringFunction(){ return (Function<E, String>) ToStringFunction.INSTANCE; } // enum singleton pattern private enum ToStringFunction implements Function<Object, String>{ INSTANCE; // @Override public String toString(){ // return "Functions.toStringFunction()"; // } @Override public String apply(Object o){ return o.toString(); } } 

I can indicate the type:

 Function<Integer, Double> f1 = Functions.compose(Functions.forMap(someMap), Functions.<Integer>toStringFunction()); 

and it works great. But what is the motivation for the Guava team version now?

+6
source share
4 answers

After discussion in the comments:

To fix a compilation error:

 Function<Object, Double> f1 = Functions.compose(Functions.forMap(someMap), Functions.toStringFunction()); 

Your question implies that you want Function<Integer, Double> instead of Function<Object, Double> . Based on the discussion in the comments and trying to think about other scenarios, the only reason you want to is checking. The code in the toStringFunction function is out of your control, so checking in this function is out of the question. As for the validation in the forMap function, this code is also uncontrollable. If you want to protect against an IllegalArgumentException, you will need to perform this check to see if the input you provided is an object or an integer. Thus, the check also goes beyond the scope of forMap, toStringFunction and is, because none of these functions provides such a check, and you must write your own code for this (either catch the exception, or handle it, or check which this way before calling this compiled function).

For a particular compilation, you do not get anything by changing the input parameter Function from Object to Integer, because functions only need methods that are present in Object, so this is a good solution, because it simplifies in this case and makes it more widely used. Suppose you were able to apply the parameter type as Integer, you still did not get absolutely any benefit, whether it was checking for IllegalArgumentException or something else that you are trying to do.

When developing an API, you want it to be used by as many consumers as possible, without having to go out of your way, that is, you must use the highest level class that meets your requirements. Using Object in toStringFunction, the Guava function satisfies this principle. This makes the function more general and more widely used. This is the reason for choosing an object instead of parameterizing this function.

If a function accepts a parameter, it will not do anything differently as it is now, so why use a parameter when it is not needed. This approach greatly simplifies the design, instead of adding something that is not required.

Original answer:

Each class is a child of the object, and the function only calls a toString on the input that is present in the Object class. Therefore, in this case, declaring a function is equivalent to declaring a function. You will not get anything by changing the input parameter of the function from the object to the whole, because functions only need the methods present in the object, so this is a good solution, because in this case it simplifies.

As much as possible, you should use the highest level class that suits your requirements. This makes the function more general and more widely used. This is the reason for choosing an object instead of parameterizing this function.

To fix a compilation error: Function f1 = Functions.compose (Functions.forMap (someMap), Functions.toStringFunction ());

+4
source

The reason toStringFunction() is Function<Object, String> , and not Function<T, String> , it is simple because you can call toString() on any Object . This is literally a function that takes an object and returns a string. Guava avoids introducing type parameters (and wildcards) into methods in which they do not serve the purpose.

The problem in your example is that you are trying to introduce your function f1 as Function<Integer, Double> when it is actually Function<Object, Double> : it can accept any object, call toString() on it, pass this line to your forMap function and get the result.

In addition, provided that the generics are used correctly, there should be no real need for your function type to be Function<Integer, Double> , not Function<Object, Double> , since most of the code that takes a function and uses it to execute something then should accept something like Function<? super F, ? extends T> Function<? super F, ? extends T> Function<? super F, ? extends T> . So the real question here is: "Why do you want to refer to Function<Object, Double> as Function<Integer, Double> "?

+7
source

The reason Guava does not declare it as Function<T, String> (although this can be done) is explained by one of the members of the Guava team in this comment on a similar question in his error tracker :

We decided that the purpose of type parameters and wildcards in the library API is to ensure that this method can be called with any types of parameters that make logical sense. Once this condition is met, we will stop, instead of continuing to add types of params / wildcards, which serve only to allow you to "massage" the type of specific result that you want to get.

So basically they decided it wasn’t worth it. Instead of changing methods such as toStringFunction to return Function<T, String> , instead, consumers of generic types should be changed so that they allow.

+4
source

Regarding the motivation of the Guava team for this behavior, I really don't know. Perhaps one of them should answer this.

As for the compilation error, you need a helper method to execute the cast:

 public class Sample { @SuppressWarnings("unchecked") private static <T> Function<T, String> checkedToStringFunction() { return (Function<T, String>) Functions.toStringFunction(); } public static void main(String[] args) { Map<String, Double> someMap = new HashMap<>(); Function<Integer, Double> f1 = Functions.compose( Functions.forMap(someMap), checkedToStringFunction()); // compiles fine } } 

In this way, the compiler can safely infer parameter types.

EDIT:

I was told that this technique can lead to heap contamination. Although this may be true from a theoretical point of view, in practice I do not see how this can lead to a ClassCastException or to any other runtime error, since the helper method is private and is used only for Function output.

If I forcefully used the generic type, then:

 Function<Integer, Double> f1 = Functions.compose( Functions.forMap(someMap), Sample.<BigInteger> checkedToStringFunction()); // compilation error 

I get a compilation error. To fix this, I will need to change the parameter of the first type f1 Function to:

 Function<BigInteger, Double> f1 = Functions.compose( Functions.forMap(someMap), Sample.<BigInteger> checkedToStringFunction()); // compiles fine 

Note:

I know what pollution the heap is:

 private static <S, T> S convert(T arg) { return (S) arg; // unchecked warning } Number n = convert(new Long(5L)); // fine String s = convert(new Long(5L)); // ClassCastException 

In this example, I am going over to a given type variable, while in the proposed solution, I am going over to a parameterized type.

Specifically, I drop from Function<Object, Double> to Function<Integer, Double> . I see this as a narrowing type conversion, so I consider it legal and safe. Please let me know if I am fine with this and if it is worth the risk.

+1
source

Source: https://habr.com/ru/post/983993/


All Articles