Strings Java: “String s = new String (” bobo “);”

86

Sou um cara C ++ aprendendo Java. Estou lendo Effective Java e algo me confundiu. Diz para nunca escrever código como este:

String s = new String("silly");

Porque cria Stringobjetos desnecessários . Mas, em vez disso, deve ser escrito assim:

String s = "No longer silly";

Tudo bem até agora ... No entanto, dada esta aula:

public final class CaseInsensitiveString {
    private String s;
    public CaseInsensitiveString(String s) {
        if (s == null) {
            throw new NullPointerException();
        }
        this.s = s;
    }
    :
    :
}

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";
  1. Por que a primeira afirmação está correta? Não deveria ser

    CaseInsensitiveString cis = "Polish";

  2. Como faço para me CaseInsensitiveStringcomportar de Stringforma que a declaração acima esteja OK (com e sem extensão String)? O que há em String que torna OK apenas ser capaz de passar um literal como esse? Do meu entendimento, não existe um conceito de "construtor de cópia" em Java?

JavaNewbie
fonte
2
String str1 = "foo"; String str2 = "foo"; Ambas str1 e str2 pertencem ao mesmo objeto String, "foo", b'coz para Java gerencia Strings em StringPool, então uma nova variável se refere à mesma String, ela não cria outra, em vez de atribuir a mesma alerady presente em StringPool. Mas quando fazemos isso: String str1 = new String ("foo"); String str2 = new String ("foo"); Aqui str1 e str2 pertencem a objetos diferentes, b'coz new String () cria à força um novo objeto String.
Akash5288

Respostas:

111

Stringé uma classe interna especial da linguagem. É apenas para a Stringaula em que você deve evitar dizer

String s = new String("Polish");

Porque o literal "Polish"já é do tipo Stringe você está criando um objeto extra desnecessário. Para qualquer outra classe, dizendo

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");

é a coisa correta (e única, neste caso) a fazer.

Adam Rosenfield
fonte
8
um segundo ponto é que o compilador pode imaginar coisas internig / propagação com literais de string que não são necessariamente possíveis com uma chamada de função estranha como "String (literal)"
Tetha
5
Já que você nunca deve ligar new String("foo"), você pode se perguntar por que o construtor new String(String)existe. A resposta é que às vezes há um bom uso para ele: stackoverflow.com/a/390854/1442870
conectado em
Para sua informação, o comentário de Tetha acima escreveu incorretamente a palavra "interning", como em string interning .
Basil Bourque
57

Acredito que o principal benefício de usar a forma literal (ou seja, "foo" em vez de nova String ("foo")) é que todos os literais String são 'internados' pela VM. Em outras palavras, ele é adicionado a um pool de forma que qualquer outro código que crie a mesma string usará a String agrupada em vez de criar uma nova instância.

Para ilustrar, o código a seguir imprimirá verdadeiro para a primeira linha, mas falso para a segunda:

System.out.println("foo" == "foo");
System.out.println(new String("bar") == new String("bar"));
Leigh
fonte
15
Da mesma forma, é por isso que FindBugs diz para você substituir "new Integer (N)" por "Integer.valueOf (N)" - por causa desse internamento.
Paul Tomblin
6
Você também deve adicionar "foo" == new String ("foo"). Intern ()
James Schek
5
Uma correção: literais de string são feitos para apontar para a mesma referência pelo compilador, não pela VM. A VM pode internar objetos String em tempo de execução, portanto, a segunda linha pode retornar verdadeiro ou falso!
Craig P. Motlin
1
@Motlin: Não tenho certeza se isso está correto. O javadoc para a classe String exige que "Todas as strings literais e expressões constantes com valor de string sejam internadas". Portanto, podemos confiar nos literais sendo internados, o que significa que "foo" == "foo" deve sempre retornar true.
Leigh
4
@Leigh Sim, literais são internados, mas pelo compilador e não pela VM. O que Motlin está querendo dizer é que a VM pode adicionalmente internar strings, portanto, se new String ("bar") == new String ("bar") -> false é Dependente de implementação ou não.
Aaron Maenpaa
30

