Coisas que você [talvez] não sabia sobre Java

Saudações, leitor!


Este artigo diluirá meu fluxo de consciência sobre produtividade. Vamos falar sobre coisas engraçadas em Java e ao seu redor que você provavelmente não conhecia. Eu mesmo aprendi recentemente sobre alguns dos itens acima, então acredito que a maioria dos leitores encontrará pelo menos alguns momentos interessantes para si.


assert pode levar 2 argumentos


Normalmente, assert usado para verificar alguma condição e lança um AssertionError se a condição não for atendida. Na maioria das vezes, a verificação é assim:


 assert list.isEmpty(); 

No entanto, pode ser assim:


 assert list.isEmpty() : list.toString(); 

O leitor inteligente já adivinhou que a segunda expressão (a propósito, é lenta) retorna um valor do tipo Object , que é passado para AssertionError e fornece ao usuário informações adicionais sobre o erro. Para uma descrição mais formal, consulte a seção correspondente da especificação do idioma: https://docs.oracle.com/javase/specs/jls/se13/html/jls-14.html#jls-14.10


Por quase 6 anos e meio trabalhando com Java, vi o uso estendido da palavra-chave assert apenas uma vez.


strictfp


Esta não é uma palavra de maldição - é uma palavra-chave pouco conhecida. De acordo com a documentação , seu uso inclui aritmética estrita para números de ponto flutuante:


 public interface NonStrict { float sum(float a, float b); } 

pode ser transformado em


 public strictfp interface Strict { float sum(float a, float b); } 

Além disso, esta palavra-chave pode ser aplicada a métodos individuais:


 public interface Mixed { float sum(float a, float b); strictfp float strictSum(float a, float b); } 

Leia mais sobre seu uso no artigo da wiki . Em resumo: depois que essa palavra-chave foi adicionada para garantir a portabilidade, a precisão do processamento de números de ponto flutuante em diferentes processadores pode ser diferente.


continue pode levar uma discussão


Eu descobri isso na semana passada. Normalmente, escrevemos assim:


 for (Item item : items) { if (item == null) { continue; } use(item); } 

Tal uso implica implicitamente um retorno ao início do ciclo e ao próximo passo. Em outras palavras, o código acima pode ser reescrito como:


 loop: for (Item item : items) { if (item == null) { continue loop; } use(item); } 

No entanto, você pode retornar do ciclo para o ciclo externo, se houver:


 @Test void test() { outer: for (int i = 0; i < 20; i++) { for (int j = 10; j < 15; j++) { if (j == 13) { continue outer; } } } } 

Observe que o contador i ao retornar ao ponto outer não é redefinido, portanto o loop é finito.


Ao chamar o método vararg sem argumentos, uma matriz vazia é criada de qualquer maneira


Quando olhamos a chamada de um método desse tipo de fora, parece que não há nada com que se preocupar:


 @Benchmark public Object invokeVararg() { return vararg(); } 

Não passamos nada para o método, não é? Mas se você olhar de dentro, tudo não é tão róseo:


 public Object[] vararg(Object... args) { return args; } 

A experiência confirma preocupações:


 Benchmark Mode Cnt Score Error Units invokeVararg avgt 20 3,715 ± 0,092 ns/op invokeVararg:·gc.alloc.rate.norm avgt 20 16,000 ± 0,001 B/op invokeVararg:·gc.count avgt 20 257,000 counts 

Você pode se livrar de uma matriz desnecessária na ausência de argumentos, passando null :


 @Benchmark public Object invokeVarargWithNull() { return vararg(null); } 

O coletor de lixo realmente se sente melhor:


 invokeVarargWithNull avgt 20 2,415 ± 0,067 ns/op invokeVarargWithNull:·gc.alloc.rate.norm avgt 20 ≈ 10⁻⁵ B/op invokeVarargWithNull:·gc.count avgt 20 ≈ 0 counts 

Código com null parece muito feio, o compilador (e a Idea) juram, portanto, use essa abordagem em código realmente quente e forneça comentários.


A expressão de maiúsculas e minúsculas não suporta java.lang.Class


Este código simplesmente não compila:


 String to(Class<?> clazz) { switch (clazz) { case String.class: return "str"; case Integer.class: return "int"; default: return "obj"; } } 

Lide com isso.


Sutilezas de atribuição e Class.isAssignableFrom ()


Existe um código:


 int a = 0; Integer b = 10; a = b; //    

Agora pense em qual valor esse método retornará:


 boolean check(Integer b) { return int.class.isAssignableFrom(b.getClass()); } 

Depois de ler o nome do método Class.isAssignableFrom() , dá a falsa impressão de que a expressão int.class.isAssignableFrom(b.getClass()) retornará true . Podemos atribuir uma variável do tipo int a uma variável do tipo Integer , podemos?


