Armadilhas do Java. Parte 1

Olá. Quero trazer à sua atenção um pequeno artigo. Este artigo é destinado a iniciantes. Mas mesmo se você é um desenvolvedor experiente, não tire conclusões precipitadas.
Espero que esta publicação seja útil não apenas para iniciantes.

O objetivo desta publicação:
Mostre os erros mais comuns para iniciantes e alguns truques para corrigi-los. É claro que alguns erros podem ser complexos e ocorrerem por um motivo ou outro. O objetivo da publicação é, em certa medida, analisá-los e ajudar a identificar em um estágio inicial. Espero que esta publicação seja útil para iniciantes.

Lista de verificação de erros:

1. Erros de digitação. Erros irritantes que não aparecem imediatamente
2. Atribuição em condição em vez de comparação
3. Erros lógicos na condição
4. Comparação incorreta de strings
5. Inicialização incorreta de variáveis ​​de tipos primitivos
6. Uso inadequado de dupla
7. Tipo errado de valor de retorno no construtor.
8. Divisão por zero. POSITIVE_INFINITY
9. Não levando em consideração a ordem de inicialização da classe
10. Uma variável local oculta uma variável de classe
11. Ignorando vazamento automático em expressões aritméticas
12. Loop infinito com byte, difícil de detectar.
13. O nome da classe é diferente do nome do arquivo em que está armazenada.
14. Objetos que são elementos de uma matriz não são inicializados.
15. Colocando várias classes em um arquivo ao mesmo tempo com o modificador público

Armadilhas Java


Todas as linguagens de programação têm suas vantagens e desvantagens. Isto é devido a muitas razões. Java não é exceção. Tentei coletar algumas dificuldades óbvias e não óbvias encontradas por um programador Java iniciante. Estou certo de que programadores experientes também encontrarão algo útil no meu artigo. Prática, atenção e experiência adquirida em programação ajudarão a salvá-lo de muitos erros. Mas alguns erros e dificuldades são melhor considerados com antecedência. Vou dar alguns exemplos com código e explicações. Muitas explicações ficarão claras para você a partir dos comentários no código. A prática dá muito, pois algumas regras não são tão óbvias. Alguns estão na superfície, outros estão escondidos nas bibliotecas de idiomas ou na máquina virtual java. Lembre-se de que java não é apenas uma linguagem de programação com um conjunto de bibliotecas, é também uma máquina virtual java.

Para o artigo, escrevi especificamente um código de trabalho com comentários detalhados. Para escrever um artigo com exemplos de código, foi utilizado o java 8. Para testar, o código java é colocado em pacotes separados.

Exemplo: "package underwaterRocks.simple;"

Que dificuldades os iniciantes enfrentam?

Erros de digitação


Acontece que programadores iniciantes cometem erros que são difíceis de detectar rapidamente.


Exemplo de código:

Arquivo: "Simple.java"

/*   ;     */ package underwaterRocks.simple; /** * * @author Ar20L80 */ public class Simple { public static void main(String[] args) { int ival = 10; if(ival>0); { System.out.println("     "); } } } 


Explicação : “O ponto e vírgula indica o final da declaração. Neste caso; É o fim de uma declaração vazia. Este é um erro lógico. Esse erro pode ser difícil de detectar.

O compilador considerará que tudo está correto. Condição if (ival> 0); neste caso, não faz sentido. Porque significa: se ival é maior que zero, não faça nada e continue. ”

Atribuição em condição em vez de comparação


A condição é atribuição variável.

Isso não é um erro, mas o uso de tal técnica deve ser justificado.

  boolean myBool = false; if(myBool = true) System.out.println(myBool); 

Nesse código, se (myBool = true) significa: "Defina a variável myBool como true,
se a expressão for verdadeira, siga a condição após os colchetes. "

Nesse código, a condição sempre será verdadeira. E System.out.println (myBool); sempre será executado, independentemente da condição.

== é uma comparação para igualdade.
= É uma tarefa, você pode dizer a = 10; como: "mas atribua um valor de 10".

A condição entre parênteses retorna um valor booleano.
Não importa em qual ordem você escreve. Você pode comparar assim: (0 == a) ou (5 == a)
Se você esquecer um sinal de igual, por exemplo (0 = a) ou (5 = a), o compilador notificará você sobre um erro. Você atribui um valor, não uma comparação.
Você também pode escrever de forma legível em algum intervalo.
Por exemplo: você precisa escrever: maior que 5 e menor que 10.
Você escreve assim: (a> 4 && a <10), mas com o mesmo sucesso você pode escrever: (4 <a && a <10),
agora você vê que a está entre 4 e 10, excluindo esses valores. Isto é mais óbvio. É imediatamente evidente que, a está no intervalo entre 4 e 10, excluindo esses valores.

Exemplo de código (intervalo] 3.9 [):
if (3 <a && a <9) é executado;

Erro lógico


if (condition) {} if (condition) {} else {} - else refere-se ao if mais próximo.
Isso geralmente é a causa de erros para iniciantes.

Comparação de string inválida

Os iniciantes costumam usar == em vez de .equals para comparar strings.

Inicialização variável


Considere inicializar variáveis ​​de um tipo primitivo.

Primitivas (byte, curto, int, longo, char, float, duplo, booleano).

Valores iniciais.

 byte 0 short 0 int 0 long 0L float 0.0f double 0.0d char '\u0000' String (or any object) null boolean false (  jvm) 


Nota:

Variáveis ​​locais são ligeiramente diferentes;
O compilador nunca atribui um valor padrão a uma variável local não inicializada.

Se você não pode inicializar sua variável local onde é declarada,
Lembre-se de atribuir um valor a ele antes de tentar usá-lo.

O acesso a uma variável local não inicializada resultará em um erro em tempo de compilação.

Confirmação desta nota no código:

