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.

Foo f=new Builder(a,b,c).setOptionalD(d).setOptionalE(E).build();

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 class S
{
 public static final S INSTANCE=new S();

 private S()
 {
  ...
 }

 public void doSomething() { ... }

}

Etwas flexibler ist die Variante die Instanz über einen Getter zu ermitteln

public class S
{
 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:

public enum S
{
 INSTANCE;

 public void doSomething() { ... }
}

Unnötige Objekterzeugung vermeiden

-String s=new String("Hello World");-
String s= "Hello World";
public void foo()
{
 -Calculator calc=new Calculator(...);
 return calc(x);-
 return this.calc(c);
}

Achtung bei Autoboxing

-Long i;-
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
assertTrue(x.equals(x));
  • Symmetrie
if(x!=null &amp;&amp; y!=null) assertTrue(x.equals(y)==y.equals(x));

Verletzt man leicht, wenn man versucht Objekte von verschiedenen Typen vergleichbar zu machen.

  • Transitivity
if(x!=null &amp;&amp; y!=null &amp;&amp; z!=null) if(x.equals(y) &amp;&amp; y.equals(z)) assertTrue(x.equals(z));

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
if(x!=null) assertFalse(x.equals(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

Long lng=...
int hash= (int) (lng  ^ (lng >>> 32))

Dabei schiebt man den Long Wert erst um 32 Bit nach rechts:

lng >>> 32

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

lng  ^ (lng >>> 32)

Schneidet man die oberen 32 Stellen jetzt noch einfach ab

(int) (lng  ^ (lng >>> 32))

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()!=foo;
foo.clone().getClass()==foo.getClass();
foo.clone().equals(foo);

In der Klasse Object ist eine generische clone() Methode implementiert:

protected native Object clone() throws CloneNotSupportedException;

Diese Methode ist außerhalb einer Klasse nicht erreichbar (nur protected) und muss erst mal verfügbar gemacht werden:

@Override
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

long long_basic;

wird der Inhalt der Basistypen kopiert. Hier ist kein weiterer Eingriff notwendig.

Für echte Objekte

Long          long_object;
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

MyValue[]     array;

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

public List<MyValue> list;

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:

list=new ArrayList<MyValue>(list);

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 class CopyMe implements Cloneable
{
    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

public class Father
{
  @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

public CopyMe(CopyMe pOriginal) { ... }

Oder Copy Factory

public static CopyMe getACopy(CopyMe pOriginal) { ... }

Comparable

Die Klasse implementiert Comparable<TYPE> und überschreibt die Methode compareTo(TYPE t)

Dabei gilt:

!foo.compareTo(bar) ist!dann ist
<0foo<bar
>0foo>bar
==0beide 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 class Foo implements Comparable<Foo>
{
    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

(x & 1) != 0

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 a=2.00;
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

long s=10*10*10*10...;

Falls das zu befürchten ist, mindestens das erste Argument der Berechnung als long darstellen

 long s=10L*10*10*10...;

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 Father {  }

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!