Boas razões para proibir a herança em Java?


178

Quais são as boas razões para proibir a herança em Java, por exemplo, usando classes finais ou classes usando um único construtor privado sem parâmetros? Quais são as boas razões para tornar um método final?


1
Pedantry: O construtor padrão é aquele gerado para você pelo compilador Java se você não escrever explicitamente um no seu código. Você quer dizer o construtor sem argumento.
John Topley

Não é esse o "construtor padrão implícito"?
Cretzel # 20/08

2
"Você não precisa fornecer construtores para sua classe, mas deve ter cuidado ao fazer isso. O compilador fornece automaticamente um construtor padrão sem argumento para qualquer classe sem construtores." java.sun.com/docs/books/tutorial/java/javaOO/constructors.html - John Topley
John Topley

Respostas:


184

Sua melhor referência aqui é o item 19 do excelente livro de Joshua Bloch, "Effective Java", chamado "Design e documento para herança, ou então a proíbe". (É o item 17 da segunda edição e o item 15 da primeira edição.) Você realmente deve lê-lo, mas vou resumir.

A interação de classes herdadas com seus pais pode ser surpreendente e imprevisível se o ancestral não foi projetado para ser herdado.

As aulas devem, portanto, ser de dois tipos:

  1. Classes projetadas para serem estendidas e com documentação suficiente para descrever como isso deve ser feito

  2. Classes marcadas como finais

Se você estiver escrevendo código puramente interno, pode ser um pouco exagerado. No entanto, o esforço extra envolvido na adição de cinco caracteres a um arquivo de classe é muito pequeno. Se você está escrevendo apenas para consumo interno, um codificador futuro sempre pode remover o 'final' - você pode pensar nele como um aviso dizendo "esta classe não foi projetada com a herança em mente".


6
Na minha experiência, isso não é um exagero se alguém além de mim estiver usando o código. Eu nunca entendi por que o Java tem métodos substituíveis por padrão.
Eric Weilnau 20/10/08

9
Para entender parte do Java eficaz, você precisa entender a abordagem de Josh no design. Ele diz que [algo como] você deve sempre projetar interfaces de classe como se fossem uma API pública. Penso que a opinião da maioria é que essa abordagem geralmente é muito pesada e inflexível. (YAGNI, etc.)
Tom Hawtin - tackline

9
Boa resposta. (Eu não posso resistir apontando que é seis caracteres, como há também um espaço ... ;-)
joel.neely

36
Por favor note, que fazem aulas final pode tornar o teste mais difícil (mais difícil de rascunho ou simulada)
notnoop

10
Se a classe final estiver gravando em uma interface, zombar não deve ser um problema.
Fred Haslam

26

Convém finalizar um método para que as classes substituintes não possam alterar o comportamento que é contado em outros métodos. Os métodos chamados nos construtores geralmente são declarados finais, para que você não tenha surpresas desagradáveis ​​ao criar objetos.


Não entendi bem essa resposta. Se você não se importa, poderia explicar o que quer dizer com "substituir classes não pode mudar o comportamento que é contado em outros métodos"? Estou perguntando em primeiro lugar porque não entendi a frase e, em segundo lugar, porque o Netbeans recomenda que uma das minhas funções que estou chamando do construtor deve ser final.
Nav

1
@ Nav Se você finalizar um método, uma classe de substituição não poderá ter sua própria versão desse método (ou será um erro de compilação). Dessa forma, você sabe que o comportamento implementado nesse método não mudará mais tarde quando outra pessoa estender a classe.
Bill the Lizard

19

Um motivo para finalizar uma aula seria se você quisesse forçar a composição sobre a herança. Isso geralmente é desejável para evitar um acoplamento rígido entre as classes.


15

Existem 3 casos de uso em que você pode usar os métodos finais.

  1. Para evitar que a classe derivada substitua uma funcionalidade específica da classe base.
  2. Isso é para fins de segurança, onde a classe base está fornecendo algumas funcionalidades principais importantes da estrutura em que a classe derivada não deve alterá-la.
  3. Os métodos finais são mais rápidos que os métodos de instância, pois não há uso do conceito de tabela virtual para métodos finais e privados. Portanto, sempre que houver uma possibilidade, tente usar os métodos finais.

Objetivo de finalizar uma aula:

Para que nenhum corpo possa estender essas classes e mudar seu comportamento.

Por exemplo: classe Wrapper Integer é uma classe final. Se essa classe não for final, qualquer um poderá estender o Inteiro para sua própria classe e alterar o comportamento básico da classe inteira. Para evitar isso, o java criou todas as classes de wrapper como classes finais.


Não estou muito convencido com o seu exemplo. Se for esse o caso, por que não tornar os métodos e as variáveis ​​finais, em vez de tornar a classe inteira final. Você pode editar sua resposta com os detalhes.
Rajkiran

4
Mesmo se você tornar todas as variáveis ​​e métodos definitivas, outras poderão herdar sua classe e adicionar funcionalidades extras e alterar o comportamento básico da sua classe.
User1923551

3
> Se essa classe não for final, qualquer um poderá estender o Inteiro para sua própria classe e alterar o comportamento básico da classe inteira. Digamos que isso foi permitido e que essa nova classe foi chamada DerivedInteger, ela ainda não altera a classe Integer original, e quem usa usa DerivedIntegerpor sua conta e risco, por isso ainda não entendo por que é um problema.
Siddhartha