Strings são tratados um pouco especialmente em java, eles são imutáveis, então é seguro para eles serem tratados por contagem de referência.

Se você escrever

String s = "Polish";
String t = "Polish";

então s e t realmente se referem ao mesmo objeto, e s == t retornará verdadeiro, pois "==" para objetos lidos "é o mesmo objeto" (ou pode, de qualquer maneira, não tenho certeza se isso faz parte de a especificação real da linguagem ou simplesmente um detalhe da implementação do compilador - então talvez não seja seguro confiar nisso).

Se você escrever

String s = new String("Polish");
String t = new String("Polish");

then s! = t (porque você criou explicitamente uma nova string), embora s.equals (t) retornará true (porque string adiciona esse comportamento a igual).

O que você quer escrever,

CaseInsensitiveString cis = "Polish";

não pode funcionar porque você está pensando que as citações são algum tipo de construtor de curto-circuito para seu objeto, quando na verdade isso só funciona para java.lang.Strings simples e antigo.

Steve B.
fonte
1 por mencionar a imutabilidade, que para mim é a verdadeira razão em java que se escreve strA = strB, em vez de strA = new String(strB). realmente não tem a ver muito com o estágio de string.
Kritzikratzi
Eles não são tratados por contagem de referência. O agrupamento de strings é exigido pelo JLS.
user207421
20
String s1="foo";

literal irá para o pool e s1 irá se referir.

String s2="foo";

desta vez, ele irá verificar se o literal "foo" já está disponível em StringPool ou não como agora ele existe, então s2 irá se referir ao mesmo literal.

String s3=new String("foo");

O literal "foo" será criado em StringPool primeiro e, em seguida, por meio do construtor string arg String O objeto será criado, ou seja, "foo" no heap devido à criação do objeto por meio de um novo operador, então s3 irá referenciá-lo.

String s4=new String("foo");

mesmo que s3

então System.out.println(s1==s2);// **true** due to literal comparison.

e System.out.println(s3==s4);// **false** due to object

comparação (s3 e s4 são criados em locais diferentes no heap)

Vikas
fonte
1
Não use formatação de citação para texto que não está entre aspas e use formatação de código para código.
user207421
12

Strings são especiais em Java - eles são imutáveis ​​e constantes de string são automaticamente transformadas em Stringobjetos.

Não há como seu SomeStringClass cis = "value"exemplo se aplicar a qualquer outra classe.

Nem você pode estender String, porque é declarado como final, o que significa que nenhuma subclasse é permitida.

Alnitak
fonte
7

As strings Java são interessantes. Parece que as respostas cobriram alguns dos pontos interessantes. Aqui estão meus dois centavos.

strings são imutáveis ​​(você nunca pode mudá-las)

String x = "x";
x = "Y"; 
  • A primeira linha criará uma variável x que conterá o valor da string "x". A JVM irá olhar em seu conjunto de valores de string e ver se "x" existe, se existir, ela apontará a variável x para ela, se ela não existir, ela a criará e então fará a atribuição
  • A segunda linha removerá a referência a "x" e verá se "Y" existe no conjunto de valores de string. Se ele existir, ele irá atribuí-lo, se não, ele irá criá-lo primeiro e depois atribuí-lo. Conforme os valores da string são usados ​​ou não, o espaço de memória no conjunto de valores da string será recuperado.

as comparações de strings dependem do que você está comparando

String a1 = new String("A");

String a2 = new String("A");
  • a1 não é igual a2
  • a1e a2são referências de objeto
  • Quando a string é declarada explicitamente, novas instâncias são criadas e suas referências não serão as mesmas.

