Por que "final" não é permitido nos métodos de interface do Java 8?

335

Um dos recursos mais úteis do Java 8 são os novos defaultmétodos nas interfaces. Existem essencialmente duas razões (podem existir outras) pelas quais elas foram introduzidas:

Do ponto de vista de um designer de API, eu gostaria de poder usar outros modificadores nos métodos de interface, por exemplo final. Isso seria útil ao adicionar métodos de conveniência, evitando substituições "acidentais" na implementação de classes:

interface Sender {

    // Convenience method to send an empty message
    default final void send() {
        send(null);
    }

    // Implementations should only implement this method
    void send(String message);
}

O acima já é prática comum se Senderfosse uma classe:

abstract class Sender {

    // Convenience method to send an empty message
    final void send() {
        send(null);
    }

    // Implementations should only implement this method
    abstract void send(String message);
}

Agora, defaulte finalobviamente estão contradizendo palavras-chave, mas a palavra-chave padrão em si não teria sido estritamente necessária , portanto, suponho que essa contradição seja deliberada, para refletir as diferenças sutis entre "métodos de classe com corpo" (apenas métodos) e "interface métodos com corpo " (métodos padrão), ou seja, diferenças que ainda não compreendi.

Em algum ponto do tempo, o suporte para modificadores como statice finalsobre métodos de interface ainda não foi totalmente explorado, citando Brian Goetz :

A outra parte é até que ponto vamos dar suporte a ferramentas de criação de classe em interfaces, como métodos finais, métodos privados, métodos protegidos, métodos estáticos etc. A resposta é: ainda não sabemos

Desde então, no final de 2011, obviamente, staticfoi adicionado suporte a métodos em interfaces. Claramente, isso agregou muito valor às próprias bibliotecas JDK, como com Comparator.comparing().

Questão:

Qual é o motivo final(e também static final) nunca chegou às interfaces Java 8?

Lukas Eder
fonte
10
Sinto muito por ser um cobertor molhado, mas a única maneira pela qual a pergunta expressa no título será respondida nos termos do SO é através de uma citação de Brian Goetz ou do grupo de especialistas do JSR. Entendo que a BG solicitou uma discussão pública, mas é exatamente isso que contraria os termos do SO, pois é "principalmente baseado em opiniões". Parece-me que as responsabilidades estão sendo reduzidas aqui. É o trabalho do Grupo de Especialistas e o do Java Community Process mais amplo, estimular a discussão e apresentar justificativas. Não é assim. Por isso, estou votando para fechar como "principalmente baseado em opiniões".
Marquês de Lorne
2
Como todos sabemos, finalimpede que um método seja substituído e, vendo como DEVE substituir métodos herdados de interfaces, não vejo por que faria sentido torná-lo final. A menos que isso signifique que o método é final APÓS substituí-lo uma vez .. Nesse caso, talvez haja q? Se não estou entendendo direito, por favor, deixe-me entender. Parece interessante
Vince Emigh
22
@EJP: "Se eu posso fazer isso, você também pode responder sua própria pergunta, que, portanto, não precisaria ser perguntada". Isso se aplicaria a praticamente todas as perguntas deste fórum, certo? Eu sempre poderia pesquisar algum tópico no Google por 5 horas e aprender algo que todo mundo precisa aprender da mesma maneira. Ou esperamos mais alguns minutos para que alguém dê uma resposta ainda melhor da qual todos no futuro (incluindo as 12 votações e 8 estrelas até o momento) possam lucrar, porque o SO é tão bem referenciado no Google. Então sim. Essa pergunta pode se encaixar perfeitamente no formulário de perguntas e respostas da SO.
Lukas Eder
5
@VinceEmigh " ... vendo como você DEVE substituir métodos herdados de interfaces ... " Isso não é verdade no Java 8. O Java 8 permite implementar métodos em interfaces, o que significa que você não precisa implementá-los na implementação Aulas. Aqui, finalseria útil impedir que as classes de implementação substituíssem a implementação padrão de um método de interface.
Awksp
28
@EJP você nunca sabe: Brian Goetz pode responder !
assylias 5/05

