Por que a classe String é declarada final em Java?

141

Desde que soube que a classe java.lang.Stringé declarada como final em Java, fiquei me perguntando por que isso acontece. Não encontrei nenhuma resposta na época, mas este post: Como criar uma réplica da classe String em Java? me lembrou da minha consulta.

Claro, o String fornece toda a funcionalidade que eu sempre precisei e nunca pensei em nenhuma operação que exigiria uma extensão da classe String, mas você nunca saberá do que alguém pode precisar!

Então, alguém sabe qual era a intenção dos designers quando decidiram torná-lo final?

Alex Ntousias
fonte
Obrigado a todos por suas respostas, especialmente TrueWill, Bruno Reis e Thilo! Eu gostaria de poder escolher mais de uma resposta como a melhor, mas infelizmente ...!
Alex Ntousias
1
Considere também a proliferação de projetos "Oh, eu só preciso de mais alguns métodos utilitários em String" que apareceriam - todos os quais não podiam usar os Strings uns dos outros porque eram uma classe diferente.
Thorbjørn Ravn Andersen
Obrigado por esta resposta é muito útil. Temos dois fatos agora. Uma String é uma classe Final e é imutável porque não pode ser alterada, mas pode ser referida a outro objeto. mas e quanto a: - String a = new String ("test1"); então, s = "test2"; Se String é um objeto de classe Final, como ele pode ser modificado? Como posso usar o objeto final modificado. Por favor, deixe-me se eu perguntei algo errado.
Suresh Sharma #
Você pode ver este bom artigo .
Aniket Thakur
4
Uma coisa que felizmente evitamos em Java é a "todo mundo tem sua própria subclasse de String com muitos métodos extras, e nenhum deles é compatível um com o outro".
Thorbjørn Ravn Andersen

Respostas:

88

É muito útil ter cadeias implementadas como objetos imutáveis . Você deve ler sobre imutabilidade para entender mais sobre isso.

Uma vantagem de objetos imutáveis é que

Você pode compartilhar duplicatas apontando-as para uma única instância.

( daqui ).

Se String não fosse final, você poderia criar uma subclasse e ter duas seqüências parecidas quando "vistas como Strings", mas na verdade são diferentes.

Bruno Reis
fonte
70
A menos que haja uma conexão entre as classes finais e os objetos imutáveis ​​que não estou vendo, não vejo como a sua resposta está relacionada à pergunta.
sepp2k
9
Como se não for final, você pode passar um StringChild para algum método como um parâmetro String e pode ser mutável (porque um estado de classe filho muda).
helios
4
Uau! Votos negativos? Você não entende como a subclasse se relaciona com a imutabilidade? Eu apreciaria uma explicação sobre qual é o problema.
Bruno Reis
7
@ Bruno, re: downvotes: Eu não votei contra você, mas você pode adicionar uma frase sobre como a prevenção de subclasses impõe imutabilidade. No momento, é meio que uma resposta.
Thilo
12
@BrunoReis - encontrou um bom artigo ao qual você poderia vincular uma entrevista com James Gosling (criador do Java), onde ele fala brevemente sobre esse tópico aqui . Aqui está um trecho interessante: "Uma das coisas que obrigou o Strings a ser imutável foi a segurança. Você tem um método de abertura de arquivo. Você passa um String para ele. chamar Se você conseguir fazer algo que efetivamente transformou a string, após a verificação de segurança e antes da chamada oS, em seguida, crescer, você está em ...".
Anurag
60

Este é um bom artigo que descreve dois motivos já mencionados nas respostas acima:

  1. Segurança : o sistema pode distribuir bits confidenciais de informações somente leitura sem se preocupar com a alteração.
  2. Desempenho : dados imutáveis ​​são muito úteis para tornar as coisas mais seguras.

E esse provavelmente é o comentário mais detalhado nesse artigo. Tem a ver com o pool de strings em Java e questões de segurança. É sobre como decidir o que entra no pool de strings. Supondo que as duas seqüências de caracteres sejam iguais se a sequência de caracteres for a mesma, teremos uma condição de corrida para quem chegar lá primeiro e, juntamente com isso, problemas de segurança. Caso contrário, o conjunto de cadeias conterá cadeias redundantes, perdendo a vantagem de tê-lo em primeiro lugar. Basta ler por si mesmo, sim?


Estender String causaria estragos com iguais e estagiários. JavaDoc diz que é igual a:

Compara essa sequência com o objeto especificado. O resultado é verdadeiro se, e somente se, o argumento não for nulo e for um objeto String que represente a mesma sequência de caracteres que esse objeto.

