4

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..?

The Coder
  • 2,562
  • 5
  • 33
  • 62
  • 7
    You can't have a generic function whose return type depends upon a runtime parameter: return types are determined at compile time; the parameter's value is only known at runtime. – Andy Turner Jul 26 '16 at 20:22
  • Also, looks very suspicious to me. That's saying this generic relies on two types, but they have to be the same. Unless I'm missing something? EDIT: Wait, I see now, nevermind. – Edward Peters Jul 26 '16 at 20:23
  • 1
    It's not clear what the return types of `createArea()` and `createCountry()` are. Also, you might want to consider `UnaryOperator` in place of `Function` (assuming that's correct here). – shmosel Jul 26 '16 at 21:30
  • Can you clarify some of the code you haven't posted. E.g. you show an enum `LocType` with *values* `Area` and `Country`, but from the comments in the code it appears `Area` and `Country` are *classes*. And, as previously commented, what are the return types of `createArea` and `createCountry`. (Maybe just post the API for `AreaService` and `CountryService`...) – James_D Jul 27 '16 at 18:43
  • @James_D , Updated the code. – The Coder Jul 27 '16 at 19:02

2 Answers2

3

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.

James_D
  • 201,275
  • 16
  • 291
  • 322
  • I wondered what's the real purpose of `FunctionalInterface` if it can't be used for Generic Type Parameters, until now..!! It seems we can define Generic Type parameters but not in a usual way of `::`. Thank you.. – The Coder Jul 27 '16 at 19:29
  • What do you mean? I just showed you how to use it for generic type parameters. – James_D Jul 27 '16 at 19:30
  • 1
    The only reason you can use a method reference here is that the parameter and return type have to be cast. But that just means you can't use the shorthand syntax, you're still passing objects implementing various functional interfaces around. The problem you're really trying to solve is determining the value of a type parameter at runtime, which you can't do because of type erasure; using `Class` as a type token like this is a useful workaround. – James_D Jul 27 '16 at 19:37
1

It is possible to define generic type parameters. You can find examples in jdk like

andThen

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    Objects.requireNonNull(after);
    return (T t) -> after.apply(apply(t));
}

or identity

static <T> Function<T, T> identity() {
    return t -> t;
}

What is not working in your example is that areaService::createArea and countryService::createCountry are Functions for concrete types.

Compiler cannot assign Area to generic type T. Because T can be anything i.e. Integer or Stream

Nazarii Bardiuk
  • 4,272
  • 1
  • 19
  • 22