Respostas:

419

Esta questão está, em algum grau, relacionada a Qual é o motivo pelo qual “sincronizado” não é permitido nos métodos de interface Java 8?

O principal a entender sobre métodos padrão é que o objetivo principal do projeto é a evolução da interface , não "transformar interfaces em características (medíocres)". Embora exista alguma sobreposição entre os dois, e tentamos nos acomodar ao último onde não atrapalhou o primeiro, essas perguntas são melhor entendidas quando vistas sob essa luz. (Nota também que métodos de classe são vai ser diferente a partir de métodos de interface, não importa o que a intenção, em virtude do fato de que os métodos de interface pode ser multiplamente herdada.)

A idéia básica de um método padrão é: é um método de interface com uma implementação padrão e uma classe derivada pode fornecer uma implementação mais específica. E como o centro de design era a evolução da interface, era um objetivo crítico do projeto que os métodos padrão pudessem ser adicionados às interfaces após o fato de maneira compatível com a origem e compatível com o binário.

A resposta muito simples para "por que não os métodos padrão finais" é que então o corpo não seria simplesmente a implementação padrão, seria a única implementação. Embora seja uma resposta um pouco simples demais, ela nos dá uma pista de que a pergunta já está indo em uma direção questionável.

Outra razão pela qual os métodos finais de interface são questionáveis ​​é que eles criam problemas impossíveis para os implementadores. Por exemplo, suponha que você tenha:

interface A { 
    default void foo() { ... }
}

interface B { 
}

class C implements A, B { 
}

Aqui tudo está bem; Cherda foo()de A. Agora, supondo que Bseja alterado para ter um foométodo, com um padrão:

interface B { 
    default void foo() { ... }
}

Agora, quando formos recompilar C, o compilador nos dirá que não sabe para qual comportamento herdar foo(); portanto C, deve substituí-lo (e pode optar por delegar A.super.foo()se quiser manter o mesmo comportamento.) Mas e se Btinha feito o seu padrão final, e Anão está sob o controle do autor de C? Agora Cestá irremediavelmente quebrado; não pode compilar sem substituir foo(), mas não pode substituir foo()se foi final B.

Este é apenas um exemplo, mas o ponto é que a finalidade dos métodos é realmente uma ferramenta que faz mais sentido no mundo das classes de herança única (geralmente que associam o estado ao comportamento) do que às interfaces que apenas contribuem com o comportamento e podem ser multiplicadas. herdado. É muito difícil argumentar sobre "que outras interfaces podem ser misturadas no eventual implementador", e permitir que um método de interface seja final provavelmente causaria esses problemas (e eles não explodiriam na pessoa que escreveu a interface, mas no usuário pobre que tenta implementá-lo.)

