Search code examples
javagenericsjava-8functional-interface

Java 8 Generic java.util.Function


I am learning about FunctionalInterface which is present in Java 8. After doing some basic Functional examples, I tried to do the same with GenericType parameters.

public class Main {

    public enum LocType { 
        Area, Country
    }

    public <T> Function<T, T> getCreateFunction(LocType type) {

        AreaService areaService = new AreaService();
        CountryService countryService = new CountryService();
        switch(type) {
            case Area : return areaService::createArea;
            case Country : return countryService::createCountry;
            default : return null;
        }
    }

}

public class AreaService {

    public Area createArea(Area area) {
       // some logic
       return area;
    }

}

public class CountryService {

    public Country createCountry(Country country) {
        // some logic
        return country;
    }

}

// Area & Country are Model Classes

But eclipse compiler throws error as

The type AreaService does not define createArea(T) that is applicable here

Isn't it possible to define Generic Type Parameters in FunctionalInterface..?


Solution

  • You can use the Class class as a type token here. (I'll just ignore some obvious issues with the code you posted, such as not being able to call new AreaService(), etc). Instead of defining an enum for the type of thing you want your function to create (manipulate?), use Class as the parameter:

    @SuppressWarnings("unchecked")
    public <T> Function<T, T> getCreateFunction(Class<T> type) {
    
        AreaService areaService = new AreaService();
        CountryService countryService = new CountryService();
        if (type == Area.class) {
            return t -> (T) areaService.createArea((Area)t);
        } else if (type == Country.class) {
            return t -> (T) countryService.createCountry((Country)t);
        }
        return null ; // may be better to throw an IllegalArgumentException("Unsupported type") 
    }
    

    And now you can do things such as

    Main main = new Main();
    Function<Area, Area> areaChanger = main.getCreateFunction(Area.class);
    Function<Country, Country> countryChanger = main.getCreateFunction(Country.class);
    

    The way this works is that the class literal T.class for any class T is of type Class<T>. So Area.class has type Class<Area>, and Country.class has type Class<Country>.

    Thus if you call getCreateFunction(Area.class), the compiler can infer that T is Area in that invocation, and the return type is then Function<Area, Area>. On the other hand, in the implementation of getCreateFunction, while the compiler can only infer that the parameter to the lambda is of the unknown type T, we know it T must be of the type represented by the parameter type; i.e. if type==Area.class we can safely cast t to Area and (the value returned from areaService.createArea) Area to T. Thus the casts are safe (even the cast to the unknown type T).

    If you wanted to further restrict the values that could be passed to getCreateFunction(...) you could define a marker interface: e.g. public interface Region { } and make Area and Country implement that interface. Then define

    public <T extends Region> Function<T,T> getCreateFunction(Class<T> type) {...}
    

    and it would only be valid to pass class tokens for classes that implemented (or interfaces that extended) Region.