fundo
Existem três lugares onde final pode aparecer em Java:
A finalização de uma aula evita todas as subclasses da classe. A finalização de um método impede que subclasses do método o substituam. Tornar um campo final evita que seja alterado posteriormente.
Equívocos
Há otimizações que acontecem em torno dos métodos e campos finais.
Um método final facilita a otimização do HotSpot via inlining. No entanto, o HotSpot faz isso mesmo que o método não seja final, pois trabalha com a suposição de que não foi substituído até prova em contrário.Mais sobre isso no SO
Uma variável final pode ser otimizada agressivamente, e mais sobre isso pode ser lido na seção 17.5.3 do JLS . .
No entanto, com esse entendimento, deve-se estar ciente de que nenhuma dessas otimizações é sobre fazer uma classe final. Não há ganho de desempenho ao final da aula.
O aspecto final de uma classe também não tem nada a ver com imutabilidade. Pode-se ter uma classe imutável (como BigInteger ) que não é final ou uma classe que é mutável e final (como StringBuilder ). A decisão sobre se uma classe deve ser final é uma questão de design.
Design final
Strings são um dos tipos de dados mais usados. Eles são encontrados como chaves para mapas, armazenam nomes de usuário e senhas, são o que você lê de um teclado ou de um campo em uma página da web. As cordas estão em toda parte .
Mapas
A primeira coisa a considerar sobre o que aconteceria se você pudesse subclassificar String é perceber que alguém poderia construir uma classe String mutável que, de outra forma, pareceria uma String. Isso atrapalhava o Google Maps em todos os lugares.
Considere este código hipotético:
Map t = new TreeMap<String, Integer>();
Map h = new HashMap<String, Integer>();
MyString one = new MyString("one");
MyString two = new MyString("two");
t.put(one, 1); h.put(one, 1);
t.put(two, 2); h.put(two, 2);
one.prepend("z");
Esse é um problema com o uso de uma chave mutável em geral com um mapa, mas o que estou tentando entender é que, de repente, várias coisas sobre a quebra do mapa. A entrada não está mais no ponto certo no mapa. Em um HashMap, o valor do hash mudou (deveria ter) alterado e, portanto, não está mais na entrada correta. No TreeMap, a árvore agora está quebrada porque um dos nós está do lado errado.
Como o uso de uma String para essas chaves é tão comum, esse comportamento deve ser evitado, tornando a String final.
Você pode estar interessado em ler Por que o String é imutável em Java? para saber mais sobre a natureza imutável das Strings.
Cordas nefastas
Existem várias opções nefastas para Strings. Considere se eu criei uma String que sempre retornava verdadeira quando igual era chamado ... e passava isso para uma verificação de senha? Ou fez com que as atribuições para MyString enviassem uma cópia da String para algum endereço de email?
Essas são possibilidades muito reais quando você pode subclassificar String.
Otimizações de Java.lang String
Enquanto antes mencionei que final não faz String mais rápido. No entanto, a classe String (e outras classes em java.lang
) fazem uso frequente da proteção de campos e métodos no nível do pacote para permitir que outras java.lang
classes possam mexer com os internos, em vez de passar pela API pública do String o tempo todo. Funções como getChars sem verificação de intervalo ou lastIndexOf que é usado por StringBuffer, ou o construtor que compartilha a matriz subjacente (observe que isso é uma coisa do Java 6 que foi alterada devido a problemas de memória).
Se alguém fizesse uma subclasse de String, não seria possível compartilhar essas otimizações (a menos que fizesse parte java.lang
também, mas esse é um pacote selado ).
É mais difícil projetar algo para extensão
Projetar algo para ser extensível é difícil . Isso significa que você precisa expor partes de seus componentes internos para que outra coisa possa ser modificada.
Uma String extensível não poderia ter seu vazamento de memória corrigido. Essas partes precisariam ter sido expostas a subclasses e a alteração desse código significaria que as subclasses iriam quebrar.
O Java se orgulha da compatibilidade com versões anteriores e, ao abrir as classes principais para extensão, perde-se parte dessa capacidade de consertar as coisas, mantendo a computabilidade com subclasses de terceiros.
O Checkstyle possui uma regra que ela aplica (o que realmente me frustra ao escrever código interno) chamada "DesignForExtension", que impõe que todas as classes sejam:
- Abstrato
- Final
- Implementação vazia
O racional para o qual é:
Esse estilo de design da API protege as superclasses contra quebras por subclasses. A desvantagem é que as subclasses são limitadas em sua flexibilidade, em particular elas não podem impedir a execução de código na superclasse, mas isso também significa que as subclasses não podem corromper o estado da superclasse ao esquecer de chamar o método super.
Permitir que as classes de implementação sejam estendidas significa que as subclasses podem corromper o estado da classe em que se baseia e torná-lo de modo que várias garantias que a superclasse fornece sejam inválidas. Para algo tão complexo como String, é quase uma certeza que a mudança de parte dela vai quebrar alguma coisa.
Desenvolvedor hurbis
É parte de ser um desenvolvedor. Considere a probabilidade de que cada desenvolvedor crie sua própria subclasse String com sua própria coleção de utilitários . Mas agora essas subclasses não podem ser livremente atribuídas uma à outra.
WleaoString foo = new WleaoString("foo");
MichaelTString bar = foo; // This doesn't work.
Este caminho leva à loucura. Transmitindo para String em todo o lugar e verificando se a String é uma instância do seu classe String ou não, criando uma nova String com base nessa e ... apenas, não. Não.
Tenho certeza que você pode escrever uma classe bom string ... mas deixar de escrever várias implementações de cadeia para essas pessoas loucas que escrevem C ++ e tem que lidar com std::string
e char*
e algo de impulso e sString , e todo o resto .
Java String magic
Existem várias coisas mágicas que Java faz com Strings. Isso facilita o trabalho do programador, mas introduz algumas inconsistências no idioma. Permitir subclasses em String exigiria uma reflexão muito significativa sobre como lidar com essas coisas mágicas:
Literais de sequência (JLS 3.10.5 )
Ter um código que permita:
String foo = "foo";
Isso não deve ser confundido com o boxe de tipos numéricos como Inteiro. Você não pode fazer 1.toString()
, mas você pode fazer "foo".concat(bar)
.
O +
operador (JLS 15.18.1 )
Nenhum outro tipo de referência em Java permite que um operador seja usado nele. A String é especial. O operador de concatenação também trabalha no nível do compilador de modo que "foo" + "bar"
se torna "foobar"
quando ele é compilado em vez de em tempo de execução.
Conversão de Cadeia de Caracteres (JLS 5.1.11 )
Todos os objetos podem ser convertidos em Strings usando-os em um contexto de String.
Internação de strings ( JavaDoc )
A classe String tem acesso ao pool de Strings que permite ter representações canônicas do objeto que é preenchido no tipo de compilação com os literais String.
Permitir uma subclasse de String significaria que esses bits com a String que facilitam a programação se tornariam muito difíceis ou impossíveis de executar quando outros tipos de String forem possíveis.