7

A herança é como uma serra elétrica - muito poderosa, mas terrível nas mãos erradas. Você cria uma classe a ser herdada (o que pode limitar a flexibilidade e levar muito mais tempo) ou deve proibi-la.

Consulte os itens 16 e 17 da Java Effective 2nd 2nd, ou minha postagem no blog "Imposto sobre herança" .


O link para a postagem do blog está quebrado. Além disso, você acha que poderia elaborar um pouco mais sobre isso?

@MichaelT: Corrigido o link, graças - segui-lo por mais elaboração :) (Eu não tenho o tempo para adicionar a esse direito agora, estou com medo.)
Jon Skeet

@ JonSkeet, o link para a postagem do blog está quebrado.
Lucifer

@Kedarnath: Ele funciona para mim ... ele foi quebrado por um tempo, mas agora está apontando para a página correta, em codeblog.jonskeet.uk ...
Jon Skeet

4

Hmmm ... eu posso pensar em duas coisas:

Você pode ter uma classe que lida com certos problemas de segurança. Subclassificando-o e alimentando o sistema com a versão subclassificada, um invasor pode contornar as restrições de segurança. Por exemplo, seu aplicativo pode suportar plug-ins e, se um plug-in puder apenas subclassificar suas classes relevantes de segurança, ele poderá usar esse truque para, de alguma forma, contrabandear uma versão subclassificada dele no lugar. No entanto, isso é algo que a Sun precisa lidar com applets e similares, talvez não seja um caso tão realista.

Muito mais realista é evitar que um objeto se torne mutável. Por exemplo, como as Strings são imutáveis, seu código pode manter com segurança referências a ele

 String blah = someOtherString;

em vez de copiar a string primeiro. No entanto, se você puder subclassificar String, poderá adicionar métodos que permitam que o valor da string seja modificado, agora nenhum código poderá confiar mais que a string permanecerá a mesma se apenas copiar a string como acima, em vez disso, deverá duplicar o valor corda.


2

Além disso, se você estiver escrevendo uma classe comercial de código fechado, talvez não queira que as pessoas alterem a funcionalidade na linha, especialmente se você precisar dar suporte a ela e as pessoas tiverem substituído seu método e se queixarem de que a chamada resultados inesperados.


2

Se você marcar classes e métodos como final, poderá notar um pequeno ganho de desempenho, pois o tempo de execução não precisa procurar o método de classe correto para chamar um determinado objeto. Os métodos não finais são marcados como virtuais, para que possam ser estendidos adequadamente, se necessário, e os métodos finais podem ser diretamente vinculados ou compilados na linha.


1
Acredito que isso seja um mito, pois as VMs da geração atual devem poder otimizar e desoptimizar conforme necessário. Lembro-me de ter visto isso e classificado como mito.
Miguel Ping

1
A diferença na geração de código não é um mito, mas talvez os ganhos de desempenho sejam. Como eu disse, é um pequeno ganho, um site mencionou um ganho de desempenho de 3,5%, um pouco mais alto, o que na maioria dos casos não vale a pena colocar todos os nazistas no seu código.
seanalltogether

1
Não é um mito. De fato, o compilador só pode incorporar V métodos finais. Não tenho certeza se algum JIT "inline" funciona no tempo de execução da arte, mas duvido que sim, pois você pode carregar uma classe derivada posteriormente.
Uri

1
Microbenchmark == grão de sal. Dito isto, após o aquecimento do ciclo de 10B, as chamadas médias de mais de 10B não mostram essencialmente nenhuma diferença no Eclipse no meu laptop. Dois métodos retornando String, final foram 6.9643us, e não final, 6.9641us. Esse delta é ruído de fundo, IMHO.
joel.neely

1

Impedir que as pessoas façam coisas que possam confundir a si mesmas e aos outros. Imagine uma biblioteca de física onde você tenha algumas constantes ou cálculos definidos. Sem usar a palavra-chave final, alguém poderia aparecer e redefinir os cálculos básicos ou constantes que NUNCA deveriam mudar.


2
Há algo que eu não entendo sobre esse argumento - se alguém alterou os cálculos / constantes que não deveriam mudar - não há falhas devido a 100% de culpa? Em outras palavras, como a mudança beneficia alguma coisa?
Matt b

Sim, tecnicamente é culpa "deles", mas imagine que outra pessoa usando a biblioteca que não foi informada das mudanças possa causar confusão. Eu sempre pensei que essa era a razão pela qual muitas das classes Java Base eram marcadas como finais, para impedir que as pessoas alterassem a funcionalidade básica.
Anson Smith

@matt b - Você parece estar assumindo que o desenvolvedor que fez a alteração o fez conscientemente ou que se importaria com a culpa. Se alguém além de mim estiver usando meu código, marcarei como final, a menos que seja para ser alterado.
Eric Weilnau 20/10/08

Na verdade, esse é um uso diferente de 'final' daquele perguntado.
DJClayworth

Essa resposta parece confundir herança e mutabilidade de campos individuais com a capacidade de escrever uma subclasse. Você pode marcar campos e métodos individuais como finais (impedindo sua redefinição) sem proibir a herança.
8119 joel.neely

0

Você deseja finalizar um método para que a substituição de classes não mude seu comportamento. Quando você quiser alterar o comportamento, torne o método público. Quando você substitui um método público, ele pode ser alterado.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.