[JAVA] Référence implicite à l'objet englobant détenu par la classe imbriquée non statique

Cet article décrit «Soyez prudent, car les classes imbriquées non statiques saisissent l'objet englobant comme une référence implicite» dans Java.

La raison pour laquelle vous devez faire attention est que cela augmente la possibilité de provoquer une fuite de mémoire.

Et la ligne du bas est: "Les classes imbriquées sont mieux avec le modificateur static (car elles ne contiennent pas l'objet englobant comme référence implicite)." Les objets englobants sont de courte durée, surtout si les instances de classe imbriquées ont une durée de vie plus longue!

La raison en était lors du développement d'une application Android

(Cet article n'est pas spécifique au développement d'applications Android, mais au fur et à mesure que vous le lirez, vous serez impliqué dans Java, alors soyez patient au début.)

C'était un jour. Lorsque je créais une application Android en Java, Android Studio [^ 1] a émis un avertissement comme celui-ci.

[^ 1]: IDE pour le développement d'applications Android réalisé en modifiant IntelliJ IDEA

thisasynctaskclassshouldbestaticorleaksmightoccur.png

This 'AsyncTask' class should be static or leaks might occur

Cette classe, nommée CouponAsyncTask, est définie comme une classe imbriquée (classe interne / classe interne), mais l'info-bulle dit: «Ajoutez un modificateur statique, sinon cela peut provoquer une fuite de mémoire». Je suis. Eh bien, j'ai peur.

Si vous regardez l'explication détaillée de la fonction de vérification de code "Lint" d'Android Studio,

Static Field Leaks A static field will leak contexts. Non-static inner classes have an implicit reference to their outer class. If that outer class is for example a Fragment or Activity, then this reference means that the long-running handler/loader/task will hold a reference to the activity which prevents it from getting garbage collected. Similarly, direct field references to activities and fragments from these longer running instances can cause leaks. ViewModel classes should never point to Views or non-application Contexts. Issue id: StaticFieldLeak

Je vais demander la traduction à Google.

Fuite de champ statique Contexte de fuite des champs statiques. Les classes internes non statiques ont des références implicites aux classes externes. Si la classe externe est, par exemple, un fragment ou une activité, cette référence signifie que le gestionnaire / chargeur / tâche de longue durée contient une référence à l'activité qui l'empêche de récupérer le garbage collection. De même, les références de champ directes aux activités et aux fragments de ces instances plus longues peuvent provoquer des fuites. La classe ViewModel ne pointe pas vers un contexte de vue ou de non-application. ID du problème: StaticFieldLeak

Même si une phrase anglaise difficile est traduite en un japonais difficile ... C'est Chimpung Kampung.

Il a été écrit sur le site du développeur

Il a été correctement publié sur le site officiel des développeurs d'applications Android comme "un exemple de faille courante dans la conception de code utilisant des objets filetés". Je me demande s'il y en a. Amélioration des performances par threading [référence implicite]

La page japonaise de ce site officiel se traduit par "références implicites", mais le texte original est écrit par "références implicites". Dans cet article, nous utiliserons des mots composés de cinq caractères comme «référence implicite».

Classe imbriquée

Même si je parle soudain de "référence implicite d'objet englobant détenu par une classe imbriquée non statique", il y a beaucoup d'informations sur ce sujet, donc j'expliquerai les connaissances préalables une par une. Dépêchez-vous. Cependant, cela peut être un peu frustrant.

Une «classe imbriquée» est également appelée «classe interne». Cet article est unifié avec "classe imbriquée".

La classe Foo a deux classes imbriquées


public class Foo {
    class Bar {
        void methodBar() {
            System.out.println("methodBar");
        }
    }

    static class Baz {
        void methodBaz() {
            System.out.println("methodBaz");
        }
    }

    void methodFoo() {
        System.out.println("methodFoo");
    }
}

dans ce cas,

La «classe englobante» est également connue sous le nom de «classe externe». Cet article sera unifié avec «Classe englobante».

La différence entre Bar et Baz est de savoir s'ils ont ou non le modificateur static.

Pour créer une instance d'une classe imbriquée

Créer deux classes imbriquées de Foo


class Main {
    public static void main(String[] args) {
        Foo foo = new Foo();
        Foo.Bar bar = foo.new Bar();

        Foo.Baz baz = new Foo.Baz();
    }
}

Lors de la création d'une instance d'une classe imbriquée non statique

Il a fallu deux lignes pour créer une instance de la classe Bar. Si vous voulez vraiment le faire en une seule ligne, utilisez le code suivant.

Créer une instance de la classe Bar sur une seule ligne


Foo.Bar bar = new Foo().new Bar();

Dans tous les cas, si vous souhaitez créer une instance d'une classe imbriquée non statique, vous avez besoin d'une instance de clôture. Et il est également caractéristique que «.» Soit ajouté avant l'opérateur «nouveau».

Lors de la création d'une instance d'une classe imbriquée statique

