Wird clone () wirklich jemals benutzt? Was ist mit dem defensiven Kopieren in Gettern / Settern?

8

Benutzt man praktisch immer defensive Getter / Setter? Für mich ist 99% der Zeit, die Sie für das Objekt, das Sie in einem anderen Objekt festgelegt haben, eine Kopie der gleichen Objektreferenz sein sollen, und Sie beabsichtigen, dass Änderungen, die Sie daran vornehmen, auch in dem Objekt vorgenommen werden, in dem es gesetzt wurde Sie setDate ( Date dt ) und modifizieren dt später, wen interessiert das? Es sei denn, ich möchte eine einfache unveränderliche Daten-Bean, die nur primitive Elemente hat und vielleicht etwas einfaches wie ein Date, ich benutze es nie.

Was den Klon betrifft, gibt es Probleme, wie tief oder flach die Kopie ist. Es erscheint also "gefährlich" zu wissen, was beim Klonen eines Objekts herauskommt. Ich glaube, ich habe clone() nur ein- oder zweimal verwendet, und zwar den aktuellen Zustand des Objekts zu kopieren, weil ein anderer Thread (dh eine andere HTTP-Anfrage, die auf dasselbe Objekt in Session zugreift) diese ändern könnte.

Bearbeiten - Ein Kommentar, den ich unten gemacht habe, ist mehr die Frage:

Aber andererseits, du hast das Datum geändert, also ist es deine eigene Schuld, daher die ganze Diskussion des Begriffs "defensiv". Wenn der gesamte Anwendungscode von einer kleinen bis mittleren Gruppe von Entwicklern kontrolliert wird, genügt es, nur Ihre Klassen zu dokumentieren, um Kopien von Objekten zu erstellen? Oder ist das nicht nötig, da man immer davon ausgehen sollte, dass beim Aufruf eines Setter / Getters NICHT kopiert wird?

    
GreenieMeanie 29.05.2009, 14:52
quelle

8 Antworten

11

Aus Josh Blochs effektivem Java:

  

Sie müssen defensiv mit der Annahme programmieren, dass Clients Ihrer Klasse ihr Bestes tun, um ihre Invarianten zu zerstören. Dies kann tatsächlich wahr sein, wenn jemand versucht, die Sicherheit Ihres Systems zu brechen, aber wahrscheinlicher ist, dass Ihre Klasse mit unerwartetem Verhalten fertig wird, das von ehrlichen Fehlern seitens des Programmierers, der Ihre API verwendet, herrührt. In jedem Fall lohnt es sich, sich die Zeit zu nehmen, um Klassen zu schreiben, die sich gegen unsportliche Kunden behaupten.

Punkt 24: Erstellen Sie bei Bedarf defensive Kopien

    
bajafresh4life 29.05.2009 15:47
quelle
5

Dies ist eine nicht-triviale Frage. Im Grunde müssen Sie über jeden internen Zustand einer Klasse nachdenken, den Sie einer anderen Klasse über Getter oder durch Aufruf eines anderen Setter einer Klasse geben. Zum Beispiel, wenn Sie dies tun:

%Vor%

Dann haben Sie möglicherweise zwei Probleme:

  1. someObject könnte möglicherweise den Wert von " now " ändern, was bedeutet, dass die obige Methode, wenn sie später diese Variable verwendet, einen anderen Wert haben könnte als erwartet und
  2. Wenn Sie nach dem Übergeben von " now " an someObject seinen Wert ändern, und wenn someObject keine defensive Kopie erstellt hat, haben Sie den internen Status von someObject .
  3. geändert

Sie sollten entweder vor beiden Fällen geschützt sein, oder Sie sollten Ihre Erwartungen darüber, was erlaubt oder nicht erlaubt ist, dokumentieren, je nachdem, wer der Kunde des Codes ist. Ein anderer Fall ist, wenn eine Klasse eine Map hat und Sie einen Getter für die Map selbst bereitstellen. Wenn Map Teil des internen Status des Objekts ist und das Objekt erwartet, dass vollständig den Inhalt von Map verwaltet, dann sollten Sie nie den% -Zertifikator zulassen. co_de% out. Wenn Sie einen Getter für eine Karte bereitstellen müssen, geben Sie Map anstelle von Collections.unmodifiableMap(myMap) zurück. Hier möchten Sie wahrscheinlich aufgrund der potenziellen Kosten keine Klone oder Abwehrkopien erstellen. Indem Sie Ihre Map so zurückgeben, dass sie nicht geändert werden kann, schützen Sie Ihren internen Status davor, von einer anderen Klasse geändert zu werden.

