Ни один из вариантов не является более правильным, чем другой.
Кроме того, нет существенной разницы в производительности, поскольку соответствующий байт-код даже идентичен.В любом случае, в вашем классе будет метод, содержащий оператор throw, и экземпляр сгенерированного во время выполнения класса, который вызовет этот метод.
Обратите внимание, что оба шаблона можно найти в самом JDK.
Function.identity()
и Map.Entry.comparingByKey()
являются примерами фабричных методов, содержащих лямбда-выражения Double::sum
, Objects::isNull
или Objects::nonNull
являются примерами ссылок на методы только для целевых методовсуществует с целью ссылки таким образом
Как правило, если существуют также варианты использования для прямого вызова методов, предпочтительно предоставлять их как методы API, на которые также могут ссылаться ссылки на методы,например, Integer::compare
, Objects::requireNonNull
или Math::max
.
С другой стороны, предоставление фабричного метода делает метод ссылкой на деталь реализации, которую можно изменить, когда для этого есть причина.Например, знаете ли вы, что Comparator.naturalOrder()
не реализовано как T::compareTo
?В большинстве случаев вам не нужно знать.
Конечно, фабричные методы, принимающие дополнительные параметры, вообще не могут быть заменены ссылками на методы;иногда требуется, чтобы методы класса без параметров были симметричны методам, принимающим параметры.
Существует лишь незначительная разница в потреблении памяти.Учитывая текущую реализацию, каждое вхождение, например, Objects::isNull
, приведет к созданию класса времени выполнения и экземпляра, который затем будет повторно использован для конкретного местоположения кода.Напротив, реализация в Function.identity()
создает только одно местоположение кода, следовательно, один класс времени выполнения и экземпляр.См. Также этот ответ .
Но следует подчеркнуть, что это специфично для конкретной реализации, так как стратегия реализуется JRE, далее мы говорим о конечном,довольно небольшое количество мест кода и, следовательно, объектов.
Кстати, эти подходы не противоречат.Вы могли бы даже иметь оба:
// for calling directly
public static <E> E alwaysThrow(E k1, E k2) {
// by the way, k1 is not the key, see https://stackoverflow.com/a/45210944/2711488
throw new IllegalArgumentException("Duplicate key " + k1 + " not allowed!");
}
// when needing a shared BinaryOperator
public static <E> BinaryOperator<E> throwingMerger() {
return ContainingClass::alwaysThrow;
}
Обратите внимание, что есть еще один момент, который следует рассмотреть;фабричный метод всегда возвращает материализованный экземпляр определенного интерфейса, то есть BinaryOperator
.Для методов, которые должны быть связаны с различными интерфейсами, в зависимости от контекста, вам все равно нужны ссылки на методы в этих местах.Вот почему вы можете написать
DoubleBinaryOperator sum1 = Double::sum;
BinaryOperator<Double> sum2 = Double::sum;
BiFunction<Integer,Integer,Double> sum3 = Double::sum;
, что было бы невозможно, если бы был только фабричный метод, возвращающий DoubleBinaryOperator
.