Use isEmpty em vez de equals

04:58 Vinicius Knob 1 Comments


 "A perfeição é feita de pequenos detalhes - não é apenas um detalhe." (Michelangelo)

Gostaria de começar esse post ressaltando a famosa frase de Michelangelo ditada acima que fala sobre "detalhes". Em minha busca por conhecimento dedico meu tempo justamente a isso, detalhes. Não basta para mim ter a solução em mãos, é preciso mais, é preciso saber como chegar até esta solução, cada detalhe da implementação de um algoritmo é o que o faz ter usabilidade, flexibilidade e clareza, porém nesse post quero tratar de algo mais interessante, um detalhe que envolve desempenho.

Assim como qualquer linguagem, Java tem seus detalhes de implementação que gosto de explorar. Nesse post quero mostrar o porque deve ser usado isEmpty() em vez de equals(""). Primeiramente vamos a documentação de ambos os métodos:

isEmpty - "Returns true if, and only if, length() is 0".
equals - "Compares this string to the specified object. The result is true if and only if the argument is not null and is a String object that represents the same sequence of characters as this object".

Pela documentação de cada método já temos uma simples noção de implementação onde que isEmpty utiliza um atributo privado para verificar se é igual a zero, apenas isso enquanto equals utiliza verificação de argumento nulo e sequencia de caracteres, o que com certeza é uma implementação mais penosa para um caso tão simples como a verificação de uma string vazia. Equals abrange verificação para casos mais complexos. Vamos ao código de cada método:

isEmpty
public boolean isEmpty() {
    return count == 0;
}
equals
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String) anObject;
        int n = count;
        if (n == anotherString.count) {
            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;
}

Como esperado, o método equals é dedicado a comparação de strings diversas, enquanto o método isEmpty ataca um problema específico onde posso apenas querer saber se essa string esta vazia ou não.

Desempenho


Para o teste de desempenho fiz dois testes com jUnit, cada um atacando um dos métodos. Cada teste possui um loop de 100 milhões de iterações mostrando uma diferença de tempo considerável. Acredito que grandes empresas com sites que superam acessos de 1 milhão de pessoas rodando diversas classes contendo diversas vezes uma comparação utilizando equals("") resulte em algo semelhante, senão pior.

public class StringEmptyTest {
    
    @Test
    public void isEmpty() {
        String test = "";
        for (int i = 0; i < 100000000; i++) {
            test.isEmpty();
        }
    }
    
    @Test
    public void equals() {
        String test = "";
        for (int i = 0; i < 100000000; i++) {
            test.equals("");
        }
    }

}


No resultado, isEmpty() foi 5,6 vezes menor em seu tempo de execução.

Conclusão


Quando Michelangelo cunhou sua frase, assim como tantas outras, o contexto era outro obviamente, mas programar não deixa de ser uma arte e assim como no contexto de Michelangelo, nós no nosso contexto precisamos entender o quanto um detalhe é importante. Com certeza utilizar isEmpty ou equals em um programa com poucos acessos não fará diferença, o que pretendo dizer com esse post é justamente o que a frase de Michelangelo diz "A perfeição é feita de pequenos detalhes...", junte esse detalhe, mais aquele outro e mais um e terá uma melhoria de desempenho. 

1 comentários:

HashCode.java: uma alternativa

12:05 Vinicius Knob 0 Comments

Em uma das minhas últimas aquisições obtive o livro Java Efetivo de Joshua Bloch, uma referência para quem conhece Java e quer melhorar seu entendimento sobre detalhes da linguagem. O item 9 do 3º capítulo é um tanto interessante, explicando regras de sobreposição do método hashCode. Não vou transcrever aqui o que fala o livro, mas quero deixar uma alternativa que acho interessante para geração de hashCodes quando ocorre a sobreposição do método levando em consideração o que fala no livro. Conceitos como flexibilidade e manutenibilidade e até o princípio da Responsabilidade Única foram levados em consideração, já no quesito desempenho será apresentado uma alternativa.

O livro fala sobre como se deve implementar a conversão de atributos de uma classe em hashCode, o cálculo e o tratamento, mas sempre aprendi que a lógica de uma parte quando pode viver isolada deve ser isolada, isso aumenta a flexibilidade do sistema e consequentemente a manutenibilidade, com isso, a alternativa que apresento é uma classe HashCode contendo toda a lógica para anexar os atributos e obter um hashCode. Segue a classe:

public final class HashCode {
    
    private HashCode() {}
    
    public static HashCode create() {
        return new HashCode();
    }
    private int _result = 17;
    
    public HashCode append(int value) {
        _result = 31 * _result + value;
        return this;
    }
    public HashCode append(boolean value) {
        return append(value?1:0);
    }
    public HashCode append(byte value) {
        return append((int)value);
    }
    public HashCode append(char value) {
        return append((int)value);
    }
    public HashCode append(short value) {
        return append((int)value);
    }
    public HashCode append(long value) {
        return append((int)(value^(value>>>32)));
    }
    public HashCode append(float value) {
        return append(Float.floatToIntBits(value));
    }
    public HashCode append(double value) {
        return append(Double.doubleToLongBits(value));
    }
    public HashCode append(Object value) {
        return append(value==null?0:value.hashCode());
    }
    @Override
    public int hashCode() {
        return _result;
    }
}