Supondo que java.lang.Stringnão fosse final, a SafeStringpoderia ser igual a Stringe vice-versa; porque eles representariam a mesma sequência de caracteres.

O que aconteceria se você se inscrevesse internem um SafeString- entraria SafeStringno conjunto de cadeias de caracteres da JVM? Os ClassLoaderobjetos e todos os objetos às quais as SafeStringreferências retidas seriam bloqueados durante a vida útil da JVM. Você obteria uma condição de corrida sobre quem poderia ser o primeiro a estagiar uma sequência de personagens - talvez você SafeStringganhasse, talvez um String, ou talvez um SafeStringcarregado por um carregador de classe diferente (portanto, uma classe diferente).

Se você vencesse a corrida na piscina, este seria um verdadeiro singleton e as pessoas poderiam acessar todo o seu ambiente (sandbox) através de reflexão e secretKey.intern().getClass().getClassLoader().

Ou a JVM poderia bloquear esse furo, certificando-se de que apenas objetos String concretos (e nenhuma subclasse) fossem adicionados ao pool.

Se igual foi implementado de forma que SafeString! = StringEntão SafeString.intern! = String.intern, E SafeStringteria que ser adicionado ao pool. A piscina se tornaria uma piscina em <Class, String>vez de <String>e tudo o que você precisaria para entrar na piscina seria um novo carregador de classe.

Anurag
fonte
2
É claro que o motivo do desempenho é uma falácia: se String fosse uma interface, eu seria capaz de fornecer uma implementação com melhor desempenho em meu aplicativo.
211116 Stephan Eggermont
28

O motivo absolutamente mais importante pelo qual String é imutável ou final é que ele é usado pelo mecanismo de carregamento de classe e, portanto, possui aspectos de segurança profundos e fundamentais.

Se String fosse mutável ou não final, uma solicitação para carregar "java.io.Writer" poderia ter sido alterada para carregar "mil.vogoon.DiskErasingWriter"

reference: Por que String é imutável em Java

Jackob
fonte
15

String é uma classe muito essencial em Java, muitas coisas dependem dele para funcionar de certa maneira, por exemplo, ser imutável.

Fazer a classe finalevita subclasses que podem quebrar essas suposições.

Observe que, mesmo agora, se você usar reflexão, poderá quebrar Strings (alterar seu valor ou código de hash). A reflexão pode ser interrompida com um gerente de segurança. Se Stringnão fosse final, todos poderiam fazê-lo.

Outras classes que não são declaradas finalpermitem definir subclasses um pouco quebradas (você pode ter uma Listque acrescente à posição errada, por exemplo), mas pelo menos a JVM não depende daquelas para suas operações principais.

Thilo
fonte
6
finalem uma classe não garante imutabilidade. Apenas garante que os invariantes de uma classe (um dos quais pode ser imutável) não podem ser alterados por uma subclasse.
Kevin Brock
1
@ Kevin: sim. A aula final garante que não há subclasses. Não tem nada a ver com imutabilidade.
Thilo
4
Fazer uma aula final não é, por si só, imutável. Mas fazer uma aula imutável final garante que ninguém faça uma subclasse que quebre a imutabilidade. Talvez as pessoas que defendem a imutabilidade não estejam claras exatamente no que elas significam, mas suas afirmações estão corretas quando entendidas no contexto.
Jay
Algumas vezes, eu li essa resposta e achei que era uma resposta excelente, então li o hashcode & igual em 'The Effective Java' e percebi que é uma resposta muito boa. Alguém precisa de explicações, eu recomendo o ieam 8 e o iteam 9 do mesmo livro.
Abhishek Singh
6

Como Bruno disse, é sobre imutabilidade. Não se trata apenas de Strings, mas também de quaisquer invólucros, como Double, Inteiro, Caractere, etc. Há muitas razões para isso:

  • Segurança da linha
  • Segurança
  • Heap gerenciado pelo próprio Java (diferente do heap comum que é Garbage Collected de maneira diferente)
  • Gerenciamento de memória

Basicamente, para que você, como programador, tenha certeza de que sua string nunca será alterada. Além disso, se você souber como funciona, pode melhorar o gerenciamento de memória. Tente criar duas seqüências idênticas uma após a outra, por exemplo "olá". Você notará, se você depurar, que eles têm IDs idênticos, isso significa que eles são exatamente OS MESMOS objetos. Isso se deve ao fato de o Java permitir que você faça isso. Isso não seria possível se as cordas fossem mutáveis. Eles podem ter o mesmo que eu etc., porque nunca mudarão. Portanto, se você decidir criar 1.000.000 de strings "olá", o que você realmente faria é criar 1.000.000 de ponteiros para "olá". Além disso, alocar qualquer função na string ou qualquer invólucro por esse motivo resultaria na criação de outro objeto (observe novamente o ID do objeto - ele será alterado).

