Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Guava why toStringFunction not a generic function?

the Guava toStringFunction() has the following declaration:

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

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

but when I try to compose it with another function, such as:

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

where someMap variable is a map< String, Double>, So i expect toStringFunction converts Integer to String and then forMap converts 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.how to specifically tell the compiler that toStringFunction should be Function< Integer, String>? A simple cast will not work, and I am looking for a real composition of the two functions.

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

// cause error 

2.Had the toStringFunction written as the following:

 @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 specify the type:

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

and this works fine. But what is the motivation for the version Guava team have right now?

like image 363
fast tooth Avatar asked Mar 18 '15 23:03

fast tooth


People also ask

What is guava string utilities?

Guava - String Utilities. Guava introduces many advanced string utilities based on developers' experience in application development works.

What is the difference between generic types and functions in Java?

There are some fundamental differences between the two approaches to generic types. Generic Method: Generic Java method takes a parameter and returns some value after performing a task. It is exactly like a normal function, however, a generic method has type parameters that are cited by actual type.

What is a generic class in Java?

Generics in Java. Generics mean parameterized types. The idea is to allow type (Integer, String, … etc, and user-defined types) to be a parameter to methods, classes, and interfaces. Using Generics, it is possible to create classes that work with different data types. An entity such as class, interface, or method that operates on a ...

How do you detect generic types in Java?

By using the standard Java reflection API we can detect the generic types of methods and classes. If we have a method that returns a List<String>, we can use reflection to obtain the return type of that method – a ParameterizedType representing List<String>. The TypeToken class uses this workaround to allow the manipulation of generic types.


2 Answers

The reason toStringFunction() is a Function<Object, String> and not a Function<T, String> is, quite simply, because you can call toString() on any Object. It is literally a function that takes an object and returns a string. Guava avoids introducing type parameters (and wildcard generics) to methods where they serve no purpose.

The issue in your example is that you are trying to type your f1 function as Function<Integer, Double> when it is in fact a Function<Object, Double>: it can take any object, call toString() on it, pass that string to your forMap function and get a result.

Furthermore, assuming proper use of generics, there shouldn't be any actual necessity for your function's type to be Function<Integer, Double> rather than Function<Object, Double>, since most code that accepts a function and uses it to do something should accept something like Function<? super F, ? extends T>. So the real question here is "why do you want to refer to a Function<Object, Double> as a Function<Integer, Double>"?

like image 178
ColinD Avatar answered Oct 04 '22 03:10

ColinD


After discussion in comments:

To fix the compilation error:

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

Your question implies you want a Function<Integer, Double> instead of Function<Object, Double>. Based on the discussion in the comments, and trying to think of other scenarios, the only reason you will want this is for validation. The code in the toStringFunction is out of your control, so validation in that function is out of question. As far as validation in the forMap function, that code is also out of your control. If you want to protect against IllegalArgumentException, you will have to perform this validation whether the input you provide is an Object or an Integer. So that validation too is out of scope of the forMap, toStringFunction, and compose, because none of these functions provide that kind of validation and you have to write your own code to do this (either catch the exception and handle it or pre-validate in some way before calling this composed Function).

For the specific compose, you gain nothing by changing the input parameter of the Function from Object to Integer, because the Function only needs the methods present in Object, so it is a good decision as it simplifies things in this case and makes it more widely usable. Say you were able to enforce the parameter type as Integer, you still have gained absolutely no benefit whatsoever, whether it is validation for IllegalArgumentException or something else you are attempting.

When designing an API, you want it to be usable by as many consumers as possible, without having to go out of your way, i.e. you should use the highest level class that satisfies your requirements. By using Object in toStringFunction, the Guava function is satisfying this principle. This makes the function more generic and more widely usable. That is the reason for choosing Object instead of parameterizing this Function.

If the Function took a parameter, it would do nothing differently as it is doing right now, so why use a parameter when it is not required. This approach simplifies the design greatly instead of adding something that is not required.

Original answer:

Every class is a child of Object, and the Function is only calling toString on the input which is present in the Object class. So in this case, declaring a Function is equivalent to declaring Function. You will gain nothing by changing the input parameter of the Function from Object to Integer, because the Function only needs the methods present in Object, so it is a good decision as it simplifies things in this case.

As far as possible, you should use the highest level class that satisfies your requirements. This makes the function more generic and more widely usable. That is the reason for choosing Object instead of parameterizing this Function.

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

like image 37
tinker Avatar answered Oct 04 '22 04:10

tinker