Designfrage zu mutable and immutable

samosa

Mitglied
hi,

ich habe eine Klasse
Code:
Vector
die lauter statische Methoden für Operationen auf Vektoren besitz:

Code:
class Vector {

    // Berechnet Skalarmultiplikation a*x und liefert Resultat zurück
    static double[] mult(double a, double[] x) {...}

    // Berechnet Addition x + y und liefert Resultat zurück
    static double[] add(double[] x, double[] y) {...}

    // Liefert die Länge des Vektors  x zurück
    static double norm(double[] x) {...}

    // weitere Methoden

}

Die Klasse
Code:
Vektor
ist also eine Art
Code:
Math
-Klasse für Vektoren. Nun kann man die Methoden der Klasse
Code:
Vektor
sowohl mutable als auch immutable machen. Eine mutable
Code:
add(x, y)
würde beispielsweise das Ergebnis x+y der Variable x zuweisen und zurückliefern. Ein immutable
Code:
add(x, y)
lässt die Parameter x und y unverändert.


Der Vorteil von den mutable-Methoden ist, dass nicht so viele neue Objekte erzeugt werden (die Methoden werden in meiner Applikation millionenfach aufgerufen). Allerdings brauche ich auch immutable Methoden.


Wie macht man das am saubersten? Zwei getrennte Klassen wie String und StringBuilder? In einer Klasse und anhand des Methodennamens deutlich machen, was mutable und was immutable ist? Auf Listen und eine Wrapper-Klasse für double[]-Arrays würde ich dabei gerne verzichten.

Wie würdet ihr das Problem lösen?

beste Grüße

samosa
 
G

Gast2

Gast
Java Practices -> Immutable objects

Vielleicht hilft dir das schonmal weiter.

Allerdings warum sollte deine Klasse überhaupt mutable sein? Wenn es eine reine Utility Klasse ist, also nur statische Methoden und am besten einen private Konstruktor - dann musst du dir da keine Sorgen drum machen.

Du fragst doch eher ob deine Methoden final Parameter haben sollten, oder sehe ich das falsch?

Nebenbei würd ich wenn schon die Klasse [c]Vectors[/c] oder [c]VectorUtilities[/c] nennen (wie auch Collections oder Arrays). Einmal um sie von java.util.Vector klar abzugrenzen und das andere um halt zu zeigen das es reine reine Utility-Klasse ist.
 
Zuletzt bearbeitet von einem Moderator:

samosa

Mitglied
Danke, den Text habe ich vor etwa einer Stunde bei meiner Recherche zu diesem Thema gelesen, aber ich wüßte nicht, inwiefern es mir weiterhelfen könnte.
 
M

maki

Gast
Math ist kein sauberes Beispiel für OOAD, sondern schlicht ein Workaround um keine echten OOAD Mechanismen zu verwenden, wohl aus Performancegründen.

Vector ist ein sehr schlechter Name den du da gewählt hast, gibt es nämlich schon, VectorUtils wäre imho besser.

Immutable ist etwas anderes, da geht es darum, dass Objekte nicht verändert werden können, was du meinst ist schlicht ob deine (static) Methoden den alten Vector ändern oder einen neuen zurückgeben.
Bei echten immutables sind die Klassen schon so gestrickt dass man keine Objekte verändern kann.

Sauberer imho:
Eine eigene Klasse schreiben welche schon alle Methoden bereitstellt (nicht nur static), ob diese Obejkte dann Mutable sind kommt darauf an wie du sie verwendest.

Manchmal muss man eben auch einsehen, dass OOAD nciht immer die beste Wahl für jedes Problem ist, vielleicht reicht es wenn du deiner Utils Klasse mit den statischen Methoden jeweils eine Methode anlegst die den alten Vector ändert, und eine, die einen neuen erzeugt.
 

samosa

Mitglied
Allerdings warum sollte deine Klasse überhaupt mutable sein? Wenn es eine reine Utility Klasse ist, also nur statische Methoden und am besten einen private Konstruktor - dann musst du dir da keine Sorgen drum machen.

Du fragst doch eher ob deine Methoden final Parameter haben sollten, oder sehe ich das flasch?