Arquivo: “MyInitLocal.java”

 /*         */ package underwaterRocks.myInit; /** * * @author Ar20L80 */ public class MyInitLocal { float classes_f; int classes_gi; public static void main(String[] args) { float f; int i; MyInitLocal myInit = new MyInitLocal(); /*         .*/ System.out.println("myInit.classes_f = " + myInit.classes_f); System.out.println("myInit.classes_gi = " + myInit.classes_gi); // System.out.println("f = " + f); // .     // System.out.println("f = " + i); // .     } } 


Intervalos de valores:

byte ( , 1 , [-128, 127])
short ( , 2 , [-32768, 32767])
int ( , 4 , [-2147483648, 2147483647])
long ( , 8 , [-922372036854775808,922372036854775807])
float ( , 4 )
double ( , 8 )
char ( Unicode, 2 , 16 , [0, 65535])
boolean ( /, int, JVM)

char: o tipo de dados char é um único caractere Unicode de 16 bits. Ele tem um valor mínimo de '\ u0000' (ou 0) e um valor máximo de '\ uffff' (ou 65.535 inclusive).


Documentação Oracle >>

Vamos tentar inicializar uma variável do tipo long com o número: 922372036854775807.
Nada vai dar certo para nós. Porque, é um literal inteiro do tipo int.
Inicialização correta com um literal longo: 922372036854775807L;

Exemplo de código:

Arquivo: “MyInitLocalLong.java”

 /*    long  */ package underwaterRocks.myInit; /** * * @author Ar20L80 */ public class MyInitLocalLong { public static void main(String[] args) { // long al = 922372036854775807; // integer number too large long bl = 922372036854775807L; //   } } 


O que procurar ao inicializar uma variável.

O intervalo de valores de uma variável desse tipo. O fato de a variável ser inicializada com um literal de um determinado tipo. Para lançamentos explícitos e implícitos. Em compatibilidade de tipo.

Ao usar cascas do tipo Inteiro, você deve prestar atenção à embalagem automática e à descompactação automática desses tipos.

Uso inadequado de dupla


Aqui você precisa esclarecer. Não se trata de usar mal o tipo duplo.
Nós usamos corretamente. Somente o resultado pode surpreender um programador iniciante.
Arquivo: “MinusDouble.java”
 /*   */ package underwaterRocks.tstDouble; /** * * @author vvm */ public class MinusDouble { public static void main(String[] args) { double a = 4.64; double b = 2.64; System.out.println("ab = "+(ab)); } } /*   run: ab = 1.9999999999999996 */ 


Nota sobre o tipo duplo. Um ponto flutuante permite contar com um determinado erro relativo e um grande alcance. Nos cálculos científicos, geralmente é necessário um erro relativo.

Comparação dupla inválida


Considere o tipo duplo.

Exemplo de código:

Arquivo: "MyDouble.java"

 /*    double  - double. */ package underwaterRocks.myDouble; /** * * @author Ar20L80 */ public class MyDouble { public static void main(String[] args) { double dx = 1.4 - 0.1 - 0.1 - 0.1 - 0.1; System.out.println("dx = " + dx); // dx = 0.9999999999999997 System.out.print(" (dx == 1.0):"); System.out.println(dx == 1.0); // false,   1.0   0.9999999999999997 /*   double*/ final double EPSILON = 1E-14; double xx = 1.4 - 0.1 - 0.1 - 0.1 - 0.1; double xy = 1.0; /*  xx c xy */ if (Math.abs(xx - xy) < EPSILON) System.out.println(xx + "    " + xy + " EPSILON = " + EPSILON); } } 

O tipo duplo é conveniente onde não é necessária alta precisão. Para transações financeiras, esse tipo não é adequado. Embora algumas empresas não muito honestas usem o tipo duplo para arredondar para o lado de que precisam. Para operações financeiras, a classe BigDecimal é usada em cálculos financeiros, pois tipos primitivos reais não são adequados para essa finalidade devido a razões de perda de precisão e erros nos resultados de arredondamento. No entanto, resultados mais precisos são obtidos usando a classe BigInteger.

Construtor de classe


O construtor da classe corresponde ao nome da classe e não retorna nada, nem mesmo nulo.

Exemplo de código:

Arquivo: "MyConstructor.java"

 /*      ,  void    void -    */ package underwaterRocks.myConstructor; /** * * @author Ar20L80 */ public class MyConstructor { public MyConstructor(){ System.out.println("   void"); } public void MyConstructor(){ System.out.println("  c void"); } public static void main(String[] args) { MyConstructor myconst = new MyConstructor(); myconst.MyConstructor(); //    } } 

Como podemos ver no código, dois métodos com os mesmos nomes: MyConstructor () e MyConstructor (). Um dos métodos não retorna nada. Este é o construtor da nossa classe. Outro método com void é o método de classe regular. No caso em que você não criou um construtor ou, na sua opinião, criou um construtor de classe com void, o compilador criará um construtor padrão e você ficará surpreso com o motivo pelo qual o construtor não funciona.

Divisão por zero


O que você acha que será o resultado da execução desse código.

Arquivo: “DivisionByZero.java”

 /* */ package divisionByZero; import static java.lang.Double.POSITIVE_INFINITY; /** * * @author Ar20L80 */ public class DivisionByZero { public static void main(String[] args) { try{ float f = 12.2f; double d = 8098098.8790d; System.out.println(f/0); System.out.println(d/0); System.out.println(POSITIVE_INFINITY == f/0); System.out.println(POSITIVE_INFINITY == d/0); } catch (NumberFormatException ex) { System.out.println("NumberFormatException"); } catch (ArithmeticException ex) { System.out.println("ArithmeticException"); } } } 

A execução do código produzirá:

 Infinity Infinity true true 

Dividir o tipo inteiro por zero fornecerá uma ArithmeticException.

A classe java.lang.Double define a constante POSITIVE_INFINITY;

 public static final float POSITIVE_INFINITY = 1.0d / 0.0d; 

É convertido em uma sequência igual a Infinito.

Ordem de inicialização


Arquivo: “InitClass.java”

 /*     */ package myInitClass; /** * * @author Ar20L80 */ public class InitClass { InitClass(){ //   System.out.print(""); } { //   System.out.print("3 "); } public static void main(String[] args) { System.out.print("2"); new InitClass(); } static { //    System.out.print("1"); } } 

Primeiro, todos os blocos estáticos são executados, depois os blocos de inicialização e o construtor da classe.

Será exibido: "123 Constructor"

Uma variável local oculta uma variável de classe
Embora os IDEs modernos detectem facilmente esse erro, gostaria de considerá-lo com mais detalhes. Vamos começar com a atribuição de variável clássica no construtor. O exemplo está correto. Não há erro.
 public class MyClass { private int val = 0; public MyClass(int val) { this.val = val; } } 

No entanto, o que acontece se você usar essa técnica em um método e não em um construtor de classe? No método usual, o uso dessa técnica não é recomendado. A questão está relacionada ao design adequado de uma classe.

Uma explicação simples: em um método, uma variável com o mesmo nome que uma variável de classe é local para o método. Você pode acessar a variável de classe usando this.val. No entanto, esse apelo do método, se a classe for projetada incorretamente, causará apenas efeitos colaterais e poderá prejudicar a legibilidade do código.

A fundição do tipo aritmético é feita automaticamente

Isso pode causar erros irritantes.
 // byte a = 1; // byte b = 1; // byte  = a + b; //  // byte a = (byte) 1; // byte b = (byte) 1; // byte  = a + b; //  


 //     —     . byte a = 1; byte b = 1; byte c = (byte) (a + b); 


 //     —  final // final byte a = 1; // final byte b = 1; // byte c = a + b; //    ,  a  b final 


Uma solução possível ao trabalhar com uma string:
 byte bHundr = Byte.parseByte("100"); //      byte 


Outro erro é dado no código a seguir.
 for (byte i = 1; i <= 128; i++) { System.out.println(i); } 

Nesse caso, obtemos um loop infinito.

A explicação Digite byte [-128, 127]. 128 não está mais neste intervalo. O estouro ocorre e o ciclo se repete. A necessidade de usar byte nesse caso é duvidosa. Embora ocorra em casos raros. A recomendação é usar int em vez de byte. Outra recomendação é não usar um loop no seu algoritmo.

Objetos que são elementos de matriz não são inicializados
 int[] cats = new int[10]; for(int i=0; i<cats.length;i++){ System.out.println("cats " + i + " = " + cats[i]); } 


Neste exemplo, temos uma matriz de elementos do tipo primitivo. E nada de ruim acontece quando não os inicializamos. Eles receberão o valor padrão. Nesse caso, o valor = 0.

Vamos considerar outro exemplo, não com primitivas na matriz, mas com objetos na matriz.
 public class ArrInitObj { public static void main(String[] args) { MyObj[] cats = new MyObj[10]; for(int i=0; i<cats.length;i++){ System.out.println("cats " + i + " = " + cats[i]); System.out.println("cats " + i + ".val = " + cats[i].val); //    java.lang.NullPointerException } } } class MyObj{ public int val; } 


A solução para esse problema é inicializar todas as variáveis ​​de objeto antes de usá-las. A inicialização pode ser feita no construtor da classe MyObj.

O nome da classe é diferente do nome do arquivo em que está armazenado
O IDE moderno detecta facilmente esse tipo de erro. No entanto, esses erros são encontrados, embora muito raramente. Isso ajudará a atenção, levando em consideração as diferenças nos nomes de letras maiúsculas e minúsculas.

Colocando várias classes em um arquivo de uma vez com o modificador público
O erro é bastante raro. O IDE imediatamente emitirá um aviso.
O nome do arquivo deve corresponder ao nome da classe pública.

Conclusões
Muitos erros não são óbvios à primeira vista. Até programadores experientes fazem isso, mas em menor número. Cuidado, experiência prática, o uso de um depurador e a leitura da documentação ajudarão a evitar muitos erros.

Espero que você tenha gostado do artigo e tenha sido útil. Ficarei feliz por seus comentários, comentários, sugestões, desejos. Para ser continuado. Pelo contrário, a adição segue.

Referências
Diretrizes de design de código Oracle Java >>>

PS. Meus amigos Não tenho como continuar publicando sem a sua ajuda. Ou seja, nenhuma oportunidade financeira. Se a publicação realmente o ajudou e você deseja continuar, me apoie. Em algum lugar, há um botão: "Apoie o autor".
Espero sua compreensão. Obrigada Agradeço a Habr pela oportunidade de publicar.

O autor será forçado a excluir suas publicações ou ocultá-las em rascunhos em caso de falta de suporte. Este não é um ultimato. Se você tiver a oportunidade, foi útil, você pode ajudar e clique no botão de suporte ao autor.

Source: https://habr.com/ru/post/pt439642/


All Articles