Sucre syntaxique pour l'apprentissage de Java

Syntactic Sugar (Syntactic Sugar), également connu sous le nom de grammaire enrobée de sucre, est un terme inventé par l'informaticien britannique Peter.J.Landin. Il fait référence à une certaine grammaire ajoutée à un langage informatique. Cette grammaire n'a aucun effet sur la fonction de le langage, mais il est plus pratique à utiliser pour les programmeurs.

Du point de vue des principes de compilation Java, cet article approfondit le bytecode et les fichiers de classe, et comprend les principes et l'utilisation du sucre syntaxique en Java. Il vous aidera à comprendre les principes sous-jacents à ces sucres syntaxiques tout en apprenant à utiliser le sucre syntaxique Java. Le contenu principal est le suivant :

Qu'est-ce que le sucre syntaxique ? —points auxquels il faut prêter attention lors de l'utilisation du sucre grammatical Application complète du
sucre grammatical

Syntactic Sugar (Syntactic Sugar), également connu sous le nom de grammaire enrobée de sucre, est un terme inventé par l'informaticien britannique Peter.J.Landin. Il fait référence à une certaine grammaire ajoutée à un langage informatique. Cette grammaire n'a aucun effet sur la fonction de le langage, mais il est plus pratique à utiliser pour les programmeurs. En bref, le sucre syntaxique rend les programmes plus concis et plus lisibles.

有意思的是,在编程领域,除了语法糖,还有语法盐和语法糖精的说法,篇幅有限这里不做扩展了。

Presque tous les langages de programmation que nous connaissons ont du sucre syntaxique. L'auteur estime que la quantité de sucre grammatical est l'un des critères permettant de juger si une langue est suffisamment puissante. Beaucoup de gens disent que Java est un "langage à faible teneur en sucre". En fait, depuis Java 7, divers sucres ont été ajoutés au niveau du langage Java, principalement développés dans le cadre du projet "Project Coin". Bien que certaines personnes à Java pensent encore que le Java actuel est à faible teneur en sucre, il continuera à se développer dans le sens du "sucre élevé" à l'avenir.
sucre non syntaxique

Comme mentionné précédemment, l'existence du sucre syntaxique est principalement pour la commodité des développeurs. Mais en fait, la machine virtuelle Java ne supporte pas ces sucres syntaxiques. Ces sucres grammaticaux seront réduits à de simples structures grammaticales de base lors de la phase de compilation, et ce processus est la solution des sucres grammaticaux.

En parlant de compilation, tout le monde doit savoir que dans le langage Java, la commande javac peut compiler un fichier source avec un suffixe .java en un bytecode avec un suffixe .class qui peut s'exécuter sur une machine virtuelle Java. Si vous regardez le code source de com.sun.tools.javac.main.JavaCompiler, vous constaterez que l'une des étapes de compile() consiste à appeler desugar().Cette méthode est responsable de l'implémentation du sucre syntaxique.

Le sucre syntaxique le plus couramment utilisé en Java comprend principalement les génériques, les paramètres de longueur variable, la compilation conditionnelle, le déballage automatique, les classes internes, etc. Cet article analyse principalement les principes de ces sucres grammaticaux. Étape par étape pour décoller le glaçage et voir ce que c'est.
Candy 1. Le commutateur prend en charge la chaîne et l'énumération

Comme mentionné précédemment, à partir de Java 7, le sucre syntaxique du langage Java s'enrichit progressivement, l'un des plus importants étant que le commutateur de Java 7 commence à prendre en charge String.

Avant de commencer à coder, vulgarisons la science. Le commutateur en Java lui-même prend en charge les types de base. Tels que int, char, etc. Pour le type int, comparez les valeurs directement. Pour le type char, comparez son code ascii. Par conséquent, pour le compilateur, seuls des entiers peuvent être utilisés dans le commutateur, et tout type de comparaison doit être converti en entier. Par exemple octet. short, char (le code ackii est un entier) et int.

Examinons ensuite la prise en charge de String par le commutateur, avec le code suivant :