Ich brauche die Methoden mal so, dass beispielsweise mult(a, x) den Vektor x ändert und ich brauche die Methoden mult(a, x) aber auch so, dass der Parameter x unverändert bleibt. In meiner Anwendung werden beide Formen millionenfach aufgerufen. Beschränke ich mich nur auf immutable-Methoden erzeuge ich häufig unnötig viele Objekte ebi Aufrufen der Form x = mult(a, x). Mache ich alle Methoden mutable muss ich vorher eine Kopie von x erzeugen und diese dann der immutable Methode übergeben. Das finde ich lästig.

Die Frage ist nun, wie macht man das am cleversten?
 

samosa

Mitglied
Math ist kein sauberes Beispiel für OOAD, sondern schlicht ein Workaround um keine echten OOAD Mechanismen zu verwenden, wohl aus Performancegründen.
.

Scheint bei mir ähnlich zu sein. Mich stört auch, dass ich dann jeden dahergelaufenen double[] array in ein Vektor-Objekt wrappen muss.


Vector ist ein sehr schlechter Name den du da gewählt hast, gibt es nämlich schon, VectorUtils wäre imho besser.

Danke für den Tip.


Eine eigene Klasse schreiben welche schon alle Methoden bereitstellt (nicht nur static), ob diese Obejkte dann Mutable sind kommt darauf an wie du sie verwendest.

Manchmal muss man eben auch einsehen, dass OOAD nciht immer die beste Wahl für jedes Problem ist, vielleicht reicht es wenn du deiner Utils Klasse mit den statischen Methoden jeweils eine Methode anlegst die den alten Vector ändert, und eine, die einen neuen erzeugt.

Die zweite Variante sagt mir mehr zu. Die Frage ist getrennte Klassen oder alle Methoden in eine Utility-Klasse? Was macht mehr Sinn?
 
G

Gast2

Gast
Wie maki und auch ich schon sagte - es geht hier weniger um mutuable oder immutable... Der Begriff wird eigentlich eher im Bezug auf Klassen benutzt und nicht auf Methoden.

Viele sagen, dass solange es keinen zwingenen Grund gibt die Parameter einer Methode nicht final zu machen, sollte man dies auch tun.

Wenn du definitv beide Varianten brauchst würd ich mir da eine NamingConvention für ausdenken aber es in einer Utility Klasse lassen. Normalerweise würd ich bei einer Methode die direkt in meinem Object rumschmiert (oder es zumindest so aussieht als ob) keinen Returnvalue erwarten, siehe z.B. Collections.shuffle(). Bei der Version mit final Parametern wäre ein return value zwingend.

Du musst auch beachten ob die Object die du in der Methode "bearbeitest" (wie auch immer) mutuable sind oder nicht, das hat natürlich auch einen Einfluss darauf.

Was meinst du denn ist ein double[]? Mutuable oder nicht?
 
Zuletzt bearbeitet von einem Moderator:
M

maki

Gast
Die zweite Variante sagt mir mehr zu. Die Frage ist getrennte Klassen oder alle Methoden in eine Utility-Klasse? Was macht mehr Sinn?
Ganz ehrlich: das ist Jacke wie Hose, nimm das was dir besser gefällt

Wenn du 2 Utlility Klassen hast, kannst du imho einfacher trennen zwischen den Methoden, die neue Vektoren anlegen und denen, die die alten abändern.
Wenn es allerdings insgesamt nur 5 Methoden sind, kann man sich das imho auch sparen, kommt also immer darauf an.
 

samosa

Mitglied
Wenn du definitv beide Varianten brauchst würd ich mir da eine NamingConvention für ausdenken aber es in einer Utility Klasse lassen.

Normalerweise würd ich bei einer Methode die direkt in meinem Object rumschmiert (oder es zumindest so aussieht als ob) keinen Returnvalue erwarten, siehe z.B. Collections.shuffle(). Bei der Version mit final Parametern wäre ein return value zwingend.

Die List.toArray() Methode schmiert auch im übergebenen Object herum und liefert es anschließend zurück. Gibt es da allgemeine Kriterien für return?

Du musst auch beachten ob die Object die du in der Methode "bearbeitest" (wie auch immer) mutuable sind oder nicht, das hat natürlich auch einen Einfluss darauf.

Was meinst du denn ist ein double[]? Mutuable oder nicht?

worauf zielt die frage ab? das man immutable parameter eh nicht ändern kann?
 
G

Gast2

