[JAVA] Impliziter Verweis auf das einschließende Objekt, das von der nicht statisch verschachtelten Klasse gehalten wird

Dieser Artikel beschreibt "Seien Sie vorsichtig, da nicht statische verschachtelte Klassen das einschließende Objekt als implizite Referenz erfassen" in Java.

Der Grund, warum Sie vorsichtig sein müssen, ist, dass dies die Wahrscheinlichkeit eines Speicherverlusts erhöht.

Und das Fazit lautet: "Verschachtelte Klassen sind mit dem Modifikator" statisch "besser dran (weil sie das einschließende Objekt nicht als implizite Referenz enthalten)." Umschließende Objekte sind von kurzer Dauer, insbesondere wenn die verschachtelten Klasseninstanzen eine längere Lebensdauer haben!

Der Grund dafür war bei der Entwicklung einer Android-App

(Dieser Artikel ist nicht spezifisch für die Entwicklung von Android-Apps, aber wenn Sie ihn lesen, werden Sie in Java involviert sein. Bitte haben Sie zuerst etwas Geduld.)

Es war eines Tages. Als ich eine Android-App in Java erstellte, gab Android Studio [^ 1] eine solche Warnung aus.

[^ 1]: IDE für die Entwicklung von Android-Anwendungen durch Ändern von IntelliJ IDEA

thisasynctaskclassshouldbestaticorleaksmightoccur.png

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

Diese Klasse mit dem Namen CouponAsyncTask ist als verschachtelte Klasse (innere Klasse / innere Klasse) definiert. In der Sprechblase heißt es jedoch: "Fügen Sie einen statischen Modifikator hinzu, da dies sonst zu einem Speicherverlust führen kann." Ich bin. Nun, ich habe Angst.

Wenn Sie sich die ausführliche Erklärung ansehen, dass die Codeprüffunktion "Lint" von Android Studio funktioniert,

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

Ich werde Google Übersetzung fragen.

Statisches Feldleck Statische Felder verlieren Kontext. Nicht statische innere Klassen haben implizite Verweise auf äußere Klassen. Wenn die äußere Klasse beispielsweise ein Fragment oder eine Aktivität ist, bedeutet diese Referenz, dass der lang laufende Handler / Loader / Task einen Verweis auf die Aktivität enthält, die verhindert, dass die Garbage Collection abgerufen wird. In ähnlicher Weise können direkte Feldverweise auf Aktivitäten und Fragmente dieser länger laufenden Instanzen zu Undichtigkeiten führen. Die ViewModel-Klasse verweist nicht auf eine Ansicht oder einen Nichtanwendungskontext. Problem ID: StaticFieldLeak

Auch wenn ein schwieriger englischer Satz in ein schwieriges Japanisch übersetzt wird ... Es ist Chimpung Kampung.

Es wurde auf der Entwicklerseite geschrieben

Es wurde ordnungsgemäß auf der offiziellen Website für Android-App-Entwickler als "Beispiel für einen häufigen Fehler im Code-Design unter Verwendung von Thread-Objekten" veröffentlicht. Ich frage mich, ob es das gibt. Leistungsverbesserung durch Threading [implizite Referenz]

Die japanische Seite dieser offiziellen Website wird als "implizite Verweise" übersetzt, der Originaltext wird jedoch als "implizite Verweise" geschrieben. In diesem Artikel werden zusammengesetzte Wörter mit fünf Zeichen als "implizite Referenz" verwendet.

Verschachtelte Klasse

Selbst wenn ich plötzlich von "impliziter Referenz des Einschlusses von Objekten, die von einer nicht statisch verschachtelten Klasse gehalten werden" spreche, gibt es viele Informationen zu diesem Thema, daher werde ich die erforderlichen Kenntnisse einzeln erläutern. Beeile dich. Es kann jedoch ein wenig frustrierend sein.

Eine "verschachtelte Klasse" wird auch als "innere Klasse" bezeichnet. Dieser Artikel ist mit "verschachtelte Klasse" vereinheitlicht.

