8 Stimmen

Wird clone() wirklich jemals verwendet? Was ist mit defensivem Kopieren in Gettern/Settern?

Verwenden die Leute praktisch immer defensive Getter/Setter? Meiner Meinung nach beabsichtigen Sie in 99 % der Fälle, dass das Objekt, das Sie in einem anderen Objekt festlegen, eine Kopie desselben Objektverweises ist, und Sie beabsichtigen, dass Änderungen, die Sie daran vornehmen, auch in dem Objekt vorgenommen werden, in dem es festgelegt wurde. Wenn Sie setDate ( Date dt ) und dt später ändern, wen interessiert das? Es sei denn, ich will einige grundlegende unveränderliche Daten Bean, die nur Primitive und vielleicht etwas Einfaches wie ein Datum hat, ich nie verwenden es.

Was das Klonen anbelangt, so gibt es Probleme damit, wie tief oder flach die Kopie ist, so dass es irgendwie "gefährlich" erscheint, zu wissen, was herauskommt, wenn man ein Objekt klont. Ich glaube, ich habe nur Folgendes verwendet clone() ein- oder zweimal, und zwar, um den aktuellen Zustand des Objekts zu kopieren, da ein anderer Thread (d.h. eine andere HTTP-Anfrage, die auf dasselbe Objekt in der Sitzung zugreift) es ändern könnte.

Edit - Eine Bemerkung, die ich unten gemacht habe, ist eher die Frage:

Aber andererseits haben Sie das Datum geändert, also sind Sie selbst schuld, daher die ganze Diskussion über den Begriff "defensiv". Wenn der gesamte Anwendungscode von einer kleinen bis mittelgroßen Gruppe von Entwicklern selbst kontrolliert wird, reicht es dann aus, die Klassen zu dokumentieren, anstatt Objektkopien zu erstellen? Oder ist dies nicht notwendig, da man immer davon ausgehen sollte, dass etwas NICHT kopiert wird, wenn man einen Setter/Getter aufruft?

12voto

bajafresh4life Punkte 11775

Aus dem Buch Effective Java von Josh Bloch:

Sie müssen defensiv programmieren und davon ausgehen, dass die Clients Ihrer Klasse ihr Bestes tun werden, um ihre Invarianten zu zerstören. Dies kann tatsächlich der Fall sein, wenn jemand versucht, die Sicherheit Ihres Systems zu knacken, aber wahrscheinlicher ist, dass Ihre Klasse mit unerwartetem Verhalten zurechtkommen muss, das aus ehrlichen Fehlern seitens der Programmierer resultiert, die Ihre API verwenden. In jedem Fall lohnt es sich, sich die Zeit zu nehmen, um Klassen zu schreiben, die gegenüber unvorsichtigen Clients robust sind.

Punkt 24: Bei Bedarf Kopien zu Verteidigungszwecken anfertigen

5voto

Eddie Punkte 52504

Dies ist eine nicht triviale Frage. Grundsätzlich müssen Sie über jeden internen Zustand einer Klasse nachdenken, den Sie einer anderen Klasse über Getter oder durch den Aufruf von Settern einer anderen Klasse geben. Zum Beispiel, wenn Sie dies tun:

Date now = new Date();
someObject.setDate(now);
// another use of "now" that expects its value to not have changed

dann haben Sie möglicherweise zwei Probleme:

  1. someObject könnte möglicherweise den Wert von " now ", was bedeutet, dass die obige Methode, wenn sie später diese Variable verwendet, einen anderen Wert haben könnte als erwartet, und
  2. wenn nach der Übergabe von " now " zu someObject ändern Sie seinen Wert, und wenn someObject keine Verteidigungskopie erstellt haben, haben Sie den internen Zustand von someObject .

Sie sollten sich entweder gegen beide Fälle absichern, oder Sie sollten dokumentieren, was Sie erwarten, je nachdem, wer der Kunde des Codes ist, was erlaubt oder nicht erlaubt ist. Ein anderer Fall ist, wenn eine Klasse eine Map und Sie stellen einen Getter für die Map selbst. Wenn die Map ist Teil des internen Zustands des Objekts und das Objekt erwartet, dass vollständig den Inhalt des Map dann sollten Sie niemals soll die Map aus. Wenn Sie einen Getter für eine Karte bereitstellen müssen, dann geben Sie Collections.unmodifiableMap(myMap) 代わりに myMap . Hier wollen Sie wahrscheinlich nicht einen Klon oder eine defensive Kopie wegen der möglichen Kosten zu machen. Indem Sie Ihre Map so verpackt zurückgeben, dass sie nicht geändert werden kann, schützen Sie Ihren internen Zustand vor Änderungen durch eine andere Klasse.

Aus vielen Gründen, clone() ist oft nicht die richtige Lösung. Einige bessere Lösungen sind:

  1. Für Getter:
    1. Statt der Rückgabe einer Map , nur Rückgabe Iterator s entweder an den keySet oder an den Map.Entry oder was auch immer dem Client-Code erlaubt, das zu tun, was er tun muss. Mit anderen Worten: Geben Sie etwas zurück, das im Wesentlichen eine schreibgeschützte Ansicht Ihres internen Zustands ist, oder
    2. Geben Sie Ihr veränderbares Zustandsobjekt zurück, das in einen unveränderlichen Wrapper verpackt ist, ähnlich wie bei Collections.unmodifiableMap()
    3. Anstatt eine Map bieten eine get Methode, die einen Schlüssel annimmt und den entsprechenden Wert aus der Map zurückgibt. Wenn alle Clients mit der Methode Map ist, Werte daraus zu gewinnen, dann geben Sie den Kunden nicht die Map selbst; bieten Sie stattdessen einen Getter an, der die Map 's get() Methode.
  2. Für Konstrukteure:
    1. Verwenden Sie Kopierkonstruktoren in Ihren Objektkonstruktoren, um eine Kopie von allem zu erstellen, was übergeben wird und veränderbar ist.
    2. Entwerfen Sie, wenn möglich, unveränderliche Mengen als Konstruktorargumente, anstatt veränderliche Mengen zu verwenden. Manchmal ist es sinnvoll, die von new Date().getTime() zum Beispiel, statt einer Date Objekt.
    3. Machen Sie so viel aus Ihrem Staat final wie möglich, aber denken Sie daran, dass ein final Objekt kann immer noch veränderbar sein und ein final Array kann noch geändert werden.

