Otimização de código

44 respostas
lina

Oi,

Um colega de trabalho, perguntou qual a diferenca e qual situação usar para comparar uma String… se vazia ou se existir conteudo.

situação 1:

if ( !string.equals("") )

ou

situação 2:

if ( string.lenght() > 0 )

Então, respondi a segunda opção, porém não souber explicar o motivo hehehe…
algum comentario a respeito?

Tchauzin!

44 Respostas

Foxlol

Com a primeira opção vc estará criando uma String a mais na memória.
Já com a segunda é apenas uma comparação com um tipo primitivo.

aleck

equals() compara o conteudo da String e == compara a referencia.

De qualquer forma, tem o compareTo e compareToIgnoreCase.

ViniGodoy

O length é mais rápido por se tratar apenas da leitura de um atributo e da comparação no seu if, observe (esses códigos foram retirados da classe String):

public int length() {
        return count;
    }

No caso do equals, nessa situação específica, você vai ter um processo lijeiramente mais longo, pois serão feitas três comparações. A primeira, testa se a string passada no parâmetro é a própria instância (o que nunca vai ocorrer). A segunda, testa se o objeto passado por parâmetro é realmente um String (o que sempre vai ser). E a terceira, finalmente, testa se o tamanho das Strings bate (o que só será verdade para um string vazio).

public boolean equals(Object anObject) {
   if (this == anObject) { //Comparação 1
      return true;
   }
   if (anObject instanceof String) {  //Comparação 2
      String anotherString = (String)anObject;    
      int n = count;
      if (n == anotherString.count) {    //Comparação 3
         char v1[] = value;
         char v2[] = anotherString.value;
         int i = offset;
         int j = anotherString.offset;
         while (n-- != 0) {
            if (v1[i++] != v2[j++])
               return false;
      }
      return true;
   }
}
	return false;
    }

Usando o equals é ainda mais recomendado colocar a constante na frente, assim:

if (!"".equals(string))

Isso dá uma vantagem adicional, pois o código também falha caso a string seja nula. Nos seus exemplos, você acabaria com um NullPointerException.

Agora, não creio que isso seja otimização de código, a menos que você esteja num loop realmente intenso, comparando milhares de Strings por segundo. Provavelmente haverá outros locais muito piores na sua aplicação. Não adianta “otimizar” pontos fora desses gargalos. Você só aumentará o tempo ocioso do sistema, que espera o gargalo passar. Sempre que se falar em otimização, rode um profiler, como o do Netbeans, e identifique os pontos de baixa performance. Otimize apenas esses pontos.

Otimização tem um custo: ela torna o código mais complexo e muito menos flexível. E deve ser uma preocupação apenas nos momentos de projeto e nos pontos críticos identificados no código.

ViniGodoy

Foxlol:
Com a primeira opção vc estará criando uma String a mais na memória.
Já com a segunda é apenas uma comparação com um tipo primitivo.

É muito provável que essa String venha do Pool de Strings. Não lembro onde li, mas creio que a String nula sempre esteja por lá.

aleck

Respondendo, o == é mais rapido, tinha esquecido :slight_smile:

Explicando o por que:

O Java suporta algo chamado de “interning”. Quando o metodo intern() é invocado em uma String, é feito um lookup em uma tabela interna de Strings. Se uma String com o mesmo conteudo já está nesta tabela, a referencia para ela e retornada, do contrario, a String é adicionada a tabela e a referencia a ela é retornada.

No fim, este processo faz com que todas as Strings com o mesmo conteudo apontem para o mesmo objeto, salvando espaço e permitindo a comparação com ==, que é muito mais rapida do que o equals, como o vinny explicou.

Referencia: http://javatechniques.com/blog/string-equality-and-interning/

ViniGodoy

Realmenta. Mas isso não serve para esse tipo de comparação que a Lina propôs.
Strings de conteúdo igual não são necessariamente iguais e dificilmente podemos afirmar que a String na variável veio de uma constante ou teve o seu intern() invocado.

Teste você mesmo:

String x = "Vinicius";
String y = new String("Vinicius");

if (x == y)
   System.out.println("É igual");
else
   System.out.println("É diferente");

