Métodos predeterminados de Java 8 como rasgos: ¿seguro?

¿Es una práctica segura usar los métodos predeterminados como una versión de los rasgos del hombre pobre en Java 8?

Algunos afirman que puede hacer que los pandas sean tristes si los usas solo por el mero hecho de hacerlo, porque es genial, pero esa no es mi intención. También se recuerda a menudo que se introdujeron métodos predeterminados para admitir la evolución de la API y la compatibilidad con versiones anteriores, lo que es cierto, pero esto no lo convierte en incorrecto o torcido para usarlos como rasgos per se.

Tengo el siguiente caso de uso práctico en mente:

public interface Loggable { default Logger logger() { return LoggerFactory.getLogger(this.getClass()); } } 

O tal vez, defina un PeriodTrait :

 public interface PeriodeTrait { Date getStartDate(); Date getEndDate(); default isValid(Date atDate) { ... } } 

Admitidamente, la composición podría ser utilizada (o incluso clases de ayuda) pero parece más prolija y desordenada y no permite beneficiarse del polymorphism.

Entonces, ¿ está bien / seguro usar métodos predeterminados como rasgos básicos , o debería preocuparme por los efectos secundarios imprevistos?

Varias preguntas sobre SO están relacionadas con los rasgos de Java vs Scala; ese no es el punto aquí. No estoy pidiendo solamente opiniones. En cambio, estoy buscando una respuesta autorizada o al menos una visión de campo: si usó métodos por defecto como rasgos en su proyecto corporativo, ¿resultó ser una bomba de tiempo?

La respuesta corta es: es seguro si los usa con seguridad 🙂

La respuesta sarcástica: dime a qué te refieres con los rasgos, y tal vez te dé una mejor respuesta 🙂

Con toda seriedad, el término “rasgo” no está bien definido. Muchos desarrolladores de Java están más familiarizados con los rasgos tal como se expresan en Scala, pero Scala está lejos de ser el primer idioma en tener rasgos, ya sea de nombre o en efecto.

Por ejemplo, en Scala, los rasgos son con estado (pueden tener variables var ); en Fortress son un comportamiento puro. Las interfaces de Java con los métodos predeterminados son sin estado; ¿Significa esto que no son rasgos? (Sugerencia: esa fue una pregunta capciosa)

De nuevo, en Scala, los rasgos se componen a través de la linealización; si la clase A extiende los rasgos X e Y , entonces el orden en que se mezclan X e Y determina cómo se resuelven los conflictos entre X e Y En Java, este mecanismo de linealización no está presente (fue rechazado, en parte, porque era demasiado “no similar a Java”).

La razón más próxima para agregar métodos predeterminados a las interfaces fue para respaldar la evolución de la interfaz , pero estábamos conscientes de que estábamos yendo más allá de eso. Si considera que ser “evolución de interfaz ++” o “rasgos–” es una cuestión de interpretación personal. Por lo tanto, para responder a su pregunta sobre la seguridad … siempre y cuando se adhiera a lo que realmente es compatible con el mecanismo, en lugar de tratar de estirarlo a algo que no apoya, debería estar bien.

Un objective clave del diseño era que, desde la perspectiva del cliente de una interfaz, los métodos predeterminados no se pudieran distinguir de los métodos de interfaz “regulares”. El valor predeterminado de un método, por lo tanto, solo es interesante para el diseñador y el implementador de la interfaz.

Aquí hay algunos casos de uso que están dentro de los objectives de diseño:

  • Evolución de la interfaz Aquí, estamos agregando un nuevo método a una interfaz existente, que tiene una implementación predeterminada sensata en términos de métodos existentes en esa interfaz. Un ejemplo sería agregar el método forEach a Collection , donde la implementación predeterminada se escribe en términos del método forEach iterator() .

  • Métodos “opcionales” Aquí, el diseñador de una interfaz dice: “Los implementadores no necesitan implementar este método si están dispuestos a vivir con las limitaciones de funcionalidad que eso conlleva”. Por ejemplo, a Iterator.remove se le dio un valor predeterminado que arroja UnsupportedOperationException ; dado que la gran mayoría de las implementaciones de Iterator tienen este comportamiento de todos modos, el valor predeterminado hace que este método sea esencialmente opcional. (Si el comportamiento de AbstractCollection se expresó como predeterminado en Collection , podríamos hacer lo mismo con los métodos mutativos).

  • Métodos de conveniencia Estos son métodos que son estrictamente por conveniencia, de nuevo generalmente implementados en términos de métodos no predeterminados en la clase. El método logger() en su primer ejemplo es una ilustración razonable de esto.

  • Combinators. Estos son métodos de composición que instancian instancias nuevas de la interfaz en función de la instancia actual. Por ejemplo, los métodos Predicate.and() o Comparator.thenComparing() son ejemplos de combinadores.

Si proporciona una implementación predeterminada, también debe proporcionar alguna especificación para el valor predeterminado (en el JDK, usamos la etiqueta @implSpec javadoc para esto) para ayudar a los implementadores a entender si desean sobrescribir el método o no. Algunos valores predeterminados, como los métodos de conveniencia y los combinadores, casi nunca se anulan; otros, como los métodos opcionales, a menudo se anulan. Debe proporcionar suficiente especificación (no solo documentación) sobre lo que se promete hacer por defecto, para que el implementador pueda tomar una decisión sensata sobre si es necesario o no reemplazarla.