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:
@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!