У меня есть класс SFunction
, который является продолжением Function в JDK 8.
@FunctionalInterface
public interface SFunction<T, R> extends Function<T, R>, Serializable {
}
Теперь у меня есть несколько переменных sFunction
, и я хочу назначить их в массив. Если я написал, как показано ниже, то все работает нормально и ошибок компиляции нет.
SFunction<FrameModel, ?> getFrameType = FrameModel::getFrameType;
SFunction<FrameModel, Integer> getLength = FrameModel::getLength;
SFunction<FrameModel, ?>[] funcArray = new SFunction[]{getFrameType, getLength};
Если я изменю его, как показано ниже, он не скомпилируется, ошибка: на нестатический метод нельзя ссылаться из статического контекста.
SFunction<FrameModel, ?>[] funcArray = new SFunction[]{FrameModel::getFrameType,FrameModel::getLength};
Мне интересно, почему второй подход не работает, а первый работает.
Если я хочу использовать второй подход, как это исправить.
Заранее спасибо!
Обновлять:
Исходный код FrameModel
@Getter
@Setter
public class FrameModel {
// it's enum
private FrameTypeEnum frameType;
private Integer length;
}
ты пробовал this::getFrameType
?? при условии, что вы находитесь в зоне действия FrameModel
Учтите SFunction getLength = FrameModel::getLength;
Вот как печатаются отдельные записи в инициализаторе массива. Компилятор не может понять, как связать различные типы для выражения ссылки на метод.
@SotiriosDelimanolis Как я уже сказал в посте, это будет работать нормально, и это первый подход, но я хочу использовать второй подход, чтобы сократить количество строк блока кода.
@Sweeper Это просто обычные свойства с методами получения и установки.
@ElliottFrisch Он также не скомпилируется, и ошибка такая же, как указано в вопросе.
Ваша ошибка не имеет ничего общего с реальным вызовом статического метода. Вы делаете здесь много вещей, и ваша IDE фиксирует верхнюю ошибку. Актуальной проблемой здесь является стирание типа.
В Java массивы не могут быть универсальными. Здесь вы имеете дело с допускаемыми типами.
Поскольку некоторая информация о типах удаляется во время компиляции, не все типы доступны во время выполнения. Типы, которые полностью доступны во время выполнения, называются повторно доступными типами.
Тип является повторным тогда и только тогда, когда выполняется одно из следующих условий:
- Это относится к необобщенному объявлению класса или типа интерфейса.
- Это параметризованный тип, в котором все аргументы типа являются неограниченными подстановочными знаками (раздел 4.5.1).
- Это необработанный тип (§4.8).
- Это примитивный тип (раздел 4.2).
- Это тип массива (§10.1), тип элемента которого можно повторно использовать.
- Это вложенный тип, где для каждого типа T, разделенного знаком ".", T сам по себе может быть повторно использован.
Раздел 10.6 JLS гласит:
Это ошибка времени компиляции, если тип компонента инициализируемого массива не может быть повторно использован.
Новая SFunction понятия не имеет, что такое T, когда вы ее настраиваете, поэтому она создает общий объект. Общий объект не имеет функций, на которые вы ссылаетесь. Вы можете увидеть это легче, если расширите свою лямбду.
SFunction<FrameModel, ?>[] funcArray = new SFunction[] { val -> val.getFrameType(), val -> val.getLength() };
Предупреждение теперь должно быть более ясным
cannot resolve method 'getFrameType' in 'Object'
Если вы хотите увидеть повторное предупреждение более четко, вы можете напрямую объявить типы SFunction при создании, вот так.
SFunction<FrameModel, ?>[] funcArray = new SFunction<FrameModel, ?>[] {val -> val.getFrameType(), val -> val.getLength() };
Теперь вы увидите ошибку при создании общего массива.
Здесь у вас есть несколько вариантов:
SFunction
, чтобы T расширял FrameModel.@FunctionalInterface
public interface SFunction<T extends FrameModel, R> extends Function<T, R>, Serializable {
}
SFunction<FrameModel, Integer>[] funcArray = new SFunction[] {val -> ((FrameModel) val).getFrameType(), val -> ((FrameModel) val).getLength() };
List<SFunction<FrameModel, ?>> funcArray = List.of(FrameModel::getLength, FrameModel::getLength);
Все это будет работать.
В инициализаторе массива все элементы массива (две ссылки на методы) должны быть совместимы по присваиванию с типом элемента массива (необработанный тип SFunction
). Цитата из спецификации языка Java:
Каждый инициализатор переменной должен быть совместим по присваиванию с типом компонента массива, иначе произойдет ошибка времени компиляции.
Они не компилируются:
SFunction x = FrameModel::getLength;
SFunction y = FrameModel::getFrameType;
так же как и инициализатор массива.
Почему эти ссылки на методы несовместимы по присваиванию с SFunction
? Для этого нам нужно посмотреть на тип функции необработанного типа SFunction
. JLS говорит:
Тип функции необработанного типа универсального функционального интерфейса
I<...>
— это стирание типа функции универсального функционального интерфейсаI<...>
.
Тип универсальной функции SFunction<T, R>
такой же, как и Function<T, R>
— принимает T
в качестве параметра и возвращает R
(я напишу это как T -> R
). Таким образом, тип функции необработанного типа SFunction
равен Object -> Object
. T
и R
являются переменными типа без границ, поэтому они стираются до Object
.
Совместим ли FrameModel::getLength
с типом функции Object -> Object
? Очевидно, нет — этот метод принимает в качестве параметра FrameModel
, а не Object
. Ситуация в принципе такая же:
Function<? super Object, ? extends Object> f = FrameModel::getLength;
Дополнительную информацию смотрите в разделе Тип ссылки на метод.
Хотя это будет работать, если тип элемента массива равен SFunction<FrameModel, ?>
, тип элемента массива в инициализаторе массива должен быть повторно доступным, как объяснено в ответе tbatch. Я настоятельно рекомендую вместо этого использовать List
, как также упоминалось в ответе tbatch.
Если вы все еще действительно хотите использовать массив и использовать выражения ссылок на методы в инициализаторе массива, вы можете добавить приведения:
SFunction<FrameModel, ?>[] funcArray = new SFunction[]{
(SFunction<FrameModel, ?>)FrameModel::getFrameType,
(SFunction<FrameModel, ?>)FrameModel::getLength
};
Все выражение (SFunction<FrameModel, ?>)FrameModel::getLength
теперь является выражением приведения, а не выражением ссылки на метод. Очевидно, он имеет тип SFunction<FrameModel, ?>
, и это присваивание совместимо с необработанным типом SFunction
.
Если вы считаете этот состав довольно громоздким. Вы можете написать вспомогательный метод, который по сути представляет собой функцию идентификации. Это решает проблему так же, как и приведение, заставляя выражения элементов массива больше не быть выражением ссылки на метод, а выражением вызова.
static <T, R> SFunction<T, R> makeFunction(SFunction<T, R> x) { return x; }
SFunction<FrameModel, ?>[] funcArray = new SFunction[]{
makeFunction(FrameModel::getFrameType),
makeFunction(FrameModel::getLength)
};
SFunction<FrameModel, ?>[] funcArray = { getFrameType, getLength };
работает? Кроме того, это сырой тип. Вы должны увидеть предупреждение (по крайней мере).