Adicionalmente final em Java não significa necessariamente que o objeto não pode ser alterado (é diferente, por exemplo, do C ++). Isso significa que o endereço para o qual ele aponta não pode ser alterado, mas você ainda pode alterar suas propriedades e / ou atributos. Portanto, entender a diferença entre imutabilidade e final em alguns casos pode ser realmente importante.

HTH

Referências:

Artur
fonte
1
Eu não acredito que as seqüências de caracteres vão para uma pilha diferente ou usam um gerenciamento de memória diferente. Eles certamente são colecionáveis ​​de lixo.
Thilo
2
Além disso, a palavra-chave final em uma classe é completamente diferente da palavra-chave final de um campo.
Thilo
1
Ok, na JVM da Sun, as Strings que são internas () podem entrar no perm-gen, que não faz parte do heap. Mas isso definitivamente não acontece para todas as Strings ou todas as JVM.
Thilo
2
Nem todas as Strings vão para essa área, apenas as Strings que foram internadas. A internação é automática para Strings literais. (@Thilo, digitando quando você enviou seu comentário).
Kevin Brock
Obrigado por esta resposta é muito útil. Temos dois fatos agora. Uma String é uma classe Final e é imutável porque não pode ser alterada, mas pode ser referida a outro objeto. mas e quanto a: - String a = new String ("test1"); então, s = "test2"; Se String é um objeto de classe Final, como ele pode ser modificado? Como posso usar o objeto final modificado. Por favor, deixe-me se eu perguntei algo errado.
Suresh Sharma
2

Pode ter sido para simplificar a implementação. Se você projetar uma classe que será herdada pelos usuários da classe, terá um novo conjunto de casos de uso a serem considerados no design. O que acontece se eles fizerem isso ou aquilo com o campo protegido por X? Para finalizar, eles podem se concentrar em fazer com que a interface pública funcione corretamente e garantir que ela seja sólida.

AaronLS
fonte
3
+1 para "projetar para herança é difícil". BTW, isso é muito bem explicado no "Java Efetivo" de Bloch.
Sleske # 29/10
2

Com muitos pontos positivos já mencionados, gostaria de acrescentar outro - um dos motivos pelos quais String é imutável em Java é permitir que String armazene em cache seu código de hash , sendo imutável String em Java armazena em cache seu código de hash e não calcula todos os No momento, chamamos o método hashcode de String , o que o torna muito rápido como chave hashmap para ser usado no hashmap em Java.

Em resumo, porque String é imutável, ninguém pode alterar seu conteúdo depois de criado, o que garante que o hashCode de String seja o mesmo em várias chamadas.

Se você vir a Stringclasse has é declarada como

/** Cache the hash code for the string */
private int hash; // Default to 0

e hashcode()função é a seguinte -

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

Se já é computador, basta retornar o valor.

Aniket Thakur
fonte
2

Para garantir uma melhor implementação. Obviamente, deveria ter sido uma interface.

[edit] Ah, obtendo mais votos sem noção. A resposta é perfeitamente séria. Eu tive que programar meu caminho em torno da implementação estúpida de String várias vezes, levando a um desempenho severo e perda de produtividade

Stephan Eggermont
fonte
2

Além dos motivos óbvios sugeridos em outras respostas, um pensamento de tornar a classe String final também pode estar relacionada à sobrecarga de desempenho dos métodos virtuais. Lembre-se de que String é uma classe pesada, tornando isso final, significa nenhuma sub-implementação, com certeza, significa que não há nenhuma chamada indireta de sobrecarga. É claro que agora temos coisas como invocação virtual e outras, que sempre fazem esse tipo de otimização para você.

Abhishek Singh
fonte
2

Além dos motivos mencionados em outras respostas (segurança, imutabilidade, desempenho), deve-se notar que Stringpossui suporte especial ao idioma. Você pode escrever Stringliterais e há suporte para o +operador. Permitir que os programadores subclasses Stringincentivaria hacks como:

class MyComplex extends String { ... }

MyComplex a = new MyComplex("5+3i");
MyComplex b = new MyComplex("7+4i");
MyComplex c = new MyComplex(a + b);   // would work since a and b are strings,
                                      // and a string + a string is a string.
aioobe
fonte
1