public class switchDemoString { public static void main(String[] args) { String str = "world" ; switch (str) { case "bonjour": System.out.println("bonjour"); casser; cas « monde » : System.out.println(« monde »); casser; par défaut : pause ; } } }













Après décompilation, le contenu est le suivant :

public class switchDemoString
{ public switchDemoString() { } public static void main(String args[]) { String str = "world" ; Chaîne s ; switch((s = str).hashCode()) { par défaut : break ; cas 99162322 : if(s.equals("hello")) System.out.println("hello"); casser; cas 113318802 : if(s.equals("world")) System.out.println("world"); casser; } } }





















En voyant ce code, vous savez que le changement de la chaîne d'origine est implémenté via les méthodes equals() et hashCode(). Heureusement, la méthode hashCode() renvoie un int, pas un long.

仔细看下可以发现,进行switch的实际是哈希值,然后通过使用equals方法比较进行安全检查,这个检查是必要的,因为哈希可能会发生碰撞。因此它的性能是不如使用枚举进行switch或者使用纯整数常量,但这也不是很差。

Bonbons 2. Génériques

Nous savons tous que de nombreux langages prennent en charge les génériques, mais ce que beaucoup de gens ignorent, c'est que différents compilateurs gèrent les génériques de différentes manières.Habituellement, un compilateur gère les génériques de deux manières : la spécialisation du code et le partage de code. C++ et C# utilisent le mécanisme de traitement de la spécialisation de code, tandis que Java utilise le mécanisme de partage de code.

Code sharing方式为每个泛型类型创建唯一的字节码表示,并且将该泛型类型的实例都映射到这个唯一的字节码表示上。将多种泛型类形实例映射到唯一的字节码表示是通过类型擦除(type erasue)实现的。

En d'autres termes, pour la machine virtuelle Java, il ne connaît pas du tout la syntaxe de Map<String, String> map. Il est nécessaire de désyntaxiser le sucre par effacement de type au stade de la compilation.

Le processus principal d'effacement de type est le suivant : 1. Remplacez tous les paramètres génériques par leur type limite le plus à gauche (type parent le plus haut). 2. Supprimez tous les paramètres de type.

Le code suivant :

Map<String, String> map = new HashMap<String, String>();
map.put("nom", "hollis");
map.put("wechat", "Hollis");
map.put("blog", "www.hollischuang.com");

Après désucrage du sucre syntaxique, il deviendra :

Carte map = new HashMap();
map.put("nom", "hollis");
map.put("wechat", "Hollis");
map.put("blog", "www.hollischuang.com");

Le code suivant :

public static <A extend Comparable> A max(Collection xs) { Iterator xi = xs.iterator(); A w = xi.next(); tandis que (xi.hasNext()) { A x = xi.next(); si (w.compareTo(x) < 0) w = x; } retourne w; }








Après l'effacement du type, il devient :

public static Comparable max(Collection xs){ Iterator xi = xs.iterator(); Comparable w = (Comparable)xi.next(); while(xi.hasNext()) { Comparable x = (Comparable)xi.next(); si(w.compareTo(x) < 0) w = x; } retourne w; }









Il n'y a pas de génériques dans la machine virtuelle, seulement des classes ordinaires et des méthodes ordinaires. Les paramètres de type de toutes les classes génériques seront effacés au moment de la compilation, et les classes génériques n'ont pas leurs propres objets Class uniques. Par exemple, il n'y a ni List.class ni List.class, mais uniquement List.class.
Candy 3. Boxe et déballage automatiques

L'autoboxing signifie que Java convertit automatiquement les valeurs de type primitif en objets correspondants, comme la conversion d'une variable int en un objet Integer. Ce processus est appelé boxing. Inversement, la conversion d'un objet Integer en une valeur de type int s'appelle unboxing. Parce que le boxing et unboxing ici est une conversion automatique non humaine, on l'appelle boxing et unboxing automatiques. Les classes d'encapsulation correspondant aux types primitifs byte, short, char, int, long, float, double et boolean sont Byte, Short, Character, Integer, Long, Float, Double, Boolean.