Perceba que utilizei alguns outros conceitos como um construtor estático, sobrecarga e fluent interfaces. A classe é final, pois acredito que ela não deva ser estendida e o próprio método hashCode de Object é sobrescrito aqui para retornar o resultado final.

Usabilidade


No caso de atributos onde não exista uma coleção o código ficaria como mostrado no exemplo abaixo.

@Override 
public int hashCode() {
    return HashCode.create()
            .append(_name) // String
            .append(_url) // URL
            .append(_descriptiveStatus) // String
            .append(_timer) // long
            .append(_effect) // boolean
            .hashCode();
}

Já no caso de uma coleção, preferi não implementar nada, pois o que deve ser levado em conta numa coleção somente cada programador saberá, lembrando que no caso de arrays a classe Arrays possui vários métodos para geração de hashCodes, vale a pena conferir. Quando existir uma coleção, prefiro iterar sobre ela e obter os atributos que considero melhor. Abaixo um exemplo:

@Override
public int hashCode() {
    HashCode result = HashCode.create();
    result.append(_nameBedroom); // String
    Enumeration<String> keys = _bedroom.keys();
    while (keys.hasMoreElements()) {
        String key = (String) keys.nextElement();
        result.append(_bedroom.get(key).hashCode()); // int
    }
    return result.hashCode();
}

Desempenho


Para testar o desempenho do código com e sem a classe HashCode fiz dois testes com jUnit, ambos efetuam o mesmo calculo, cada um sobre um for de 100 milhões de loops que proporciona uma visão mais humana, um dos testes não utiliza a classe HashCode, o outro sim.


Percebe-se a diferença de tempo, quase o dobro do tempo para executar o mesmo cálculo quando se utiliza  a classe HashCode. This is bad!!!

public class HashCodeTest {
    
    String nome = "Vinicius Knob";
    int anoNascimento = 1989;
    long dataHoje = Calendar.getInstance().getTimeInMillis();
    boolean java = true;

    @Test
    public void testSemClasse() {
        for (int i = 0; i <= 100000000; i++) {
            int result = 17;
            result = 31 * result + nome.hashCode();
            result = 31 * result + anoNascimento;
            result = 31 * result + (int)(dataHoje^(dataHoje>>>32));
            result = 31 * result + (java?1:0);
        }
    }
    
    @Test
    public void testComClasse() {
        for (int i = 0; i <= 100000000; i++) {
            HashCode.create().append(nome)
                .append(anoNascimento).append(dataHoje)
                .append(java).hashCode();
        }
    }

}

Em um sistema onde o método hashCode é muito utilizado e/ou possui um cálculo custoso é necessário fazer uma escolha: não utilizar a classe HashCode ou utilizar o conceito de cache e armazenar na própria classe consumidora de HashCode um atributo contendo o hashCode gerado na primeira execução. Isso ajudaria muito, pois somente na primeira vez o hashCode seria gerado, enquanto nas próximas apenas consumido. Cuidado, caches desse tipo não são uma boa alternativa quando se trata de uma classe mutável, já que o hashCode pode estar baseado em atributos que são alterados entre uma solicitação e outra do método hashCode. Desempenho sempre é uma questão delicada.


Considerável ganho de desempenho quando utilizado cache e uma diferença de 3ms quando utilizado a classe HashCode.

public class HashCodeTest {
    
    String nome = "Vinicius Knob";
    int anoNascimento = 1989;
    long dataHoje = Calendar.getInstance().getTimeInMillis();
    boolean java = true;

    @Test
    public void testSemClasse() {
        int cacheResult = 0;
        for (int i = 0; i <= 100000000; i++) {
            if (cacheResult == 0) {
                int result = 17;
                result = 31 * result + nome.hashCode();
                result = 31 * result + anoNascimento;
                result = 31 * result + (int)(dataHoje^(dataHoje>>>32));
                result = 31 * result + (java?1:0);
                cacheResult = result;
            }
        }
    }
    
    @Test
    public void testComClasse() {
        int cacheResult = 0;
        for (int i = 0; i <= 100000000; i++) {
            if (cacheResult == 0) {
                cacheResult = HashCode.create().append(nome)
                    .append(anoNascimento).append(dataHoje)
                    .append(java).hashCode();
            }
        }
    }

}

Conclusão


Essa alternativa atende alguns requisitos para proporcionar flexibilidade e manutenibilidade ao código, porém seria interessante fornecer um atributo para servir como cache em casos onde cálculos exigem muito processamento, mesmo sem utilizar a classe. Alguns críticos podem considerar ela totalmente desnecessária, eu vejo como uma classe encapsuladora de um padrão documentado e que tecnicamente poderia ser seguido dessa forma. Alternativas para essa classe poderia proporcionar melhoria a flexibilidade ou até ao desempenho, uma estrutura diferente com nomes diferentes também é aceitável, mas tudo depende sempre de conhecimento, o que busco incansavelmente.