Acho que você está no caminho errado ao tentar usar a classe que não diferencia maiúsculas de minúsculas. Deixe as cordas em paz. O que realmente importa é como você exibe ou compara os valores. Use outra classe para formatar a string ou para fazer comparações.

ie

TextUtility.compare(string 1, string 2) 
TextUtility.compareIgnoreCase(string 1, string 2)
TextUtility.camelHump(string 1)

Como você está criando a classe, pode fazer as comparações fazerem o que quiser - comparar os valores de texto.

mson
fonte
O compilador cria o conjunto de sequências, não a JVM. Variáveis ​​não contêm objetos, elas se referem a eles. O espaço do pool de strings para literais de string nunca é recuperado.
user207421
6

Você não pode. Coisas entre aspas duplas em Java são especialmente reconhecidas pelo compilador como Strings e, infelizmente, você não pode sobrescrever isso (ou estender java.lang.String- é declarado final).

Dan Vinton
fonte
final é uma pista falsa aqui. Mesmo se String não fosse final, estender String não o ajudaria neste caso.
Darron
1
Acho que você quis dizer que felizmente você não pode ignorar isso. :)
Craig P. Motlin
@Motlin: Ha! Você bem pode estar certo. Acho que li em algum lugar que o Java foi projetado para impedir qualquer pessoa de fazer qualquer coisa estúpida, excluindo deliberadamente qualquer coisa interessante como isso ...
Dan Vinton
6

A melhor maneira de responder à sua pergunta seria familiarizá-lo com o "pool de constantes de string". Em java string, os objetos são imutáveis ​​(ou seja, seus valores não podem ser alterados depois de inicializados), então, ao editar um objeto string, você acaba criando um novo objeto string editado, enquanto o objeto antigo apenas flutua em uma área de memória especial chamada de "string piscina constante ". criando um novo objeto string por

String s = "Hello";

irá apenas criar um objeto string no pool e as referências irão se referir a ele, mas usando

String s = new String("Hello");

você cria dois objetos de string: um no pool e outro no heap. a referência se referirá ao objeto no heap.

Surender Thakran
fonte
4

- Como faço para que CaseInsensitiveString se comporte como String para que a declaração acima esteja ok (com e sem extensão de String)? O que há em String que torna ok apenas ser capaz de passar um literal como esse? Do meu entendimento, não existe um conceito de "construtor de cópia" em Java, certo?

Já foi dito o suficiente desde o primeiro ponto. "Polish" é um literal de string e não pode ser atribuído à classe CaseInsentiviveString.

Agora sobre o segundo ponto

Embora você não possa criar novos literais, você pode seguir o primeiro item desse livro para uma abordagem "semelhante", de modo que as seguintes afirmações sejam verdadeiras:

    // Lets test the insensitiveness
    CaseInsensitiveString cis5 = CaseInsensitiveString.valueOf("sOmEtHiNg");
    CaseInsensitiveString cis6 = CaseInsensitiveString.valueOf("SoMeThInG");

    assert cis5 == cis6;
    assert cis5.equals(cis6);

Aqui está o código.

C:\oreyes\samples\java\insensitive>type CaseInsensitiveString.java
import java.util.Map;
import java.util.HashMap;

public final class CaseInsensitiveString  {


    private static final Map<String,CaseInsensitiveString> innerPool 
                                = new HashMap<String,CaseInsensitiveString>();

    private final String s;


    // Effective Java Item 1: Consider providing static factory methods instead of constructors
    public static CaseInsensitiveString valueOf( String s ) {

        if ( s == null ) {
            return null;
        }
        String value = s.toLowerCase();

        if ( !CaseInsensitiveString.innerPool.containsKey( value ) ) {
             CaseInsensitiveString.innerPool.put( value , new CaseInsensitiveString( value ) );
         }

         return CaseInsensitiveString.innerPool.get( value );   
    }

    // Class constructor: This creates a new instance each time it is invoked.
    public CaseInsensitiveString(String s){
        if (s == null) {
            throw new NullPointerException();
         }         
         this.s = s.toLowerCase();
    }

