Método Java `final`: o que promete?

141

Em uma classe Java, um método pode ser definido para ser final, para marcar que esse método não pode ser substituído:

public class Thingy {
    public Thingy() { ... }
    public int operationA() {...}
    /** this method does @return That and is final. */
    public final int getThat() { ...}
}

Isso é claro e pode ser útil para proteger contra a substituição acidental ou talvez o desempenho - mas essa não é a minha pergunta.

Minha pergunta é: Do ponto de vista do POO, entendi que, ao definir um método, finalo designer da classe promete que esse método sempre funcionará como descrito ou implícito. Mas muitas vezes isso pode estar fora da influência do autor da classe, se o que o método está fazendo é mais complicado do que apenas entregar uma propriedade .

A restrição sintática é clara para mim, mas qual é a implicação no sentido de POO? É finalusado corretamente nesse sentido pela maioria dos autores de classe?

Que tipo de "contrato" finalpromete um método?

towi
fonte

Respostas:

155

Como mencionado, finalé usado com um método Java para marcar que o método não pode ser substituído (para o escopo do objeto) ou oculto (para estático). Isso permite que o desenvolvedor original crie funcionalidades que não podem ser alteradas por subclasses, e essa é toda a garantia que ele oferece.

Isso significa que, se o método se basear em outros componentes personalizáveis, como campos / métodos não públicos, a funcionalidade do método final ainda poderá ser personalizável. Isso é bom, já que (com polimorfismo) permite a personalização parcial.

Existem várias razões para impedir que algo seja personalizável, incluindo:

  • Desempenho - Alguns compiladores podem analisar e otimizar a operação, especialmente aquela sem efeitos colaterais.

  • Obter dados encapsulados - observe objetos imutáveis ​​onde seus atributos são definidos no momento da construção e nunca devem ser alterados. Ou um valor calculado derivado desses atributos. Um bom exemplo é a Stringclasse Java .

  • Confiabilidade e Contract - Objectos são compostas de primitivas ( int, char, double, etc.) e / ou outros objetos. Nem todas as operações aplicáveis ​​a esses componentes devem ser aplicáveis ​​ou mesmo lógicas quando usadas no objeto maior. Métodos com o finalmodificador podem ser usados ​​para garantir isso. A classe Counter é um bom exemplo.


public class Counter {
    private int counter = 0;

    public final int count() {
        return counter++;
    }

    public final int reset() {
        return (counter = 0);
    }
}

Se o public final int count()método não for final, podemos fazer algo assim:

Counter c = new Counter() {   
    public int count() {
        super.count();   
        return super.count();   
    } 
}

c.count(); // now count 2

Ou algo parecido com isto:

Counter c = new Counter() {
    public int count() {
        int lastCount = 0;
        for (int i = super.count(); --i >= 0; ) {
            lastCount = super.count();
        }

        return lastCount;
    }
}

c.count(); // Now double count
NawaMan
fonte
27

Que tipo de "contrato" promete um método final?

Olhe para o outro lado, qualquer método não final oferece a garantia implícita de que você pode substituí-lo por sua própria implementação e a classe ainda funcionará conforme o esperado. Quando você não pode garantir que sua classe suporta a substituição de um método, você deve torná-lo final.

josefx
fonte
Mas essa visão não implica na minha pergunta original "esse método final sempre se comportará como prometido" que não devo chamar nenhum método não final de dentro de um método final? Porque, se eu fizer, o método chamado pode ter sido substituído e, portanto, não posso garantir o comportamento do meu método final?
26416
8

Primeiro, você pode marcar classes não abstratas final, além de campos e métodos. Dessa forma, toda a classe não pode ser subclassificada. Portanto, o comportamento da classe será corrigido.

Concordo que os métodos de marcação finalnão garantem que seu comportamento será o mesmo nas subclasses se esses métodos estiverem chamando métodos não finais. Se o comportamento realmente precisa ser corrigido, isso deve ser alcançado por convenção e design cuidadoso. E não se esqueça de fazer isso no javadoc! (Documentação em java)

Por último, mas não menos importante, a finalpalavra-chave tem um papel muito importante no Java Memory Model (JMM). É garantido pelo JMM que, para obter visibilidade dos finalcampos, você não precisa de sincronização adequada. Por exemplo:

class A implements Runnable {
  final String caption = "Some caption";                           

  void run() {
    // no need to synchronize here to see proper value of final field..
    System.out.println(caption);
  }
}  
Victor Sorokin
fonte
sim, eu sei sobre aulas finais - caso fácil. Bom ponto sobre os campos finais com o JMM, não é necessário sincronizar ... hmm: isso se refere apenas ao "ponteiro", certo? Eu ainda podia modificar fora de sincronia o objeto ao qual ele se refere (ok, não dentro String, mas nas classes definidas pelo usuário). Mas o que você disse sobre "final não garante o comportamento" é exatamente o meu ponto. Concordo, doc e design são importantes.
towi
@ você está certo, que finalnão garantirá a visibilidade das alterações feitas nos objetos compostos, como Map.
51111 Victor Sorokin #
0

Não tenho certeza de que você possa fazer afirmações sobre o uso de "final" e como isso afeta o contrato geral de design do software. Você tem a garantia de que nenhum desenvolvedor pode substituir esse método e anular seu contrato dessa maneira. Mas, por outro lado, o método final pode depender de variáveis ​​de classe ou instância cujos valores são definidos por subclasses e pode chamar outros métodos de classe que são substituídos. Portanto, final é, no máximo, uma garantia muito fraca.

Jim Ferrans
fonte
1
Sim, foi isso que eu quis dizer. Certo. Eu gosto do termo "garantia fraca" :-) E eu (principalmente) gosto de C ++ 's const. Como em char const * const = "Hello"ou char const * const addName(char const * const name) const...
towi
0

Não, não está fora da influência do autor da classe. Você não pode substituí-lo em sua classe derivada; portanto, ele fará o que o autor da classe base pretendia.

http://download.oracle.com/javase/tutorial/java/IandI/final.html

É importante notar que é a parte em que sugere que os métodos chamados pelos construtores deveriam ser final.

Brian Roach
fonte
1
Bem, tecnicamente, ele fará o que o autor da classe base escreveu .
Joey