0 comentários:

Minicurso na SenaInfo: Código bom é código limpo

07:05 Vinicius Knob 1 Comments

Quando fui convidado para criar um minicurso logo pensei em algo que eu pudesse passar aos outros e que também fosse proveitoso. Falar sobre boas práticas ao nível de código parecia algo fácil, parecia, mas percebi que diante de outras pessoas eu acabo tendo vários "apagões", quebras de pensamento que quando estava em casa treinando não tinha.


Mesmo ocorrendo vários pequenos problemas para que esse minicurso realmente existisse, eu fui preparado para muitas pessoas. O que aconteceu? Bom, eu tinha uma turma de 4 pessoas, duas conhecidas e outras duas que acabei por conhecer. Minha conclusão? Foi bom. Ter poucas pessoas me permitiu ficar calmo e apesar dos apagões, passar alguma coisa para eles.


O lado bom de enfiar a cara em algo que eu tinha apenas uma pequena noção é que todo erro se torna um grande aprendizado para as próximas vezes (tomara que aconteça =D). Então enumerei algumas coisas que aprendi com esse minicurso:

Título: O título é muito importante, pois determina o nível que sua turma vai ter, bem como apresenta o assunto específico, um bom título ti tras justamente as pessoas que você quer ter. Se eu pudesse trocar o título desse minicurso colocaria "Boas Práticas para desenvolvedores Java".

Assunto: O assunto a ser abordado deve ser específico. Tentei abraçar mais do que deveria e por fim a teoria se tornou extensa e o exercício inviável.

Público: Esse foi um grande problema, apesar de a própria organização do evento SenaInfo me deixar na mão por não publicar meu minicurso no site, ainda por cima, mesmo que tivesse publicado, não havia em lugar algum detalhes dos minicursos contendo dados como público alvo, foco, assunto e afins que poderiam eleger um público mais propicio ao minicurso. Enfim, aqui um título melhor poderia fortalecer o público.

Agradeço aos que estiveram no minicurso, peço desculpas também por qualquer coisa que deixei passar ou por qualquer atitude que demonstrou despreparo. Uma grande experiência se mostrou nesse momento permitindo evolução, porém quero evoluir ainda mais.


1 comentários:

SenaInfo: Computação Soberana com Klaus Wuestefeld

08:10 Vinicius Knob 2 Comments


Essa semana está acontecendo na UPF (Universidade de Passo Fundo) a SenaInfo (Semana Acadêmica de Informática) e grandes nomes estarão palestrando nesse evento. Ontem, dia 04 de setembro foi o dia de Klaus Wuestefeld falar sobre Computação Soberana.

Palco de palestras da SenaInfo

Vários colegas tanto de faculdade quanto do trabalho estavam presentes. Abaixo a foto tirada com o amigo Édipo Federle.



Cerca de um ano acompanho o projeto Prevayler, tendo já utilizado, mesmo que para projetos pessoais, as versões 1.2 e 2.3 e, a pouco tempo, o Sneer. Admiro Klaus pelo seu conhecimento apresentado tanto em posts de sua autoria como em vídeos espalhados pela internet e, estar lá no mesmo ambiente participando de uma palestra do próprio Klaus foi demais.
A palestra teve como assunto Computação Soberana e tivemos, eu e alguns colegas da área, a oportunidade de um "brainstorming" com a fera ao final da palestra, saiu até uma foto! =D

Édipo, Maurício, Klaus, Robson e eu

Klaus, o que tenho a lhe dizer é que pessoas como você, buscadoras do conhecimento e da inovação, é que nos dão estímulo para sempre buscar mais. Nossa área precisa disso. Muito obrigado!

2 comentários:

Introdução

09:32 Vinicius Knob 1 Comments


Confesso que sempre tive medo de expor uma ideia e essa ideia se mostrar um fracasso ao ser aceita e implantada, porém sempre soube que isso é uma consequência de minha ganancia por perfeição. Ter o conhecimento exige paciência, exige dedicação, exige prática. Expor uma ideia deixou de significar medo e passou a significar necessidade a partir do momento que conheci outras pessoas como eu, pessoas que observam comportamentos e vêem, com isso, comportamentos aleatórios, atitudes possível e ignoradas. Estou falando de competência.

Iniciar um blog onde pretendo expor minhas ideias, meus conhecimentos, minhas atitudes e comportamentos tornou-se uma necessidade. Fazer com que o mundo saiba como sua mente funciona torna-se interessante quando outras pessoas te entendem e passam a precisar desse entendimento também.

Com esse blog quero passar minhas ideias, meus conhecimentos, minhas atitudes e comportamentos, quero que outras pessoas vejam e gostem dele, quero acreditar que isso é possível. Crescemos através do erro e da prática, o sucesso só mostra o resultado esperado, não trás conhecimento, trás apenas certeza sobre o que já sabíamos, o erro mostra o caminho aleatório, mostra aquilo que não esperávamos ou muitas vezes, o que temíamos. Tomara que eu erre! Espero sua cooperação, seja ela construtiva ou destrutiva.

1 comentários: