JavaRush /Blogue Java /Random-PT /Pausa para café #146. 5 erros que 99% dos desenvolvedores...

Pausa para café #146. 5 erros que 99% dos desenvolvedores Java cometem. Strings em Java - visão interna

Publicado no grupo Random-PT

5 erros que 99% dos desenvolvedores Java cometem

Fonte: Medium Neste post você conhecerá os erros mais comuns que muitos desenvolvedores Java cometem. Pausa para café #146.  5 erros que 99% dos desenvolvedores Java cometem.  Strings em Java - visão interna - 1Como programador Java, sei como é ruim gastar muito tempo corrigindo bugs no seu código. Às vezes isso leva várias horas. Porém, muitos erros aparecem devido ao fato do desenvolvedor ignorar regras básicas - ou seja, são erros de nível muito baixo. Hoje veremos alguns erros comuns de codificação e explicaremos como corrigi-los. Espero que isso ajude você a evitar problemas em seu trabalho diário.

Comparando objetos usando Objects.equals

Presumo que você esteja familiarizado com esse método. Muitos desenvolvedores o usam com frequência. Essa técnica, introduzida no JDK 7, ajuda a comparar objetos rapidamente e evitar efetivamente a irritante verificação de ponteiro nulo. Mas esse método às vezes é usado incorretamente. Aqui está o que quero dizer:
Long longValue = 123L;
System.out.println(longValue==123); //true
System.out.println(Objects.equals(longValue,123)); //false
Por que substituir == por Objects.equals() produziria o resultado errado? Isso ocorre porque o compilador == obterá o tipo de dados subjacente correspondente ao tipo de pacote longValue e então o comparará com esse tipo de dados subjacente. Isso é equivalente ao compilador converter automaticamente constantes para o tipo de dados de comparação subjacente. Depois de usar o método Objects.equals() , o tipo de dados base padrão da constante do compilador é int . Abaixo está o código fonte para Objects.equals() onde a.equals(b) usa Long.equals() e determina o tipo do objeto. Isso acontece porque o compilador assumiu que a constante era do tipo int , portanto o resultado da comparação deve ser falso.
public static boolean equals(Object a, Object b) {
        return (a == b) || (a != null && a.equals(b));
    }

  public boolean equals(Object obj) {
        if (obj instanceof Long) {
            return value == ((Long)obj).longValue();
        }
        return false;
    }
Sabendo o motivo, consertar o erro é muito simples. Basta declarar o tipo de dados das constantes, como Objects.equals(longValue,123L) . Os problemas acima não surgirão se a lógica for estrita. O que precisamos de fazer é seguir regras de programação claras.

Formato de data incorreto

No desenvolvimento diário, muitas vezes é necessário alterar a data, mas muitas pessoas usam o formato errado, o que leva a coisas inesperadas. Aqui está um exemplo:
Instant instant = Instant.parse("2021-12-31T00:00:00.00Z");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss")
.withZone(ZoneId.systemDefault());
System.out.println(formatter.format(instant));//2022-12-31 08:00:00
Isso usa o formato AAAA-MM-dd para alterar a data de 2021 para 2022. Você não deveria fazer isso. Por que? Isso ocorre porque o padrão Java DateTimeFormatter “YYYY” é baseado no padrão ISO-8601, que define o ano como a quinta-feira de cada semana. Mas 31 de dezembro de 2021 caiu numa sexta-feira, então o programa indica incorretamente 2022. Para evitar isso, você deve usar o formato aaaa-MM-dd para formatar a data . Este erro ocorre com pouca frequência, apenas com a chegada do ano novo. Mas na minha empresa causou uma falha na produção.

Usando ThreadLocal em ThreadPool

