Choses que vous [peut-ĂȘtre] ne saviez pas sur Java

Salutations, lecteur!


Cet article va diluer mon flux de conscience sur la productivitĂ©. Parlons de choses amusantes en Java et autour de vous que vous ne connaissiez probablement pas. J'ai moi-mĂȘme rĂ©cemment entendu parler de certains des Ă©lĂ©ments ci-dessus, donc je pense que la plupart des lecteurs trouveront au moins quelques moments intĂ©ressants pour eux-mĂȘmes.


assert peut prendre 2 arguments


Généralement, assert utilisé pour vérifier une condition et renvoie une AssertionError si la condition n'est pas satisfaite. Le plus souvent, le chÚque ressemble à ceci:


 assert list.isEmpty(); 

Cependant, cela peut ĂȘtre comme ceci:


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

Le lecteur intelligent a déjà deviné que la deuxiÚme expression (incidemment, elle est paresseuse) renvoie une valeur de type Object , qui est transmise à AssertionError et donne à l'utilisateur des informations supplémentaires sur l'erreur. Pour une description plus formelle, voir la section correspondante de la spécification de langue: https://docs.oracle.com/javase/specs/jls/se13/html/jls-14.html#jls-14.10


Pendant prÚs de 6 ans et demi de travail avec Java, je n'ai vu l'utilisation étendue du mot-clé assert qu'une seule fois.


strictfp


Ce n'est pas un mot maudit - c'est un mot-clé peu connu. Selon la documentation , son utilisation inclut une arithmétique stricte pour les nombres à virgule flottante:


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

peut ĂȘtre transformĂ© en


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

En outre, ce mot clĂ© peut ĂȘtre appliquĂ© Ă  des mĂ©thodes individuelles:


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

En savoir plus sur son utilisation dans l' article wiki . En bref: une fois ce mot clĂ© ajoutĂ© pour garantir la portabilitĂ©, comme la prĂ©cision du traitement des nombres Ă  virgule flottante sur diffĂ©rents processeurs peut ĂȘtre diffĂ©rente.


continuer peut prendre un argument


Je l'ai découvert la semaine derniÚre. Habituellement, nous écrivons comme ceci:


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

Une telle utilisation suppose implicitement un retour au dĂ©but du cycle et Ă  la passe suivante. En d'autres termes, le code ci-dessus peut ĂȘtre rĂ©Ă©crit comme suit:


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

Cependant, vous pouvez revenir du cycle au cycle externe, le cas échéant:


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

Notez que le compteur i lors du retour au point outer n'est pas réinitialisé, donc la boucle est finie.


Lors de l'appel de la mĂ©thode vararg sans arguments, un tableau vide est quand mĂȘme crĂ©Ă©


Quand on regarde l'appel d'une telle méthode de l'extérieur, il semble qu'il n'y ait rien à craindre:


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

Nous n'avons rien transmis à la méthode, n'est-ce pas? Mais si vous regardez de l'intérieur, tout n'est pas si rose:


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

L'expérience confirme les préoccupations:


 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 

Vous pouvez vous débarrasser d'un tableau inutile en l'absence d'arguments en passant null :


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

Le garbage collector se sent vraiment mieux:


 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 

Le code avec null air trÚs moche, le compilateur (et l'idée) jurent, alors utilisez cette approche dans du code vraiment chaud et fournissez-lui des commentaires.


L'expression de casse de commutateur ne prend pas en charge java.lang.Class


Ce code ne compile tout simplement pas:


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

Traitez-le.


Subtilités de l'affectation et Class.isAssignableFrom ()


Il y a un code:


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

Réfléchissez maintenant à la valeur de cette méthode:


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

AprÚs avoir lu le nom de la méthode Class.isAssignableFrom() , Class.isAssignableFrom() donne la fausse impression que l'expression int.class.isAssignableFrom(b.getClass()) renverra true . Nous pouvons affecter une variable de type int à une variable de type Integer , n'est-ce pas?


Cependant, la méthode check() retournera false , car la documentation indique clairement 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); 

Bien que int ne int pas le successeur d' Integer (et vice versa), une éventuelle affectation mutuelle est une caractéristique du langage, et afin de ne pas induire les utilisateurs en erreur, une réserve spéciale est faite dans la documentation.


Morale: quand il semble - besoin d'ĂȘtre baptisĂ© besoin de relire la documentation.


Un autre fait non évident découle de cet exemple:


 assert int.class != Integer.class; 

La classe int.class est en fait Integer.TYPE , et pour voir cela, regardez simplement dans quoi ce code sera compilé:


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

Whack:


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

En ouvrant le code source de java.lang.Integer nous verrons ceci:


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

En regardant l'appel Ă  Class.getPrimitiveClass("int") pourriez ĂȘtre tentĂ© de le couper et de le remplacer par:


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

La chose la plus étonnante est que le JDK avec des changements similaires (pour toutes les primitives) s'assemblera et que la machine virtuelle démarrera. Certes, elle ne travaillera pas longtemps:


 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) 

L'erreur apparaĂźt ici:


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

Avec les modifications mentionnées, byte.class renvoie null et rompt la sécurité.


Spring Data JPA annonce un référentiel partiellement fonctionnel


Je termine l'article par une curieuse erreur survenue à la jonction de Spring Date et Hibernate. Rappelez-vous comment nous déclarons un référentiel desservant une certaine entité:


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

Les utilisateurs expĂ©rimentĂ©s savent que lors de l'Ă©lĂ©vation du contexte, Spring Date vĂ©rifie tous les rĂ©fĂ©rentiels et dĂ©truit immĂ©diatement l'application entiĂšre en essayant de dĂ©crire, par exemple, une courbe de requĂȘte:


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

Cependant, rien ne nous empĂȘche de dĂ©clarer un rĂ©fĂ©rentiel avec un type de clĂ© gauche:


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

Ce référentiel augmentera non seulement, mais sera également partiellement opérationnel, par exemple, la méthode findAll() fonctionnera "avec un bang". Mais les méthodes utilisant la clé devraient échouer avec une erreur:


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

Le fait est que Spring Date ne compare pas les classes de la clé d'entité et la clé du référentiel qui lui est attachée. Cela ne se produit pas d'une bonne vie, mais en raison de l'incapacité d'Hibernate à émettre le type de clé correct dans certains cas: https://hibernate.atlassian.net/browse/HHH-10690


Dans ma vie, je n'ai rencontrĂ© cela qu'une seule fois: dans les tests (chariot) de Spring Dates lui-mĂȘme, par exemple, org.springframework.data.jpa.repository.query.PartTreeJpaQueryIntegrationTests$UserRepository tapĂ© en Long et Integer utilisĂ© dans l'entitĂ© User . Et ça marche!


C'est tout, j'espÚre que ma critique vous a été utile et intéressante.


Je vous félicite pour la nouvelle année et vous souhaite de creuser plus profondément et plus largement!

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


All Articles