Strings lidas de bancos de dados, ou capturadas de um JTextField não entram nesse caso.
É exatamente o alerta que o artigo dá no final.

Em todo caso, comparar com == envolve uma comparação de endereços de memória, que é um int. Enquanto com o length() envolve a comparação com um inteiro, que também é um int. A performance dos dois é provavelmente a mesma. :wink:

aleck

E como vc garante que o conteudo é o mesmo com o length? Já que ele só compara o tamanho.

Provavelmente você terá que fazer uma comparação posterior, que somada deixara o processo mais lento.

Ah! Deixa pra la, acabei perdendo o foco do topico que era a comparação do equal com o lenght :stuck_out_tongue:

thegoergen

Realmenta. Mas isso não serve para esse tipo de comparação que a Lina propôs.
Strings de conteúdo igual não são necessariamente iguais e dificilmente podemos afirmar que a String na variável veio de uma constante ou teve o seu intern() invocado.

Teste você mesmo:

String x = "Vinicius";
String y = new String("Vinicius");

if (x == y)
   System.out.println("É igual");
else
   System.out.println("É diferente");

Strings lidas de bancos de dados, ou capturadas de um JTextField não entram nesse caso.
É exatamente o alerta que o artigo dá no final.

Em todo caso, comparar com == envolve uma comparação de endereços de memória, que é um int. Enquanto com o length() envolve a comparação com um inteiro, que também é um int. A performance dos dois é provavelmente a mesma. ;)

Bah… que macabro, essas 2 Strings são diferentes mesmo…

Mas se for para testar se está vazia ou não, o lenght é melhor.

GustavoLaguna

Retirado do livro da Kathy

ViniGodoy

Não garante. Mas o objetivo era testar se a String era vazia. Nesse caso, com certeza o length é 0.

Não adianta… se precisar comparar duas Strings, ou você terá que usar o equals da classe String, ou o equals da classe Collator (caso queira, por exemplo, desprezar os acentos).

D

ViniGodoy:
Foxlol:
Com a primeira opção vc estará criando uma String a mais na memória.
Já com a segunda é apenas uma comparação com um tipo primitivo.

É muito provável que essa String venha do Pool de Strings. Não lembro onde li, mas creio que a String nula sempre esteja por lá.

Isso, toda constante string fica na tabela de símbolos do interpretador. Será “desperdício” de memória se essa constante aparecer uma única vez na aplicação.

Flw

lina

Oi,

vlw pelas respostas!

Tchauzin!

Dieval_Guizelini

daniel_s:

Isso, toda constante string fica na tabela de símbolos do interpretador. Será “desperdício” de memória se essa constante aparecer uma única vez na aplicação.
Flw

Daniel,

em tese o próprio paradigma orientado objeto tem esse efeito colateral (de desperdício de memória), mas em função da grande necessidade de ponteiros associativos…

Com relação ao cache interno de Constantes String do java e elembrando que um objeto String é “imutável”, a equipe da SUN quando escrever essa parte da especificação estava em mente com a seguintes situação:

String a = SACANA;

String b = CANA;

String c = ANA;

Neste caso seriam necessários os famosos ponteiros para os endereçõs de memória mais 16 bytes para a informação texto (lembrando que cada String tende a ter um terminador…). Com o Cache intern o java faz com que as mesmas String ocupem apenas 7 bytes. Economia de mais de 50% neste exemplo.
A Lógica vem de que a String é “imutável”. O ponteiro de a, supomos inicia na posição 1000, o ponteiro de b inicia na posição 1002 e a de c na posição 1003.