Outro motivo para proibi-los é que eles não significam o que você pensa que eles significam. Uma implementação padrão é considerada apenas se a classe (ou suas superclasses) não fornecer uma declaração (concreta ou abstrata) do método. Se um método padrão fosse final, mas uma superclasse já o implementasse, o padrão seria ignorado, o que provavelmente não é o que o autor padrão esperava ao declará-lo final. (Esse comportamento de herança é um reflexo do centro de design para métodos padrão - evolução da interface. Deve ser possível adicionar um método padrão (ou uma implementação padrão a um método de interface existente) a interfaces existentes que já possuem implementações, sem alterar o comportamento de classes existentes que implementam a interface,

Brian Goetz
fonte
88
Fantástico vê-lo respondendo perguntas sobre os novos recursos de idioma! É muito útil obter esclarecimentos sobre a intenção e os detalhes do design ao descobrir como devemos usar os novos recursos. Há outras pessoas envolvidas no design contribuindo para o SO - ou você está fazendo isso sozinho? Seguirei suas respostas com a tag java-8 - gostaria de saber se há outras pessoas fazendo o mesmo para que eu possa segui-las também.
Shorn
10
@Shorn Stuart Marks está ativo na tag java-8. Jeremy Manson postou no passado. Também me lembro de ter visto mensagens de Joshua Bloch, mas não consigo encontrá-las agora.
Assylias
11
Parabéns por apresentar os métodos de interface padrão como uma maneira muito mais elegante de realizar o que o C # faz com seus métodos de extensão mal concebidos e bastante feios. Quanto a essa pergunta, a resposta sobre a impossibilidade de resolver um conflito de nomes resolve o problema, mas o restante dos motivos apresentados não eram convincentes. (Se eu quiser um método de interface para ser final, então você deve presumir que eu devo ter meus próprios muito muito boas razões para querer alguém disallow de prestar qualquer diferente implementação dos meus.)
Mike Nakis
11
Ainda assim, a resolução de conflitos de nomes poderia ter sido alcançada de maneira semelhante à maneira como é feita em C #, adicionando o nome da interface específica que está sendo implementada à declaração do método de implementação. Então, o que eu entendo disso tudo é que os métodos de interface padrão não podem ser finais em Java, porque isso exigiria modificações adicionais na sintaxe, sobre a qual você não se sentiu bem. (Too c-sharpish talvez?)
Mike Nakis
18
@Tentar as classes abstratas ainda é a única maneira de introduzir estado ou implementar os principais métodos de objeto. Os métodos padrão são para comportamento puro ; classes abstratas são para comportamento associado a estado.
Brian Goetz
43

Na lista de correio lambda, há muitas discussões sobre isso . Um dos que parece conter muita discussão sobre tudo isso é o seguinte: Visibilidade do método de interface variada (eram os defensores finais) .

Nesta discussão, Talden, o autor da pergunta original, pergunta algo muito semelhante à sua pergunta:

A decisão de tornar públicos todos os membros da interface foi de fato uma decisão infeliz. Que qualquer uso da interface no design interno expõe detalhes particulares da implementação é um grande problema.

É difícil corrigir isso sem adicionar algumas nuances obscuras ou de compatibilidade ao idioma. Uma quebra de compatibilidade dessa magnitude e sutileza em potencial pareceria inescrupulável, portanto, uma solução deve existir e não quebra o código existente.

É possível reintroduzir a palavra-chave 'package' como um especificador de acesso. Sua ausência de um especificador em uma interface implicaria acesso público e a ausência de um especificador em uma classe implica em acesso ao pacote. Quais especificadores fazem sentido em uma interface não são claros - especialmente se, para minimizar a carga de conhecimento dos desenvolvedores, precisamos garantir que os especificadores de acesso tenham o mesmo significado na classe e na interface, caso estejam presentes.

Na ausência de métodos padrão, eu teria especulado que o especificador de um membro em uma interface deve ser pelo menos tão visível quanto a própria interface (para que a interface possa realmente ser implementada em todos os contextos visíveis) - com métodos padrão que não são tão certo.

Houve alguma comunicação clara sobre se essa é mesmo uma possível discussão dentro do escopo? Caso contrário, deve ser realizada em outro lugar.

Eventualmente, a resposta de Brian Goetz foi:

Sim, isso já está sendo explorado.

No entanto, deixe-me definir algumas expectativas realistas - os recursos de idioma / VM têm um longo tempo de espera, mesmo os que parecem triviais como este. O tempo para propor novas idéias de recursos de linguagem para o Java SE 8 já passou.

Portanto, provavelmente nunca foi implementado porque nunca fez parte do escopo. Nunca foi proposto a tempo de ser considerado.

Em outra discussão acalorada sobre os métodos de defensor final sobre o assunto, Brian disse novamente :

E você conseguiu exatamente o que desejava. É exatamente isso que esse recurso adiciona - herança múltipla de comportamento. É claro que entendemos que as pessoas as usarão como traços. E trabalhamos duro para garantir que o modelo de herança que eles oferecem seja simples e limpo o suficiente para que as pessoas possam obter bons resultados fazendo isso em uma ampla variedade de situações. Ao mesmo tempo, optamos por não empurrá-los além dos limites do que funciona de maneira simples e limpa, e isso leva a reações "aw, você não foi longe o suficiente" em alguns casos. Mas, na verdade, a maior parte desse segmento parece estar reclamando que o copo está apenas 98% cheio. Vou pegar 98% e seguir em frente!

Portanto, isso reforça minha teoria de que simplesmente não fazia parte do escopo ou de seu design. O que eles fizeram foi fornecer funcionalidade suficiente para lidar com os problemas da evolução da API.

Edwin Dalorzo
fonte
4
Vejo que deveria ter incluído o nome antigo, "métodos de defesa", na minha odisséia no Google nesta manhã. +1 para desenterrar isso.
Marco13
11
Bom desenterrar fatos históricos. Suas conclusões alinhar bem com a resposta oficial
Lukas Eder
Não vejo por que isso iria quebrar a compatibilidade com versões anteriores. final não era permitido antes. agora eles permitem o uso privado, mas não protegido. myeh ... private não pode implementar ... uma interface que estende outra interface pode implementar partes de uma interface pai, mas precisa expor a capacidade de sobrecarga a outras pessoas ...
mmm
17

Vai ser difícil de encontrar e identificar "a" resposta, para os resons mencionados nos comentários de @EJP: Há cerca de 2 (+/- 2) pessoas no mundo que podem dar a resposta definitiva em tudo . E na dúvida, a resposta pode ser algo como "O suporte aos métodos finais padrão não parecia valer o esforço de reestruturar os mecanismos internos de resolução de chamadas". Isso é especulação, é claro, mas é pelo menos apoiado por evidências sutis, como esta declaração (por uma das duas pessoas) na lista de discussão do OpenJDK :

"Suponho que se os métodos" padrão final "forem permitidos, eles podem precisar ser reescritos de chamadas internas especiais para interfaces de chamadas visíveis ao usuário."

e fatos triviais como esse simplesmente não são considerados um método (realmente) final quando é um defaultmétodo, conforme implementado atualmente no método Method :: is_final_method no OpenJDK.

É realmente difícil encontrar informações realmente "autoritárias", mesmo com pesquisas na web excessivas e lendo logs de confirmação. Eu pensei que isso poderia estar relacionado a possíveis ambiguidades durante a resolução de chamadas de método de interface com as invokeinterfaceinstruções e chamadas de método de classe, correspondentes à invokevirtualinstrução: Para a invokevirtualinstrução, pode haver uma pesquisa simples da vtable , porque o método deve ser herdado de uma superclasse ou implementada diretamente pela classe. Em contraste com isso, uma invokeinterfacechamada deve examinar o respectivo site de chamada para descobrir a qual interface essa chamada realmente se refere (isso é explicado em mais detalhes na página InterfaceCalls do Wiki do HotSpot). Contudo,finalos métodos não são inseridos na vtable de maneira alguma ou substituem as entradas existentes na vtable (consulte klassVtable.cpp. Linha 333 ) e, da mesma forma, os métodos padrão estão substituindo as entradas existentes na vtable (consulte klassVtable.cpp, linha 202 ) . Portanto, o motivo real (e, portanto, a resposta) deve ser oculto mais profundamente nos mecanismos (bastante complexos) de resolução de chamadas do método, mas talvez essas referências sejam consideradas úteis, seja apenas para outras pessoas que conseguem derivar a resposta real a partir desse.

Marco13
fonte
Obrigado pela visão interessante. A peça de John Rose é um traço interessante. Ainda não concordo com o @EJP. Como um contra-exemplo, confira minha resposta a uma pergunta muito interessante e de estilo muito semelhante feita por Peter Lawrey . Ele é possível cavar fatos históricos, e eu estou sempre contente de encontrá-los aqui no estouro de pilha (onde mais?). Obviamente, sua resposta ainda é especulativa e não estou 100% convencido de que os detalhes da implementação da JVM sejam o motivo final (trocadilho) para que o JLS seja anotado de uma ou outra maneira ...
Lukas Eder
@LukasEder Claro, esses tipos de perguntas são interessantes e o IMHO se encaixa no padrão de perguntas e respostas. Eu acho que há dois pontos cruciais que causaram a disputa aqui: o primeiro é que você pediu "a razão". Isso pode, em muitos casos, não ser oficialmente documentado. Por exemplo, não há nenhuma "razão" mencionado nos JLS por que não há não assinados ints, ainda ver stackoverflow.com/questions/430346 ... ...
Marco13
... ... A segunda é que você pediu apenas "citações autorizadas", o que reduz o número de pessoas que se atrevem a escrever uma resposta de "algumas dúzias" para ... "aproximadamente zero". Além disso, não tenho certeza de como o desenvolvimento da JVM e a escrita do JLS são entrelaçados, ou seja, até que ponto o desenvolvimento influencia o que está escrito no JLS, mas ... evitarei qualquer especulação aqui; -)
Marco13
11
Eu ainda descanso meu caso. Veja quem respondeu à minha outra pergunta :-) Agora ficará claro para sempre com uma resposta autorizada, aqui no Stack Overflow, por que foi decidido não dar suporte synchronizedaos defaultmétodos.
Lukas Eder
3
@LukasEder eu vejo, o mesmo aqui. Quem poderia esperar isso? As razões são bastante convincentes, especialmente para esta finalpergunta, e é um tanto humilhante que ninguém mais parecesse pensar em exemplos semelhantes (ou, talvez, alguns pensassem nesses exemplos, mas não parecessem suficientemente autoritários para responder). Então agora a palavra final (desculpe, eu tenho que fazer isso :) é falada.
Marco13
4