    public boolean equals( Object other ) {
         if ( other instanceof CaseInsensitiveString ) {
              CaseInsensitiveString otherInstance = ( CaseInsensitiveString ) other;
             return this.s.equals( otherInstance.s );
         }

         return false;
    }


    public int hashCode(){
         return this.s.hashCode();
    }

// Teste a classe usando a palavra-chave "assert"

    public static void main( String [] args ) {

        // Creating two different objects as in new String("Polish") == new String("Polish") is false
        CaseInsensitiveString cis1 = new CaseInsensitiveString("Polish");
        CaseInsensitiveString cis2 = new CaseInsensitiveString("Polish");

        // references cis1 and cis2 points to differents objects.
        // so the following is true
        assert cis1 !=  cis2;      // Yes they're different
        assert cis1.equals(cis2);  // Yes they're equals thanks to the equals method

        // Now let's try the valueOf idiom
        CaseInsensitiveString cis3 = CaseInsensitiveString.valueOf("Polish");
        CaseInsensitiveString cis4 = CaseInsensitiveString.valueOf("Polish");

        // References cis3 and cis4 points to same  object.
        // so the following is true
        assert cis3 == cis4;      // Yes they point to the same object
        assert cis3.equals(cis4); // and still equals.

        // Lets test the insensitiveness
        CaseInsensitiveString cis5 = CaseInsensitiveString.valueOf("sOmEtHiNg");
        CaseInsensitiveString cis6 = CaseInsensitiveString.valueOf("SoMeThInG");

        assert cis5 == cis6;
        assert cis5.equals(cis6);

        // Futhermore
        CaseInsensitiveString cis7 = CaseInsensitiveString.valueOf("SomethinG");
        CaseInsensitiveString cis8 = CaseInsensitiveString.valueOf("someThing");

        assert cis8 == cis5 && cis7 == cis6;
        assert cis7.equals(cis5) && cis6.equals(cis8);
    }

}

C:\oreyes\samples\java\insensitive>javac CaseInsensitiveString.java


C:\oreyes\samples\java\insensitive>java -ea CaseInsensitiveString

C:\oreyes\samples\java\insensitive>

Ou seja, crie um pool interno de objetos CaseInsensitiveString e retorne a instância correspondente a partir daí.

Desta forma, o operador "==" retorna verdadeiro para referências de dois objetos que representam o mesmo valor .

Isso é útil quando objetos semelhantes são usados ​​com muita frequência e a criação de custos é cara.

A documentação da classe string afirma que a classe usa um pool interno

A aula não está completa, alguns problemas interessantes surgem quando tentamos percorrer o conteúdo do objeto na implementação da interface CharSequence, mas este código é bom o suficiente para mostrar como aquele item do livro pode ser aplicado.

É importante notar que usando o objeto internalPool , as referências não são liberadas e, portanto, não podem ser coletadas como lixo, e isso pode se tornar um problema se muitos objetos forem criados.

Funciona para a classe String porque é usada intensamente e o pool é constituído apenas de objeto "internado".

Também funciona bem para a classe Booleana, porque existem apenas dois valores possíveis.

E, finalmente, essa também é a razão pela qual valueOf (int) na classe Integer está limitado a -128 a 127 valores int.

OscarRyz
fonte
3

Em seu primeiro exemplo, você está criando uma String, "boba" e, em seguida, passando-a como um parâmetro para outro construtor de cópia de String, o que faz uma segunda String idêntica à primeira. Uma vez que Java Strings são imutáveis ​​(algo que freqüentemente incomoda pessoas que estão acostumadas com strings C), isso é um desperdício de recursos desnecessário. Em vez disso, você deve usar o segundo exemplo porque ele pula várias etapas desnecessárias.