E até onde eu me lembro, o cache é implementado segundo o conceito de LRU (http://en.wikipedia.org/wiki/Cache_algorithms).

fw

D

No caso de variáveis sim, estava referenciando constantes. Essas são tratadas de forma diferente:

String a = "ANA";
"SACANA".equals(a);

(new StringBuilder()).append(a).append("SACANA");

A constante "SACANA" é tratada como uma única instância String.

[url]http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html[/url]

"(...) The Java programming language requires that identical string literals (that is, literals that contain the same sequence of characters) must refer to the same instance of class String (...)"

Abraços

T

De qualquer maneira, a principal otimização que pode ser feita com strings é o uso de StringBuffer (<= 1.4) ou StringBuilder (>= 5.0) quando for possível e razoável.
O uso do StringBuilder, no entanto, deve ser feito corretamente para evitar código incompreensível, já que normalmente só vale a pena se houver mais de 5 ou 6 concatenações numa mesma string, e essas concatenações não puderem ser resolvidas com o operador “+” em uma mesma expressão. Ou seja, é melhor você usar:

String sql = "INSERT "
   + "INTO  "
   + nomeTabela
   + " "
   + " ( " 
   + campo1 
   + ","
   + ....;

que isto aqui:

// Evitar
String sql = "INSERT "
   sql += "INTO  ";
   sql += nomeTabela;
   sql += " ";
   sql += " ( " ;
   sql += campo1 ;
   sql += ",";
   sql += ...

ou isto aqui:

// Relativamente rápido, mas incompreensível
StringBufferl sbSql = new StringBuffer();
   sbSql.append ("INSERT ");
   sbSql.append ("INTO  ");
   sbSql.append (nomeTabela);
   sbSql.append (" ");
   sbSql.append (" ( ") ;
   sbSql.append (campo1) ;
   sbSql.append (",");
   sbSql.append (...
   String sql = sbSql.toString();
ViniGodoy

Isso sem falar no clássico erro:

StringBuilder sb = new StringBuilder(); sb.append("E os valores são: " + var1 + var2 + var3 + var4 + var5 + var6); return sb.toString();

Vocês não tem idéia de quantas vezes já vi isso em códigos por aí…

victorwss

ViniGodoy:
Isso sem falar no clássico erro:

StringBuilder sb = new StringBuilder(); sb.append("E os valores são: " + var1 + var2 + var3 + var4 + var5 + var6); return sb.toString();

Vocês não tem idéia de quantas vezes já vi isso em códigos por aí…

Hehe, isso é comum mesmo. Muitas vezes você faz e nem percebe. Só no dia seguinte que você vê e tenta imaginar no que você estava pensando quando escreveu isso.

LPJava

como Martin Fowler diz na primeira vez vc apenas digita, na segunda vc melhora e na terceira vc refatora :slight_smile:

sergiotaborda

Não. “abc” e “def” têm o mesmo length. Só se o tamanho é igual é que se tem que passar a uma analise caracter a caracter. Os outros testes são otimizações. O segundo teste tb teste o null. Nem sempre vai ser uma string

Comfronte com isto:

// compra se duas coleções são iguais.
// elas são iguais se contêm os mesmos itens
public boolean equals ( Collection a , Collection b){
     if ( a == b ){ // null == null 
          return true;
     }  

     if (a.size()!=b.size()){
        return false;
     } 
     
     // compara item a item.
// so posso fazer deta forma porque
// sei que o numero de elementos é o mesmo
     for (Iterator it = a.iterator();it.hasNext();){
          if (!b.contains(it.next)){
                return false;
          }
     }
     return true;
}
sergiotaborda

e que tal isto aqui :

// rápido, mas compreensível
StringBuffer sbSql = new StringBuffer()
   .append ("INSERT INTO  ")
   .append (nomeTabela)
   .append (" ( ") 
   .append (campo1) 
   .append (",")
   .append (...

   String sql = sbSql.toString();

StringBuilder/Buffer é o cara. Usar + é gamb.

S

sergiotaborda:
ViniGodoy:

No caso do equals, nessa situação específica, você vai ter um processo lijeiramente mais longo, pois serão feitas três comparações. A primeira, testa se a string passada no parâmetro é a própria instância (o que nunca vai ocorrer). A segunda, testa se o objeto passado por parâmetro é realmente um String (o que sempre vai ser). E a terceira, finalmente, testa se o tamanho das Strings bate (o que só será verdade para um string vazio).

Não. “abc” e “def” têm o mesmo length. Só se o tamanho é igual é que se tem que passar a uma analise caracter a caracter. Os outros testes são otimizações. O segundo teste tb teste o null. Nem sempre vai ser uma string

Ué, mas ele não disse que você terá uma comparação caractere a caractere.

sergiotaborda

Schuenemann:
sergiotaborda:
ViniGodoy:

No caso do equals, nessa situação específica, você vai ter um processo lijeiramente mais longo, pois serão feitas três comparações. A primeira, testa se a string passada no parâmetro é a própria instância (o que nunca vai ocorrer). A segunda, testa se o objeto passado por parâmetro é realmente um String (o que sempre vai ser). E a terceira, finalmente, testa se o tamanho das Strings bate (o que só será verdade para um string vazio).

Não. “abc” e “def” têm o mesmo length. Só se o tamanho é igual é que se tem que passar a uma analise caracter a caracter. Os outros testes são otimizações. O segundo teste tb teste o null. Nem sempre vai ser uma string

Ué, mas ele não disse que você terá uma comparação caractere a caractere.

??

S

Você disse “Não” e “Só se o tamanho é igual é que se tem que passar a uma analise caracter a caracter”, sendo que isto não foi dito. O que você negou, afinal?

sergiotaborda

Nem sempre vai ser : null.

Não é verdade “que só será verdade para um string vazio”. Dai o exemplo de “abc” e “def”. O teste é feito porque se o tamnho das strings é diferente com certeza elas são diferentes, mas se os tamanhos são iguais tem que analizar caracter a carater. Dai a comparação com a analize que se faz para coleções ( afinal um String é uma coleção de char)

Paulo_Silveira

ViniGodoy:
Isso sem falar no clássico erro:

StringBuilder sb = new StringBuilder(); sb.append("E os valores são: " + var1 + var2 + var3 + var4 + var5 + var6); return sb.toString();

Vocês não tem idéia de quantas vezes já vi isso em códigos por aí…

Esse codigo nao tem problema, o compilador é esperto e substitui pelo uso de StringBuilder.
O problema de usar + em concatenacao de String é em geral só dentro de laco.

Paulo_Silveira

sergiotaborda:

// rápido, mas compreensível
StringBuffer sbSql = new StringBuffer()
   .append ("INSERT INTO  ")
   .append (nomeTabela)
   .append (" ( ") 
   .append (campo1) 
   .append (",")
   .append (...

   String sql = sbSql.toString();

StringBuilder/Buffer é o cara. Usar + é gamb.

Oi Sergio. Esse é outro caso que usar + não seria gambi: como esta fora de um laco, é muito facil e o compilador detecta que o StringBuilder aqui poderia ter sido usado totalmente sem problemas. Alias, como ele usaria StringBuilder, esse código abaixo é mais rapido que o seu: :slight_smile:

// rápido, mas compreensível
String sql = "INSERT INTO  " + nomeTabela + " ( " + campo1 + ",";

Early optimization is the root of all evil…

B

Isso era verdade antes do Java 6, o tratamento de Strings foi otimizado nesta versão.

Porém, ainda há um caso em que StringBuffer/Builder é melhor:

StringBuffer buffer = new StringBuffer(NUMERO_DE_CARACTERES_ESPERADO);

Esse número tem que ser perfeitamente suficiente p/ que StringBuffer não aloque mais memória durante a execução.

Paulo_Silveira

Isso era verdade antes do Java 6, o tratamento de Strings foi otimizado nesta versão.

Mesmo assim, ainda nao deve ser usado para concatenar dentro de um laco se a string que voce esta mexendo esta referenciada fora do laco! Ele geraria milhares de StringBuilders, estressando o GC. Fica uma carroca

B

Aí não tem versão de Java que resolva né? É problema de estrutura de código, não da linguagem.

Paulo_Silveira

Aí não tem versão de Java que resolva né? É problema de estrutura de código, não da linguagem.

Perfeito, isso mesmo.

Foxlol

Peraee!!!

Dexa eu ver se eu entendi ou to boiando.

Quer dizer que depois do Java 6, ao usar “+” para concatenar String, o compilador substitui automaticamente para StringBuilder?

O.o

Paulo_Silveira

Foxlol:
Peraee!!!

Dexa eu ver se eu entendi ou to boiando.

Quer dizer que depois do Java 6, ao usar “+” para concatenar String, o compilador substitui automaticamente para StringBuilder?

O.o

Desde o Java 1.0 é usando o StringBuffer.

O problema é que a primeira vez ele da new em String Buffer. Se estiver dentro de um loop, ja viu… Qual eh a solucao? Dar new FORA do laco, e dentro so chamar append. Mas isso ai o compilador nao faz.

Foxlol

Paulo Silveira:
Foxlol:
Peraee!!!

Dexa eu ver se eu entendi ou to boiando.

Quer dizer que depois do Java 6, ao usar “+” para concatenar String, o compilador substitui automaticamente para StringBuilder?

O.o

Desde o Java 1.0 é usando o StringBuffer.

O problema é que a primeira vez ele da new em String Buffer. Se estiver dentro de um loop, ja viu… Qual eh a solucao? Dar new FORA do laco, e dentro so chamar append. Mas isso ai o compilador nao faz.

Eita, não sabia dessa!

Então dá pra concatenar Strings com “+” sem problemas, desde que não esteja em um loop. Ai tem que tomar este cuidade que vc falou.

Vlw

ViniGodoy

sergiotaborda:
ViniGodoy:

No caso do equals, nessa situação específica, você vai ter um processo lijeiramente mais longo, pois serão feitas três comparações. A primeira, testa se a string passada no parâmetro é a própria instância (o que nunca vai ocorrer). A segunda, testa se o objeto passado por parâmetro é realmente um String (o que sempre vai ser). E a terceira, finalmente, testa se o tamanho das Strings bate (o que só será verdade para um string vazio).

Não. “abc” e “def” têm o mesmo length. Só se o tamanho é igual é que se tem que passar a uma analise caracter a caracter. Os outros testes são otimizações. O segundo teste tb teste o null. Nem sempre vai ser uma string

Sérgio, estou falando do teste:

!string.equals("")

Que foi proposto pela Lina, no início do tópico. Não tem absolutamente nada a ver com coleções.

E, como mostrado no código fonte da classe String (está no início do tópico), são feitas 3 comparações, sendo 2 delas desnecessárias.

A primeira, verifica se o objeto recebido no parâmetro e o this são exatamente o mesmo objeto. No caso desse exemplo, as chances disso ocorrer são ridiculamente pequenas.

O segundo teste, verifica se o objeto recebido é realmente uma String. Da forma como a Lina usa isso sempre vai ser verdade. “” nunca será nulo e sempre será uma String. Não estou criticando o mérito desse teste, no caso de um método equals de verdade, ele é necessário. Mas o que ela quer não é testar igualdade, mas se um determinado String está vazio ou não.

Só então, é feito um cast, uma atribuição a uma variável “n” e uma comparação em para só então ocorrer a comparação com o tamanho, que você falou. Por favor, leia o tópico com mais atenção da próxima vez. Aliás, em momento algum falei sobre ser mais lento por haver comparação caracter a caracter.

A forma que eu recomendei foi o simples de tamanho == 0. Essa forma é, sim, mais rápida, já que poupa os dois testes iniciais que só são necessários e servem de otimização no caso do equals.

Mas, como eu ressaltei e volto a ressaltar, não adianta otimizar código se você não estiver num ponto que constitui um gargalo em sua aplicação.

B
package testes;

public class TesteStrings {

    private static final long ITERACOES = 100*1000*1000;
    private static final String TESTE_VAZIO = "";
    private static final String TESTE_NAO_VAZIO = "        ";
        
    public static void main (String[] args)
    {
        teste(TESTE_VAZIO, 10, ITERACOES);
        teste(TESTE_NAO_VAZIO, 10, ITERACOES);
    }
    
    private static void teste(String s, int loops, long iter)
    {
        long r1, r2, r3, r4;

        r1 = r2 = r3 = r4 = 0;
        
        for (int n = 0; n < loops; n++)
        {
            r1 += testeLength(s, iter);
            r2 += testeEquals1(s, iter);
            r3 += testeEquals2(s, iter);
            r4 += testeIsEmpty(s, iter);
            
        }
        
        System.out.println("String \"" + s + "\"");
        System.out.println("Média em " + loops + " loops:");
        System.out.println("s.length()   " + r1/loops);
        System.out.println("s.equals(\"\") " + r2/loops);
        System.out.println("\"\".equals(s) " + r3/loops);
        System.out.println("s.isEmpty()  " + r4/loops + "\n");
    }

    private static long testeLength(String s, long iteracoes)
    {
        long t1, t2, i;
        
        t1 = System.currentTimeMillis();
        for (i = 0; i < iteracoes; i++)
            if (s.length() == 0);
        t2 = System.currentTimeMillis();
        
        return t2 - t1;        
    }
    
    private static long testeEquals1(String s, long iteracoes)
    {
        long t1, t2, i;
        
        t1 = System.currentTimeMillis();
        
        for (i = 0; i < iteracoes; i++)
            if (s.equals(""));
        
        t2 = System.currentTimeMillis();
        
        return t2 - t1;        
    }

    private static long testeEquals2(String s, long iteracoes)
    {
        long t1, t2, i;
        
        t1 = System.currentTimeMillis();
        
        for (i = 0; i < iteracoes; i++)
            if ("".equals(s));
        
        t2 = System.currentTimeMillis();
        
        return t2 - t1;        
    }
    
    private static long testeIsEmpty(String s, long iteracoes)
    {
        long t1, t2, i;
        
        t1 = System.currentTimeMillis();
        
        for (i = 0; i < iteracoes; i++)
            if (s.isEmpty());
        
        t2 = System.currentTimeMillis();
        
        return t2 - t1;        
    }
}

100 milhões de iterações, em milissegundos:

String vazia
Média em 10 loops:
s.length()   317
s.equals("") 993
"".equals(s) 1022
s.isEmpty()  390

String não vazia
Média em 10 loops:
s.length()   351
s.equals("") 1396
"".equals(s) 1521
s.isEmpty()  496

Edit 2: Corrigido o benchmark
Edit 3: Refatorado o benchmark

B

Editei o post acima.

ViniGodoy

Tem algo errado nesse seu benchmark.

Veja só, o método isEmpty está implementado assim:
return count == 0;

Isso é exatamente o que faz o seu teste com length.
Alguém saberia explicar pq ele tem a mesma performance do equals?

De qualquer forma, ele vem a provar que a performance desses métodos é tão boa, que dificilmente eles ocasionariam problemas de performance para essa situação.

thegoergen

Erro na linha 16!

System.out.println("s.isEmpty()  " + testeEquals2(TESTE, ITERACOES) + "\n");

Você está chamando o método testeEquals2 e não o testeIsEmpty…

B

Corrigi o benchmark. Agora ele faz só médias.

sergiotaborda

ViniGodoy:
sergiotaborda:
ViniGodoy:

No caso do equals, nessa situação específica, você vai ter um processo lijeiramente mais longo, pois serão feitas três comparações. A primeira, testa se a string passada no parâmetro é a própria instância (o que nunca vai ocorrer). A segunda, testa se o objeto passado por parâmetro é realmente um String (o que sempre vai ser). E a terceira, finalmente, testa se o tamanho das Strings bate (o que só será verdade para um string vazio).

Não. “abc” e “def” têm o mesmo length. Só se o tamanho é igual é que se tem que passar a uma analise caracter a caracter. Os outros testes são otimizações. O segundo teste tb teste o null. Nem sempre vai ser uma string

Sérgio, estou falando do teste:

!string.equals("")

Que foi proposto pela Lina, no início do tópico. Não tem absolutamente nada a ver com coleções.

E, como mostrado no código fonte da classe String (está no início do tópico), são feitas 3 comparações, sendo 2 delas desnecessárias.

A primeira, verifica se o objeto recebido no parâmetro e o this são exatamente o mesmo objeto. No caso desse exemplo, as chances disso ocorrer são ridiculamente pequenas.

Sim. Mas vc escreveu “o que nunca vai ocorrer” :stuck_out_tongue: lol
Agora eu entendi que vc estava falando do caso especifico de string.equals(""). Peço desculpas se o ofendi.

thegoergen

Interessante… o

"".equals(s)

Demora mais que o

s.equals("")

Por que isso??

pgioseffi

Foxlol:
Com a primeira opção vc estará criando uma String a mais na memória.
Já com a segunda é apenas uma comparação com um tipo primitivo.

Foxlol,

Conforme sugere o método java.lang.String#intern(), acredito que no caso específico da String “”, ela já esteja na memória através do pool de Strings mantido pela própria classe, não onerando assim a memória ao instanciar mais um objeto.

pgioseffi

ViniGodoy:
O length é mais rápido por se tratar apenas da leitura de um atributo e da comparação no seu if, observe (esses códigos foram retirados da classe String):

public int length() {
        return count;
    }

No caso do equals, nessa situação específica, você vai ter um processo lijeiramente mais longo, pois serão feitas três comparações. A primeira, testa se a string passada no parâmetro é a própria instância (o que nunca vai ocorrer). A segunda, testa se o objeto passado por parâmetro é realmente um String (o que sempre vai ser). E a terceira, finalmente, testa se o tamanho das Strings bate (o que só será verdade para um string vazio).

public boolean equals(Object anObject) {
   if (this == anObject) { //Comparação 1
      return true;
   }
   if (anObject instanceof String) {  //Comparação 2
      String anotherString = (String)anObject;    
      int n = count;
      if (n == anotherString.count) {    //Comparação 3
         char v1[] = value;
         char v2[] = anotherString.value;
         int i = offset;
         int j = anotherString.offset;
         while (n-- != 0) {
            if (v1[i++] != v2[j++])
               return false;
      }
      return true;
   }
}
	return false;
    }

Usando o equals é ainda mais recomendado colocar a constante na frente, assim:

if (!"".equals(string))

Isso dá uma vantagem adicional, pois o código também falha caso a string seja nula. Nos seus exemplos, você acabaria com um NullPointerException.

Agora, não creio que isso seja otimização de código, a menos que você esteja num loop realmente intenso, comparando milhares de Strings por segundo. Provavelmente haverá outros locais muito piores na sua aplicação. Não adianta “otimizar” pontos fora desses gargalos. Você só aumentará o tempo ocioso do sistema, que espera o gargalo passar. Sempre que se falar em otimização, rode um profiler, como o do Netbeans, e identifique os pontos de baixa performance. Otimize apenas esses pontos.

Otimização tem um custo: ela torna o código mais complexo e muito menos flexível. E deve ser uma preocupação apenas nos momentos de projeto e nos pontos críticos identificados no código.

ViniGodoy,

Conforme falei acima, se vc olhar o javadoc do método java.lang.String#intern(), a primeira comparação se faz necessária, pois o objeto pode estar no pool de Strings mantido pela própria classe.

Quanto a vc afirmar que a segunda comparação tbm será sempre verdadeira, tbm discordo… O método equals espera um Object, então qualquer coisa pode ser passada e em um projeto com muitos estagiários você pode ver a invocação do método equals de uma String recebendo como parâmetro um Double, por exemplo.

Já a terceira comparação não entendi pq vc afirmou ser possível somente para String vazia.

ViniGodoy

Observe que falei que é “nessa situação específica”. Ele estava querendo testar se uma string era vazia através da comparação “”.equals(string) - Aliás, isso também é explicado para o sergio taborda bem no início do post onde vc fez quote.

No caso dessa comparação é muitíssimo raro que a string recebida seja igual idêntica a String vazia (passe no ==). O método intern pode garantir isso, mas ele também exige custo computacional, e muito maior que o do equals. Como ele também disse que passaria uma string por parâmetro, sabíamos que não se tratava de outra classe.

É por isso mesmo que o método equals é mais lento que o length para testar String vazia. Por que ele:
a) Se previne contra o uso incorreto (já que pode receber um object);
b) Testa se os objetos são idênticos (o que normalmente não vai ocorrer).

No caso, o length não precisa fazer nada disso.

Em todo caso, desde o Java 6, essa dúvida já não existe mais. Inseriram o método isEmpty() na classe String. Por isso que não é muito saudável ressuscitar discussões de 2008. E, lógico, sua implementação baseia-se no length().

Não estou falando que a implementação do equals é errada. Na verdade, esses fatores são otimizações para quando se quer testar se duas strings são iguais. Porém, isso não quer dizer que elas sejam eficientes para testar o vazio. E, como eu já disse outras vezes no tópico, a diferença de 1 if para 3 ifs em termos de performance é apenas acadêmica pois, na prática, dificilmente isso aí constituirá um gargalo de performance em qualquer aplicação comercial.

Criado 25 de abril de 2008
Ultima resposta 24 de mai. de 2011
Respostas 44
Participantes 16