Java 8 agregó el concepto de interfaces funcionales , así como numerosos métodos nuevos diseñados para tomar interfaces funcionales. Las instancias de estas interfaces se pueden crear de manera sucinta utilizando expresiones de referencia de método (por ejemplo SomeClass::someMethod
) y expresiones lambda (por ejemplo (x, y) -> x + y
).
Un colega y yo tenemos opiniones diferentes sobre cuándo es mejor usar una forma u otra (donde "mejor" en este caso realmente se reduce a "más legible" y "más acorde con las prácticas estándar", ya que básicamente son lo contrario equivalente). Específicamente, esto implica el caso en que todo lo siguiente es cierto:
- la función en cuestión no se usa fuera de un solo alcance
- dar un nombre a la instancia ayuda a la legibilidad (en lugar de, por ejemplo, que la lógica sea lo suficientemente simple como para ver lo que sucede de un vistazo)
- No hay otras razones de programación por las cuales una forma sería preferible a la otra.
Mi opinión actual sobre el asunto es que agregar un método privado y referirlo por referencia de método es el mejor enfoque. Parece que así es como se diseñó la función para ser utilizada, y parece más fácil comunicar lo que está sucediendo a través de nombres y firmas de métodos (por ejemplo, "boolean isResultInFuture (Resultado del resultado)" claramente está diciendo que está devolviendo un booleano). También hace que el método privado sea más reutilizable si una mejora futura de la clase quiere hacer uso de la misma verificación, pero no necesita el envoltorio de interfaz funcional.
La preferencia de mi colega es tener un método que devuelva la instancia de la interfaz (por ejemplo, "Predicate resultInFuture ()"). Para mí, parece que no es exactamente cómo se pretendía utilizar la función, se siente un poco más complicado y parece que es más difícil realmente comunicar la intención a través del nombramiento.
Para concretar este ejemplo, aquí está el mismo código, escrito en los diferentes estilos:
public class ResultProcessor {
public void doSomethingImportant(List<Result> results) {
results.filter(this::isResultInFuture).forEach({ result ->
// Do something important with each future result line
});
}
private boolean isResultInFuture(Result result) {
someOtherService.getResultDateFromDatabase(result).after(new Date());
}
}
vs.
public class ResultProcessor {
public void doSomethingImportant(List<Result> results) {
results.filter(resultInFuture()).forEach({ result ->
// Do something important with each future result line
});
}
private Predicate<Result> resultInFuture() {
return result -> someOtherService.getResultDateFromDatabase(result).after(new Date());
}
}
vs.
public class ResultProcessor {
public void doSomethingImportant(List<Result> results) {
Predicate<Result> resultInFuture = result -> someOtherService.getResultDateFromDatabase(result).after(new Date());
results.filter(resultInFuture).forEach({ result ->
// Do something important with each future result line
});
}
}
¿Existe alguna documentación oficial o semioficial o comentarios sobre si un enfoque es más preferido, más acorde con las intenciones de los diseñadores de idiomas o más legible? Salvo una fuente oficial, ¿hay alguna razón clara por la cual sería el mejor enfoque?
Object::toString
). Entonces, mi pregunta es más acerca de si es mejor en este tipo particular de instancia que estoy presentando aquí, que si existen casos en los que uno es mejor que el otro, o viceversa.