5 erros que 99% dos desenvolvedores Java cometem
Fonte:
Medium Neste post você conhecerá os erros mais comuns que muitos desenvolvedores Java cometem. Como 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);
System.out.println(Objects.equals(longValue,123));
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));
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());
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(()->{
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.
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);
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);
Interning pode ser usado com
o método firstName3
intern() , embora isso geralmente não seja o preferido.
firstName3 = firstName3.intern();
System.out.println(firstName3 == firstName2);
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");
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.
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));
System.out.println(firstName3.equals(firstName2));
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));
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);
System.out.println(lastName);
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);
GO TO FULL VERSION