Best Practice Tiefe Kopie eines Arrays unbekannter Dimension und unbekannten Typs

Complexus

Mitglied
Guten Tag,

ich möchte hier meine Methode zur tiefen Kopie eines Arrays unbekannter Dimension und unbekannten Typs vorstellen. Damit die Kritik mir gegenüber rational und konstruktiv ausfällt, bitte ich potenzielle Antwortgeber, ihre Verbesserungsvorschläge detailreich zu präsentieren und nicht nur allgemein zu beschreiben. Antworten wie "Verwende doch ... oder ..., damit geht es leichter!" sind völlig fehl am Platz. Falls ihr prinzipiell eine elegantere Lösung parat habt, prüft diese bitte ordentlich auf Fehler. Vielen Dank im Voraus für die Berücksichtigung des hier Geschriebenen.

Simples Interface, welches die Methode der tiefen Kopie für einen komplexen Datentypen bereitstellt:

Kopie.Java
Java:
public interface Kopie {

	public Kopie getKopie();
	
}
Die
Code:
.clone()
aus
Code:
Object
ist protected. Auf diese kann von Außen nicht zu gegriffen werden. Folglich können generische Typen, die hier zwingend notwendig sind, auch nicht darauf zugreifen.


Eine Testklasse, welche das Interface implementiert, die Methode des Kopierens korrekt umsetzt und eine neue Instanz des Typs zurückgibt:

TestKlasse.java
Java:
public class TestKlasse implements Kopie {

	char charakter;

	/* Anfang: Konstruktoren */

	public TestKlasse(char charakter) {
		this.charakter = charakter;
	}

	// Standardkonstruktor

	public TestKlasse() {
		this('0');
	}

	// Kopierkonstruktor

	public TestKlasse(TestKlasse Original) {
		this.charakter = Original.getCharakter();
	}

	/* Ende: Konstruktoren */

	/* Anfang: Setter */

	public void setZahlenwert(char charakter) {
		this.charakter = charakter;
	}

	/* Ende: Setter */

	/* Anfang: Getter */

	public char getCharakter() {
		return charakter;
	}

	/* Ende: Getter */

	@Override
	public Kopie getKopie() {
		TestKlasse echteKopie = new TestKlasse(this.charakter);
		return echteKopie;
	}

}

Die Hauptklasse, welche die generische Methode der tiefen Arraykopie durchführt! Eine Methode, welche nur rekursiv funktionieren kann! Bzw. Rekursion und Iteration kombiniert.

Java:
/******************************
 *                            *
 *  @author      Tim Lehmann  *
 *  Datum        18.12.2013   *
 *                            *
 ******************************/

public class Steuerung {

	public static void main(String[] args) {

		ausfuehren();

	}

	public static void ausfuehren() {

		/* Anfang: getestete fehleranfaellige Uebergabeparameter */

		int int_var = 0;
		int[] int_Array_var = new int[1];
		Object Object_var = new Object();
		Object[] Object_Array_var = new Object[1];
		TestKlasse testKlasse_var = new TestKlasse('a');
		TestKlasse[] testKlasse_array_var = new TestKlasse[1];

		/* Ende: getestete fehleranfaellige Uebergabeparameter */

		TestKlasse tk[][][] = new TestKlasse[2][3][3];

		tk[0][0][0] = new TestKlasse('0');
		tk[0][0][1] = new TestKlasse('1');
		tk[0][0][2] = new TestKlasse('2');
		tk[0][1][0] = new TestKlasse('3');
		tk[0][1][1] = new TestKlasse('4');
		tk[0][1][2] = new TestKlasse('5');
		tk[0][2][0] = new TestKlasse('6');
		tk[0][2][1] = new TestKlasse('7');
		tk[0][2][2] = new TestKlasse('8');
		tk[1][0][0] = new TestKlasse('a');
		tk[1][0][1] = new TestKlasse('b');
		tk[1][0][2] = new TestKlasse('c');
		tk[1][1][0] = new TestKlasse('d');
		tk[1][1][1] = new TestKlasse('e');
		tk[1][1][2] = new TestKlasse('f');
		tk[1][2][0] = new TestKlasse('g');
		tk[1][2][1] = new TestKlasse('h');
		tk[1][2][2] = new TestKlasse('i');

		TestKlasse tk_flache_kopie[][][] = TiefeArrayKopie(tk);

		// Ausgabe

		for (int z = 0; z < tk.length; z++) {

			for (int y = 0; y < tk[0].length; y++) {

				for (int x = 0; x < tk[0][0].length; x++) {

					System.out.print(tk[z][y][x].getCharakter() + "   ");

				}

				System.out.println();

			}

			System.out.println("\n");

		}

		for (int z = 0; z < tk.length; z++) {

			for (int y = 0; y < tk[0].length; y++) {

				for (int x = 0; x < tk[0][0].length; x++) {

					System.out.print(tk_flache_kopie[z][y][x].getCharakter()
							+ "   ");

				}

				System.out.println();

			}

			System.out.println("\n");

		}

	}