Regardons d'abord le code pour la boxe automatique :

public static void main(String[] args) { int i = 10; Entier n = i ; }


Le code décompilé est le suivant :

public static void main(String args[])
{ int i = 10; Entier n = Entier.valueOf(i); }


Regardons le code pour le déballage automatique :

public static void main(String[] args) { Entier i = 10 ; int n = je ; }



Le code décompilé est le suivant :

public static void main(String args[])
{ Integer i = Integer.valueOf(10); int n = i.intValue(); }


On peut voir à partir du contenu décompilé que la méthode valueOf(int) de Integer est automatiquement appelée lors de la boxe. La méthode intValue de Integer est automatiquement appelée lors du déballage.

Par conséquent, le processus de boxing est réalisé en appelant la méthode valueOf du wrapper, et le processus de déballage est réalisé en appelant la méthode xxxValue du wrapper.
Cube de sucre 4. Paramètres de longueur variable de la méthode

Les arguments variables (arguments variables) sont une fonctionnalité introduite dans Java 1.5. Il permet à une méthode de prendre n'importe quel nombre de valeurs comme paramètres.

Regardez le code de paramètre variable suivant, où la méthode d'impression reçoit des paramètres variables :

public static void main(String[] args)
{ print("Holis", "公众号:Hollis", "博客:www.hollischuang.com", "QQ:907607222"); } ​public static void print(String… strs) { for (int i = 0; i < strs.length; i++) { System.out.println(strs[i]); } }









Code décompilé :