Bem, eu tenho um pensamento diferente, não tenho certeza se estou correto ou não, mas em Java String é o único objeto que pode ser tratado como um tipo de dados primitivo, bem, quero dizer que podemos criar um objeto String como String name = "java " . Agora, como outros tipos de dados primitivos que são copiar por valor, não copiar por referência, espera-se que o String tenha o mesmo comportamento, por isso o String é final. Isso é o que o meu pensamento. Por favor, ignore se é completamente ilógico.

webdev
fonte
1
Na minha opinião, String nunca se comporta como um tipo primitivo. Um literal de string como "java" é realmente um objeto da classe String (você pode usar o operador de ponto nele, imediatamente após a citação de fechamento). Portanto, atribuir um literal a uma variável de string é apenas atribuir referências a objetos, como de costume. O que é diferente é que a classe String possui suporte no nível de idioma incorporado ao compilador ... transformando aspas duplas em objetos String e o operador + conforme mencionado acima.
68568 Georgie
1

A finalidade das strings também as defende como padrão. No C ++, você pode criar subclasses de string, para que cada loja de programação possa ter sua própria versão de string. Isso levaria à falta de um padrão forte.

ncmathsadist
fonte
1

Digamos que você tenha uma Employeeclasse que tenha um método greet. Quando o greetmétodo é chamado, ele simplesmente imprime Hello everyone!. Então esse é o comportamento esperado do greetmétodo

public class Employee {

    void greet() {
        System.out.println("Hello everyone!");
    }
}

Agora, deixe a GrumpyEmployeesubclasse Employeee substitua o greetmétodo, como mostrado abaixo.

public class GrumpyEmployee extends Employee {

    @Override
    void greet() {
        System.out.println("Get lost!");
    }
}

Agora, no código abaixo, dê uma olhada no sayHellométodo Ele toma a Employeeinstância como um parâmetro e chama o método greet, esperando que ele diga Hello everyone!Mas o que obtemos é Get lost!. Essa mudança de comportamento se deve aEmployee grumpyEmployee = new GrumpyEmployee();

public class TestFinal {
    static Employee grumpyEmployee = new GrumpyEmployee();

    public static void main(String[] args) {
        TestFinal testFinal = new TestFinal();
        testFinal.sayHello(grumpyEmployee);
    }

    private void sayHello(Employee employee) {
        employee.greet(); //Here you would expect a warm greeting, but what you get is "Get lost!"
    }
}

Esta situação pode ser evitada se a Employeeaula foi feita final. Agora depende da sua imaginação a quantidade de caos que um programador atrevido poderia causar se a StringClasse não fosse declarada como final.

Andy
fonte
0

A JVM sabe o que é imutável? A resposta é Não, o pool constante contém todos os campos imutáveis, mas todos os campos / objetos imutáveis ​​não são armazenados apenas no pool constante. Somente nós o implementamos de maneira a alcançar a imutabilidade e seus recursos. CustomString poderia ser implementado sem torná-lo final usando MarkerInterface, o que forneceria um comportamento especial do java para seu pool, o recurso ainda é aguardado!

hi.nitish
fonte
0

A maioria das respostas está relacionada à imutabilidade - por que um objeto do tipo String não pode ser atualizado no local. Há muita discussão boa aqui, e a comunidade Java faria bem em adotar a imutabilidade como principal. (Não prendo a respiração.)

No entanto, a pergunta do OP é sobre por que é final - por que não pode ser estendida. Alguns aqui aceitaram isso, mas eu concordaria com o OP de que existe uma lacuna real aqui. Outro idioma permite que os desenvolvedores criem novos tipos nominais para um tipo. Por exemplo, em Haskell, posso criar os seguintes novos tipos que são idênticos em tempo de execução como texto, mas fornecem segurança de ligação em tempo de compilação.

newtype AccountCode = AccountCode Text
newtype FundCode = FundCode Text

Então, eu apresentaria a seguinte sugestão como um aprimoramento da linguagem Java:

newtype AccountCode of String;
newtype FundCode of String;

AccountCode acctCode = "099876";
FundCode fundCode = "099876";

acctCode.equals(fundCode);  // evaluates to false;
acctCode.toString().equals(fundCode.toString());  // evaluates to true;

acctCode=fundCode;  // compile error
getAccount(fundCode);  // compile error

(Ou talvez possamos começar a nos livrar do Java)

dsmith
fonte
-1

Se você criar uma string uma vez. Considerará que é um objeto se você deseja modificar isso; não é possível; ele criará um novo objeto.

Suresh
fonte
Por favor, esclareça sua resposta.
Marko Popovic 24/02