In allen Fällen, in denen es eine Frage darüber gibt, wem der veränderbare Zustand "gehört", dokumentieren Sie es in den Gettern oder Settern oder Konstruktoren. Dokumentieren Sie es irgendwo.

Hier ist ein triviales Beispiel für schlechten Code:

import java.util.Date;

public class Test {
  public static void main(String[] args) {
    Date now = new Date();
    Thread t1 = new Thread(new MyRunnable(now, 500));
    t1.start();
    try { Thread.sleep(250); } catch (InterruptedException e) { }
    now.setTime(new Date().getTime());   // BAD!  Mutating our Date!
    Thread t2 = new Thread(new MyRunnable(now, 500));
    t2.start();
  }

  static public class MyRunnable implements Runnable {
    private final Date date;
    private final int  count;

    public MyRunnable(final Date date, final int count) {
      this.date  = date;
      this.count = count;
    }

    public void run() {
      try { Thread.sleep(count); } catch (InterruptedException e) { }
      long time = new Date().getTime() - date.getTime();
      System.out.println("Runtime = " + time);
    }
  }
}

Sie sollte sehen, dass jeder Runnable 500 msec schläft, aber stattdessen erhalten Sie die falsche Zeitinformation. Wenn Sie den Konstruktor ändern, um eine defensive Kopie zu erstellen:

    public MyRunnable(final Date date, final int count) {
      this.date  = new Date(date.getTime());
      this.count = count;
    }

dann erhalten Sie die richtige Zeitangabe. Dies ist ein triviales Beispiel. Sie wollen nicht ein kompliziertes Beispiel debuggen müssen.

ANMERKUNG: A gemeinsame Ergebnis einer unzureichenden Verwaltung des Staates ist eine ConcurrentModificationException bei der Iteration über eine Sammlung.

Sollten Sie defensiv programmieren? Wenn Sie können Garantie Wenn Sie davon ausgehen, dass Ihr Projekt immer von demselben kleinen Team erfahrener Programmierer geschrieben und gewartet wird, dass diese kontinuierlich daran arbeiten und sich die Details des Projekts merken können, dass dieselben Personen während der gesamten Lebensdauer des Projekts daran arbeiten und dass das Projekt nie "groß" wird, dann können Sie vielleicht darauf verzichten. Aber die Kosten der defensiven Programmierung sind nicht hoch, außer in den seltensten Fällen - und der Nutzen ist groß. Außerdem ist defensives Programmieren eine gute Angewohnheit. Sie wollen nicht die Entwicklung schlechter Gewohnheiten fördern, indem Sie veränderbare Daten an Stellen weitergeben, die sie nicht haben sollten. Diese wird dich eines Tages beißen. All dies hängt natürlich von der erforderlichen Betriebszeit Ihres Projekts ab.

3voto

Steve B. Punkte 52372

In beiden Fällen geht es um die ausdrückliche Kontrolle des Staates. Es mag sein, dass man die meiste Zeit "durchkommt", ohne über diese Dinge nachzudenken. Dies gilt umso weniger, je größer Ihre Anwendung ist und je schwieriger es ist, über den Zustand und seine Weitergabe zwischen Objekten nachzudenken.

Sie haben bereits einen wichtigen Grund genannt, warum Sie die Kontrolle darüber haben müssen: Sie müssen in der Lage sein, die Daten sicher zu verwenden, während ein anderer Thread darauf zugreift. Es ist auch leicht, solche Fehler zu machen:

class A {
   Map myMap;
}

class B {
   Map myMap;
   public B(A a)
   {
        myMap = A.getMap();//returns ref to A's myMap
   }
    public void process (){ // call this and you inadvertently destroy a
           ... do somethign destructive to the b.myMap... 
     }
}

Der Punkt ist nicht, dass man immer klonen will, das wäre dumm und teuer. Es geht nicht darum, pauschale Aussagen darüber zu treffen, wann diese Art von Dingen angemessen ist.

1voto

sal Punkte 22948

Ich habe Clone() verwendet, um den Objektstatus in der Sitzung des Benutzers zu speichern, um ein Rückgängigmachen während der Bearbeitung zu ermöglichen. Ich habe es auch in Unit-Tests verwendet.

1voto

Brian Punkte 24901

Ich kann mir eine Situation vorstellen, in der Klonen dem Kopieren von Konstruktoren vorzuziehen ist. Wenn Sie eine Funktion haben, die ein Objekt des Typs X aufnimmt und dann eine modifizierte Kopie davon zurückgibt, kann es besser sein, wenn diese Kopie ein Klon ist, wenn Sie die internen, nicht X-bezogenen Informationen beibehalten wollen. Zum Beispiel kann eine Funktion, die eine Date um 5 Stunden zu verlängern, könnte auch dann nützlich sein, wenn es ein Objekt des Typs SpecialDate . Allerdings würde die Verwendung von Komposition anstelle von Vererbung solche Bedenken in den meisten Fällen völlig ausräumen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X