Die Foo-Klasse hat zwei verschachtelte Klassen


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

in diesem Fall,

Die "einschließende Klasse" ist auch als "äußere Klasse" bekannt. Dieser Artikel wird mit "Enclosing Class" vereinheitlicht.

Der Unterschied zwischen Bar und Baz besteht darin, ob sie den Modifikator "statisch" haben oder nicht.

So erstellen Sie eine Instanz einer verschachtelten Klasse

Erstellen Sie zwei verschachtelte Klassen von 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();
    }
}

Beim Erstellen einer Instanz einer nicht statisch verschachtelten Klasse

Es dauerte zwei Zeilen, um eine Instanz der Bar-Klasse zu erstellen. Wenn Sie es wirklich in einer Zeile tun möchten, verwenden Sie den folgenden Code.

Erstellen Sie eine Instanz der Bar-Klasse in einer Zeile


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

Wenn Sie eine Instanz einer nicht statisch verschachtelten Klasse erstellen möchten, benötigen Sie in jedem Fall eine Instanz des Einschlusses. Und es ist auch charakteristisch, dass "." Vor dem "neuen" Operator hinzugefügt wird.

Beim Erstellen einer Instanz einer statisch verschachtelten Klasse

Sie benötigen keine umschließende Instanz im Voraus. Sie können plötzlich "neu".

Einzigartig ist, dass der Klassenname " Foo.Baz "anstelle von" Baz" lautet. Es ist charakteristisch, dass . zwischen den Klassennamen enthalten ist.

Entspricht dem Buch "Effective Java"

Effective Java hat ab der ersten Ausgabe (veröffentlicht 2001) "Statische Mitgliedsklassen statt nicht statische Mitgliedsklassen" veröffentlicht. "Wählen" wird akkumuliert. Es ist eine Erfolgs- oder Misserfolgszeremonie, die auch in der dritten Ausgabe von 2018 erhalten bleibt.

Speicherlecks können katastrophal sein.

Und

Speicherlecks sind normalerweise schwer zu erkennen.

Und

Bewahren Sie zusätzliche Referenzen auf und verschwenden Sie Speicher und Zeit.

Zum Beispiel werden beängstigende Dinge geschrieben. weil

** Wenn Sie den statischen Modifikator weglassen, hat jede Instanz einen irrelevanten Verweis auf das umschließende Objekt. Das Speichern dieser Referenz erfordert Zeit und Speicher. Das Ernsthafte ist, dass ohne diese Referenz die einschließende Instanz verbleiben kann, wenn sie einer Speicherbereinigung unterliegt. ** ** **

Ist geschrieben.

Gehäuse-Instanzen werden nicht mehr benötigt, aber nicht in den Himmel gerufen

Ich habe das obige Programm ein wenig modifiziert. Die statisch verschachtelte Klasse Baz wurde etwas verworfen.

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 ist aufgetreten
        bar.methodBar();
    }
}

Ich habe der Foo-Instanz der einschließenden Klasse "null" zugewiesen, um eine Garbage Collection (GC) durchzuführen, und dann die Methode der "Bar" -Instanz der nicht statisch verschachtelten Klasse aufgerufen. Wenn ich versuche, es auszuführen, ist es wie folgt.

Ausführungsergebnis


methodBar

Ja, es funktioniert! Ich warf einen Blick darauf.

Gemäß der Java-Spezifikation ist das Objekt mit dem Namen "foo" unabhängig davon, wie viel ich der "Foo" -Instanz der einschließenden Klasse "null" zuweise, solange die "Bar" -Instanz der nicht statisch verschachtelten Klasse funktioniert Es ist kein GC (es wurde brillant durchlaufen, ohne als GC-Ziel betrachtet zu werden). Weil ** bar`` foo als implizite Referenz ** enthält.

Wenn Sie eine Illustration als lustige Analogie machen, sieht es so aus.

joubutudekinai.png