Vous n'avez pas besoin d'une instance englobante à l'avance. Vous pouvez soudainement «nouveau».

Ce qui est unique, c'est que le nom de la classe est " Foo.Baz "au lieu de" Baz". Il est caractéristique que «.» Soit inclus entre les noms de classe.

Correspond au livre "Effective Java"

Effective java a publié "Static member classes plutôt que non-static member classes" à partir de la première édition (publiée en 2001). J'ai beaucoup de choses à choisir. C'est une cérémonie de réussite ou d'échec qui reste même dans la troisième édition publiée en 2018.

Les fuites de mémoire peuvent être catastrophiques.

Et

Les fuites de mémoire sont généralement difficiles à détecter.

Et

Conservez des références supplémentaires et perdez de la mémoire et du temps.

Par exemple, des choses effrayantes sont écrites. Parce que

** Si vous omettez le modificateur statique, chaque instance aura une référence non pertinente à l'objet englobant. La sauvegarde de cette référence prend du temps et de la mémoire. La chose sérieuse est que sans cette référence, l'instance englobante peut rester si elle est soumise au garbage collection. ** **

Est écrit.

Les instances de clôture ne sont plus nécessaires, mais ne sont pas appelées au ciel

J'ai un peu modifié le programme ci-dessus. La classe imbriquée statique Baz a été un peu rejetée.

python


public class Main {
    public static void main(String[] args) {
        Foo foo = new Foo();
        Foo.Bar bar = foo.new Bar();

        foo = null;
        System.gc();
//        foo.methodFoo(); //NullPointerException s'est produite
        bar.methodBar();
    }
}

J'ai assigné null à l'instance Foo de la classe englobante pour effectuer le garbage collection (GC), puis j'ai appelé la méthode à l'instance Bar de la classe imbriquée non statique. Quand j'essaye de l'exécuter, c'est comme suit.

Résultat d'exécution


methodBar

Ouais, ça marche! Je l'ai regardé.

Selon la spécification Java, peu importe combien j'attribue null à l'instance Foo de la classe englobante, tant que l'instance Bar de la classe imbriquée non statique fonctionne, l'objet nommé foo sera Ce n'est pas GC (il a été brillamment passé sans être considéré comme une cible GC). Parce que ** bar contient foo comme référence implicite **.

Si vous faites une illustration par analogie amusante, cela ressemble à ceci.

joubutudekinai.png

La femme qui monte les escaliers pas à pas est pleine d'énergie au «bar».

D'un autre côté, le vieil homme qui veut devenir Bouddha est «foo», mais parce que «bar» le tient, il n'est pas facilement appelé au paradis. Cela peut être un esprit terrestre.

--Gripping = référence

Veuillez lire comme.

Ensuite, ajoutez simplement de la statique à la classe imbriquée dans un court-circuit et tout sera résolu! Y allez-vous?

Arrêtez d'utiliser Foo, Bar et Baz et consultez le programme ci-dessous.

Avant: la classe imbriquée n'est pas statique


class Fizz {
    private Integer x = 12;
    private static Integer y = 34;

    class Buzz {
        void m() {
            x = 56;
            y = 78;
            System.out.println(x + y);
        }
    }
}

class FizzBuzzMain {
    public static void main(String[] args) {
        Fizz.Buzz fb = new Fizz().new Buzz();
        fb.m();
    }
}

Avant le résultat de l'exécution


134

C'est une classe imbriquée non statique, même si c'est (intentionnellement) fait. Maintenant, ajoutons simplement le modificateur static à cette classe imbriquée nommée Buzz.

Après vente


class Fizz {
    private Integer x = 12;
    private static Integer y = 34;

    static class Buzz {
        void m() {
            x = 56; //Impossible de compiler car x n'est pas un champ statique
            y = 78;
            System.out.println(x + y); //Impossible de compiler car x n'est pas un champ statique
        }
    }
}

class FizzBuzzMain {
    public static void main(String[] args) {
        Fizz.Buzz fb = new Fizz.Buzz();
        fb.m();
    }
}

Immédiatement, je n'ai pas pu compiler. L'EDI se met en colère s'il ne fonctionne pas car «x» n'est pas un champ statique.

Pourquoi. En effet, les ** classes imbriquées statiques ne peuvent accéder qu'aux membres statiques de la clôture **.

Revenons à l'histoire du développement d'applications Android

Pour l'application Android que vous vouliez créer, consultez ce GIF animé.

countup.gif

Dès que vous le démarrez, le décompte démarre automatiquement. Ce GIF animé est une animation qui se répète START! Quand il atteint 3, mais en réalité, cette application commence à partir de START! Et compte jusqu'à "9" et se termine (continue à afficher "9"). ..

Et le programme est le suivant. Même ceux qui n'ont aucune expérience dans le développement d'applications Android doivent être patients.

Classe d'écran


