Невозможно создать массив функций в Java 8

У меня есть класс 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;
}

SFunction<FrameModel, ?>[] funcArray = { getFrameType, getLength }; работает? Кроме того, это сырой тип. Вы должны увидеть предупреждение (по крайней мере).

Elliott Frisch 18.07.2024 12:14

ты пробовал this::getFrameType ?? при условии, что вы находитесь в зоне действия FrameModel

Antoniossss 18.07.2024 12:21

Учтите SFunction getLength = FrameModel::getLength; Вот как печатаются отдельные записи в инициализаторе массива. Компилятор не может понять, как связать различные типы для выражения ссылки на метод.

Sotirios Delimanolis 18.07.2024 18:16

@SotiriosDelimanolis Как я уже сказал в посте, это будет работать нормально, и это первый подход, но я хочу использовать второй подход, чтобы сократить количество строк блока кода.

flyingfox 19.07.2024 03:18

@Sweeper Это просто обычные свойства с методами получения и установки.

flyingfox 19.07.2024 03:22

@ElliottFrisch Он также не скомпилируется, и ошибка такая же, как указано в вопросе.

flyingfox 19.07.2024 03:23
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
6
138
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Проблема

Ваша ошибка не имеет ничего общего с реальным вызовом статического метода. Вы делаете здесь много вещей, и ваша IDE фиксирует верхнюю ошибку. Актуальной проблемой здесь является стирание типа.

В Java массивы не могут быть универсальными. Здесь вы имеете дело с допускаемыми типами.

Раздел 4.7 JLS гласит:

Поскольку некоторая информация о типах удаляется во время компиляции, не все типы доступны во время выполнения. Типы, которые полностью доступны во время выполнения, называются повторно доступными типами.

Тип является повторным тогда и только тогда, когда выполняется одно из следующих условий:

  • Это относится к необобщенному объявлению класса или типа интерфейса.
  • Это параметризованный тип, в котором все аргументы типа являются неограниченными подстановочными знаками (раздел 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() };

Теперь вы увидите ошибку при создании общего массива.

Решение

Здесь у вас есть несколько вариантов:

  1. Вы можете настроить SFunction, чтобы T расширял FrameModel.
@FunctionalInterface
public interface SFunction<T extends FrameModel, R> extends Function<T, R>, Serializable {
}
  1. Если вы не можете изменить функциональный интерфейс напрямую, вы можете использовать значение val в том месте, где вы его используете.
  SFunction<FrameModel, Integer>[] funcArray = new SFunction[] {val -> ((FrameModel) val).getFrameType(),  val -> ((FrameModel) val).getLength() };
  1. Или, на мой взгляд, лучший вариант — использовать список, который лучше справляется с дженериками.
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)
};

Другие вопросы по теме

Похожие вопросы

Понимание порядка выполнения с помощью .then(Mono{})
Изменение часового пояса не приводит к тому, что LocalTime.now() возвращает другое значение, почему?
Может ли один и тот же код, использующий HttpClient, скомпилироваться с Spring-5.x и 6.x?
Улучшите качество подписи, извлеченной с помощью OpenCV из отсканированного листа бумаги
Переверните последовательные нули в единицы за k операций, чтобы получить максимальное количество единиц, найдите максимальное количество единиц
Влияет ли расстояние между операторами, отступами и комментариями в коде на его временную и пространственную сложность?
Почему мой код не удаляет zip-файл, созданный моим модульным тестом?
Приложение Spring Boot не запускается после добавления интеграции с клавиатурой
Исключение в потоке «main» java.time.format.DateTimeParseException: текст «1 апреля 2022 г., 12:00:00:000AM» не удалось проанализировать по индексу 19
Создайте Map<Long, CustomObject> , повторяя List<CustomObject>