Se você criar uma variável ThreadLocal , um thread acessando essa variável criará uma variável local de thread. Dessa forma, você pode evitar problemas de segurança de thread. No entanto, se você estiver usando ThreadLocal em um pool de threads , precisará ter cuidado. Seu código pode produzir resultados inesperados. Para um exemplo simples, digamos que temos uma plataforma de e-commerce e os usuários precisam enviar um e-mail para confirmar a conclusão da compra de produtos.
private ThreadLocal<User> currentUser = ThreadLocal.withInitial(() -> null);

    private ExecutorService executorService = Executors.newFixedThreadPool(4);

    public void executor() {
        executorService.submit(()->{
            User user = currentUser.get();
            Integer userId = user.getId();
            sendEmail(userId);
        });
    }
Se usarmos ThreadLocal para salvar as informações do usuário, um erro oculto aparecerá. Como um conjunto de threads é usado e os threads podem ser reutilizados, ao usar ThreadLocal para obter informações do usuário, ele pode exibir erroneamente as informações de outra pessoa. Para resolver este problema, você deve usar sessões.

Use HashSet para remover dados duplicados

Ao codificar, muitas vezes temos a necessidade de desduplicação. Quando você pensa em desduplicação, a primeira coisa que muitas pessoas pensam é em usar um HashSet . No entanto, o uso descuidado do HashSet pode causar falha na desduplicação.
User user1 = new User();
user1.setUsername("test");

User user2 = new User();
user2.setUsername("test");

List<User> users = Arrays.asList(user1, user2);
HashSet<User> sets = new HashSet<>(users);
System.out.println(sets.size());// the size is 2
Alguns leitores atentos deverão ser capazes de adivinhar o motivo do fracasso. HashSet usa um código hash para acessar a tabela hash e usa o método equals para determinar se os objetos são iguais. Se o objeto definido pelo usuário não substituir o método hashcode e o método equals , o método hashcode e o método equals do objeto pai serão usados ​​por padrão. Isso fará com que o HashSet assuma que são dois objetos diferentes, causando falha na desduplicação.

Eliminando um thread de pool "comido"

ExecutorService executorService = Executors.newFixedThreadPool(1);
        executorService.submit(()->{
            //do something
            double result = 10/0;
        });
O código acima simula um cenário em que uma exceção é lançada no pool de threads. O código comercial deve assumir várias situações, portanto, é muito provável que ele gere uma RuntimeException por algum motivo . Mas se não houver tratamento especial aqui, essa exceção será “comida” pelo pool de threads. E você nem terá como verificar a causa da exceção. Portanto, é melhor capturar exceções no pool de processos.

Strings em Java - visão interna

Fonte: Medium O autor deste artigo decidiu dar uma olhada detalhada na criação, funcionalidade e recursos de strings em Java. Pausa para café #146.  5 erros que 99% dos desenvolvedores Java cometem.  Strings em Java - visão interna - 2

Criação

Uma string em Java pode ser criada de duas maneiras diferentes: implicitamente, como uma string literal, e explicitamente, usando a palavra-chave new . Literais de string são caracteres entre aspas duplas.
String literal   = "Michael Jordan";
String object    = new String("Michael Jordan");
Embora ambas as declarações criem um objeto string, há uma diferença em como esses dois objetos estão localizados na memória heap.

Representação interna

Anteriormente, as strings eram armazenadas no formato char[] , o que significa que cada caractere era um elemento separado na matriz de caracteres. Como eram representados no formato de codificação de caracteres UTF-16 , isso significava que cada caractere ocupava dois bytes de memória. Isso não é muito correto, pois as estatísticas de uso mostram que a maioria dos objetos string consiste apenas em caracteres Latin-1 . Os caracteres Latin-1 podem ser representados usando um único byte de memória, o que pode reduzir significativamente o uso de memória – em até 50%. Um novo recurso de string interno foi implementado como parte da versão JDK 9 baseada no JEP 254, chamado Compact Strings. Nesta versão, char[] foi alterado para byte[] e um campo de flag do codificador foi adicionado para representar a codificação usada (Latin-1 ou UTF-16). Depois disso, a codificação ocorre com base no conteúdo da string. Se o valor contiver apenas caracteres Latin-1, então a codificação Latin-1 será usada (a classe StringLatin1 ) ou a codificação UTF-16 será usada (a classe StringUTF16 ).

Alocação de memória