Gast
Die List.toArray() Methode schmiert auch im übergebenen Object herum und liefert es anschließend zurück. Gibt es da allgemeine Kriterien für return?

du meinst [c]T[] List.toArray(T[] contents)[/c]?

Das ist hier ganz praktisch um soewtas zu tun:
Java:
		List<String> o = new ArrayList<String>(){{
			add("a");
			add("b");
			add("c");
		}};
		String[] stringArray = o.toArray(new String[0]);

Gegenüber:

Java:
		List<String> o = new ArrayList<String>(){{
			add("a");
			add("b");
			add("c");
		}};
		String[] stringArray = new String[0];
		stringArray = o.toArray(stringArray);

Was sich irgendwie letztlich gleich bleibt. Ausser im String[] sind schon Elemente vorhanden. Kommt halt immer drauf an was du genau machen möchtst. Wichtig ist halt das du klar dokumentierst was passiert in deiner Methode.

Guck doch mal was hier passiert:
Java:
		List<String> o = new ArrayList<String>(){{
			add("a");
			add("b");
			add("c");
		}};
		String[] stringArray = new String[0];
		o.toArray(stringArray);
worauf zielt die frage ab? das man immutable parameter eh nicht ändern kann?

Ja, muss man sich halt auch immer überlegen. Gut beim double[] hast du da kein Problem. Aber sonst?
 
Zuletzt bearbeitet von einem Moderator:

Antoras

Top Contributor
Gibt es einen Grund warum du keine Vektoren-Klassen erstellst? Diese könntest du bequem in mutable und immutable unterteilen und mit den benötigten Methoden ausstatten.
Code:
package collection.immutable
class Vector2 {
  type x, y
  Vector2 add(Vector2 v) {
    return new Vector2(x+v.x, y+v.y)
  }
}
class Vector3 {
  type x, y, z
  Vector3 add(Vector3 v) {
    return new Vector3(x+v.x, y+v.y, z+v.z)
  }
}

package collection.mutable
class Vector2 {
  type x, y
  void add(Vector2 v) {
    x += v.x
    y += v.y
  }
}
class Vector3 {
  type x, y, z
  void add(Vector3 v) {
    x += v.x
    y += v.y
    z += v.z
  }
}
 

samosa

Mitglied
Gibt es einen Grund warum du keine Vektoren-Klassen erstellst? Diese könntest du bequem in mutable und immutable unterteilen und mit den benötigten Methoden ausstatten.

Ja, es ist bei mir der gleiche Grund, wie wenn man zur Verwendung der Math-Klasse stets autoboxing machen müsste, weil die Methoden von der Form
Code:
Double f(Double x)
statt
Code:
double f(double x)
sind.
 

slawaweis

Bekanntes Mitglied
mit mutable oder immutable hat es wenig zu tun, eher ob man die übergebenen Parameter verändert. Es gibt verschiedene Wege das mit rein statischen Funktionen zu lösen.

Variante 1:
Java:
// VectorUtilities
public class VU
{
 public static double [] add(double [] x, double [] y) { /*x = x+y; return x;*/ }
}

// VectorUtilitiesCopy
public class VUC
{
 public static double [] add(double [] x, double [] y) { /*z = x+y; return z;*/ }
}

Variante 2:
Java:
// VectorUtilities
public class VU
{
 public static double [] add(double [] x, double [] y) { /*x = x+y; return x;*/ }
 public static double [] addCopy(double [] x, double [] y) { /*z = x+y; return z;*/ }
}

Variante 3:
Java:
// VectorUtilities
public class VU
{
 public static double [] add(double [] x, double [] y) { /*add(x, y, false) oder add(x, y, true)*/ }
 public static double [] add(double [] x, double [] y, boolean copy) { /*...*/ }
}

Variante 4:
Java:
// VectorUtilities
public class VU
{
 public static double [] copy(double [] x) { /*z = x; return z;*/ }
 public static double [] add(double [] x, double [] y) { /*x = x+y; return x;*/ }
}

// Verwendung
VU.add(VU.copy(x), y);

am Ende kommt es darauf an, was man braucht und wie man programmieren möchte. Es gäbe da noch eine Variante mit Interfaces, aber das wäre nicht mehr statisch.

Weiterhin gibt es in Java3D eine gute Bibliothek für Vektoren, die "vecmath.jar". Ich weis aber nicht, unter welcher Lizenz diese ist. Sie wurde nicht von SUN entwickelt, sondern für Java3D eingekauft.

