Я пытаюсь создать библиотеку, в которой вы можете добавлять и удалять прослушиватели событий в системе pub/sub, но столкнулся с проблемой, используя ссылки на методы:
// here, this::printMessage is being passed as an instance of Consumer<String>
pubSub.subscribe(this::printMessage);
pubSub.unsubscribe(this::printMessage);
Внутри вызов subscribe() добавит экземпляр Consumer<T> в Set<Consumer<T>>, а unsubscribe() удалит его. Эта проблема возникает из-за того, что каждое использование this::printMessage здесь фактически заставляет компилятор генерировать новую ссылку/экземпляр объекта, поэтому отказ от подписки на самом деле не работает.
Обходной путь до сих пор, который мне удалось:
final Consumer<String> consumer = this::printMessage;
pubSub.subscribe(consumer);
pubSub.unsubscribe(consumer);
Но это не совсем идеально. Меня беспокоит то, что кто-то с меньшим опытом использования этой библиотеки может предположить, что они могут использовать ссылки на методы напрямую при подписке/отмене подписки, когда это не совсем так, и в худшем случае это приводит к утечке памяти.
Итак, вопрос в том, есть ли какой-нибудь умный способ избежать этого или заставить ссылку на метод всегда разрешать одну и ту же ссылку/экземпляр объекта?
Там нет никакого реального способа обойти это. Вместо этого вы можете вернуть объект Subscription с помощью метода unsubscribe(), чтобы избежать подобных ошибок в API.
вернуть дескриптор подписки и использовать его для отказа от подписки (конечно, используйте этот дескриптор в качестве ключа карты, где все ваши потребители хранятся внутри, и сделайте так, чтобы этот дескриптор последовательно реализовывал hashCode и equals)
Возможный дубликат Есть ли способ сравнить лямбды?
@JoachimSauer, к вашему сведению, вы должны создать фактический ответ на вопрос, чтобы ОК мог пометить его как правильный ответ.
Если один из приведенных ниже ответов решает вашу проблему, вы должны принять его (установите флажок рядом с соответствующим ответом). Это делает две вещи. Это позволяет всем узнать, что ваша проблема была решена к вашему удовлетворению, и дает человеку, который помогает вам, кредит на помощь. Глянь сюда для полного объяснения.




Когда вы пишете код вроде:
pubSub.subscribe(this::printMessage);
pubSub.unsubscribe(this::printMessage);
Это аналогичный код, например:
pubSub.subscribe(new Consumer() {
@Override
public void accept(Object t) {
// your code here
};
});
pubSub.unsubscribe(new Consumer() {
@Override
public void accept(Object t) {
// your code here
};
});
Таким образом, из приведенного выше кода ясно, что каждый раз будет создаваться новый объект.
Now why those are similar code?
Java представила инструкцию байт-кода invokedynamic для создания анонимного класса, а затем generate byte code для lambdas/метод ref. Итак, в случае lambdas/method ref java генерирует класс реализации и генерирует байтовый код во время выполнения.. После того, как байт-код сгенерирован, остальная часть шага аналогична обычному вызову метода.
Is there some clever way to avoid this or coerce the method reference to always resolve to the same object reference/instance?
-> Я не верю, что есть какой-то другой умный способ (кроме того, что вы сделали в качестве обходного пути), чтобы сделать это.
Спасибо, я понимаю, почему это происходит, мне просто интересно, есть ли способ обойти это, поскольку это неочевидное поведение для тех, кто пришел из других языков, у которых нет этого ограничения.
Я не верю, что есть какой-то обходной путь. Не могли бы вы сообщить мне, на каком языке он концептуально отличается от Java. Спасибо!!!
С# — это то, что я могу придумать навскидку: попробуйте это: dotnetfiddle.net/TOIn3s
Вы можете заставить subscribe возвращать фактический экземпляр Consumer или идентификатор для добавленного Consumer. Это возвращаемое значение можно использовать в unsubscribe для повторного удаления Consumer.
Может быть, что-то похожее на это:
Map<UUID, Consumer<?>> consumers = new ConcurrentHashMap<>();
public UUID subscribe(Consumer<?> consumer) {
UUID identifier = UUID.randomUUID();
consumers.put(identifier, consumer);
return identifier;
}
public void unsubscribe(UUID identifier) {
consumers.remove(identifier);
}
Использование идентификатора вместо фактического экземпляра Consumer в качестве возвращаемого значения имеет то преимущество, что пользователи вашего кода сразу увидят, что им нужно отслеживать возвращенный UUID вместо использования unsubscribe с другим «идентичным» (с точки зрения поведения) ) Consumer.
Но я бы не стал использовать для этого UUID, как обычный объект без каких-либо свойств.
@Holger вам нужен простой объект с правильной реализацией равенства и хэш-кода. Можно было бы использовать строковое представление UUID, но при этом вы ничего не выиграете.
Каждый объект имеет реализацию equals и hashCode. Реализация по умолчанию делает этот объект равным ничему, кроме самого себя, что и является желаемым поведением. Напротив, UUID позволяет создавать другой равный объект с помощью строкового представления, что вам нужно здесь нет. Вы хотите, чтобы подписчик сохранил возвращенный объект и передал его обратно в unsubscribe, а не выполнял с ним преобразования или преобразования.
@Holger На самом деле мы точно не знаем полных требований здесь. Возможно, ОП хочет сохранить ссылку для последующего удаления, а может и нет. Да, вы можете использовать простой объект, но это излишне ограничит возможные варианты использования. Однако приведенный выше пример был только для демонстрации возможного решения. Это не означало «сделай так, или ты сделал это неправильно».
OP имеет, чтобы сохранить ссылку, хотят они этого или нет. Возможность преобразовать этот UUID в String этого не меняет. Тогда вам придется сохранить строку. И вопрос был об ограничении ссылок на методы над обычными объектами. Обычные слушатели также не имеют возможности реконструировать их через строковое представление. И нет смысла предоставлять форму постоянного ключа для регистрации, которая в любом случае не длится дольше этого времени выполнения.
Однако тот, кто использует
this::printMessage, должен знать, что компилятор генерирует новый экземпляр объекта. На самом деле это не проблема вашей библиотеки.