No entanto, o método check() retornará false , pois a documentação afirma claramente que:


 /** * Determines if the class or interface represented by this * {@code Class} object is either the same as, or is a superclass or * superinterface of, the class or interface represented by the specified * {@code Class} parameter. It returns {@code true} if so; * otherwise it returns {@code false}. If this {@code Class} // <---- !!! * object represents a primitive type, this method returns * {@code true} if the specified {@code Class} parameter is * exactly this {@code Class} object; otherwise it returns * {@code false}. * */ @HotSpotIntrinsicCandidate public native boolean isAssignableFrom(Class<?> cls); 

Embora int não int o sucessor de Integer (e vice-versa), a atribuição mútua possível é um recurso do idioma e, para não enganar os usuários, é feita uma reserva especial na documentação.


Moral: quando parece - precisa ser batizado precisa reler a documentação.


Outro fato não óbvio segue deste exemplo:


 assert int.class != Integer.class; 

A classe int.class é realmente Integer.TYPE e, para ver isso, basta olhar para o que esse código será compilado:


 Class<?> toClass() { return int.class; } 

Whack:


 toClass()Ljava/lang/Class; L0 LINENUMBER 11 L0 GETSTATIC java/lang/Integer.TYPE : Ljava/lang/Class; ARETURN 

Abrindo o código-fonte para java.lang.Integer veremos isso:


 @SuppressWarnings("unchecked") public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int"); 

Observando a chamada para Class.getPrimitiveClass("int") pode ficar tentado a cortá-la e substituí-la por:


 @SuppressWarnings("unchecked") public static final Class<Integer> TYPE = int.class; 

O mais surpreendente é que o JDK com alterações semelhantes (para todas as primitivas) será montado e a máquina virtual será iniciada. É verdade que ela não vai trabalhar muito:


 java.lang.IllegalArgumentException: Component type is null at jdk.internal.misc.Unsafe.allocateUninitializedArray(java.base/Unsafe.java:1379) at java.lang.StringConcatHelper.newArray(java.base/StringConcatHelper.java:458) at java.lang.StringConcatHelper.simpleConcat(java.base/StringConcatHelper.java:423) at java.lang.String.concat(java.base/String.java:1968) at jdk.internal.util.SystemProps.fillI18nProps(java.base/SystemProps.java:165) at jdk.internal.util.SystemProps.initProperties(java.base/SystemProps.java:103) at java.lang.System.initPhase1(java.base/System.java:2002) 

O erro rastreia aqui:


 class java.lang.StringConcatHelper { @ForceInline static byte[] newArray(long indexCoder) { byte coder = (byte)(indexCoder >> 32); int index = (int)indexCoder; return (byte[]) UNSAFE.allocateUninitializedArray(byte.class, index << coder); //<-- } } 

Com as alterações mencionadas, byte.class retorna nulo e quebra a segurança.


Spring Data JPA Anuncia Repositório Parcialmente Funcional


Concluo o artigo com um erro curioso que surgiu na junção de Spring Date e Hibernate. Lembre-se de como declaramos um repositório que atende a uma determinada entidade:


 @Entity public class SimpleEntity { @Id private Integer id; @Column private String name; } public interface SimpleRepository extends JpaRepository<SimpleEntity, Integer> { } 

Usuários experientes sabem que, ao elevar o contexto, o Spring Date verifica todos os repositórios e destrói imediatamente o aplicativo inteiro ao tentar descrever, por exemplo, uma curva de consulta:


 public interface SimpleRepository extends JpaRepository<SimpleEntity, Integer> { @Query(", ,  ?") Optional<SimpleEntity> findLesserOfTwoEvils(); } 

No entanto, nada nos impede de declarar um repositório com um tipo de chave esquerda:


 public interface SimpleRepository extends JpaRepository<SimpleEntity, Long> { } 

Este repositório não apenas aumentará, mas também estará parcialmente operacional, por exemplo, o método findAll() funcionará "com um estrondo". Porém, espera-se que os métodos que usam a chave falhem com um erro:


 IllegalArgumentException: Provided id of the wrong type for class SimpleEntity. Expected: class java.lang.Integer, got class java.lang.Long 

O fato é que o Spring Date não compara as classes da chave da entidade e a chave do repositório anexado a ela. Isso não acontece de uma vida boa, mas devido à incapacidade do Hibernate de emitir o tipo de chave correto em certos casos: https://hibernate.atlassian.net/browse/HHH-10690


Na minha vida, encontrei isso apenas uma vez: nos testes (carrinho) do Spring Dates, por exemplo, org.springframework.data.jpa.repository.query.PartTreeJpaQueryIntegrationTests$UserRepository digitado em Long e Integer usado na entidade User . E funciona!


Só isso, espero que minha análise tenha sido útil e interessante para você.


Quero parabenizá-lo pelo Ano Novo e desejo que você se aprofunde cada vez mais!

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


All Articles