Die Frau, die Schritt für Schritt die Treppe hinaufsteigt, ist an der Bar voller Energie.

Auf der anderen Seite ist der alte Mann, der Buddha werden will, "foo", aber weil "bar" es hält, wird er nicht leicht in den Himmel gerufen. Es kann ein irdischer Geist sein.

--Gripping = Verweisen

Bitte lesen Sie als.

Fügen Sie dann der verschachtelten Klasse in einem Kurzschluss einfach statisch hinzu, und alles wird gelöst! Gehst du?

Verwenden Sie Foo, Bar und Baz nicht mehr und sehen Sie sich das folgende Programm an.

Vorher: Verschachtelte Klasse ist nicht statisch


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

Vor dem Ausführungsergebnis


134

Es ist eine nicht statische verschachtelte Klasse, obwohl sie (absichtlich) ausgeführt wird. Fügen wir nun einfach den statischen Modifikator zu dieser verschachtelten Klasse mit dem Namen "Buzz" hinzu.

Nach dem Verkauf


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

    static class Buzz {
        void m() {
            x = 56; //Kann nicht kompiliert werden, da x kein statisches Feld ist
            y = 78;
            System.out.println(x + y); //Kann nicht kompiliert werden, da x kein statisches Feld ist
        }
    }
}

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

Sofort konnte ich nicht kompilieren. Die IDE wird wütend, wenn sie nicht funktioniert, weil "x" kein statisches Feld ist.

Warum. Dies liegt daran, dass ** statisch verschachtelte Klassen nur auf statische Mitglieder des Einschlusses ** zugreifen können.

Kehren wir noch einmal zur Geschichte der Entwicklung von Android-Anwendungen zurück

Die Android-App, die Sie erstellen möchten, finden Sie in diesem animierten GIF.

countup.gif

Sobald Sie es starten, startet der Count-up automatisch. Dieses animierte GIF ist eine Animation, die START wiederholt! Wenn es 3 erreicht, aber in Wirklichkeit startet diese App von START! Und zählt bis "9" und endet (gibt weiterhin "9" aus). ..

Und das Programm ist wie folgt. Auch diejenigen, die keine Erfahrung in der Entwicklung von Android-Apps haben, sollten geduldig sein.

Bildschirmklasse


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

Wenn Sie eine "Bildschirmklasse" unter Android erstellen möchten, codieren Sie eine Klasse, die "AppCompatActivity" erbt.

Die Klasse "TextView" ist eine Ansicht (Teil), in der Zeichenfolgen angezeigt werden (nur können), die in der Mitte des Bildschirms platziert sind und "START!", "0" oder "1" anzeigen. Ich möchte diese "Textansicht" durch asynchrone Verarbeitung in Schritten von 1000 Millisekunden von "0" auf "1" und dann auf "2" ändern. Dies ist die Voraussetzung für diese App.

Wenn Sie eine asynchrone Verarbeitung wünschen, definieren Sie eine Unterklasse von "AsyncTask" und codieren Sie die Verarbeitung darin.

MainActivity_MyTask.png

Wenn Sie die Methode "execute ()" für die "MyTask" aufrufen, die die asynchrone Verarbeitung ausführt, beginnt die "MyTask" mit der Verarbeitung. Das rote Kästchen in diesem Bild ist "TextView". "MyTask" überschreibt diese "Textansicht" mit einer Zeichenfolge wie "0", "1" oder "2". Das sieht so aus, als würde es hochzählen.

Fügen wir nun der Klasse "MyTask" "static" hinzu.

Non-static field.png

Die IDE wurde wütend rot. Weil das Feld "textView" nicht den statischen Modifikator hat!

Ich brauche keine Instanz von MainActivity mehr, aber ich bin nicht in den Himmel berufen

Bitte werfen Sie einen Blick auf die Abbildung der Frau, die die Treppe hinaufsteigt, und des alten Mannes des erdgebundenen Geistes.

