javaarraysgenericsfunctional-interface

Can not create Function array in Java 8


I have a class called SFunction which extends from Function in JDK 8

@FunctionalInterface
public interface SFunction<T, R> extends Function<T, R>, Serializable {
}

Now I have several sFunction variables and I want to assign them into an array,if I wrote as below,then it works fine and no compile error.

SFunction<FrameModel, ?> getFrameType = FrameModel::getFrameType;
SFunction<FrameModel, Integer> getLength = FrameModel::getLength;
SFunction<FrameModel, ?>[] funcArray = new SFunction[]{getFrameType, getLength};

If I change it to as below,then it will not compile,the error is Non-static method cannot be referenced from a static context

SFunction<FrameModel, ?>[] funcArray = new SFunction[]{FrameModel::getFrameType,FrameModel::getLength};

enter image description here

I am wondering why the second approach is not work and the first one works.

If I want to use the second approach,how to fix it.

Thanks in advance!


Update:

Source code of FrameModel

@Getter
@Setter
public class FrameModel {

    // it's enum
    private FrameTypeEnum frameType;

    private Integer length;
}

Solution

  • In an array initialiser, all the elements of the array (the two method references) must be assignment compatible with the array's element type (the raw type SFunction). Quote from the Java Language Specification:

    Each variable initializer must be assignment-compatible with the array's component type, or a compile-time error occurs.

    These don't compile:

    SFunction x = FrameModel::getLength;
    SFunction y = FrameModel::getFrameType;
    

    so neither does the array initialiser.

    Why are these method references not assignment compatible with SFunction? For that we need to look at the function type of the raw type SFunction. The JLS says:

    The function type of the raw type of a generic functional interface I<...> is the erasure of the function type of the generic functional interface I<...>.

    The function type of the generic SFunction<T, R> is the same as Function<T, R> - taking a T as parameter and returning a R (I'll write this as T -> R). The function type of the raw type SFunction is therefore Object -> Object. The T and R are type variables without bounds, and so they get erased to Object.

    Is FrameModel::getLength compatible with a function type Object -> Object? Obviously not - this method takes a FrameModel as the parameter, not Object. The situation is basically the same as:

    Function<? super Object, ? extends Object> f = FrameModel::getLength;
    

    For more information, see the section Type of a Method Reference.


    While this will work if the array's element type is SFunction<FrameModel, ?>, the array element type in an array initialiser must be reifiable, as tbatch's answer explained. I strongly recommend using a List instead, as tbatch's answer also mentioned.

    If you still really want to use an array, and use method reference expressions in the array initialiser, you can add casts:

    SFunction<FrameModel, ?>[] funcArray = new SFunction[]{
        (SFunction<FrameModel, ?>)FrameModel::getFrameType, 
        (SFunction<FrameModel, ?>)FrameModel::getLength
    };
    

    The whole expression (SFunction<FrameModel, ?>)FrameModel::getLength is now a cast expression, not a method reference expression. It is obviously of type SFunction<FrameModel, ?>, and this is assignment compatible with the raw type SFunction.

    If you find this cast rather cumbersome. You can write a helper method, that is basically an identity function. This fixes the problem in the same way as the cast, by causing the array element expressions to no longer be a method reference expression, but a invocation expression.

    static <T, R> SFunction<T, R> makeFunction(SFunction<T, R> x) { return x; }
    
    SFunction<FrameModel, ?>[] funcArray = new SFunction[]{
        makeFunction(FrameModel::getFrameType), 
        makeFunction(FrameModel::getLength)
    };