javax.vecmath (Java 3D 1.3.2)

Slawa
 

Antoras

Top Contributor
Ja, es ist bei mir der gleiche Grund, wie wenn man zur Verwendung der Math-Klasse stets autoboxing machen müsste, weil die Methoden von der Form
Code:
Double f(Double x)
statt
Code:
double f(double x)
sind.
Dir ist aber schon klar, dass Arrayzugriffe in Java relativ langsam sind (weil jedes mal geprüft wird ob die Arraygrenzen nicht überschritten werden)? Ich vermute, dass das Arbeiten mit Arrays länger dauert als wenn du spezialisierte Klassen dafür verwendest.
 

samosa

Mitglied
@slawaweis

vielen dank! ich glaube ich mache variante 2.


@fassy

zum return-Statement eine Bemerkung. Wir betrachten Vektor-Operationen, die den übergebenen Parameter ändern. Dein Vorschlag war, kein return Statement zu verwenden. Dann kann man allerdings auch die Operationen nicht verketten. Eine Linearkombination
Code:
x <- a*x + b*y
lässt sich dann nicht mehr so schreiben:

Code:
add(mult(a, x), multcopy(b, y))

sondern man muss

Code:
mult(a, x);
add(x, multcopy(b, y));

schreiben.
 

samosa

Mitglied
Dir ist aber schon klar, dass Arrayzugriffe in Java relativ langsam sind (weil jedes mal geprüft wird ob die Arraygrenzen nicht überschritten werden)? Ich vermute, dass das Arbeiten mit Arrays länger dauert als wenn du spezialisierte Klassen dafür verwendest.

Nein, das ist mir nicht klar. Wie werde ich die Arrays in einer spezalisierten Klasse los?
 

Marco13

Top Contributor
Einen ähnlichen Thread wollte ich kürzlich auch schonmal aufmachen. Die Frage ist nämlich IMHO alles andere als trivial. Man hat sooo viele Möglichkeiten
Java:
class Vector { void add(Vector v1) { ... } } // Addiert v1 zu this, gibt nichts zurück
class Vector { Vector add(Vector v1) { ... } } // Addiert v1 zu this, gibt this zurück
class Vector { Vector add(Vector v1) { ... } } // Addiert v1 und this, gibt die Summe zurück
(nur die dritte könnte Immutable sein)
Java:
class VectorUtils { static Vector add(Vector v1, Vector v2) { ... } } // Addiert v1 zu v2, gibt v1 oder v2 zurück
class VectorUtils { static Vector add(Vector v1, Vector v2) { ... } } // Addiert v1 zu v2, gibt summe zurück
Und spätestens bei Arrays werden die Möglichkeiten undurchsichtig
Java:
class VectorUtils { static float[] add(float v1[], float v2[]) { ... } } // Addiert v1 zu v2, gibt v1 oder v2 zurück
// Addiert v1 zu v2, schreibt die summe in "result", gibt "result" zurück,
// und wenn result 'null' ist, wird ein neuer Array angelegt und zurückgegeben
class VectorUtils { static float[] add(float v1[], float v2[], float result[]) { ... } }
(oder belieibge weitere Kombinationen). Die letzte wirkt auf den ersten Blick etwas "unhandlich", hat aber einige Vorteile.

