A resposta curta é: é seguro se você usá-los com segurança :)
A resposta sarcástica: diga-me o que você entende por traços, e talvez eu lhe dê uma resposta melhor :)
Com toda a seriedade, o termo "traço" não é bem definido. Muitos desenvolvedores Java estão mais familiarizados com as características conforme são expressas no Scala, mas o Scala está longe de ser a primeira linguagem a ter características, seja no nome ou em efeito.
Por exemplo, em Scala, os traits são stateful (podem ter var
variáveis); em Fortaleza, são comportamento puro. As interfaces do Java com métodos padrão não têm estado; isso significa que eles não são traços? (Dica: essa era uma pergunta capciosa.)
Novamente, em Scala, os traços são compostos por linearização; se a classe A
estende os traços X
e Y
, então a ordem em que X
e Y
são misturados determina como os conflitos entre X
e Y
são resolvidos. Em Java, esse mecanismo de linearização não está presente (foi rejeitado, em parte, porque era muito "não semelhante ao Java".)
O motivo aproximado para adicionar métodos padrão às interfaces era para suportar a evolução da interface , mas estávamos bem cientes de que estávamos indo além disso. Se você considera isso uma "evolução da interface ++" ou "características -" é uma questão de interpretação pessoal. Então, para responder à sua pergunta sobre segurança ... contanto que você se atenha ao que o mecanismo realmente suporta, ao invés de tentar esticá-lo para algo que ele não suporta, você deve ficar bem.
Um dos principais objetivos do projeto era que, da perspectiva do cliente de uma interface, os métodos padrão fossem indistinguíveis dos métodos de interface "regulares". O default de um método, portanto, só é interessante para o designer e implementador da interface.
Aqui estão alguns casos de uso que estão bem dentro dos objetivos do design:
Evolução da interface. Aqui, estamos adicionando um novo método a uma interface existente, que tem uma implementação padrão sensata em termos de métodos existentes nessa interface. Um exemplo seria adicionar o forEach
método a Collection
, onde a implementação padrão é escrita em termos do iterator()
método.
Métodos "opcionais". Aqui, o designer de uma interface está dizendo "Os implementadores não precisam implementar este método se estiverem dispostos a conviver com as limitações de funcionalidade que isso acarreta". Por exemplo, Iterator.remove
foi dado um padrão que lança UnsupportedOperationException
; como a grande maioria das implementações de Iterator
possui esse comportamento, o padrão torna esse método essencialmente opcional. (Se o comportamento de AbstractCollection
foi expresso como padrão Collection
, podemos fazer o mesmo para os métodos mutativos.)
Métodos de conveniência. Esses são métodos estritamente por conveniência, novamente geralmente implementados em termos de métodos não padrão na classe. O logger()
método em seu primeiro exemplo é uma ilustração razoável disso.
Combinadores. Esses são métodos de composição que instanciam novas instâncias da interface com base na instância atual. Por exemplo, os métodos Predicate.and()
ou Comparator.thenComparing()
são exemplos de combinadores.
Se você fornecer uma implementação padrão, também deverá fornecer algumas especificações para o padrão (no JDK, usamos a @implSpec
tag javadoc para isso) para ajudar os implementadores a entender se desejam substituir o método ou não. Alguns padrões, como métodos de conveniência e combinadores, quase nunca são substituídos; outros, como métodos opcionais, são freqüentemente substituídos. Você precisa fornecer especificações suficientes (não apenas documentação) sobre o que o padrão promete fazer, para que o implementador possa tomar uma decisão sensata sobre se eles precisam substituí-la.