No entanto, o literal de String não é um CaseInsensitiveString, portanto, você não pode fazer o que deseja em seu último exemplo. Além disso, não há como sobrecarregar um operador de conversão como você faz em C ++, portanto, literalmente, não há como fazer o que você deseja. Em vez disso, você deve passá-lo como um parâmetro para o construtor de sua classe. Claro, eu provavelmente apenas usaria String.toLowerCase () e acabaria com isso.

Além disso, seu CaseInsensitiveString deve implementar a interface CharSequence, bem como provavelmente as interfaces Serializable e Comparable. Obviamente, se você implementar Comparable, deverá substituir equals () e hashCode () também.

James
fonte
3

Só porque você tem a palavra Stringem sua classe, não significa que você obtém todos os recursos especiais da Stringclasse integrada .

javaguy
fonte
3

CaseInsensitiveStringnão é um, Stringembora contenha um String. Um Stringliteral, por exemplo "exemplo", só pode ser atribuído a um String.

fastcodejava
fonte
2

CaseInsensitiveString e String são objetos diferentes. Você não pode fazer:

CaseInsensitiveString cis = "Polish";

porque "Polish" é uma String, não uma CaseInsensitiveString. Se String extendesse CaseInsensitiveString String, então você estaria bem, mas obviamente não está.

E não se preocupe com a construção aqui, você não estará fazendo objetos desnecessários. Se você olhar o código do construtor, tudo o que ele faz é armazenar uma referência à string que você transmitiu. Nada extra está sendo criado.

No caso de String s = new String ("foobar"), ele está fazendo algo diferente. Você está criando primeiro a string literal "foobar" e, em seguida, criando uma cópia dela, construindo uma nova string a partir dela. Não há necessidade de criar essa cópia.

Herms
fonte
Mesmo se você estendesse String, isso não funcionaria. Você precisaria de String para estender CaseInsensitiveString.
Darron
em ambos os casos é impossível, seja porque a string está embutida ou porque é declarada como final
luke
2

quando eles dizem para escrever

String s = "Silly";

ao invés de

String s = new String("Silly");

eles querem dizer isso ao criar um objeto String porque ambas as instruções acima criam um objeto String, mas a nova versão String () cria dois objetos String: um no heap e outro no conjunto de constantes de string. Portanto, usando mais memória.

Mas quando você escreve

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");

você não está criando uma String, em vez disso, está criando um objeto da classe CaseInsensitiveString. Portanto, você precisa usar o novo operador.


fonte
1

Se entendi corretamente, sua pergunta significa por que não podemos criar um objeto atribuindo-lhe um valor diretamente, não vamos restringi-lo a uma classe Wrapper of String em java.

Para responder a isso, eu apenas diria que as linguagens de Programação Orientada a Objetos puramente têm algumas construções e dizem que todos os literais quando escritos sozinhos podem ser transformados diretamente em um objeto do tipo dado.

Isso significa precisamente que, se o interpretador vir 3, ele será convertido em um objeto Integer, pois integer é o tipo definido para tais literais.

Se o interpretador vir qualquer coisa entre aspas simples como 'a', ele criará diretamente um objeto do tipo caractere, você não precisa especificá-lo, pois o idioma define o objeto padrão do tipo caractere para ele.

Da mesma forma, se o interpretador vir algo em "", ele será considerado um objeto de seu tipo padrão, ou seja, string. Este é um código nativo trabalhando em segundo plano.

Agradeço ao curso de vídeo-aula 6.00 do MIT, onde obtive a dica para essa resposta.

dharam
fonte
0

Em Java, a sintaxe "text" cria uma instância da classe java.lang.String. A atribuição:

String foo = "text";

é uma atribuição simples, sem a necessidade de um construtor de cópia.

MyString bar = "text";

É ilegal o que quer que você faça porque a classe MyString não é java.lang.String ou uma superclasse de java.lang.String.

Darron
fonte
0