(BTW: Der Anlass für diese Frage wäre bei mir eigentlich ein Fall, wo es nicht um 3-elementige Arrays ginge, sondern um belieibig große, d.h. da gibt's nochmal 1000e andere Möglichkeiten, aber auch so ist es schon schwer genug).

Die Vecmath verwende ich auch in einigen Projekten. Die sollte man aber nicht als "Best practice" ansehen: Die Fields (x,y,z) von Vector3f sind dort public :autsch: das macht unglaublich viele schöne Abstraktionsmöglichkeiten kaputt...


EDIT: Die Arrays kann man loswerden, wenn die Dimension bekannt ist
Code:
class Vector3 { float v[] = new float[3]; }
// vs.
class Vector3 { float x,y,z; }
Aber das funtkioniert natürlich nicht immer, und an manchen Stellen haben Arrays auch den Vorteil, dass man die Daten für 1000 Vector3's in einen Array packen kann. (Kann praktisch sein).
 
Zuletzt bearbeitet:
G

Gast2

Gast
@fassy

zum return-Statement eine Bemerkung. Wir betrachten Vektor-Operationen, die den übergebenen Parameter ändern. Dein Vorschlag war, kein return Statement zu verwenden. Dann kann man allerdings auch die Operationen nicht verketten. Eine Linearkombination
Code:
x <- a*x + b*y
lässt sich dann nicht mehr so schreiben

Ist richtig. Wie gesagt, hängt alles davon was du machen möchtest. Du erstellst deine API und solange sie in sich konsistent ist und vorallem gut dokumentiert bleibt es letztlich dir überlassen. Generell bin ich eigenlich kein Freund von so einer Art IN OUT oder OUT parametern wie z.B. in PL/SQL.

Wen man da dann sowas sieht:
SQL:
FUNCTION a (
  param1 IN      VARCHAR2,
  param2 IN OUT  VARCHAR2,
  param3 OUT     VARCHAR2,
  ) RETURN VARCHAR2
Da stolpert man sehr sehr schnell, grade über die Nebenwirkungen in param2. Aber wie gesagt... Geschmacksfrage.

Wenn es irgend geht* versuche ich mit final Parametern zu arbeiten und über den return value mein Ergebnis zu bekommen

*Manchmal halt leider nicht, aber das muss man sehr genau überlegen ob es aus Performance und Designtechnischer Sicht wirklich Sinn macht, oder einem schon von der Standard API vorgeschrieben wird.
 

samosa

Mitglied
Java:
// Addiert v1 zu v2, schreibt die summe in "result", gibt "result" zurück,
// und wenn result 'null' ist, wird ein neuer Array angelegt und zurückgegeben
class VectorUtils { static float[] add(float v1[], float v2[], float result[]) { ... } }
Die letzte wirkt auf den ersten Blick etwas "unhandlich", hat aber einige Vorteile.

Welche Vorteile hat man denn da?

EDIT: Die Arrays kann man loswerden, wenn die Dimension bekannt ist
Code:
[/QUOTE]
Alles klar, ich dachte es gibt eine Möglichkeit für den allgemeinen Fall.
 

Marco13

Top Contributor
Welche Vorteile hat man denn da?

Das Verhältnis zwischen "Anzahl Methoden" und "Angebotene Funktionalität" ist recht günstig: Man kann verschiedene Funktionalitäten erreichen, die alle in einer Methode abgebildet werden. Man kann damit
Mit einem vor-allokierten Array für die Ergebnsise arbeiten
Code:
float result = new float[n];
for (int i=0; i<100; i++)
{
    float v0[] = get(i);
    float v1[] = get(i+1);
    VectorUtils.add(v0, v1, result); // Verwendet immer dasselbe result
    process(result);
}
Alternativ mit der gleichen Methode passende result-Arrays erstellen lassen
Code:
for (int i=0; i<100; i++)
{
    float v0[] = get(i);
    float v1[] = get(i+1);
    float result[] = VectorUtils.add(v0, v1, null); // Erstellt immer neue Arrays
    store(result);
}
Oder "aufsummieren"
Code:
float v0[] = get(i);
for (int i=0; i<100; i++)
{
    float v1[] = get(i+1);
    VectorUtils.add(v0, v1, v0); // Addiert alles in v0 auf
}
process(v0);

Natürlich könnte man sich dafür auch Utility-Methoden mit "passenderen" Signaturen anbieten, die intern die "allgemeinste" Funktion aufrufen, aber ... zumindest kann es nicht schaden, sich mal zu überlegen, wie diese "allgemeinste" Funktion aussehen könnte. Ob man sie dann implementiert/verwendet oder nicht, ist nochmal eine andere Frage. Die genannten Vorteile werden vermutlich erst bei Arrays mit mehr als 3 Elementen relevant - aber vielleicht auch schon, wenn die Methoden oft aufgerufern werden, was du ja meintest. Es könnte aber genausogut sein, dass man eine Funktion hat, die man "irgendwie" auf die "allgemeinste" abbilden könnte, wo es aber effizienter wäre, sie speziell zu implementieren. Ab einem bestimmten Punkt kommt wieder die alte Leier von "Das ist von Fall zu Fall verschieden" und "Das muss man mit einem Profiler testen"....
 

Ähnliche Java Themen

Neue Themen


Oben