Z mojego doświadczenia wynika, że istnieje jeden i jedyny powód zastąpienia Object.finalize()
, ale jest to bardzo dobry powód :
Aby wstawić kod rejestrujący błędy, w finalize()
którym powiadamia Cię, jeśli zapomnisz wywołać close()
.
Analiza statyczna może wychwycić pominięcia tylko w trywialnych scenariuszach użycia, a ostrzeżenia kompilatora wspomniane w innej odpowiedzi mają tak uproszczony obraz rzeczy, że faktycznie trzeba je wyłączyć, aby zrobić wszystko, co nie jest trywialne. (Mam znacznie więcej ostrzeżeń niż jakikolwiek inny programista, o którym wiem lub kiedykolwiek słyszałem, ale nie mam włączonych głupich ostrzeżeń).
Finalizacja może wydawać się dobrym mechanizmem zapewniającym, że zasoby nie pozostaną niezdyscyplinowane, ale większość ludzi postrzega to w całkowicie niewłaściwy sposób: uważają to za alternatywny mechanizm awaryjny, zabezpieczenie „drugiej szansy”, które automatycznie uratuje dzień, pozbywając się zapomnianych zasobów. To jest bardzo złe . Musi istnieć tylko jeden sposób na zrobienie czegoś: albo zawsze zamykasz wszystko, albo finalizacja zawsze zamyka wszystko. Ale ponieważ finalizacja jest niewiarygodna, finalizacja nie może nią być.
Istnieje więc schemat, który nazywam obowiązkowym usuwaniem , i stanowi, że programista jest odpowiedzialny za zawsze jawne zamykanie wszystkiego, co implementuje Closeable
lub AutoCloseable
. (Instrukcja try-with-resources nadal liczy się jako wyraźne zamknięcie.) Oczywiście programista może zapomnieć, więc tutaj zaczyna się finalizacja, ale nie jako magiczna wróżka, która magicznie naprawi wszystko w końcu: jeśli finalizacja odkryje że close()
nie została wywołana, to jednak niepróbuj go przywołać, właśnie dlatego, że (z matematyczną pewnością) będą hordy programistów n00b, którzy będą na nim polegać, wykonując zadanie, za które byli zbyt leniwi lub zbyt nieumyślni. Tak więc, przy obowiązkowym usuwaniu, gdy finalizacja odkryje, że close()
nie został wywołany, rejestruje jasny czerwony komunikat o błędzie, informując programistę dużymi dużymi, dużymi literami, aby naprawił swoje ... jego rzeczy.
Dodatkową korzyścią jest pogłoska, że „JVM zignoruje trywialną metodę finalize () (np. Taką, która wraca bez robienia niczego, jak ta zdefiniowana w klasie Object)”, więc przy obowiązkowym usuwaniu można uniknąć całej finalizacji narzut w całym systemie ( zobacz odpowiedź alip, aby dowiedzieć się, jak straszny jest ten narzut), kodując swoją finalize()
metodę w następujący sposób:
@Override
protected void finalize() throws Throwable
{
if( Global.DEBUG && !closed )
{
Log.Error( "FORGOT TO CLOSE THIS!" );
}
//super.finalize(); see alip's comment on why this should not be invoked.
}
Chodzi o to, że Global.DEBUG
jest to static final
zmienna, której wartość jest znana w czasie kompilacji, więc jeśli tak, to false
kompilator nie wyemituje żadnego kodu dla całej if
instrukcji, co sprawi, że będzie to trywialny (pusty) finalizator, który z kolei oznacza, że twoja klasa będzie traktowana tak, jakby nie miała finalizatora. (W języku C # byłoby to zrobione z ładnym #if DEBUG
blokiem, ale co możemy zrobić, to jest Java, w którym płacimy pozorną prostotę kodu z dodatkowym obciążeniem mózgu).
Więcej informacji na temat obowiązkowego usuwania, wraz z dodatkową dyskusją na temat usuwania zasobów w dot Net, tutaj: michael.gr: Obowiązkowe usuwanie kontra obrzydliwość „pozbywać się”
finalize()
jest nieco pomieszana. Jeśli kiedykolwiek go zaimplementujesz, upewnij się, że jest bezpieczny dla wszystkich innych metod tego samego obiektu.