Primeiro, você não pode fazer uma classe que se estenda de String, porque String é uma classe final. E o java gerencia Strings de maneira diferente de outras classes, então apenas com String você pode fazer

String s = "Polish";

Mas com sua classe, você deve invocar o construtor. Então, esse código está bom.

Lucas Gabriel Sánchez
fonte
0

Gostaria apenas de acrescentar que o Java tem construtores de cópia ...

Bem, esse é um construtor comum com um objeto do mesmo tipo que o argumento.

PhiLho
fonte
2
É um padrão de design, não uma construção de linguagem. Com Java, existem poucos usos em que um construtor de cópia seria interessante, pois tudo é sempre "por referência", e cada objeto tem apenas uma cópia. Na verdade, fazer cópias causaria MUITOS problemas.
Bill K
0

Na maioria das versões do JDK, as duas versões serão iguais:

String s = nova String ("bobo");

String s = "Não é mais bobo";

Como as strings são imutáveis, o compilador mantém uma lista de constantes de string e, se você tentar fazer uma nova, primeiro verificará se a string já está definida. Se for, então uma referência à string imutável existente será retornada.

Para esclarecer - quando você diz "String s =" você está definindo uma nova variável que ocupa espaço na pilha - então se você disser "Não é mais bobo" ou uma nova String ("bobo") exatamente a mesma coisa acontece - um novo string constante é compilada em seu aplicativo e os pontos de referência para isso.

Eu não vejo a diferença aqui. No entanto, para sua própria classe, que não é imutável, esse comportamento é irrelevante e você deve chamar seu construtor.

ATUALIZAÇÃO: eu estava errado! Com base em uma votação negativa e um comentário anexado, testei isso e percebi que meu entendimento está errado - nova String ("Silly") de fato cria uma nova string em vez de reutilizar a existente. Não estou claro por que isso seria (qual é o benefício?), Mas o código fala mais alto do que as palavras!

Ewan Makepeace
fonte
0

String é uma das classes especiais em que você pode criá-los sem a nova parte Sring

é o mesmo que

int x = y;

ou

char c;

Tolga E
fonte
0

É uma lei básica que Strings em java são imutáveis ​​e diferenciam maiúsculas de minúsculas.

user1945649
fonte
0
 String str1 = "foo"; 
 String str2 = "foo"; 

Ambas str1 e str2 pertencem ao mesmo objeto String, "foo", b'coz Java gerencia Strings em StringPool, então se uma nova variável se refere à mesma String, ela não cria outra, em vez de atribuir a mesma alerta presente em StringPool .

 String str1 = new String("foo"); 
 String str2 = new String("foo");

Aqui str1 e str2 pertencem a objetos diferentes, b'coz new String () cria à força um novo objeto String.

Akash5288
fonte
-1

Java cria um objeto String para cada literal de string que você usa em seu código. Qualquer tempo ""é usado, é o mesmo que chamar new String().

Strings são dados complexos que apenas "agem" como dados primitivos. Literais de string são na verdade objetos, embora finjamos que são literais primitivos, como 6, 6.0, 'c',etc. Portanto, o "literal" de String "text"retorna um novo objeto String com valor char[] value = {'t','e','x','t}. Portanto, chamando

new String("text"); 

é realmente semelhante a ligar

new String(new String(new char[]{'t','e','x','t'}));

Esperançosamente, a partir daqui, você pode ver por que seu livro considera isso redundante.

Para referência, aqui está a implementação de String: http://www.docjar.com/html/api/java/lang/String.java.html

É uma leitura divertida e pode inspirar alguns insights. Também é ótimo para iniciantes ler e tentar entender, pois o código demonstra um código muito profissional e compatível com as convenções.

Outra boa referência é o tutorial Java sobre Strings: http://docs.oracle.com/javase/tutorial/java/data/strings.html

Patrick Michaelsen
fonte
Sempre que "" é usado, é uma referência à mesma string, no pool de constantes.
user207421