Unter Android werden Bildschirme, die nicht mehr benötigt werden, mit GC versehen. Der "unnötige Bildschirm" ist, wenn der Benutzer den Bildschirm durch Drücken der Zurück-Taste ausblendet (ausschaltet). Im Allgemeinen haben Android-Geräte weniger Speicher als PCs [^ 2]. Daher wird der vom Benutzer = der Instanz der Aktivitätsklasse ausgeblendete Bildschirm von der Laufzeit schnell überprüft.

[^ 2]: Android "Gaming" -Smartphones mit 10 GB RAM sind auf dem Markt, aber lassen Sie sie in Ruhe.

Der Benutzer drückt einfach die Zurück-Taste und dieser Bildschirm = Aktivitätsinstanz ist nicht erforderlich! Nja GC! Es neigt dazu, kurzlebig und schicksalhaft zu sein. Also im Hintergrund (nein, der Begriff "Hintergrund" ist nicht richtig, es ist besser zu sagen "in asynchroner Verarbeitung im Arbeitsthread") "MyTask" ist mein eigener Sie werden leben, bis Sie Ihre Arbeit abgeschlossen haben (hochzählen).

Es ist eine IDE namens Android Studio, die mich gewarnt hat, dass es sich bei diesem Vergleich der Überlebenszeit um eine [einschließende Instanz <verschachtelte Klasseninstanz] handelt.

Starten Sie diese App, die ich erstellt habe, und selbst wenn der Countdown aufgrund der Arbeit von "MyTask" beginnt, drückt der Benutzer die Zurück-Taste, um den Bildschirm auszublenden (ausschalten, App stoppen). Trotzdem enthält ** das MyTask-Objekt, das eine Instanz der nicht statisch verschachtelten Klasse ist, einen impliziten Verweis auf die MainActivity-Instanz **, daher sollte es nicht mehr angezeigt werden, also sollte es GC sein. Außerdem fällt es in einen Zustand, in dem es nicht GC ist. Wenn der Benutzer nicht sichtbar ist, führt MyTask ständig Count-up-Arbeiten durch. In der Abbildung klettert die Frau "MyTask" Schritt für Schritt. Aber in seiner rechten Hand hält er den alten Mann "Hauptaktivität" fest, der kein Buddha sein kann. Der Benutzer kann diesen alten Mann nicht sehen.

Auch wenn Sie es lösen wollen

Das Dilemma hier beeindruckt mich.

--Diese MyTask ist eine Klasse, die nur in dieser MainActivity verwendet wird, daher möchte ich sie zu einer verschachtelten Klasse machen. --Aber die IDE warnt Sie, es zu einer statisch verschachtelten Klasse zu machen. --Aber ich möchte (und sollte) ein Feld vom Typ "TextTview" nicht zu einem statischen Feld machen.

Ich habe mir die ganze Zeit Sorgen gemacht. Möglicherweise müssen Sie irgendwo einen Kompromiss eingehen.

das ist alles. Ich mache mir Sorgen um mein Gewissen und möchte diesen Artikel mit den Worten von Dr. Satoshi Fukuzawa schreiben.

Der Schmerz, von einem bösen Kind getroffen zu werden, ist jedoch schmerzhafter als das höllische Schwert, und ich habe keine andere Wahl, als dieses Schwert mit meinem aktuellen Körper zu schlagen.

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

Ein kleiner Yukichi, "Es ist schmerzhafter als die Schuld der Hölle", ist zu streng, um es zu zeichnen. Gestoppt. Ich werde Mr. Watsuji fragen.

Vor Michimotos Gnade ist "das Böse nicht unbedingt etwas, über das man sich lustig machen muss".

_ Tetsuro Watsuji "Studium der japanischen Geistesgeschichte" 1922 (Taisho 11) _

das ist alles.

Recommended Posts

Impliziter Verweis auf das einschließende Objekt, das von der nicht statisch verschachtelten Klasse gehalten wird
Zuordnung zu einer Klasse mit einem Wertobjekt in How to My Batis