	@SuppressWarnings("unchecked")
	public static <T> T TiefeArrayKopie(T Array) {

		String Fehlermeldung = null;
		T Kopie = null;

		// Uebergabeparameter ist ein Array

		if (Array.getClass().isArray()) {

			// Pruefen, ob Array Typen eines komplexen Datentypen beinhaltet
			// Meines Wissens nach, nur ueber die Auswertung der Zeichenkette
			// moeglich.
			// Das 'L' in ".getClass().toString()" steht fuer einen komplexen
			// Datentypen

			if (Array.getClass().toString().matches("^.*?\\[L.*\\;")) {

				int arraybereite = ((T[]) Array).length;
				Kopie = (T) ((T[]) Array).clone();

				// Iterativ alle Elemente dieses Arrays durchlaufen

				for (int z1 = 0; z1 < arraybereite; z1++) {

					// Untere Dimension enthaelt keine nicht instanziierte
					// Elemente (null-Werte)

					if (((T[]) Array)[z1] != null) {

						// Untere Dimension enthaelt ebenfalls Arrays

						if (((T[]) Array)[z1].getClass().isArray()) {

							// Rekursiver Aufruf der Methode

							((T[]) Kopie)[z1] = TiefeArrayKopie(((T[]) Kopie)[z1]);

						}
						// Untere Dimension enthaelt keine Arrays
						else {

							// Untersten Elemente des Arrays implementieren die
							// Schnittstelle zur tiefen Kopie

							if (((T[]) Array)[0] instanceof Kopie) {

								for (int z = 0; z < arraybereite; z++) {

									((T[]) Kopie)[z] = (T) ((Kopie) ((T[]) Kopie)[z])
											.getKopie();

								}

								// Untersten Elemente des Arrays implementieren
								// keine Schnittstelle zur tiefen Kopie

							} else {

								Fehlermeldung = "Elemente des Uebergabeparameters impelementieren nicht die noetige Schnittstelle!";

							}

						}

					}

					// Untere Dimension enthaelt nicht instanziierte Elemente

					else {

						Fehlermeldung = "Uebergabeparameter enthält nicht instanziierte Elemente (null Werte)";

					}
				}

				// Array enthaelt keine komplexen Datentypen

			} else {

				Fehlermeldung = "Uebergabeparameter darf kein Array primitiver Datentypen sein!";

			}

			// Uebergabeparameter ist kein Array

		} else {

			Fehlermeldung = "Uebergabeparameter muss ein Array sein!";

		}

		// Wurf der Ausnahme

		if (Fehlermeldung != null) {

			throw new IllegalArgumentException(Fehlermeldung);

		}

		return Kopie;

	}

}

Sicher kann man die Verzweigungen zusammenfassen. Ich wollte hier dem Benutzer der Methode hinreichend auf seine Fehler hinweisen.
 
Zuletzt bearbeitet:
Hallo,

Eine Methode, welche nur rekursiv funktionieren kann!

So etwas gibt es nicht. Man kann jede rekursive Methode in eine iterative verwandeln, das ist ein zentrales Ergebnis der Informatik.

Deine Methode kann doch nun nur Arrays kopieren, deren Inhalte "Kopie" implementieren, oder? Wozu sind dann der Hack mit dem Klassennamen und die ungetypten Dinge nötig? Dank Polymorphie kannst du die Objekte doch alle auf "Kopie" casten.

Für welche Zwecke findet diese Methode eigentlich Anwendung? Hast du das nur aus Interesse gemacht oder brauchst du das irgendwo für? Ich war noch nie in einer Situation, in der etwas derartiges getan werden musste..
 
So etwas gibt es nicht. Man kann jede rekursive Methode in eine iterative verwandeln, das ist ein zentrales Ergebnis der Informatik.
Wie sollte man dann eine Methode entwickeln, die unbekannt dimensionierte Arrays verwaltet? Um auf die Elemente des Arrays zur Laufzeit zugreifen zu können, sind so viele Schleifen, wie Dimensionen nötig. Ich weiß doch vorher nicht, wie viele Dimensionen das Array desjendigen hat, welcher diese Methode aufruft.
Nur durch Rekursion ist es mir möglich, dem Array eine Dimension zu nehmen und die restlichen Dimensionen mit der selben Methode zu verarbeiten.

Deine Methode kann doch nun nur Arrays kopieren, deren Inhalte "Kopie" implementieren, oder?
Richtig!

Wozu sind dann der Hack mit dem Klassennamen und die ungetypten Dinge nötig?
Damit ich als Übergabeparameter Arrays jeglicher Dimension verwenden kann. Der Typ ist tatsächlich statisch, dahingehend sind die generischen Typen unnötig, werden aber bei der Übergabe eines Array essentiel. Der generische Typ kann X-dimensionierte Arrays annehmen.

