Java Best Practice
Wie man Objekte erzeugt
Statische Factory Methoden
Es hat Vorteile in einer Klasse statt oder zusätzlich zu einem Konstruktor eine statische Factory Methode anzubieten.
- hat einen sprechenden Namen
- müssen nicht unbedingt ein neues Objekte erzeugen, z.B. muss es von Boolean nur zwei Instanzen geben
- können auch einen anderen Typ zurückliefern (z.B. einen Subtypen)
- Vor Java 1.7 ersparen sie auch Tippaufwand weil beim new Aufruf die Generics des Typen wiederholt werden müssen (siehe Diamon Operator)
Aber auch einige Nachteile
- Ohne Konstruktoren kann von man einer Klasse nicht erben
- Static Factory Methoden sehen aus wie normale static Methoden, Konstruktoren sind dafür immer als solche zu erkennen
Das ist nicht genau das Gleiche wie das .
Viele optionale Parameter für den Konstruktor
Man hat für eine Klasse sehr viele oder optionale Parameter für den Konstruktor. In diesem Fall kann man Telescoping Konstruktoren schreiben (verschieden Kontruktoren mit allen benötigten Kombinationen an Parametern). Das ist aber schreibaufwendig und schwer zu warten und zu verstehen (für den Benutzer unterscheiden sich die Kontruktoren nur durch die Typen der Paramter). Alternativ könnte man nur für die immer erforderlichen Parameter einen Konstruktor anbieten und restlichen per Setter nachfüllen. Alledings ist das Objekt dann zeitweise in einem nicht fertigen Zustand und es ist durch die Setter immer veränderbar (häufig sind Objekte, die nach der Erzeugung nicht mehr verändert werden können sehr vorteilhaft). Also nutzt man das . Dazu erzeugt man ein Hilfsobjekt aus einer Builder Klasse über deren Konstruktor und übergibt diesem alle erforderlichen Parameter. Zusätzlich hat die Builder Klasse noch Setter, die die optionalen Parameter setzen. Die eigentliche Klasse hat dann nur noch einen Konstruktor, der als Parameter die Hilfsklasse erwartet und sich aus ihr alle Paramter zieht. Liefern die Setter noch das Builder Objekt zurück, kann man den Builder sehr lesbar füllen. Außerdem hat die Builderklasse noch eine build() Methode, in der sie den Konstruktor der eigentlichen Klasse mit sich selbst als Argument aufrufen. Wenn die Builder Klasse eine innere Klasse der eigentlichen Klasse ist, kann der Konstruktor der eigentliche Klasse private werden und man muss den Builder nutzen. In der build() Methode kann man dann auch testen, ob alle Argumente sinnvoll gefüllt sind.
Klasse von der es keine Instanz geben soll
Klasse bekommt einen private Konstruktor, der sicherheitshalber noch eine Exception wirft. Damit kann man von der Klasse allerdings auch nicht erben.
Singleton
Der Konstruktor ist private und damit von aussen nicht erreichbar, es gibt eine von außen abzufragende instance Variable, die irgendwann mal initial mit einem Objekt der Klasse initialisiert wird. Es wird also nur genau diese eine Instanz der Klasse geben und keine zweite. Der Konstruktor kann intern noch mal testen, dass er nicht schon mal eine Instanz erzeugt hat falls doch jemand über Reflektion an den privaten Konstruktor gekommen ist.
{
public static final S INSTANCE=new S();
private S()
{
...
}
public void doSomething() { ... }
}
Etwas flexibler ist die Variante die Instanz über einen Getter zu ermitteln
{
private static final S INSTANCE=new S();
public static S getInstance() { return INSTANCE; }
private S()
{
...
}
public void doSomething() { ... }
}
Achtung, wenn die Singleton Klasse serialisierbar sein soll wird beim Einlesen wird ohne einen Eingriff ein neues Objekt erzeugt. Also readResolve() überschreiben!
Die beste Variante für einen Singelton geht über ein Enum:
{
INSTANCE;
public void doSomething() { ... }
}
Unnötige Objekterzeugung vermeiden
String s= "Hello World";
{
-Calculator calc=new Calculator(...);
return calc(x);-
return this.calc(c);
}
Achtung bei Autoboxing
long i;
for(i=0; i<MAX; i++)
Nicht mehr benötigte Objektreferenzen
Bei Klassen, die folgendes beinhalten
- einen eigenen Speicher
- einen eigenen Cache
Muss man aufpassen, dass man nicht mehr benötigte Referenzen auf Objekte nicht dauerhaft behält. Ein Beispiel ist z.B. eine Stack Implementierung, die beim Entfernen eines Eintrages zwar die Nummer des höchstens Eintrages reduziert, aber nicht das freigewordene Objekt im Stackspeicher freigibt (also z.B. mit null überschreibt). Auch Callbacks (z.B. Listener) sollte man wieder abhängen.
finalize()
finalize() wird aufgerufen, wenn der Garbage Collector ein Objekt wegräumt. Wann und ob das jemals passiert ist aber nicht sicher vorhersagbar, also nicht benutzen. Außerdem hat es einen Performance Verlust zur Folge. Statt dessen eine eigene Methode anbieten, die man manuell aufrufen muss.
Methoden die alle Objekte haben
equals
Siehe auch .
Equals zu überschreiben ist komplizierter als es aussieht. In folgenden Fällen sollte man es besser nicht überschreiben
- Ein Objekt ist nur zu sich selbst (der selben Instanz, gleiche Adresse im Speicher) gleich (das leistet die equals Implementierung per Default)
- Ein Test auf logische Gleichheit wird nicht benötigt
- Unsere Vaterklasse implemtiert bereits ein equals und das ist auch für uns richtig
Falls man equals doch selbst implementieren möchte muss man das hier auf jeden Fall erfüllen
- Reflexivität
- Symmetrie
Verletzt man leicht, wenn man versucht Objekte von verschiedenen Typen vergleichbar zu machen.
- Transitivity
Man kein keine Klasse von der es Instanzen geben kann mit Equals relevanten Attributen beerben und dabei weiterer solcher Attribute hinzufügen ohne hiergegen (oder gegen eine der anderen Regeln) zu verstossen. Also entweder abstrakte Vaterklassen oder die neue Klasse beinhaltet eine Instanz der alten Klasse.
- Konsistenz
Wiederholte Aufrufe von equals für unveränderte x,y sollten auch das gleiche Ergebnis liefern. Verletzt man vielleicht wenn man externe Resourcen für den Vergleich abfragt, die sich einfach ändern können.
- Null
D.h. keine Exception werfen! Ein expliziter Test auf null ist nicht notwendig wenn man schon mit instanceof testet, das liefert bei null auch falsch.
Achtung: hashCode() muss auch überschrieben werden, wenn equals überschrieben wird!
hashCode
Wenn zwei Objekte laut equals() gleich sind, muss auch der hashCode gleich sein. Umgekehrt können zwei Objekte natürlich den gleichen hashCode haben, auch wenn sie gar nicht gleich sind (und equals entsprechend false liefern würde). Während eines Programmlaufes darf sich der hashCode nicht ändern außer das Objekt wird in relevanten Attributen verändert.
Aus einem long einen int hashCode zu bauen
int hash= (int) (lng ^ (lng >>> 32))
Dabei schiebt man den Long Wert erst um 32 Bit nach rechts:
Damit sind die oberen 32 Bits leer geworden, deren Inhalt steht jetzt in den unteren 32 Bits. Das verbindet man jetzt per XOR mit den ursprünglichen unteren 32 Bits und schreibt es in die unteren 32 Stellen
Schneidet man die oberen 32 Stellen jetzt noch einfach ab
Dann hat man 32 Bit die aus einer Mischung aus den oberen und unteren 32 Bit der Long Zahl bestehen.
toString()
Man sollte immer toString() überschreiben und alle relevanten Informationen (möglichst selbsterklärend) mit ausgeben. Erleichtert Debug und Fehlersuche enorm.
Falls man sich darauf einlassen will, dass das Format geparst werden können soll, ist es gut eine statische Methoden für die hin und her Konvertierung anzubieten. Auf jeden Fall sollten die im toString() enthaltenen Informationen auch über direkte Methoden erreichbar sein damit niemand gezwungen ist, die Information durch Parsen zu ermitteln.
clone
Die Erwartungen an eine clone() Methode in Java sind normalerweise
foo.clone().getClass()==foo.getClass();
foo.clone().equals(foo);
In der Klasse Object ist eine generische clone() Methode implementiert:
Diese Methode ist außerhalb einer Klasse nicht erreichbar (nur protected) und muss erst mal verfügbar gemacht werden:
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
Allerdings wird jeder Aufruf der clone() Methode mit einer CloneNotSupportedException Exception quittiert, bis man das Interface Cloneable implementiert hat.
Da die Methode ein Objekt vom Typ Object zurückgibt, muss jeder Nutzer das Ergebnis auf den richtigen Typ casten. Solange jede clone() Methode das Objekt zurückgibt, welches die clone() Methode des Vaters zurückgegeben hat (bis hoch zur clone() Methode in Object) ist das Ergebnis (ab Java 1.5) aber vom selben Typ wie wir selbst. Der Rückgabetyp kann also einfach unsere Klasse sein.
Was leistet die clone() Methode in der Klasse Object für verschiedene Variablentypen?
Für Basistypen
wird der Inhalt der Basistypen kopiert. Hier ist kein weiterer Eingriff notwendig.
Für echte Objekte
MyValue object_with_value;
wird nur die Referenz auf den Ort im Speicher kopiert, wo sich das Objekt befindet. Der Klon zeigt also genau auf dasselbe Objekt wie die Vorlage. Das ist dann ein großes Problem, wenn man den Inhalt des Objektes ändern kann. Long bietet z.B. keine Methoden um den Wert einer Long Variable zu ändern, beliebige andere Klassen aber natürlich schon. Ändert man dann den Wert eines solchen Objektes, ändert man es natürlich auch für alle Klone mit. Wenn das nicht gewünscht ist, muss man in der clone() Methode auch diese Objekte duplizieren.
Für Arrays
zeigen Klon und Original denselben Array im Speicher, Änderungen an dem Array haben Auswirkungen auf alle Klone. Dafür bietet jeder Array in Java ebenfalls eine clone() Methode, mit der man einen neuen Array erhält, der denselben Inhalt hat wie der original Array. Auch wenn man dann einen neuen Array hat, zeigen die Objekte im Array aber weiterhin auf dieselben Objekte. Will man das verhindern, muss man für alle Objekte im Array gegebenenfalls wieder clone() aufrufen.
Für Container
gibt manchmal keine clone() Methode, List hat z.B. keine clone() Methode definiert, ArrayList hingegen schon. Einfache Lösung, statt clone(), einen neuen Container erzeugen und mit dem Inhalt des alten Containers füllen:
Allerdings zeigen auch hier die Einträge in der Liste auf dieselbe Stelle im Speicher, wie in der original Liste. Auch hier muss man also für die Objekte im Container gegebenenfalls wieder clone() aufrufen. Wenn das zu klonende Objekt ein Interfacetyp hat, sich selbst fragen, ist das neue konkrete Objekt, welches das Interface implementiert, vom selben Typ wie das alte konkrete Objekte (z.B. ArrayList vs LinkedList). Falls nein, ist das ein Problem?
Beispiel
{
public long long_basic;
public Long long_object;
public MyValue object_with_value;
public MyValue[] array;
public List<MyValue> list;
public CopyMe()
{
}
@Override
public CopyMe clone()
{
try
{
CopyMe result=(CopyMe) super.clone();
result.object_with_value=object_with_value.clone();
result.array=array.clone();
for(int i=0; i<array.length; i++)
{
result.array[i]=array[i].clone();
}
result.list=new ArrayList<MyValue>(list);
for(int i=0; i<list.size(); i++)
{
result.list.set(i, list.get(i).clone());
}
return result;
}
catch(CloneNotSupportedException e)
{
// this should never happen, as we implement Cloneable
throw new IllegalStateException("This class needs to implement Cloneable! "+this.getClass(), e);
}
}
}
Falls der Fokus der Klasse darauf liegt, dass man von Ihr erbt, und die Erben geklont werden können sollen, sollte man clone() auch überschreiben, protected lassen, CloneNotSupportedException werfen und Cloneable nicht implementieren
{
@Override
protected Father clone() throws CloneNotSupportedException {
Father result=super.clone();
...
return result;
}
}
Probleme / Einschränkungen
- alle Vaterklassen müssen clone() richtig implementiert
- final Attribute können in der clone() Methode nicht überschrieben werden um sie zu duplizieren
- man sollte in der clone() Methode keine Methoden des neuen Klons aufrufen, die nicht private oder final sind (weil es sonst in einem Erben überschrieben worden sein könnte)
- Object.clone() ist nicht synchronized, also selbst absichern
- Achtung, manchmal müssen beim clonen auch Inhalte verändert werden (z.B. IDs)
Alternativen: Am besten nie Cloneable implementieren und nie clone() überschreiben. Statt dessen
Copy Constructor
Oder Copy Factory
Comparable
Die Klasse implementiert Comparable<TYPE> und überschreibt die Methode compareTo(TYPE t)
Dabei gilt:
!foo.compareTo(bar) ist | !dann ist |
<0 | foo<bar |
>0 | foo>bar |
==0 | beide haben die gleiche Position |
Es müssen folgende Regeln eingehalten werden: Richtung darf keine Rolle spielen: a.compareTo(b)<0 dann b.compareTo(c)>0 a.compareTo(b)>0 dann b.compareTo(c)<0 a.compareTo(b)==0 dann b.compareTo(c)==0 a.compareTo(b) wirft Exception dann wirft auch b.compareTo(a) Exception
Transitivität z.B. a.compareTo(b)>0, b.compareTo(c)>0 dann auch a.compareTo(c)>0
Gleiche verhalten sich gleich Wenn x.compareTo(y)==0 müssen beide im Vergleich mit bel. anderen Objekten auch ein inhaltliches gleiches compareTo Ergebnis liefern. x.compareTo(a) und y.compareTo(a) müssen sich also für alle a entsprechen.
Wenn man Comparable implementiert ist es oft sehr störend, dass man dann als Parameter jede beliebige andere Klasse akzeptieren muss. So kann man es nur für eine Bestimmte Klasse implementieren
{
public int compareTo(Foo pOther)
{
}
}
Equals vs Comparable Es wird empfohlen dass compareTo genau dann 0 liefert, wenn equals auch true liefert.
Vergleiche auch Comparator Siehe auch .
Modulo
So kann man den Rest beim Teilen (hier durch 2) ermitteln
Achtung, für negative x erhält man auch einen negativen Rest. Eine schnellere Methode, um zu testen, ob eine Zahl durch 2 teilbar
Dabei wird bitweise verglichen, ob die Binärzahl auf 1 endet (durch 2 teilbar ist)
Dezimalzahlen
Dezimalzahlen sind nicht geeignet, um exakte Ergebnisse zu berechnen
double b=1.10;
double c=a-b; // 0.8999999999999999 vs. 0.9
Besser: BigDecimal nehmen (aber nur den String Konstruktur von BigDecimal nutzen) oder z.B. bei Geldberechnungen die Zahlen durch Multiplikation ganzzahlig machen.
Long int Overflow
Wenn man einer long Variable eine Berechnung zuweist wird die per Default mit int durchgeführt und kann deshalb leicht überlaufen
Falls das zu befürchten ist, mindestens das erste Argument der Berechnung als long darstellen
Dabei aber immer einen großes L benutzen, das kleine l ist leicht mit einer Eins 1 zu verwechseln.
Java Quiz
Welche Methode wird aufgerufen?
public static class Child extends Father { }
public static void test(Father f)
{
System.out.println("Father!");
}
public static void test(Child b)
{
System.out.println("Child!");
}
Father x =new Child();
test(x);
Antwort: Father!