Conforme afirmado anteriormente, há uma diferença na forma como a memória é alocada para esses objetos no heap. Usar a palavra-chave new explícita é bastante simples, pois a JVM cria e aloca memória para a variável no heap. Portanto, o uso de uma string literal segue um processo chamado internamento. A internação de strings é o processo de colocar strings em um pool. Ele usa um método de armazenamento de apenas uma cópia de cada valor de string individual, que deve ser imutável. Os valores individuais são armazenados no pool String Intern. Este pool é um armazenamento Hashtable que armazena uma referência para cada objeto string criado usando literais e seu hash. Embora o valor da string esteja no heap, sua referência pode ser encontrada no pool interno. Isso pode ser facilmente verificado usando o experimento abaixo. Aqui temos duas variáveis ​​com o mesmo valor:
String firstName1   = "Michael";
String firstName2   = "Michael";
System.out.println(firstName1 == firstName2);             //true
Durante a execução do código, quando a JVM encontra firstName1 , ela procura o valor da string no conjunto de strings interno Michael . Se não conseguir encontrá-lo, uma nova entrada será criada para o objeto no pool interno. Quando a execução atinge firstName2 , o processo se repete novamente e desta vez o valor pode ser encontrado no pool com base na variável firstName1 . Dessa forma, ao invés de duplicar e criar uma nova entrada, o mesmo link é retornado. Portanto, a condição de igualdade é satisfeita. Por outro lado, se uma variável com o valor Michael for criada usando a palavra-chave new, não ocorre nenhum internamento e a condição de igualdade não é satisfeita.
String firstName3 = new String("Michael");
System.out.println(firstName3 == firstName2);           //false
Interning pode ser usado com o método firstName3 intern() , embora isso geralmente não seja o preferido.
firstName3 = firstName3.intern();                      //Interning
System.out.println(firstName3 == firstName2);          //true
A internação também pode ocorrer ao concatenar dois literais de string usando o operador + .
String fullName = "Michael Jordan";
System.out.println(fullName == "Michael " + "Jordan");     //true
Aqui vemos que em tempo de compilação, o compilador adiciona os literais e remove o operador + da expressão para formar uma única string, conforme mostrado abaixo. Em tempo de execução, tanto fullName quanto o “literal adicionado” são internados e a condição de igualdade é satisfeita.
//After Compilation
System.out.println(fullName == "Michael Jordan");

Igualdade

Nos experimentos acima, você pode ver que apenas literais de string são internados por padrão. Porém, uma aplicação Java certamente não terá apenas strings literais, pois poderá receber strings de diferentes fontes. Portanto, o uso do operador de igualdade não é recomendado e pode produzir resultados indesejáveis. O teste de igualdade só deve ser realizado pelo método equals . Ele executa a igualdade com base no valor da string e não no endereço de memória onde está armazenado.
System.out.println(firstName1.equals(firstName2));       //true
System.out.println(firstName3.equals(firstName2));       //true
Há também uma versão ligeiramente modificada do método equals chamada equalsIgnoreCase . Pode ser útil para fins que não diferenciam maiúsculas de minúsculas.
String firstName4 = "miCHAEL";
System.out.println(firstName4.equalsIgnoreCase(firstName1));  //true

Imutabilidade

Strings são imutáveis, o que significa que seu estado interno não pode ser alterado depois de criadas. Você pode alterar o valor de uma variável, mas não o valor da string em si. Cada método da classe String que trata da manipulação de um objeto (por exemplo, concat , substring ) retorna uma nova cópia do valor em vez de atualizar o valor existente.
String firstName  = "Michael";
String lastName   = "Jordan";
firstName.concat(lastName);

System.out.println(firstName);                       //Michael
System.out.println(lastName);                        //Jordan
Como você pode ver, nenhuma alteração ocorre em nenhuma das variáveis: nem firstName nem lastName . Os métodos da classe String não alteram o estado interno, eles criam uma nova cópia do resultado e retornam o resultado conforme mostrado abaixo.
firstName = firstName.concat(lastName);

System.out.println(firstName);                      //MichaelJordan
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION