Stellen Sie sich eine Situation vor, in der eine Klasse dasselbe grundlegende Verhalten, dieselben Methoden usw. implementiert, jedoch mehrere unterschiedliche Versionen dieser Klasse für unterschiedliche Verwendungszwecke vorhanden sein können. In meinem speziellen Fall habe ich einen Vektor (einen geometrischen Vektor, keine Liste) und dieser Vektor könnte auf jeden N-dimensionalen euklidischen Raum (1-dimensional, 2-dimensional, ...) angewendet werden. Wie kann diese Klasse / dieser Typ definiert werden?
Dies wäre in C ++ einfach, wo Klassenvorlagen tatsächliche Werte als Parameter haben können, aber wir haben diesen Luxus in Java nicht.
Die zwei Ansätze, die ich mir vorstellen kann, um dieses Problem zu lösen, sind:
Implementierung jedes möglichen Falls zur Kompilierungszeit.
public interface Vector { public double magnitude(); } public class Vector1 implements Vector { public final double x; public Vector1(double x) { this.x = x; } @Override public double magnitude() { return x; } public double getX() { return x; } } public class Vector2 implements Vector { public final double x, y; public Vector2(double x, double y) { this.x = x; this.y = y; } @Override public double magnitude() { return Math.sqrt(x * x + y * y); } public double getX() { return x; } public double getY() { return y; } }
Diese Lösung ist offensichtlich sehr zeitaufwändig und äußerst mühsam für den Code. In diesem Beispiel scheint es nicht schlecht zu sein, aber in meinem eigentlichen Code habe ich es mit Vektoren zu tun, die jeweils mehrere Implementierungen mit bis zu vier Dimensionen (x, y, z und w) haben. Ich habe derzeit über 2.000 Codezeilen, obwohl jeder Vektor wirklich nur 500 benötigt.
Parameter zur Laufzeit angeben.
public class Vector { private final double[] components; public Vector(double[] components) { this.components = components; } public int dimensions() { return components.length; } public double magnitude() { double sum = 0; for (double component : components) { sum += component * component; } return Math.sqrt(sum); } public double getComponent(int index) { return components[index]; } }
Leider beeinträchtigt diese Lösung die Codeleistung, führt zu unübersichtlichem Code als die vorherige Lösung und ist zur Kompilierungszeit nicht so sicher (es kann zur Kompilierungszeit nicht garantiert werden, dass der Vektor, mit dem Sie sich befassen, tatsächlich zweidimensional ist. zum Beispiel).
Ich entwickle derzeit in Xtend. Wenn also Xtend-Lösungen verfügbar sind, sind diese ebenfalls akzeptabel.
quelle
Antworten:
In solchen Fällen verwende ich die Codegenerierung.
Ich schreibe eine Java-Anwendung, die den eigentlichen Code generiert. Auf diese Weise können Sie einfach eine for-Schleife verwenden, um eine Reihe verschiedener Versionen zu generieren. Ich benutze JavaPoet , was es ziemlich einfach macht, den eigentlichen Code aufzubauen. Anschließend können Sie die Codegenerierung in Ihr Build-System integrieren.
quelle
Ich habe ein sehr ähnliches Modell in meiner Anwendung und unsere Lösung bestand darin, einfach eine Karte mit einer dynamischen Größe zu führen, ähnlich Ihrer Lösung 2.
Mit einem solchen Java-Array-Grundelement müssen Sie sich einfach keine Gedanken über die Leistung machen. Wir generieren Matrizen mit Obergrenzengrößen von 100 Spalten (gelesen: 100 dimensionale Vektoren) mit 10.000 Zeilen, und wir haben eine gute Leistung mit viel komplexeren Vektortypen als Ihre Lösung 2 erzielt. Sie könnten versuchen, die Klasse oder die Markierungsmethoden als endgültig zu versiegeln um es zu beschleunigen, aber ich denke, Sie optimieren vorzeitig.
Sie können Codeeinsparungen erzielen (auf Kosten der Leistung), indem Sie eine Basisklasse erstellen, um Ihren Code gemeinsam zu nutzen:
Wenn Sie Java-8 + verwenden, können Sie natürlich standardmäßige Schnittstellen verwenden, um dies noch enger zu gestalten:
Darüber hinaus haben Sie mit der JVM keine Optionen mehr. Sie können sie natürlich in C ++ schreiben und sie mit JNA überbrücken - dies ist unsere Lösung für einige der schnellen Matrixoperationen, bei denen wir die MKL von fortran und Intel verwenden -, aber dies wird die Dinge nur verlangsamen, wenn Sie schreiben einfach Ihre Matrix in C ++ und rufen ihre Getter / Setter von Java aus auf.
quelle
data class
, um auf einfache Weise 10 Vektorunterklassen zu erstellen. Unter der Annahme, dass Sie mit Java alle Ihre Funktionen in die Basisklasse ziehen können, benötigt jede Unterklasse 1-10 Zeilen. Warum nicht eine Basisklasse erstellen?asArray
Methode implementieren könnte, würden diese verschiedenen Methoden zur Kompilierungszeit nicht überprüft (Sie könnten ein Punktprodukt zwischen einem Skalar und einem kartesischen Vektor ausführen und es würde gut kompilieren, aber zur Laufzeit fehlschlagen). .Stellen Sie sich eine Aufzählung vor, bei der jeder benannte Vektor einen Konstruktor hat, der aus einem Array (initialisiert in der Parameterliste mit den Dimensionsnamen oder ähnlichem oder vielleicht nur einer Ganzzahl für die Größe oder einem leeren Komponentenarray - Ihrem Design) und einem Lambda für besteht die getMagnitude-Methode. Sie könnten die Aufzählung auch eine Schnittstelle für setComponents / getComponent (s) implementieren lassen und einfach feststellen, welche Komponente welche in ihrer Verwendung war, wodurch getX et al. Sie müssen jedes Objekt vor der Verwendung mit seinen tatsächlichen Komponentenwerten initialisieren und möglicherweise überprüfen, ob die Größe des Eingabearrays mit den Dimensionsnamen oder der Größe übereinstimmt.
Wenn Sie die Lösung dann auf eine andere Dimension erweitern, ändern Sie einfach die Aufzählung und das Lambda.
quelle
Warum nicht einfach basierend auf Ihrer Option 2? Wenn Sie die Verwendung der Rohdatenbasis verhindern möchten, können Sie sie abstrakt gestalten:
quelle
double[]
ist jedoch im Vergleich zu einer Implementierung, bei der lediglich zwei Grundelemente verwendet werden, unerwünschtdouble
. In einem so minimalen Beispiel scheint dies eine Mikrooptimierung zu sein, aber betrachten Sie einen viel komplexeren Fall, in dem viel mehr Metadaten beteiligt sind und der betreffende Typ eine kurze Lebensdauer hat.Vector
durch eine spezialisiertere Implementierung ersetzen (z. B.Vector3
), wenn seine Lebensdauer relativ lang sein sollte.Eine Idee:
Dies bietet Ihnen in typischen Fällen eine gute Leistung und eine gewisse Sicherheit während der Kompilierung (kann noch verbessert werden), ohne den allgemeinen Fall zu beeinträchtigen.
Code-Skelett:
quelle