public static void main(String args[])
{ print(new String[] { "Holis", "\u516C\u4F17\u53F7:Hollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com", "QQ\uFF1A907607222 ” }); } ​public static transient void print(String strs[]) { for(int i = 0; i < strs.length; i++) System.out.println(strs[i]); ​}










On peut voir à partir du code décompilé que lorsqu'un paramètre variable est utilisé, il crée d'abord un tableau dont la longueur est le nombre de paramètres réels passés en appelant la méthode, puis place toutes les valeurs de paramètre dans ce tableau, puis passez ce tableau en paramètre à la méthode appelée.

PS:反编译后的print方法声明中有一个transient标识,是不是很奇怪?transient不是不可以修饰方法吗?transient不是和序列化有关么?transient在这里的作用是什么?因为这个与本文关系不大,这里不做深入分析了。相了解的同学可以关注我微信公众号或者博客。

Candy cinq, énumération

Java SE5 fournit un nouveau type - le type d'énumération de Java. Le mot-clé enum peut créer un ensemble limité de valeurs nommées en tant que nouveau type, et ces valeurs nommées peuvent être utilisées comme composants de programme réguliers. C'est une fonctionnalité très utile.

Si vous voulez voir le code source, vous devez d'abord avoir une classe, alors quel type de classe est le type d'énumération ? Est-ce une énumération ? La réponse est évidemment non, enum est comme une classe, c'est juste un mot-clé, ce n'est pas une classe, donc par quelle classe l'énumération est-elle maintenue, nous écrivons simplement une énumération :

public enum t { PRINTEMPS, ÉTÉ ; }

Ensuite, nous utilisons la décompilation pour voir comment ce code est implémenté. Après décompilation, le contenu du code est le suivant :

public final class T extend Enum
{ private T(String s, int i) { super(s, i); } public static T[] valeurs() { T at[] ; int je ; T à1[] ; System.arraycopy(at = ENUM KaTeX parse error : 'EOF' attendu, obtenu '}' à la position 71 : …eturn at1 ; }̲ ​ public s… VALUES[] ; static { SPRING = new T(“SPRING”, 0 ); ETE = new T("ETE", 1); ENUM$VALUES = (new T[] { PRINTEMPS, ETE }); } }


















Après décompilation du code, nous pouvons voir que la classe finale publique T extend Enum indique que cette classe hérite de la classe Enum, et le mot-clé final nous indique que cette classe ne peut pas être héritée. Lorsque nous utilisons enum pour définir un type d'énumération, le compilateur crée automatiquement une classe finale pour que nous héritions de la classe Enum, de sorte que le type d'énumération ne peut pas être hérité.
Candy six, classe interne

La classe interne est également appelée classe imbriquée, et la classe interne peut être comprise comme un membre ordinaire de la classe externe.

La raison pour laquelle la classe interne est également du sucre syntaxique est qu'il ne s'agit que d'un concept au moment de la compilation. Une classe interne interne est définie dans outer.java. Une fois compilé avec succès, deux fichiers .class complètement différents seront générés, à savoir externe .class et outer$inner.class. Ainsi, le nom de la classe interne peut être le même que son nom de classe externe.

public class OutterClass { private String userName; public String getUserName() { return userName; } ​public void setUserName(String userName) { this.userName = userName; } ​public static void main(String[] args) { ​} ​class InnerClass{ private String name; ​public String getName() { return name; } ​public void setName(String name) { this.name = name; } } }

























Une fois le code ci-dessus compilé, deux fichiers de classe seront générés : OutterClass Inner Class .class et O utter Class .class. Lorsque nous essayons de décompiler le fichier O utter Class .class, la ligne de commande affichera ce qui suit : Analyser O utter Class .class . . . P arsinginnerclass O utter Class InnerClass.class , OutterClass.class . Lorsque nous essayons de décompiler le fichier OutterClass.class, la ligne de commande imprime ce qui suit : Parsing OuterClass.class...Parsing inner class OuterClassClasse intérieure . classe , classe extérieure . classe . _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ Lorsque nous essayons de décompiler le fichier O u tt er C a ss . c l a ss , la ligne de commande affichera ce qui suit : P a rs in g O u tt er C a ss . c l a ss ... Pa rs in g inn erc l a ss O u tt er Class InnerClass . class … Génération d'OuterClass. Il va décompiler les deux fichiers, puis générer ensemble un fichier OutterClass.jad. Le contenu du fichier est le suivant :

public class OutterClass
{ class InnerClass { public String getName() { return name; } public void setName(String name) { this.name = name; } nom de chaîne privée ; final OutterClass this$0; ​InnerClass () { this.this$0 = OutterClass.this; super(); } } ​public OutterClass() { } public String getUserName() { return userName; } public void setUserName(String userName){ this.userName = userName; }






























public static void main(String args1[])
{ } private String userName ; }


Bonbons sept, compilation conditionnelle

— Dans des circonstances normales, chaque ligne de code du programme doit participer à la compilation. Mais parfois, dans un souci d'optimisation du code du programme, vous souhaitez ne compiler qu'une partie du contenu. À ce stade, vous devez ajouter des conditions au programme, afin que le compilateur ne puisse compiler que le code qui remplit les conditions, et compiler le code qui ne respecte pas les conditions. Abandonné, il s'agit d'une compilation conditionnelle.

Comme en C ou CPP, la compilation conditionnelle peut être réalisée à l'aide d'instructions préparées. En fait, la compilation conditionnelle peut également être implémentée en Java. Regardons d'abord un morceau de code:

public class ConditionalCompilation { public static void main(String[] args) { final boolean DEBUG = true; if(DEBUG) { System.out.println("Bonjour, DEBUG!"); } ​final booléen EN LIGNE = faux ; ​if (EN LIGNE){ System.out.println("Bonjour, EN LIGNE !"); } } }












Le code décompilé est le suivant :

public class ConditionalCompilation
{ ​public ConditionalCompilation() { } ​public static void main(String args[]) { boolean DEBUG = true; System.out.println("Bonjour, DEBUG!"); booléen EN LIGNE = faux ; } }











Tout d'abord, nous avons constaté qu'il n'y a pas de System.out.println("Hello, ONLINE!"); dans le code décompilé, qui est en fait une compilation conditionnelle. Lorsque if(ONLINE) est faux, le compilateur ne compile pas le code qu'il contient.

Par conséquent, la compilation conditionnelle de la grammaire Java est réalisée par l'instruction if dont la condition de jugement est constante. Son principe est aussi le sucre syntaxique du langage Java. Selon le vrai ou le faux de la condition if, le compilateur élimine directement le bloc de code dont la branche est fausse. La compilation conditionnelle ainsi implémentée doit être implémentée dans le corps de la méthode, mais ne peut pas être effectuée sur la structure de toute la classe Java ou sur les attributs de la classe, par rapport à la compilation conditionnelle de C/C++, elle est en effet plus limitée. Au début de la conception du langage Java, la fonction de compilation conditionnelle n'a pas été introduite, bien qu'il y ait des limitations, c'est mieux que rien.
Pain de sucre 8. Assertions

En Java, le mot clé assert a été introduit à partir de JAVA SE 1.4. Afin d'éviter les erreurs causées par l'utilisation du mot clé assert dans l'ancienne version du code Java, Java n'active pas la vérification d'assertion par défaut lors de l'exécution (pour le moment, toutes les instructions Assertion sera ignoré !), si vous souhaitez activer la vérification des assertions, vous devez utiliser le commutateur -enableassertions ou -ea pour l'activer.

Considérez un morceau de code qui contient des assertions :

public class AssertTest { public static void main(String args[]) { int a = 1; entier b = 1 ; affirmer a == b ; System.out.println(“公众号:Hollis”); assert a != b : "Hollis" ; System.out.println(“博客:www.hollischuang.com”); } }








Le code décompilé est le suivant :

public class AssertTest { public AssertTest() { } public static void main(String args[]) { int a = 1; entier b = 1 ; if(! KaTeX parse error : 'EOF' attendu, obtenu '&' à la position 20 : …rtionsDisabled &̲& a != b) … assertionsDisabled && a == b) { throw new AssertionError(“Hollis”); } else { System.out.println("\u535A\u5BA2\uFF1Awww.hollischuang.com"); retour; } } ​booléen final statique

















}

Évidemment, le code décompilé est beaucoup plus compliqué que notre propre code. Par conséquent, nous économisons beaucoup de code en utilisant le sucre syntaxique d'assertion. En fait, l'implémentation sous-jacente de l'assertion est le langage if. Si le résultat de l'assertion est vrai, rien ne sera fait et le programme continuera à s'exécuter. Si le résultat de l'assertion est faux, le programme lèvera une AssertError pour interrompre le exécution du programme. -enableassertions définira la valeur du champ $assertionsDisabled.
Candy Nine, Littéraux numériques

Dans Java 7, les littéraux numériques, qu'il s'agisse d'entiers ou de nombres à virgule flottante, permettent d'insérer n'importe quel nombre de traits de soulignement entre les nombres. Ces traits de soulignement n'affecteront pas la valeur littérale, le but est de faciliter la lecture.

Par exemple:

public class Test { public static void main(String… args) { int i = 10_000; System.out.println(i); } }




Après décompilation :

public class Test
{ public static void main(String[] args) { int i = 10000; System.out.println(i); } }





Après décompilation, _ est supprimé. C'est-à-dire que le compilateur ne reconnaît pas le _ dans le littéral numérique et doit le supprimer au stade de la compilation.
Bonbons dix, pour chacun

La boucle for améliorée (for-each) est censée être familière à tout le monde. Elle est souvent utilisée dans le développement quotidien. Elle écrira beaucoup moins de code que la boucle for. Alors, comment ce sucre syntaxique est-il implémenté ?

public static void main(String… args) { String[] strs = {"Hollis", "Compte officiel : Hollis", "Blog : www.hollishuang.com"} ; for (String s : strs) { System.out. println(s); } List strList = ImmutableList.of("Hollis", "Compte officiel : Hollis", "Blog : www.hollishuang.com"); for (String s : strList) { System.out.println(s ); } }








Le code décompilé est le suivant :

public static transient void main(String args[])
{ String strs[] = { "Hollis", "\u516C\u4F17\u53F7\uFF1AHollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com" } ; Chaîne args1[] = chaînes ; int je = args1.longueur ; for(int j = 0; j < i; j++) { Chaîne s = args1[j]; System.out.println(s); } List strList = ImmutableList.of("Hollis", "\u516C\u4F17\u53F7\uFF1AHollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com"); Chaîne s ; for(Iterator iterator = strList.iterator(); iterator.hasNext(); System.out.println(s)) s = (String)iterator.next(); ​}
















Le code est très simple, et le principe de for-each est en fait d'utiliser des boucles for et des itérateurs ordinaires.
Candy Eleven, essayez-avec-ressource

En Java, pour les ressources très coûteuses telles que les opérations sur les fichiers, les flux d'E/S et les connexions à la base de données, elles doivent être fermées par la méthode de fermeture dans le temps après utilisation, sinon les ressources seront toujours ouvertes, ce qui peut entraîner des problèmes tels que des fuites de mémoire. .

La façon courante de fermer une ressource est de la libérer dans le bloc finally, c'est-à-dire d'appeler la méthode close. Par exemple, nous écrivons souvent du code comme celui-ci :

public static void main(String[] args) { BufferedReader br = null; essayez { Ligne de chaîne ; br = nouveau BufferedReader(nouveau FileReader("d:\hollischuang.xml")); while ((line = br.readLine()) != null) { System.out.println(line); } } catch (IOException e) { // gérer l'exception } finally { try { if (br != null) { br.close(); } } catch (IOException ex) { // gérer l'exception } } }


















À partir de Java 7, jdk offre un meilleur moyen de fermer les ressources. Utilisez l'instruction try-with-resources pour réécrire le code ci-dessus. L'effet est le suivant :

public static void main(String… args) { try (BufferedReader br = new BufferedReader(new FileReader(“d:\ hollischuang.xml”))) { String line; while ((line = br.readLine()) != null) { System.out.println(line); } } catch (IOException e) { // gérer l'exception } }








Regardez, c'est une grande aubaine. Bien que j'utilise habituellement IOUtils pour fermer le flux avant, je n'utilise pas la méthode d'écriture de beaucoup de code finalement, mais ce nouveau sucre syntaxique semble être beaucoup plus élégant. Découvrez son derrière :

public static transient void main(String args[])
{ BufferedReader br; jetable jetable; br = nouveau BufferedReader(nouveau FileReader("d:\ hollischuang.xml")); jetable = null; Ligne de cordes ; essayez { while((line = br.readLine()) != null) System.out.println(line); } catch(Jetable jetable2) { jetable = jetable2; lancer jetable2 ; } if(br != null) if(throwable != null) try { br.close(); } catch(Throwable throwable1) { throwable.addSuppressed(throwable1); } sinon


























br.close();
casser MISSING_BLOCK_LABEL_113 ;
exception exception ;
exception;
if(br != null)
if(throwable != null)
try
{ br.close(); } catch(Throwable throwable3) { throwable.addSuppressed(throwable3); } sinon br.close(); lancer une exception ; IOException ioexception; ioexception ; } }












En fait, le principe est aussi très simple : le compilateur a fait pour nous les opérations de fermeture de ressources que nous n'avons pas faites. Par conséquent, il est confirmé à nouveau que la fonction du sucre syntaxique est de faciliter l'utilisation des programmeurs, mais au final, il doit être converti dans un langage que le compilateur comprend.
Candy Twelve, expression lambda

Concernant les expressions lambda, certaines personnes peuvent avoir des doutes, car certaines personnes sur Internet disent que ce n'est pas du sucre syntaxique. En fait, je veux corriger cette affirmation. Les expressions Labmda ne sont pas du sucre syntaxique pour les classes internes anonymes, mais elles sont aussi du sucre syntaxique. La méthode de mise en œuvre repose en fait sur plusieurs API liées à lambda fournies par la couche inférieure de la JVM.

Regardons d'abord une simple expression lambda. Itérer sur une liste :

public static void main(String… args) { List strList = ImmutableList.of(“Hollis”, “Official Account: Hollis”, “Blog: www.hollishuang.com”); ​strList.forEach ( s -> { System. out.println(s); } ); }



Pourquoi dites-vous que ce n'est pas le sucre syntaxique de la classe interne ?Nous avons dit précédemment que la classe interne aura deux fichiers de classe après compilation, mais la classe contenant l'expression lambda n'aura qu'un seul fichier après compilation.

Le code décompilé est le suivant :

public static /* varargs */ void main(String … args) { ImmutableList strList = ImmutableList.of((Object)"Hollis", (Object)"\u516c\u4f17\u53f7\uff1aHollis", (Object)"\u535a\ u5ba2\uff1awww.hollischuang.com » ); strList.forEach((Consumer)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main Erreur d'analyse KaTeX : 'EOF' attendu, obtenu '}' à la position 50 : …String ; )V)()); }̲ ​ private stat… main$0(String s) { System.out.println(s); }



On peut voir que dans la méthode forEach, la méthode java.lang.invoke.LambdaMetafactory#metafactory est réellement appelée, et le quatrième paramètre implMethod de la méthode spécifie l'implémentation de la méthode. On peut voir qu'une méthode lambda$main$0 est en fait appelée ici pour la sortie.

Regardons un peu plus compliqué, filtrons d'abord la liste, puis sortons :

public static void main(String… args) { List strList = ImmutableList.of(“Hollis”, “公众号:Hollis”, “博客:www.hollischuang.com”); ​List HollisList = strList.stream().filter (string -> string.contains(“Hollis”)).collect(Collectors.toList()); ​HollisList.forEach ( s -> { System.out.println(s); } ); }





Le code décompilé est le suivant :

public static /* varargs */ void main(String … args) { ImmutableList strList = ImmutableList.of((Object)"Hollis", (Object)"\u516c\u4f17\u53f7\uff1aHollis", (Object)"\u535a\ u5ba2\uff1awww.hollischuang.com » ); Liste HollisList = strList.stream().filter((Prédicat)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)Z, lambda$main

0 ( java . lang . String ) , ( L java / lang / String ; ) Z ) ( ) ) . collect ( C ollectors . to L ist ( ) ) ; H ollis L ist . for E ach ( ( C onsumer < O bject > ) L ambda Metafactory . metafactory ( null , null , null , ( L java / lang / O bject ; ) V , lambda 0(java.lang.String ), (Ljava/ lang/String;)Z)()).collect(Collectors.toList()); HollisList.forEach((Consumer<Object>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda0 ( java . l an g . String ) , _ _ _ _ _( L ja v a / l an g / St r in g ;) Z ) ( )) . co ll ec t ( C o ll ec t ors . to List ( ) ) ; _ _ _Ho ll i s List . _ _ _ f or E a c h (( C o n s u m er<O bj et _>) L amb d a M e t a f a c t ory . m e t a f a c tory ( n u ll , _non , _ _non , _ _( L ja v a / l an g / O bj ect ; _) V ,l amb d une erreur d'analyse KaTeX principale: 'EOF' attendu, '}' obtenu à la position 50 : …Object;)V)()); }̲ ​ privé stat…principal1 ( O bjets ) S ystème . dehors . println(s); ​ privatestatic / ∗ synthétique ∗ / booleanlambda 1(Object s) { System.out.println(s); } ​ privé statique /* synthétique */ booléen lambda1 ( O bj ect s ) S ys t e m . _ o u t . p r in tl n ( s ) ; ​essais privés / _ _ _ _ _ _ _ _ _ _synth e t i c _ _ _/ boo l e an l amb d a main$ 0 (String string) { return string.contains("Hollis"); }

Deux expressions lambda appellent respectivement lambda$main 1 et lambda 1 et lambda1 et l amb d a main$0 deux méthodes.

Par conséquent, l'implémentation des expressions lambda repose en fait sur certaines API sous-jacentes.Pendant la phase de compilation, le compilateur désucre les expressions lambda et les convertit en moyens d'appeler des API internes.
Pièges possibles
Génériques

1. Lorsque les génériques rencontrent une surcharge public class GenericTypes {

public static void method(List<String> list) {
    System.out.println("invoke method(List<String> list)");
}

​public
static void method(List list) { System.out.println("invoke method(List list)"); } }


Le code ci-dessus a deux fonctions surchargées car leurs types de paramètres sont différents, l'un est List et l'autre est List.Cependant, ce code ne peut pas être compilé. Comme nous l'avons dit précédemment, les paramètres List et List sont effacés après la compilation et deviennent le même type natif List. L'action d'effacement fait que les signatures de caractéristiques de ces deux méthodes deviennent exactement les mêmes.

2. Lorsque les génériques rencontrent catch Les paramètres de type générique ne peuvent pas être utilisés dans les instructions catch pour la gestion des exceptions Java. Parce que la gestion des exceptions est effectuée par la JVM au moment de l'exécution. Étant donné que les informations de type sont effacées, la JVM ne peut pas faire la distinction entre les deux types d'exception MyException et MyException

3. Lorsque le générique contient des variables statiques

public class StaticTest{ public static void main(String[] args){ GT gti = new GT(); gti.var=1 ; GT gts = nouveau GT(); gts.var=2 ; System.out.println(gti.var); } } classe GT{ public static int var=0 ; public void rien(T x){} }











La sortie du code ci-dessus est : 2 ! En raison de l'effacement de type, toutes les instances de classe générique sont associées au même bytecode et toutes les variables statiques de la classe générique sont partagées.
Boxing et unboxing automatiques

Comparaison d'égalité d'objets

test de boxe de classe publique {

public static void main(String[] args) { Entier a = 1000 ; Entier b = 1000 ; Entier c = 100 ; Entier d = 100 ; System.out.println("a == b est " + (a == b)); System.out.println(("c == d est " + (c == d))); }






Résultat de sortie :

a == b est faux
c == d est vrai

Dans Java 5, une nouvelle fonctionnalité a été introduite sur les opérations sur Integer pour économiser de la mémoire et améliorer les performances. Les objets entiers permettent la mise en cache et la réutilisation en utilisant la même référence d'objet.

适用于整数值区间-128 至 +127。

只适用于自动装箱。使用构造函数创建对象不适用。

Boucle for améliorée

ConcurrentModificationExceptionConcurrentModificationException

for (Étudiant stu : étudiants) { if (stu.getId() == 2) étudiants.remove(stu); }


Une ConcurrentModificationException sera levée.

Iterator fonctionne dans un thread indépendant et possède un verrou mutex. Une fois l'itérateur créé, il créera une table d'index à lien unique pointant vers l'objet d'origine. Lorsque le nombre d'objets d'origine change, le contenu de la table d'index ne change pas de manière synchrone. Ainsi, lorsque le pointeur d'index recule, il introuvable pour itérer. objet, donc selon le principe de l'échec rapide, Iterator lèvera java.util.ConcurrentModificationException immédiatement.

Ainsi, l'itérateur ne permet pas de modifier l'objet itéré pendant qu'il fonctionne. Mais vous pouvez utiliser la méthode remove() de Iterator lui-même pour supprimer l'objet, et la méthode Iterator.remove() maintiendra la cohérence de l'index tout en supprimant l'objet d'itération actuel.
Résumer

La section précédente a présenté 12 sucres syntaxiques couramment utilisés en Java. Le soi-disant sucre syntaxique n'est qu'une sorte de syntaxe fournie aux développeurs pour faciliter le développement. Mais cette syntaxe n'est connue que des développeurs. Pour être exécuté, il doit être désucré, c'est-à-dire converti dans une syntaxe reconnue par la JVM. Lorsque nous désucrons la grammaire, vous constaterez que les grammaires pratiques que nous utilisons tous les jours sont en fait composées d'autres grammaires plus simples.

Avec ces sucres grammaticaux, nous pouvons grandement améliorer l'efficacité du développement quotidien, mais en même temps éviter une utilisation excessive. Il est préférable d'en comprendre le principe avant de l'utiliser pour éviter de tomber dans la fosse.

Je suppose que tu aimes

Origine blog.csdn.net/zy_dreamer/article/details/132306868
conseillé
Classement