0

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;
}
6
  • Does SFunction<FrameModel, ?>[] funcArray = { getFrameType, getLength }; work? Also, that's a raw-type. You should be seeing a warning (at least). Commented Jul 18, 2024 at 10:14
  • did you try this::getFrameType ?? assuming you are in scope of FrameModel Commented Jul 18, 2024 at 10:21
  • Consider SFunction getLength = FrameModel::getLength; That's what individual entries in the array initializer are typed as. The compiler can't figure out how to bind various types for your method reference expression. Commented Jul 18, 2024 at 16:16
  • @SotiriosDelimanolis As I said in the post,this will works fine and it's the first approach,but I want to use the second approach to reduace the lines of code block Commented Jul 19, 2024 at 1:18
  • @Sweeper They are just oridinary properties with getter and setter method Commented Jul 19, 2024 at 1:22

2 Answers 2

2

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)
};
Sign up to request clarification or add additional context in comments.

Comments

1

Problem

Your error has nothing to do with an actual static method call. You are doing a lot of things here, and your IDE is grabbing the top error. The actual issue here is type erasure.

In Java, arrays cannot be generic. You're dealing with Reifiable Types here.

Section 4.7 of the JLS states:

Because some type information is erased during compilation, not all types are available at run time. Types that are completely available at run time are known as reifiable types.

A type is reifiable if and only if one of the following holds:

  • It refers to a non-generic class or interface type declaration.
  • It is a parameterized type in which all type arguments are unbounded wildcards (§4.5.1).
  • It is a raw type (§4.8).
  • It is a primitive type (§4.2).
  • It is an array type (§10.1) whose element type is reifiable.
  • It is a nested type where, for each type T separated by a ".", T itself is reifiable

Section 10.6 of the JLS states:

It is a compile-time error if the component type of the array being initialized is not reifiable

The new SFunction has no idea what T is when you set it up, so it creates a generic Object. The Generic Object doesn't have the functions you are referencing. You can see this more easily if you expand your lambda

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

The warning should now be more clear

cannot resolve method 'getFrameType' in 'Object'

If you want to see the reifiable warning more clearly, than you can directly declare the SFunction types at creation, like this.

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

Now you will see an error for generic array creation.

Solution

You have a few options here:

  1. You can adjust your SFunction to have T extend FrameModel.
@FunctionalInterface
public interface SFunction<T extends FrameModel, R> extends Function<T, R>, Serializable {
}
  1. If you can't modify the functional interface directly, than you can cast the val at the point you use it
  SFunction<FrameModel, Integer>[] funcArray = new SFunction[] {val -> ((FrameModel) val).getFrameType(),  val -> ((FrameModel) val).getLength() };
  1. Or, the better option in my opinion, is to use a List which can better deal with generics.
List<SFunction<FrameModel, ?>> funcArray = List.of(FrameModel::getLength, FrameModel::getLength);

All of these will work.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.