Aus vielen Gründen ist myMap oft nicht die richtige Lösung. Einige bessere Lösungen sind:

  1. Für Getter:
    1. Anstatt eine clone() zurückzugeben, geben Sie nur Map s entweder an die Iterator oder an die keySet zurück oder an was auch immer Client-Code tun kann, was er tun muss. Mit anderen Worten, geben Sie etwas zurück, das im Wesentlichen eine schreibgeschützte Ansicht Ihres internen Status oder
    2. ist
    3. Geben Sie Ihr veränderbares Statusobjekt zurück, das in einem unveränderlichen Wrapper ähnlich wie Map.Entry verpackt ist.
    4. Liefern Sie nicht Collections.unmodifiableMap() zurück, sondern stellen Sie eine Map -Methode bereit, die einen Schlüssel akzeptiert und den entsprechenden Wert aus der Map zurückgibt. Wenn alle Clients mit get arbeiten, erhalten Werte daraus, dann geben Sie den Clients nicht Map selbst; Stellen Sie stattdessen einen Getter bereit, der die Map -Methode von Map umschließt.
  2. Für Konstruktoren:
    1. Verwenden Sie Kopierkonstruktoren in Ihren Objektkonstruktoren, um eine Kopie von allem, das übergeben wird, das veränderbar ist, zu erstellen.
    2. Entwerfen Sie möglichst unveränderliche Mengen als Konstruktorargumente und nicht als änderbare Mengen. Manchmal ist es sinnvoll, zum Beispiel das von get() zurückgegebene long zu nehmen, anstatt eines new Date().getTime() -Objekts.
    3. Machen Sie so viel von Ihrem Status Date wie möglich, aber denken Sie daran, dass ein final -Objekt immer noch veränderbar sein kann und ein final -Array immer noch modifiziert werden kann.

In allen Fällen, wenn es eine Frage darüber gibt, wer den veränderlichen Status "besitzt", dokumentieren Sie es auf den Gettern oder Setter oder Konstruktoren. Dokumentieren Sie es irgendwo.

Hier ist ein triviales Beispiel für schlechten Code:

%Vor%

Sie sollten sehen, dass jeder runnable für 500 ms schläft, aber stattdessen erhalten Sie die falsche Zeitinformation. Wenn Sie den Konstruktor ändern, um eine defensive Kopie zu erstellen:

%Vor%

dann erhalten Sie die richtige Zeitinformation. Dies ist ein triviales Beispiel. Sie möchten kein kompliziertes Beispiel debuggen müssen.

ANMERKUNG: Ein häufiges Ergebnis, wenn der Status nicht richtig verwaltet wird, ist ein final beim Iterieren einer Sammlung.

Sollten Sie defensiv codieren? Wenn Sie garantieren können, wird immer das gleiche kleine Team von erfahrenen Programmierern diejenigen sein, die Ihr Projekt schreiben und warten, dass sie kontinuierlich daran arbeiten werden, damit sie die Details des Projekts im Gedächtnis behalten dieselben Leute werden für die gesamte Projektlaufzeit daran arbeiten, und das Projekt wird niemals "groß" werden, dann könnt ihr vielleicht damit davonkommen, es nicht zu tun. Aber die Kosten für die defensive Programmierung sind nicht groß, außer in den seltensten Fällen - und der Nutzen ist groß. Plus: defensive Codierung ist eine gute Angewohnheit. Sie wollen nicht die Entwicklung von schlechten Angewohnheiten fördern, wenn veränderbare Daten an Orte weitergegeben werden, die es nicht haben sollten. Dieser Wille wird dich eines Tages beißen. Das hängt natürlich von der benötigten Verfügbarkeit Ihres Projekts ab.

    
Eddie 29.05.2009 17:02
quelle
3

Bei beiden Problemen handelt es sich um eine explizite Zustandskontrolle. Es kann sein, dass Sie die meiste Zeit "entkommen" können, ohne an diese Dinge zu denken. Dies ist weniger wahr, wenn Ihre Anwendung größer wird und es schwieriger wird, über den Zustand und die Art und Weise zu sprechen, wie er sich zwischen Objekten ausbreitet.

Sie haben bereits einen wichtigen Grund erwähnt, warum Sie die Kontrolle darüber haben müssen - die Daten sicher zu verwenden, während ein anderer Thread darauf zugreift. Es ist auch leicht, Fehler wie folgt zu machen:

