1. Points de connaissance sur le contrôle des objets et de la mémoire
1. Le processus d'initialisation des variables Java, y compris les variables locales, les variables membres (variables d'instance et variables de classe).
2. Dans la relation d'héritage, lorsque le type de temps de compilation et le type d'exécution sont différents du type de temps de compilation de la variable de référence de l'objet utilisé, il y a une différence dans les propriétés et les méthodes d'accès à l'objet.
3. Caractéristiques finales du modificateur.
2. Le processus de division et d'initialisation des variables Java
Les variables des programmes Java peuvent être à peu près divisées en variables membres et variables locales. Les variables des membres peuvent être divisées en variables d'instance (variables non statiques) et des variables de classe (variables statiques). Généralement, les variables locales que nous rencontrons apparaîtront dans les situations suivantes:
(1) Paramètre formel: les variables locales définies dans la signature de la méthode sont attribuées par l'appelant et disparaissent à mesure que la méthode se termine.
(2) Les variables locales dans la méthode: les variables locales définies dans la méthode doivent être initialisées (attribuer une valeur initiale) dans la méthode et disparaît lorsque l'initialisation variable commence et se termine.
(3) Les variables locales dans le bloc de code: les variables locales définies dans le bloc de code doivent être initialisées (attribuer des valeurs initiales) qui doivent être affichées dans les blocs de code. Ils prendront effet à la fin de l'initialisation et mourront à la fin du bloc de code.
package com.zlc.array; classe publique TestField {{String b; // S'il n'est pas initialisé, le compilateur rapportera que la variable locale B n'a peut-être pas été initialisée System.out.println (b); } public static void main (String [] args) {int a; // S'il n'est pas initialisé, le compilateur rapportera la variable locale, A n'a peut-être pas été initialisé System.out.println (a); }} Les variables membres modifiées avec des variables de classe statiques sont des variables, qui appartiennent à la classe elle-même. Les variables membres qui ne sont pas modifiées avec des variables d'instance sont des variables d'instance. Instances appartenant à cette classe, dans le même JVM, chaque classe ne peut correspondre qu'à un seul objet de classe, mais chaque classe peut créer plusieurs objets Java. (C'est-à-dire qu'une variable de classe ne nécessite qu'un seul espace mémoire, et chaque fois que la classe crée une instance, il doit allouer un morceau d'espace à la variable d'instance)
Processus d'initialisation des variables d'instance: du point de vue de la syntaxe, le programme peut effectuer l'initialisation des variables d'instance à trois endroits:
(1) Spécifiez la valeur initiale lors de la définition d'une variable d'instance.
(2) Spécifiez la valeur initiale par exemple les variables dans les blocs non statiques.
(3) Spécifiez les variables de valeur initiale par exemple dans le constructeur.
Parmi eux, le temps d'initialisation des deux méthodes (1) et (2) est plus tôt que celui de (3) dans le constructeur, et les deux ordres d'initialisation (1) et (2) sont déterminés dans l'ordre où ils sont organisés dans le code source.
package com.zlc.array; classe publique TestField {public testField (int Age) {System.out.println ("Initialiser this.age dans le constructor =" + this.age); this.age = âge; } {System.out.println ("Initialiser dans les blocs non statiques"); âge = 22; } // Initialize int Age = 15; public static void main (String [] args) {TestField field = new TestField (24); System.out.println ("Final Age =" + Field.age); }} Le résultat de l'exécution est: initialiser ce.age = 15 dans le constructeur d'initialisation dans le bloc non statique
Âge final = 24
Si vous savez comment utiliser Javap, vous pouvez utiliser Javap -C XXXX (fichier de classe) pour voir comment la classe Java est compilée.
Lors de la définition d'une variable d'instance, spécifiez la valeur initiale. Dans le bloc d'initialisation, l'état de l'instruction spécifiant la valeur initiale de la variable d'instance est égal. Une fois le compilateur compilé et traité, ils sont tous mentionnés dans le constructeur. L'âge intra-mentionné = 15 sera divisé en les deux étapes suivantes pour exécuter:
1) en âge; Lors de la création d'un objet Java, le système alloue la mémoire à l'objet en fonction de l'instruction.
2) âge = 15; Cette déclaration sera extraite dans le constructeur de la classe Java et exécutée.
Processus d'initialisation des variables de classe: du point de vue de la syntaxe, un programme peut initialiser et attribuer des valeurs aux variables de classe à partir de deux endroits.
(1) Spécifiez la valeur initiale lors de la définition d'une variable de classe.
(2) Spécifiez la valeur initiale des variables de classe dans un bloc statique.
Les deux ordonnances d'exécution sont les mêmes que leur arrangement dans le code source. Donnons un petit exemple anormal:
package com.zlc.array; classe TestStatic {// Classe Member Demo TestStatic Instance final static testStatic Demo = new TestStatic (15); // Membre de classe statique Int Age = 20; // Curage de curage variable d'instance INT; Public TestStatic (INT Years) {// TODO Générteur généré de constructeur Curage = Age - ans; }} Public Class Test {public static void main (String [] args) {System.out.println (testStatic.demo.curage); TestStatic staticdemo = new testStatic (15); System.out.println (staticdemo.curage); }} Le résultat de sortie est imprimé en deux lignes. L'une consiste à imprimer la variable d'instance de la démo d'attribut de classe TestStatic, et la seconde consiste à sortir l'attribut d'instance de TestStatic via le statique de l'objet Java. Selon le processus d'initialisation de la variable d'instance et des variables de classe que nous avons analysées ci-dessus, nous pouvons le déduire:
1) Dans la première étape de l'initialisation, lors du chargement de la classe, allouez l'espace mémoire pour la démo et l'âge des variables de classe. À l'heure actuelle, les valeurs par défaut de démo et d'âge sont nuls et 0 respectivement.
2) Dans la deuxième étape de l'initialisation, le programme attribue des valeurs initiales à la démo et à l'âge en séquence. TestStatic (15) doit appeler le constructeur de TestStatique. À ce moment, l'âge = 0, donc le résultat de l'impression est -15. Lorsque StaticDemo est initialisé, l'âge a été attribué à 20, donc le résultat de sortie est 5.
3. La différence entre les variables des membres héritées et les méthodes héréditaires des membres dans les relations successives
Lors de la création de n'importe quel objet Java, le programme appellera toujours le bloc non statique et le constructeur de classe parent de la classe parent d'abord et appellera enfin le bloc et le constructeur non statiques de cette classe. Appeler le constructeur de la classe parent à travers le constructeur de la sous-classe est généralement divisé en deux situations: l'une est un appel implicite, et l'autre est un super affichage pour appeler le constructeur de la classe parent.
La méthode de classe infantile peut appeler la variable d'instance de la classe parent. En effet Cependant, la méthode de la classe parent ne peut pas accéder à la variable d'instance de la classe enfant car la classe parent ne sait pas quelle classe il héritera et quel type de variables de membre sa sous-classe ajoutera. Bien sûr, dans certains exemples extrêmes, la classe parent peut toujours appeler la variable de classe enfant. Par exemple: la classe infantile réécrit la méthode de la classe parent et imprime généralement la valeur par défaut, car la variable d'instance de la classe enfant n'a pas été initialisée pour le moment.
package com.zlc.array; classe père {int age = 50; public Père () {// TODO Auto-généré de constructeur Stub System.out.println (this.getClass ()); //this.sonMethod (); Impossible d'appeler info (); } public void info () {System.out.println (âge); }} La classe publique Son étend le père {int Age = 24; Public Son (int Age) {// TODO Auto-généré de constructeur Stume ce.age = âge; } @Override public void info () {// TODO Méthode générée automatique Stub System.err.println (âge); } public static void main (String [] args) {new fils (28); } // Méthode spécifique de la sous-classe publique void SonMethod () {System.out.println ("SON Method"); }} Selon notre inférence normale, le constructeur de classe parent est implicitement appelé via la sous-classe, et la méthode info () est appelée dans le constructeur de la classe parent (note: je n'ai pas dit que la classe parent est appelée). En théorie, il produit la variable d'instance d'âge de la classe parent. Le résultat de l'impression devrait être de 50, mais le résultat de sortie réel est 0. Analyse de la raison:
1) L'allocation de mémoire des objets Java n'est pas terminée dans le constructeur. Le constructeur termine uniquement le processus d'attribution d'initialisation. Autrement dit, avant d'appeler le constructeur de la classe parent, JVM a classé l'espace mémoire pour l'objet Son. Cet espace stocke deux attributs d'âge, l'un est l'âge de la sous-classe et l'autre est l'âge de la classe parent.
2) Lors de l'appel du nouveau fils (28), le courant cet objet représente un objet qui est un fils sous-classe. Nous pouvons imprimer l'objet.getClass () et obtenir le résultat de la classe com.zlc.array.son. Cependant, le processus d'initialisation actuel est effectué dans le constructeur de la classe parent, et il ne peut pas être appelé via ce.sonMethod (), car ce type de compilation est père.
3) Lorsque le type de temps de compilation de la variable est différent du type d'exécution, lors de l'accès à la variable d'instance de son objet de référence via la variable, la valeur de la variable d'instance est déterminée par le type de la variable déclarée. Cependant, lorsque la méthode d'instance de l'objet qu'il fait référence via la variable, le comportement de la méthode est déterminé par l'objet qu'il fait réellement référence. Par conséquent, la méthode d'information de la sous-classe est appelée ici, donc l'âge de la sous-classe est imprimé. Étant donné que l'âge n'a pas encore été initialisé d'urgence, la valeur par défaut est 0.
En termes de laïque, lorsque le type déclaré est incompatible avec le nouveau type réel, l'attribut utilisé est la classe parent et la méthode appelée est la classe enfant.
Grâce à Javap -C, nous pouvons mieux comprendre pourquoi il existe une grande différence entre les attributs et les méthodes hérités. Si nous supprimons la méthode de réécriture d'informations du fils de sous-classe dans l'exemple ci-dessus, la méthode d'information de la classe parent sera appelée pour le moment, car lors de la compilation, la méthode d'information de la classe parent sera transférée à la sous-classe et la variable membre de la réputation sera laissée dans la classe parent et non transférée. De cette façon, la sous-classe et la classe parent ont des variables d'instance avec le même nom. Si la sous-classe réécrit la méthode de la classe parent avec le même nom, la méthode de sous-classe écrasera complètement la méthode de la classe parent (quant à pourquoi Java est conçu comme ceci, je ne suis pas très clair). Les variables avec le même nom peuvent exister et ne pas écraser en même temps. Les sous-classes de méthodes avec le même nom écraseront complètement la méthode du même nom de la classe parent.
En général, pour une variable de référence, lors de l'accès à une variable d'instance de l'objet, il fait référence via la variable, la valeur de la variable d'instance dépend du type lorsque la variable est déclarée, et lors de l'accès à la méthode de l'objet qu'il fait référence via la variable, le comportement de la méthode dépend du type de l'objet qui fait réellement référence.
Enfin, je vais le passer en revue avec un petit cas:
package com.zlc.array; classe Animal {int Age; Animal public () {} Animal public (int Age) {// TODO AUTO AUTO AUTO-AUTORATEUR Stume This.age = Age; } void run () {System.out.println ("Animal Run" + Age); }} Class Dog étend Animal {int Age; Nom de chaîne; Dog public (Int Age, Nom de la chaîne) {// TODO AUTO GÉNÉRÉ AUTO Stume ce.age = Age; this.name = name; } @Override void run () {System.out.println ("Dog Run" + Age); }} classe publique TesTentend {public static void main (String [] args) {animal animal = new animal (5); System.out.println (Animal.age); animal.run (); Chien de chien = nouveau chien (1, "Xiaobai"); System.out.println (dog.age); dog.run (); Animal animal2 = nouveau chien (11, "wangcai"); System.out.println (Animal2.age); animal2.run (); Animal animal3; animal3 = chien; System.out.println (Animal3.age); animal3.run (); }} Si vous souhaitez appeler la méthode de la classe parent: vous pouvez l'appeler via Super, mais le mot-clé Super ne fait référence à aucun objet, et il ne peut être utilisé comme une variable de référence réelle. Les amis intéressés peuvent l'étudier vous-même.
Les éléments ci-dessus sont des variables et des méthodes. Les variables de classe et les méthodes de classe sont beaucoup plus simples, donc en utilisant directement les noms de classe. Les méthodes sont beaucoup plus pratiques et vous ne rencontrerez pas autant de problèmes.
4. Utilisation de modificateurs finaux (en particulier le remplacement macro)
(1) Inal peut modifier les variables. Une fois que la variable modifiée par final se voit attribuer une valeur initiale, il ne peut plus être attribué.
(2) Inal peut modifier la méthode et la méthode modifiée finale ne peut pas être réécrite.
(3) Inal peut modifier les classes et les classes modifiées par final ne peuvent pas dériver des sous-classes.
La valeur initiale spécifiée que la variable modifiée par final doit être affichée:
Par exemple, variables modifiées finales, la valeur initiale ne peut être attribuée qu'aux trois positions spécifiées suivantes.
(1) Spécifiez la valeur initiale lors de la définition de la variable d'instance finale.
(2) Spécifiez la valeur initiale de la variable d'instance finale dans un bloc non statique.
(3) Spécifiez la valeur initiale de la variable d'instance finale dans le constructeur.
Ils seront finalement mentionnés dans le constructeur pour l'initialisation.
Pour les variables de classe spécifiées avec final: les valeurs initiales ne peuvent être attribuées que dans deux endroits spécifiés.
(1) Spécifiez la valeur initiale lors de la définition de la variable de classe finale.
(2) Spécifiez la valeur initiale de la variable de classe finale dans un bloc statique.
Également traités par le compilateur, contrairement aux variables d'instance, les variables de classe sont toutes mentionnées pour attribuer des valeurs initiales dans des blocs statiques, tandis que les variables d'instance sont mentionnées aux constructeurs.
Il existe une autre caractéristique des variables de classe modifiées par Final, qui est "Remplacement de macro". Lorsque la variable de classe modifiée satisfait la valeur initiale lors de la définition de la variable, la valeur initiale peut être déterminée pendant la compilation (par exemple: 18, "AAAA", 16.78 et d'autres quantités directes), alors la variable de classe modifiée par final n'est pas une variable, et le système le traitera comme une "variable macro" (ce que nous appelons souvent une constante). Si la valeur initiale peut être déterminée pendant la compilation, elle ne sera pas mentionnée dans le bloc statique pour l'initialisation, et la valeur initiale sera directement remplacée par la variable finale dans la définition de la classe. Donnons un exemple de l'âge moins l'année:
package com.zlc.array; classe TestStatic {// Classe Member Demo TestStatic Instance final static testStatic Demo = new TestStatic (15); // membre de la classe Âge final statique fin d'âge = 20; // Curage de curage variable d'instance INT; Public TestStatic (INT Years) {// TODO Générteur généré de constructeur Curage = Age - ans; }} Public Class Test {public static void main (String [] args) {System.out.println (testStatic.demo.curage); TestStatic static1 = nouveau testStatique (15); System.out.println (static1.curage); }} À l'heure actuelle, l'âge est modifié par final, donc lors de la compilation, tous les âges de la classe parent deviennent 20, pas une variable, afin que le résultat de sortie puisse répondre à nos attentes.
Surtout lorsque vous comparez les chaînes, il peut être affiché plus
package com.zlc.array; classe publique testString {Static String static_name1 = "java"; State String static_name2 = "me"; chaîne statique statique statique stati_name3 = static_name1 + static_name2; chaîne statique finale final_static_name1 = "java"; chaîne statique finale final_static_name2 = "me"; // Ajouter final ou non, il peut être remplacé par une macro à l'avant. Chaîne finale final_statci_name3 = final_static_name1 + final_static_name2; public static void main (String [] args) {String name1 = "java"; String name2 = "me"; String name3 = name1 + name2; // (1) System.out.println (name3 == "javame"); // (2) System.out.println (testString.statci_name3 == "javame"); // (3) System.out.println (testString.Final_statci_name3 == "Javame"); }} Il n'y a rien à dire sur l'utilisation de méthodes et de classes de modification finales, juste que l'un ne peut pas être réécrit par des sous-classes (comme privé), et l'autre ne peut pas dériver des sous-classes.
Lors de la modification des variables locales avec Final, Java exige que les variables locales accessibles par les classes internes soient modifiées avec final. Il y a une raison. Pour les variables locales ordinaires, leur portée reste dans la méthode. Lorsque la méthode se termine, la variable locale disparaît, mais la classe interne peut générer une "fermeture" implicite, ce qui fait que la variable locale reste séparée de la méthode où elle est située.
Parfois, un fil sera nouveau dans une méthode, puis la variable locale de la méthode est appelée. À l'heure actuelle, la variable de modification doit être déclarée modifiée finale.
5. Méthode de calcul de la mémoire d'occupation des objets
Utilisez les méthodes FreeMemory (), TotalMemory () et MaxMemory () dans les classes java.lang.runtime pour mesurer la taille d'un objet Java. Cette méthode est généralement utilisée lorsque de nombreuses ressources doivent être déterminées avec précision. Cette méthode est presque inutile pour implémenter le cache du système de production. L'avantage de cette méthode est que le type de données est indépendant de la taille et que différents systèmes d'exploitation peuvent obtenir la mémoire occupée.
Il utilise l'API de réflexion pour traverser la hiérarchie des variables membre d'un objet et calculer la taille de toutes les variables d'origine. Cette approche ne nécessite pas autant de ressources et peut être utilisée pour les implémentations mises en cache. L'inconvénient est que la taille du type d'origine est différente et différentes implémentations JVM ont des méthodes de calcul différentes.
Après JDK5.0, l'API d'instrumentation fournit la méthode GetObjectSize pour calculer la taille de la mémoire occupée par l'objet.
Par défaut, la taille de l'objet référencé n'est pas calculée. Afin de calculer l'objet référencé, vous pouvez utiliser la réflexion pour l'obtenir. La méthode suivante est une implémentation fournie dans l'article ci-dessus qui calcule la taille de l'objet de référence:
classe publique Sizeofagent {instrumentation statique Inst; / ** Initialize Agent * / public static void Premain (String AgentArgs, Instrumentation Instp) {Inst = Instp; } / ** * Renvoie la taille de l'objet sans sous-objets membres. * @param o Objet pour obtenir la taille de * @return objet Taille * / public static long sizeof (objet o) {if (inst == null) {lancez un nouveau IllégalStateException ("ne peut accéder à l'environnement d'instrumentation./N" + "Veuillez vérifier si le fichier jar contenant une classe de commandes de la taille de la Java est / n"). } return inst.getObjectSize (o); } / ** * Calcule pleine grandeur d'objet itérant sur * son graphique de hiérarchie. * @param objet pour calculer la taille de * @return objet taille * / public static long fullsizeof (objet obj) {map <objet, objet> visité = new identityhashmap <objet, objet> (); Stack <objet> pile = new Stack <Bject> (); long résultat = InternalSizeOf (obj, pile, visité); while (! stack.isempty ()) {result + = interneSizeof (stack.pop (), pile, visité); } visité.clear (); Résultat de retour; } private static boolean skipObject (objet obj, map <objet, objet> visité) {if (obj instanceof string) {// skip string interned if (obj == ((string) obj) .intern ()) {return true; }} return (obj == null) // sauter l'objet visité || visité.ContainsKey (OBJ); } Private Static Long InternalSizeOf (objet obj, pile <objet> pile, map <objet, objet> visité) {if (skipObject (obj, visité)) {return 0; } visité.put (obj, null); résultat long = 0; // Obtenez la taille de l'objet + variables primitives + Résultat des points de membre + = sizeofagent.sizeof (obj); // traite tous les éléments du tableau classe CLAZZ = obj.getClass (); if (Clazz.isArray ()) {if (Clazz.getName (). Length ()! = 2) {// Skip Primitive Type Array int Length = Array.GetLength (obj); for (int i = 0; i <length; i ++) {stack.add (array.get (obj, i)); }} Retour Résultat; } // traite tous les champs de l'objet while while (Clazz! = null) {field [] fields = Clazz.getDeclaredFields (); for (int i = 0; i <fields.length; i ++) {if (! modificier.isstatic (fields [i] .getModifiers ())) {if (fields [i] .getType (). isPrimitive ())) {continue; // Sauter les champs primitifs} else {champs [i] .SetAccessible (true); essayez {// objets à estimer sont placés pour empiler l'objet objettoadd = champs [i] .get (obj); if (objectToAdd! = null) {stack.add (objectToAdd); }} catch (illégalaccessException ex) {affirmer false; }}}}} Clazz = Clazz.getsuperclass (); } Retour Résultat; }}