public class MainActivity extends AppCompatActivity {

    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = findViewById(R.id.text_view);
        new MyTask().execute();
    }

    class MyTask extends AsyncTask<Void, String, Void> {
        @Override
        protected Void doInBackground(Void... voids) {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(1000);
                    publishProgress(String.valueOf(i));
                } catch (InterruptedException e) {
                    Log.e("MyTask", e.getMessage(), e);
                }
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(String... values) {
            textView.setText(values[0]);
        }
    }
}

Si vous souhaitez créer une "classe d'écran" sur Android, commencez à coder une classe qui hérite de ʻAppCompatActivity`.

La classe TextView est une vue (partie) qui affiche (peut uniquement) des chaînes de caractères placées au centre de l'écran et affiche" START! "," 0 "ou" 1 ". Je veux changer ce TextView de" 0 "à" 1 "puis à" 2 "par incréments de 1000 millisecondes par traitement asynchrone. C'est la condition requise pour cette application.

Si vous voulez un traitement asynchrone, définissez une sous-classe de ʻAsyncTask` et codez-y le traitement.

MainActivity_MyTask.png

Si vous appelez la méthode ʻexecute () sur MyTaskqui effectue le traitement asynchrone,MyTaskcommencera le traitement. La boîte rouge dans cette image est «TextView».MyTask écrase ce TextView avec une chaîne telle que "0" , "1" ou "2" `. Cela donne l'impression que ça compte.

Ajoutons maintenant static à la classe MyTask.

Non-static field.png

L'IDE est devenu rouge en colère. Parce que le champ nommé textView n'a pas le modificateur statique!

Je n'ai plus besoin d'une instance de MainActivity, mais je ne suis pas appelé au paradis

Veuillez jeter un œil à l'illustration de la femme qui monte les escaliers et du vieil homme à l'esprit terrestre.

Sur Android, les écrans qui ne sont plus nécessaires sont GC. L '«écran inutile» est lorsque l'utilisateur masque (éteint) l'écran en appuyant sur le bouton retour. En général, les appareils Android ont moins de mémoire que les PC [^ 2]. Par conséquent, l'écran masqué par l'utilisateur = l'instance de la classe Activity est rapidement GCed par le runtime.

[^ 2]: Les smartphones Android "gaming" avec 10 Go de RAM sont sur le marché, mais laissez-les tranquilles.

L'utilisateur appuie simplement sur le bouton retour, et cet écran = instance d'activité n'est pas nécessaire! Nja GC! Cela a tendance à être de courte durée et fatidique. Donc, en arrière-plan (non, le terme "arrière-plan" n'est pas correct, il vaut mieux dire "dans le traitement asynchrone dans le thread de travail") MaTâche est la mienne Vous vivrez jusqu'à ce que vous ayez terminé votre travail (compte à rebours).

C'est l'IDE appelé Android Studio qui m'a averti qu'il s'agissait d'une [instance englobante <instance de classe imbriquée] dans cette comparaison du temps de survie.

Démarrez cette application que j'ai créée, et même si le décompte commence en raison du travail de MyTask, l'utilisateur appuie sur le bouton de retour, etc. pour masquer l'écran (éteindre, arrêter l'application) Même ainsi, ** l'objet MyTask, qui est une instance de la classe imbriquée non statique, contient une référence implicite à l'instance MainActivity **, donc il ne devrait plus être affiché, donc ce devrait être GC. De plus, il tombe dans un état où il n'est pas GC. Là où l'utilisateur n'est pas visible, MyTask effectue constamment un travail de décompte. Dans l'illustration, la femme «MyTask» grimpe un pas à la fois. Mais dans sa main droite, il tient fermement le vieil homme «Activité principale» qui ne peut pas être un Bouddha. L'utilisateur ne peut pas voir ce vieil homme.

Même si tu veux le résoudre

Je suis frappé par le dilemme ici.

--Ce MyTask est une classe qui est utilisée uniquement dans cette MainActivity, donc je veux en faire une classe imbriquée.

Je m'inquiète à ce sujet tout le temps. Vous devrez peut-être faire un compromis quelque part.

c'est tout. Je m'inquiète pour ma conscience et j'aimerais écrire cet article avec les mots du professeur Satoshi Fukuzawa.

Cependant, la douleur d'être frappé par un mauvais enfant est plus douloureuse que l'épée infernale, et je n'ai pas d'autre choix que de frapper cette épée avec mon corps actuel.

_ Satoshi Fukuzawa "Education" 1878 (Meiji 11) _

Un petit Yukichi, "C'est plus douloureux que le blâme de l'enfer" est trop sévère pour être tiré. quitter. Je vais demander à M. Watsuji.

Avant la miséricorde de Michimoto, «le mal n'est pas forcément quelque chose à ridiculiser».

_ Tetsuro Watsuji "Etude de l'histoire spirituelle japonaise" 1922 (Taisho 11) _

c'est tout.

Recommended Posts

Référence implicite à l'objet englobant détenu par la classe imbriquée non statique
Mappage à une classe avec un objet de valeur dans How to My Batis