%Vor%

Der Punkt ist nicht, dass Sie immer klonen wollen, das wäre dumm und teuer. Es geht nicht darum, pauschale Aussagen darüber zu machen, wann solche Dinge angebracht sind.

    
Steve B. 29.05.2009 15:28
quelle
1

Ich habe Clone () verwendet, um den Objektstatus in der Sitzung des Benutzers zu speichern, um während der Bearbeitung Rückgängig zu machen. Ich habe es auch in Unit Tests verwendet.

    
sal 29.05.2009 15:57
quelle
1

Ich kann mir eine Situation vorstellen, in der Klone den Konstruktoren viel lieber sind. Wenn Sie eine Funktion haben, die ein Objekt vom Typ X aufnimmt und dann eine modifizierte Kopie davon zurückgibt, ist es möglicherweise vorzuziehen, dass diese Kopie ein Klon ist, wenn Sie die internen, nicht X-bezogenen Informationen beibehalten möchten. Zum Beispiel könnte eine Funktion, die ein Date um 5 Stunden erhöht, nützlich sein, selbst wenn ein Objekt vom Typ SpecialDate übergeben wurde. Das heißt, eine Menge Zeit mit der Verwendung von Komposition statt Vererbung würde solche Bedenken vollständig vermeiden.

    
Brian 29.05.2009 19:37
quelle
0

Ich mag die clone () -Methode nicht, da immer eine Typumwandlung benötigt wird. Aus diesem Grund benutze ich den Copy-Konstruktor die meiste Zeit. Es gibt deutlicher an, was es tut (neues Objekt) und Sie haben viel Kontrolle darüber, wie es sich verhält oder wie tief die Kopie ist.

Bei meiner Arbeit machen wir uns keine Gedanken über defensive Programmierung, obwohl das ein schlechter Habit ist. Aber die meiste Zeit geht es gut, aber ich denke, ich werde es mir genauer ansehen.

    
Salandur 29.05.2009 19:27
quelle
0

Eine Sache, die mir bei einer "defensiven Copy-Diskussion" immer wieder fehlt, ist der Performance-Aspekt. Das ist IMHO ein perfektes Beispiel für Leistung gegenüber Lesbarkeit / Sicherheit / Robustheit.

Defense-Kopien sind großartig für die Ropbust Ode. Wenn Sie es jedoch in einem zeitkritischen Teil Ihrer App verwenden, kann dies ein großes Leistungsproblem darstellen. Wir hatten kürzlich diese Diskussion, wo ein Datenvektor seine Daten in doppelten [] Werten speicherte. getValues ​​() hat values.clone () zurückgegeben. In unserem Algorithmus wurde getValues ​​() für viele verschiedener Objekte aufgerufen. Als wir uns wunderten, warum dieses einfache Stück Code so lange dauerte, untersuchten wir den Code - ersetzten die return values.clone () mit Rückgabewerten und plötzlich wurde unsere gesamte Ausführungszeit auf weniger als 1/10 des Originals gesenkt Wert. Nun, ich muss nicht sagen, dass wir die Defensive ausgelassen haben.

Hinweis: Ich bin im Allgemeinen nicht wieder defensive Kopien. Aber benutze dein Gehirn, wenn du es klonest (!)!

    
Locked 07.09.2009 08:45
quelle
0

Ich habe mit der folgenden Übung begonnen:

  1. Erstellen Sie Kopierkonstruktoren in Ihren Klassen, aber machen Sie sie schreibgeschützt. Der Grund dafür ist, dass das Erstellen von Objekten mit dem neuen Operator zu verschiedenen Problemen beim Arbeiten mit abgeleiteten Objekten führen kann.

  2. Erstellen Sie eine kopierfähige Schnittstelle wie folgt:

%Vor%

Lassen Sie die Kopiermethode der Klassen, die Copyable implementieren, den Protected Copy-Konstruktor aufrufen. Abgeleitete Klassen können dann super.Xxx (obj_to_copy) aufrufen; Verwenden Sie den Kopierkonstruktor der Basisklasse und fügen Sie nach Bedarf weitere Funktionen hinzu.

Die Tatsache, dass Java kovariante Rückgabetypen unterstützt diese Arbeit. Abgeleitete Klassen implementieren die Methode copy () einfach entsprechend und geben einen typsicheren Wert für ihre jeweilige Klasse zurück.

    
Frank Hellwig 08.04.2010 23:13
quelle