Métodos padrão do Java 8 como características: seguro?

116

É uma prática segura usar métodos padrão como uma versão pobre de características no Java 8?

Alguns afirmam que pode deixar os pandas tristes se você usá-los apenas pelo uso, porque é legal, mas essa não é minha intenção. Também é frequentemente lembrado que os métodos padrão foram introduzidos para oferecer suporte à evolução da API e à compatibilidade com versões anteriores, o que é verdade, mas isso não torna errado ou torcido usá-los como características per se.

Tenho o seguinte caso de uso prático em mente:

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

Ou talvez, defina um PeriodTrait:

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

É certo que a composição poderia ser usada (ou mesmo classes auxiliares), mas parece mais prolixo e confuso e não permite o benefício do polimorfismo.

Portanto, é ok / seguro usar métodos padrão como características básicas ou devo me preocupar com efeitos colaterais imprevistos?

Várias perguntas sobre SO estão relacionadas a características Java vs Scala; esse não é o ponto aqui. Não estou pedindo apenas opiniões. Em vez disso, estou procurando uma resposta confiável ou pelo menos uma visão de campo: se você usou métodos padrão como características em seu projeto corporativo, acabou sendo uma bomba-relógio?

youri
fonte
Parece-me que você poderia obter os mesmos benefícios de herdar uma classe abstrata e não ter que se preocupar em fazer pandas chorar ... A única razão que posso ver para usar um método padrão em uma interface é que você precisa da funcionalidade e não pode modificar um monte de código legado que é baseado na Interface.
Deven Phillips
1
Eu concordo com @ infosec812 sobre estender uma classe abstrata que define seu próprio campo de log estático. O seu método logger () não instanciaria uma nova instância do logger toda vez que for chamado?
Eidan Spiegel
Para registro, você pode querer dar uma olhada em Projectlombok.org e sua anotação @ Slf4j.
Deven Phillips

Respostas:

120

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 varvariá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 Aestende os traços Xe Y, então a ordem em que Xe Ysão misturados determina como os conflitos entre Xe Ysã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 forEachmé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.removefoi dado um padrão que lança UnsupportedOperationException; como a grande maioria das implementações de Iteratorpossui esse comportamento, o padrão torna esse método essencialmente opcional. (Se o comportamento de AbstractCollectionfoi 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 @implSpectag 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.

Brian Goetz
fonte
9
Obrigado Brian por esta resposta abrangente. Agora posso usar métodos padrão com o coração leve. Leitores: mais informações de Brian Goetz sobre a evolução da interface e métodos padrão podem ser encontradas em NightHacking Worldwide Lambdas, por exemplo.
youri
Obrigado @ brian-goetz. Pelo que você disse, acho que os métodos padrão do Java estão mais próximos da noção de características tradicionais, conforme definido no artigo de Ducasse et al ( scg.unibe.ch/archive/papers/Duca06bTOPLASTraits.pdf ). Os "traços" de Scala não parecem traços para mim de forma alguma, uma vez que eles afirmam, usam linearização em sua composição e parece que também resolvem implicitamente os conflitos de método - e essas são todas as coisas que os traços tradicionais não ter. Na verdade, eu diria que as características do Scala são mais como mixins do que características. O que você acha? PS: Nunca codifiquei em Scala.
adino
Que tal usar implementações padrão vazias para métodos em uma interface que atua como um ouvinte? A implementação do ouvinte pode estar interessada apenas em escutar alguns métodos de interface, portanto, ao tornar os métodos padrão, o implementador só precisa implementar os métodos que precisa escutar.
Lahiru Chandima de
1
@LahiruChandima Gosta com MouseListener? Na medida em que esse estilo de API faz sentido, ele se encaixa no bloco "métodos opcionais". Certifique-se de documentar claramente o ness opcional!
Brian Goetz de
Sim. Assim como em MouseListener. Obrigado pela resposta.
Lahiru Chandima de