Eu não acho que seja necessário especificar finalum método de interface de conveniência, mas posso concordar que isso pode ser útil, mas aparentemente os custos superam os benefícios.

O que você deve fazer, de qualquer maneira, é escrever o javadoc apropriado para o método padrão, mostrando exatamente o que o método é e não tem permissão para fazer. Dessa forma, as classes que implementam a interface "não têm permissão" para alterar a implementação, embora não haja garantias.

Qualquer um poderia escrever um Collectionque aderisse à interface e, em seguida, faça coisas nos métodos absolutamente contrários à intuição; não há como se proteger disso, além de escrever extensos testes de unidade.

skiwi
fonte
2
Os contratos Javadoc são uma solução válida para o exemplo concreto que listei na minha pergunta, mas a questão realmente não é sobre o caso de uso do método da interface de conveniência. A questão é sobre uma razão autorizada pela qual finalfoi decidido não ser permitido nos interfacemétodos Java 8 . A relação custo / benefício insuficiente é um bom candidato, mas até agora isso é especulação.
Lukas Eder #
0

Adicionamos defaultpalavras-chave ao nosso método dentro de um interfacequando sabemos que a classe que estende a implementação interfacepode ou não ser overrideimplementada. Mas e se quisermos adicionar um método que não queremos que nenhuma classe de implementação substitua? Bem, duas opções estavam disponíveis para nós:

  1. Adicione um default finalmétodo
  2. Adicione um staticmétodo

Agora, Java diz que, se tivermos uma classimplementação de duas ou mais, de interfacesmodo que eles tenham um defaultmétodo com exatamente o mesmo nome e assinatura de método, ou seja, duplicados, precisamos fornecer uma implementação desse método em nossa classe. Agora, no caso de default finalmétodos, não podemos fornecer uma implementação e estamos presos. E é por isso que a finalpalavra-chave não é usada nas interfaces.

Ashutosh
fonte