2013-06-04 6 views
15

Ich habe eine Frage über die Verwendung von Getter-Methoden in Java. Angenommen, ich diese Klasse hatte:Beeinflusst das Ändern des Ergebnisses eines Getters das Objekt selbst?

class Test { 
    private ArrayList<String> array = new ArrayList<String>(); 

    public ArrayList getArray() { 
     return this.array; 
    } 

    public void initArray() { 
     array.add("Test 1"); 
     array.add("Test 2"); 
    } 
} 

class Start { 
    public static void main(String args[]) { 
     initArray(); 
     getArray().remove(0); 
    } 
} 

Meine Frage ist:

Würde die tatsächliche Arraylist-Objekt modifiziert werden ("Test 1" von ihm entfernt)? Ich denke, ich habe das an einigen Stellen gesehen, aber ich dachte, dass Getter einfach eine Kopie dieses Objekts liefern würden. Kein Hinweis darauf. Wenn es auf diese Weise nicht funktioniert (als Referenz), dann würde auch diese Arbeit (Würde die Arraylist Objekt der Klasse-Test durch diese auch geändert werden) ?:

class Start { 
    public static void main(String args[]) { 
     initArray(); 
     ArrayList aVar = getArray(); 
     aVar.remove(0); 
    } 
} 
+3

Java kopiert nie Objekte. – SLaks

+0

Wo ändert sich in diesem Beispiel ein Getter ein Objekt? – raina77ow

+0

Es scheint einfach zu überprüfen. Im Wesentlichen werden in Ihrem Beispiel nur Referenzen übergeben, so dass die Änderungen das ursprüngliche Array betreffen. – assylias

Antwort

21

Java gibt Verweise auf das Array, also wird es keine Kopie sein und es wird die Liste modifizieren. Im Allgemeinen erhalten Sie einen Verweis auf das Objekt, es sei denn, es ist ein primitiver Typ (int, float, usw.).

Sie müssen das Array explizit kopieren, wenn Sie möchten, dass ein Duplikat zurückgegeben wird.

+2

Es ist eine Referenz auch im Fall von "Wrapper auf diese Typen" ... –

+0

+1 für eine allgemeine, aber knappe Antwort. – MegaWidget

+0

Großartig. Danke für die Antwort. Also würde der zweite Codeabschnitt auch das ursprüngliche Objekt verändern, richtig? – Jordanss10

2

So wie ich es verstehe, sind Objektreferenzvariablen etwas mehr als Speicheradressen der Objekte selbst. Was also von getArray() zurückgegeben wird, ist eine Referenzvariable für diese ArrayList. Ein Objekt kann viele Referenzvariablen haben, aber es ist immer noch das gleiche Objekt, das modifiziert wird.

Java geht alles nach Wert. Wenn Sie also eine Objektreferenzvariable als Parameter übergeben oder ihren Wert zurückgeben, übergeben oder geben Sie den Wert der Objektreferenzvariablen zurück.

0

Um Ihre Frage zu beantworten, erhalten Sie mit einem Getter direkten Zugriff auf eine Variable. Führen Sie diesen Code aus, und Sie können sehen, dass der String in der ArrayList entfernt wurde. Verwenden Sie jedoch keine statische ArraList wie in diesem Beispiel in Ihrem Code.

public class Test { 

     private static ArrayList<String> array = new ArrayList<String>(); 

     public static ArrayList<String> getArray() { 
      return array; 
     } 

     public static void initArray() { 
      array.add("Test 1"); 
      array.add("Test 2"); 
     } 

    public static void main(String[] args) { 
      initArray(); 
      ArrayList aVar = getArray(); 
      aVar.remove(0); 
      System.out.println(aVar.size()); 
    } 

} 
0

Wie erwähnt, hat Ihr Getter nicht ändern, es gibt einen modifizierbar Verweis auf die Liste. Tools wie Findbugs werden Sie darüber warnen ... Sie können entweder damit leben und den Benutzern Ihrer Klasse vertrauen nicht auf Ihre Liste verprügeln, oder verwenden Sie die einen unveränderbaren Verweis auf Ihre Liste zurück:

public static List<String> getArray() { 
      return Collections.unmodifiableList(array); 
     } 
+0

In diesem Fall würde dies als Pass-by-Reference oder Pass-by-Value betrachtet? Und wenn Sie sagen "Ihr Getter ändert die Liste nicht", sondern "gibt es einen modifizierbaren Verweis auf die Liste", heißt das, dass die Änderungen, die an der Referenz von getArray() vorgenommen werden, Auswirkungen auf das Arraylist-Objekt in der Testklasse? – Jordanss10

0

That Ein Getter modifiziert das Objekt, auf das Sie es nennen, nicht nur eine Frage der Konvention. Es ändert sicherlich nicht die Identität des Ziels, aber es kann seinen internen Zustand ändern. Hier ist ein gutes Beispiel, wenn ein wenig lückenhaft:

public class Fibonacci { 
    private static ConcurrentMap<Integer, BigInteger> cache = 
     new ConcurrentHashMap<>(); 

    public BigInteger fibonacci(int i) { 
     if (cache.containsKey(i)) { 
      return cache.get(i); 
     } else { 
      BigInteger fib = compute(i); // not included here. 
      cache.putIfAbsent(i, fib); 
      return fib; 
    } 
} 

So Fibonacci.fibonacci(1000) Aufruf kann den internen Zustand des Ziels ändern, aber es ist immer noch das gleiche Ziel.

Nun, hier ist eine mögliche Sicherheitsverletzung:

public class DateRange { 
    private Date start; 
    private Date end; 
    public DateRange(final Date start, final Date end) { 
     if (start.after(end)) { 
      throw new IllegalArgumentException("Range out of order"); 
     } 
     this.start = start; 
     this.end = end; 
    } 
    public Date getStart() { 
     return start; 
    } 
    // similar for setStart, getEnd, setEnd. 
} 

Das Problem ist, dass java.lang.Date wandelbar ist. Jemand kann Code schreiben wie:

DateRange range = new DateRange(today, tomorrow); 
// In another routine. 
Date start = range.getStart(); 
start.setYear(2088); // Deprecated, I know. So? 

Jetzt Bereich ist außer Betrieb. Es ist so, als würde man dem Kassierer deinen Geldbeutel übergeben.

Aus diesem Grund ist es am besten, eine dieser Methoden zu verwenden, wobei die früheren besser sind.

