La respuesta corta es: es seguro si los usa de manera segura :)
La respuesta sarcástica: dime qué quieres decir con 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 lenguaje en tener rasgos, ya sea en nombre o en efecto.
Por ejemplo, en Scala, los rasgos tienen estado (pueden tener var
variables); en Fortress son pura conducta. Las interfaces de Java con métodos predeterminados no tienen estado; ¿Significa esto que no son rasgos? (Pista: esa fue una pregunta capciosa).
De nuevo, en Scala, los rasgos se componen mediante linealización; si la clase A
extiende rasgos X
y Y
, a continuación, el orden en el que X
y Y
se mezclan en determina cómo los conflictos entre X
yY
se resuelven. En Java, este mecanismo de linealización no está presente (fue rechazado, en parte, porque era demasiado "poco parecido a Java").
La razón próxima para agregar métodos predeterminados a las interfaces era apoyar la evolución de la interfaz , pero sabíamos muy bien que íbamos más allá. Si lo considera "evolución de la interfaz ++" o "rasgos--" es una cuestión de interpretación personal. Entonces, para responder a su pregunta sobre seguridad ... siempre que se ciña a lo que el mecanismo realmente admite, en lugar de tratar de estirarlo a algo que no admite, debería estar bien.
Un objetivo de diseño clave era que, desde la perspectiva del cliente de una interfaz, los métodos predeterminados no deberían distinguirse de los métodos de interfaz "normales". El carácter predeterminado de un método, por lo tanto, solo es interesante para el diseñador e implementador de la interfaz.
A continuación, se muestran algunos casos de uso que están dentro de los objetivos 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 sensible en términos de métodos existentes en esa interfaz. Un ejemplo sería agregar el forEach
método a Collection
, donde la implementación predeterminada se escribe en términos del iterator()
método.
Métodos "opcionales". Aquí, el diseñador de una interfaz está diciendo "Los implementadores no necesitan implementar este método si están dispuestos a vivir con las limitaciones de funcionalidad que ello implica". Por ejemplo, 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 expresara como valores predeterminados Collection
, podríamos hacer lo mismo con los métodos mutativos).
Métodos de conveniencia. Estos son métodos que son estrictamente por conveniencia, nuevamente generalmente implementados en términos de métodos no predeterminados en la clase. El logger()
método en su primer ejemplo es una ilustración razonable de esto.
Combinadores. Estos son métodos de composición que crean 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 la predeterminada (en el JDK, usamos la @implSpec
etiqueta javadoc para esto) para ayudar a los implementadores a comprender si quieren anular 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 suficientes especificaciones (no solo documentación) sobre lo que promete hacer el implementador predeterminado, para que el implementador pueda tomar una decisión sensata sobre si necesita anularlo.