Also
Code:
new Klasse[]
,
Code:
new Klasse[][]
,
Code:
new Klasse[][][]
, usw.

Nur dafür ist die Verwendung generischer Typen gedacht und nicht etwa, weil ich jeden (End)Typen zulassen will. Nein, ich will natürlich nur Typen, die meine Schnittstelle (hier Kopie) implementieren, zu lassen.

Der Hack mit den Klassennamen ist zum Ausgleichen der Schwierigkeiten, welche die generischen Typen mit sich bringen.

Dank Polymorphie kannst du die Objekte doch alle auf "Kopie" casten.
Das ist richtig, aber das Problem der Übergabe eines Arrays jeglicher Dimension ist damit nicht gelöst. Bzw. ist mir keine Lösung klar.

Kannst Du mir eine Methode präsentieren, der ich sowohl
Code:
new Klasse[]
,
Code:
new Klasse[][]
,
Code:
new Klasse[][][]
, usw. übergeben kann. (Klasse instanceof Schnittstelle).

Für welche Zwecke findet diese Methode eigentlich Anwendung? Hast du das nur aus Interesse gemacht oder brauchst du das irgendwo für? Ich war noch nie in einer Situation, in der etwas derartiges getan werden musste.

Ursprünglich aus Interesse, da ich die Programmiermechanik und -Möglichkeiten besser verstehen möchte. Mittlerweile empfinde ich es als angenehm, eine Methode zu haben, die unabhängig von der Dimension des Arrays alle Arrays tiefenkopiert.

Ich habe zwar noch nie mit Matrizen operiert, deren Dimension größer als zwei waren, aber schon so spart man sich unnötige Methoden ähnlichen Inhalts.
 
Zuletzt bearbeitet:
Zur Rekursion: Dafür benutzt man eine Datenstruktur, zum Beispiel einen Stack. Wenn die untere Dimension wieder ein Array ist, legst du es erstmal auf den Stack und gehst eine Ebene tiefer. So lange, bis du zu den Elementen vordringst. Die werden dann bearbeitet - und danach alles, was noch auf dem Stack liegt. So macht es Java zur Laufzeit auch - die lokalen Variablen deiner Methode verbleiben auf dem Stack, sodass sie nach dem rekursiven Aufruf wieder zur Verfügung stehen.

Zum Anwendungsfall: Große oder vieldimensionale Matrizen speichert man üblicherweise nicht als mehrdimensionales Array, sondern entwirft eine eigene Datenstruktur dafür, die die Dimesionen in Listen verwaltet. Die erste Dimension ist eine Liste von Listen, die wiederum die Listen der dritten Dimesion enthält usw. Durch die Verwendung einer eigenen Datenstruktur kann man dann natürlich die Dimensionen speichern und eine Kopie vernünftig implementieren.

Abgesehen vom Umschreiben der Rekursion in eine Iteration fällt mir auch keine bessere Lösung ein. Allerdings haben deine Variablen sehr ungünstige Namen - "Array" und "Kopie" sind in deinem Programm beides gültige Klasennnamen, deshalb würde ich sie auf keinen Fall als Variablennamen verwenden, das hat mich beim Lesen oft verwirrt. Durch den Einsatz der Methode Arrays.getLength kannst du dir viele Type-Casts sparen. Hier hat jemand eine generische Lösung vorgeschlagen, die ähnlich funktioniert: How to elegantly copy multidimensional arrays in Java? - Stack Overflow
Aber auch dort ist man sich einig, dass man sowas in der Praxis nicht benötigt.
 
Wie Rekursion funktioniert, weiß ich.

Dass man anstelle Mehrdimensionale Arrays für gewöhnlich Listen verwendet, ist mir auch bekannt. Wenn gleich Listen jedoch viel speicherintensiver sind. In heutiger Zeit aber kein Problem mehr, da RAM gegenwärtig als spottbillig gilt. Folge dessen sind immer speicherschlamprige Programme. Und so schließt sich der Teufelskreis. 😉

Abgesehen vom Umschreiben der Rekursion in eine Iteration fällt mir auch keine bessere Lösung ein.
Ich bin nach wie vor davon überzeugt, dass es keine iterative Lösung hierzu gibt. 😉

Gut, die Namen der Variablen waren jetzt nur provisorisch gewählt. Und was Du meinst, ist
Code:
Array.getLength(Object array)
aus Reflections. Ohne 's'. Wo da jetzt Typecasts wegfallen sollen, weiß ich nocht nicht. Ist ja auch egal. Es wird seinen praktischen Nutzen schon finden.

Ich danke Dir sehr für Dein Interesse an dem Thema und dafür, dass Du Dich zu Wort gemeldet hast.

Grüße
 

Zurück
Oben