  1. Haben Sie so viele Objekte wie möglich unveränderlich. Dies ist, warum Joda-Time geschrieben wurde, und warum Daten noch einmal in Java 8 knüpfen.
  2. Machen Sie defensive Kopien von Gegenständen, die man setzt oder bekommt.
  3. Geben Sie einen unveränderlichen Wrapper eines Elements zurück.
  4. Zurückgeben von Sammlungen als Iterables, nicht als sie selbst. Natürlich könnte jemand es zurückwerfen.
  5. Geben Sie einen Proxy zurück, um auf das Element zuzugreifen, das nicht in den Typ umgewandelt werden kann.

Ich weiß, ich weiß. Wenn ich C oder C++ möchte, weiß ich, wo ich sie finden kann. 1. Zurückgeben

1

Wie andere sagten, es sei denn, es ist ein primitiver Typ, erhalten Sie einen Verweis auf das Objekt. Es ist ähnlich wie ein Zeiger in C++, es ermöglicht Ihnen den Zugriff auf das Objekt, aber im Gegensatz zu C++ - Verweis (Zeiger auf die Speicheradresse einer Variablen) können Sie es nicht durch ein anderes Objekt ersetzen. Nur Setter kann das tun.

Ich sehe zwei Varianten in Ihrer Frage, test.getArray().remove(0) und aVar.remove(0). Es gibt keinen Unterschied in den Ergebnissen von diesen, es ist immer noch nur ein paar Zeiger-ähnliche Referenz und es verändert das Original.

Sie erhalten niemals einen Klon, indem Sie nur einen Getter aufrufen. Wenn das Objekt also nicht unveränderbar ist, können Sie das Objekt ändern, auf das der Getter Ihnen Zugriff gewährt hat. Zum Beispiel ist String unveränderlich, jede Basis Collection (einschließlich ArrayList) ist veränderbar. Sie können Collections.unmodifiable*(...) aufrufen, um eine Sammlung nicht änderbar zu machen. Wenn die Sammelobjekte jedoch änderbar sind, können sie immer noch geändert werden.

In einigen Fällen ist es ratsam, einen Klon zu erstellen, in den meisten Fällen jedoch nicht. Ein Getter sollte überhaupt nichts klonen, er sollte nicht einmal Daten verändern, es sei denn, er initialisiert eine möglicherweise Null-Sammlung oder etwas Ähnliches. Wenn Sie eine unveränderbare Sammlung mit unveränderbaren Objekten wünschen, versuchen Sie es auf diese Weise. In diesem Beispiel haben wir eine Klasse FooImpl, die die Schnittstelle Foo implementiert, die Gründe, die später erklärt werden.

public interface Foo { 
    int getBar(); 
} 

public class FooImpl Foo { 
    private int bar; 
    @Override 
    public int getBar() { 
     return bar; 
    } 
    public void setBar(int newValue) { 
     this.bar = newValue; 
    } 
} 

Wie Sie sehen, hat Foo keinen Setter. Wenn Sie einige ArrayList<Foo> erstellen und es von einem Getter als Collections.unmodifiableList(myArrayList) übergeben, scheint es fast, dass Sie es getan haben. Aber die Arbeit ist noch nicht getan. Wenn die Klasse FooImpl öffentlich ist (was in diesem Fall der Fall ist), könnte jemand versuchen, wenn das foo, das er in der Liste gefunden hat, ein instanceof FooImpl ist, und dann als (FooImpl) foo wandeln, was es veränderbar macht. Wir können jedoch alle Foo in einen Wrapper namens FooWrapper einpacken. Es implementiert Foo auch:

public class FooWrapper implements Foo { 
    private Foo foo; 
    public FooWrapper(Foo foo) { 
     this.foo = foo; 
    } 
    public int getBar() { 
     return foo.getBar(); 
    } 
    // No setter included. 
} 

Dann haben wir eine new FooWrapper(myFoo) in eine Collection<FooWrapper> setzen können. Dieser Wrapper hat keinen öffentlichen Setter und der Foo-Inside ist privat. Sie können die zugrunde liegenden Daten nicht ändern. Jetzt über diese Foo Schnittstelle. Sowohl FooImpl als auch FooWrapper implementieren es, wenn eine Methode nicht beabsichtigt, die Daten zu ändern, kann es Foo auf Eingabe fragen. Es spielt keine Rolle, welche Foo Sie bekommen.

Wenn Sie also eine nicht änderbare Sammlung mit nicht änderbaren Daten wünschen, erstellen Sie eine neue Collection<Foo>, füttern Sie sie mit FooWrapper Objekte und rufen Sie dann Collections.unmodifiable*(theCollection). Oder die eigene Sammlung machen, die die ganze Sammlung von Foo Wraps, Rückkehr FooWrappers, zum Beispiel dieser Liste:

public MyUnmodifiableArrayList implements List<Foo> { 
    ArrayList<Foo> innerList; 
    public get(int index) { 
     Foo result = innerList.get(index); 
     if (!(result instanceof FooWrapper)) { 
      return new FooWrapper(result); 
     } 
     return result; // already wrapped 
    } 
    // ... some more List interface's methods to be implemented 
} 

Mit gewickelter Sammlung, Sie müssen nicht durch die ursprüngliche Sammlung iterieren und machen ihren Klon mit Wrapper von Daten. Diese Lösung ist viel besser, wenn Sie es nicht vollständig lesen, aber es erstellt einen neuen FooWrapper jedes Mal, wenn Sie get() darauf aufrufen, es sei denn, der Foo auf diesem Index ist bereits ein FooWrapper. In einem lang laufenden Thread mit Millionen von Aufrufen an get() könnte dies ein unnötiger Benchmark für den Garbage Collector werden, wodurch Sie ein inneres Array oder eine Karte verwenden, die bereits existierende FooWrappers enthält.

Jetzt können Sie die neue, benutzerdefinierte List<Foo> zurückgeben. Aber wieder, nicht von einem einfachen Getter. Machen Sie es so ähnlich wie getUnmodifiableFooList() für Ihre private ArrayList<FooImpl> fooList Feld.