Cosas que [tal vez] no sabías sobre Java

Saludos, lector!


Este artículo diluirá mi flujo de conciencia sobre la productividad. Hablemos de cosas graciosas en Java y a tu alrededor que probablemente no sabías. Hace poco aprendí algo de lo anterior, así que creo que la mayoría de los lectores encontrarán al menos un par de momentos interesantes para ellos.


afirmar puede tomar 2 argumentos


Por lo general, assert usa para verificar alguna condición y arroja un AssertionError si la condición no se cumple. Muy a menudo, el cheque se ve así:


 assert list.isEmpty(); 

Sin embargo, puede ser así:


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

El lector inteligente ya ha adivinado que la segunda expresión (por cierto, es perezosa) devuelve un valor de tipo Object , que se pasa a AssertionError y proporciona al usuario información adicional sobre el error. Para obtener una descripción más formal, consulte la sección correspondiente de la especificación del lenguaje: https://docs.oracle.com/javase/specs/jls/se13/html/jls-14.html#jls-14.10


Durante casi 6 años y medio de trabajo con Java, he visto el uso extendido de la palabra clave de assert solo una vez.


strictlyfp


Esta no es una palabra de maldición, es una palabra clave poco conocida. Según la documentación , su uso incluye aritmética estricta para números de coma flotante:


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

se puede convertir en


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

Además, esta palabra clave se puede aplicar a métodos individuales:


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

Lea más sobre su uso en el artículo wiki . En resumen: una vez que se agregó esta palabra clave para garantizar la portabilidad, como La precisión del procesamiento de números de coma flotante en diferentes procesadores podría ser diferente.


continuar puede tomar una discusión


Me enteré la semana pasada. Por lo general, escribimos así:


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

Tal uso supone implícitamente un retorno al comienzo del ciclo y al siguiente paso. En otras palabras, el código anterior se puede reescribir como:


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

Sin embargo, puede regresar del ciclo al ciclo externo, si corresponde:


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

Tenga en cuenta que el contador i cuando regresa al punto outer no se reinicia, por lo que el bucle es finito.


Cuando se llama al método vararg sin argumentos, se crea una matriz vacía de todos modos


Cuando miramos la llamada de tal método desde el exterior, parece que no hay nada de qué preocuparse:


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

No pasamos nada al método, ¿verdad? Pero si miras desde adentro, entonces no todo es tan color de rosa:


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

La experiencia confirma preocupaciones:


 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 

Puede deshacerse de una matriz innecesaria en ausencia de argumentos pasando null :


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

El recolector de basura realmente se siente mejor:


 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 

El código con null ve muy feo, el compilador (y la Idea) jurarán, así que use este enfoque en un código realmente caliente y proporcione comentarios.


La expresión de mayúsculas y minúsculas no admite java.lang.Class


Este código simplemente no compila:


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

Tratar con eso.


Sutilezas de asignación y Class.isAssignableFrom ()


Hay un código:


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

Ahora piense en qué valor devolverá este método:


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

Después de leer el nombre del método Class.isAssignableFrom() , da la falsa impresión de que la expresión int.class.isAssignableFrom(b.getClass()) devolverá true . Podemos asignar una variable de tipo int a una variable de tipo Integer , ¿podemos?


Sin embargo, el método check() devolverá false , ya que la documentación establece 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); 

Aunque int no int el sucesor de Integer (y viceversa), la posible asignación mutua es una característica del lenguaje, y para no confundir a los usuarios, se hace una reserva especial en la documentación.


Moraleja: cuando parece necesita ser bautizado Necesito volver a leer la documentación.


Otro hecho obvio se desprende de este ejemplo:


 assert int.class != Integer.class; 

La clase int.class es en realidad Integer.TYPE , y para ver esto, solo mira en qué se compilará este código:


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

Whack


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

Al abrir el código fuente de java.lang.Integer veremos esto:


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

Si Class.getPrimitiveClass("int") la llamada a Class.getPrimitiveClass("int") puede verse tentado a cortarla y reemplazarla por:


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

Lo más sorprendente es que se ensamblará el JDK con cambios similares (para todas las primitivas) y se iniciará la máquina virtual. Es cierto que ella no trabajará mucho:


 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) 

El error se arrastra aquí:


 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); //<-- } } 

Con los cambios mencionados, byte.class devuelve nulo y rompe la seguridad.


Spring Data JPA anuncia un repositorio parcialmente funcional


Concluyo el artículo con un curioso error que surgió en la unión de Spring Date e Hibernate. Recordemos cómo declaramos un repositorio que sirve a una determinada entidad:


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

Los usuarios experimentados saben que al generar el contexto, Spring Date verifica todos los repositorios e inmediatamente destruye toda la aplicación cuando intenta describir, por ejemplo, una curva de consulta:


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

Sin embargo, nada nos impide declarar un repositorio con un tipo de clave izquierda:


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

Este repositorio no solo aumentará, sino que también estará parcialmente operativo, por ejemplo, el método findAll() funcionará "con una explosión". Pero se espera que los métodos que usan la clave fallen con un error:


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

La cuestión es que Spring Date no compara las clases de la clave de entidad y la clave del repositorio adjunto. Esto no sucede a partir de una buena vida, pero debido a la incapacidad de Hibernate para emitir el tipo de clave correcto en ciertos casos: https://hibernate.atlassian.net/browse/HHH-10690


En mi vida, me encontré con esto solo una vez: en las pruebas (trolley) de Spring Dates, por ejemplo, org.springframework.data.jpa.repository.query.PartTreeJpaQueryIntegrationTests$UserRepository escribe en Long , y Integer usa en la entidad User . Y funciona!


Eso es todo, espero que mi comentario haya sido útil e interesante para usted.


¡Te felicito por el Año Nuevo y deseo que profundices más y más!

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


All Articles