[Java] Rekursion

Schattenfänger

Lt. Junior Grade
Registriert
Nov. 2010
Beiträge
273
Hallo wollte nach einiger Zeit mal wieder was schreiben und habe da so meine Probleme bei denne ich seit ein paar Stunden sitze.

Habe da ein kleines Programm geschrieben welches mir sämtliche Ordner und Dateien ausgeben soll.
Als Fehlermeldung erhalte ich einen Nullpointer.

PHP:
public void build(String path) {
        dir = new File(path);
        File[] fileList = dir.listFiles();

        for (int i = 0; i <= fileList.length; i++) {

            System.out.println(fileList[i]);

            if (fileList[i].isDirectory()) {
                File f2 = new File(fileList[i].getPath(), fileList[i].toString());
                build(f2.toString());
                
                if (!fileList[i + 1].exists()) {
                    File f3 = new File(fileList[i].getParent());
                    build(f3.toString());
                }
                System.out.println(fileList[i]);
            }
            if (fileList[i].isFile()) {
                System.out.println(fileList[i]);
                
                if (!fileList[i + 1].exists()) {
                    File f3 = new File(fileList[i].getParent());
                    build(f3.toString());
                }
            }
        }
    }

Das erste system.out wird ausgegeben.
Danach sollte er in ein weiteres Archiv kommen in dem Bilddateien gespeichert sind.
Jedoch macht er dies nicht und ich komme einfach nicht drauf was daran falsch ist.
Ich habe ein bisshen durchprobiert und kann sagen, dass es mich nach
build(f2.toString());
bzw währenddessen rausschmeist.

Und in der API beschreibung steht:
File(String parent, String child)
Creates a new File instance from a parent pathname string and a child pathname string.
Sprich, sobald ich build() aufrufe sollte er eigentlich in den Unterordner gehen.
Oder habe ich das jetzt falsch verstanden und er geht mir jetzt nicht in den Unterordner, wenn nicht wie komme ich dort hin?

Ist der Rest wie ich ihn hier geschrieben habe korrekt?

!fileList[i + 1].exists()
stimmt doch das ich damit überprüfe ob ich noch ein File über habe, wenn nicht dann geht er halt wieder nen Ordner höher?

Desweiteren ist mir aufgefallen, dass wenn ich es so mache das Programm immer höher geht (bei f3).
Würde er beim Anfangspath aufhören wenn ich einen einfachen equals machen würde?


2.) In wie fern bringt es etwas wenn man eine Klasse nur durch das aufrufen einer Methode einer anderen Klasse instanzieren kann?

Sprich:
PointerInfo info = MouseInfo.getPointerInfo();
In wie fern ist das praktikabel?

3.) Wozu anonyme Klassen?
Ich sehe einfach keinen Nutzen davon oO
 
Schattenfänger schrieb:
Code:
!fileList[i + 1].exists()
stimmt doch das ich damit überprüfe ob ich noch ein File über habe, wenn nicht dann geht er halt wieder nen Ordner höher?
Aua! Davon bekommst du eine ArrayIndexOutOfBoundsException um die Ohren geschmissen. Wenn du am Ende der Liste angekommen bist, hast du keine Dateien mehr zum durchsuchen, so einfach ist das.

Code:
File f2 = new File(fileList[i].getPath(), fileList[i].toString());
toString() ist hier fehl am Platz:
toString()
Returns the pathname string of this abstract pathname.

Der Rest sieht nach Endlosschleife aus: Du durchläufst alle Dateien in einem Order, davon durchläufst du alle Unterordner und zusätzlich noch den übergeordneten Ordner. Letzterer durchläuft wieder die Unterordner (die du schon hattest) und wieder die entsprechenden übergeordneten Order. Kurz: Streich das.
 
Hallo Schattenfänger,

ich habe mich zwar bisher noch nicht wirklich mit der Klasse File beschäftigt, versuche aber trotzdem, dir so gut es geht auszuhelfen:

1) Bezüglich des Programms fällt mir spontan auf, dass du bei Zeile 5 bis "i <= fileList.length" läufst. Das kann so nicht funktionieren, weil du dann bei beispielsweise 5 Einträgen (die von 0 bis 4 durchnummeriert sind) versuchst, einen nicht existierenden 5. Eintrag aufzurufen.
Ein ähnliches Problem hast du in Zeile 22: Du fragst nach fileList[i+1].exists(), falls i aber bereits den letzten Index erreicht hat (in obigem Beispiel: 4), würdest du auf einen Eintrag eine Stelle weiter zugreifen... der nicht mehr existiert.
Ob .exist() und der Konstruktor von File das tun, was du denkst, kann ich dir leider nicht sagen...

2) Meistens ist dieses Vorgehen dann praktisch, wenn man nur eine begrenzte Anzahl von Instanzen einer Klasse erlauben will. Nimm beispielsweise eine Klasse, die alle für ein Programm wichtigen Daten speichert, da wäre es nicht sinnvoll, mehrere Instanzen zu erzeugen, sondern alle Methoden sollten die selben Daten zur Verfügung haben. Hast du einen öffentlichen Konstruktor, dann hält niemanden etwas davon ab, gleich mehrere Objekte zu erzeugen. Mit so einer indirekten Erzeugung kannst du beim ersten Aufruf ein Objekt erzeugen, das zurück geben, und im Anschluss bei jedem weiteren Aufruf das schon erzeugte Objekt zurück geben.
Ist natürlich eher dann wichtig, wenn auch andere Personen deine Klasse verwenden können oder du ausschließen willst, dass du selber versehentlich zu viele Objekte erzeugst...

3) Anonyme Klassen werden an Stellen eingesetzt, an denen man nicht extra eine eigene Klassen-Datei (oder eine lokale Klasse) erzeugen will, weil der Aufwand nicht lohnenswert ist. Ein schönes Beispiel hierfür sind Listener bei der GUI-Programmierung; dort muss man meistens nur 1-2 Methoden ausschreiben, die selten groß werden (oder nur andere Methoden aufrufen) und daher macht man es gern in einer kleinen, anonymen Klasse. Zudem würde es irgendwann unübersichtlich werden, wenn man viele verschiedene Listener hat, die alle irgendwo als eigene Klassen definiert wurden, im wesentlichen aus 5~10 Zeilen bestehen und die alle nur ein einziges mal gebraucht werden. Da müsste man auch erstmal suchen, welcher Listener denn wo eingebunden wird, wenn man sie nicht anonym am Einsatzort definiert...

Mfg, Train
 
XD
Du warst zu schnell :P

Habe das
fileList.toString()
gestrichen und schon ist er in den Unterordner gegangen.
Das seltsame ist nur das er die erste Datei nicht liest...
Sprich:
D:/ordner1/unterordner1 fängt er an
D:/ordner1/unterordner1/unterunterordner1 geht er rein und in diesem sind nur noch pics
jedoch sind im D:/ordner1/unterordner1 an erster Stelle eine Datei welche er seltsamerweise nicht ausgibt sondern er geht sofort in den unterunterordner....

Und wenn er dann ganz unten ist und alle dateien ausgegeben hat kommt ein indexoutofbounds ;)

Aua! Davon bekommst du eine ArrayIndexOutOfBoundsException um die Ohren geschmissen. Wenn du am Ende der Liste angekommen bist, hast du keine Dateien mehr zum durchsuchen, so einfach ist das.

Ich dachte wenn ich es so schreibe würde es valide sein?!
Schließlich frage ich ja ab ob eines mehr vorhanden ist, wenn nicht soll er hoch gehn....
Wie kann ich dann überprüfen ob ich am Ende angelangt bin?
Habe probiert durch equals und == length zu überprüfen ob er am Ende ist, aber das bringt das selbe ergebnis.

Der Rest sieht nach Endlosschleife aus: Du durchläufst alle Dateien in einem Order, davon durchläufst du alle Unterordner und zusätzlich noch den übergeordneten Ordner. Letzterer durchläuft wieder die Unterordner (die du schon hattest) und wieder die entsprechenden übergeordneten Order. Kurz: Streich das.

Jaa ok kommt mir auch so vor, wo denn rausgehen?
 
Schattenfänger schrieb:
D:/ordner1/unterordner1 fängt er an
D:/ordner1/unterordner1/unterunterordner1 geht er rein und in diesem sind nur noch pics
jedoch sind im D:/ordner1/unterordner1 an erster Stelle eine Datei welche er seltsamerweise nicht ausgibt sondern er geht sofort in den unterunterordner....
Ordner werden i.d.R. zu erst gelistet, also ist die Reihenfolge logisch. Wenn du das Ändern willst, musst du den Code entsprechend umschreiben.

Schattenfänger schrieb:
Und wenn er dann ganz unten ist und alle dateien ausgegeben hat kommt ein indexoutofbounds ;)
Es wurde ja schon genannt, warum das passiert und wie du es beheben kannst. Ein Zugriff außerhalb eines Arrays ist nie valide (zumindest in Java).

Schattenfänger schrieb:
Jaa ok kommt mir auch so vor, wo denn rausgehen?
Ganz einfach: Du übergibst am Anfang der Rekursion den Ordner, den du durchsuchen willst und deine Rekursion sucht dann nur die Unterorder ab, nie den Übergeordneten.
 
Habe jetzt mal das = weggemacht dadurch durchsucht er mir den ersten Ordner komplett dann geht er wieder hoch und gibt mir 2x die datei im ordner aus dann wieder zum root zurück, danach geht er in den unterordner2 und gibt alle dateien aus, wonach ein indexoutofbounds kommt....

Ganz einfach: Du übergibst am Anfang der Rekursion den Ordner, den du durchsuchen willst und deine Rekursion sucht dann nur die Unterorder ab, nie den Übergeordneten.

Ja, aber ich muss doch wieder hoch gehen.
Und das mache ich im Endeffekt mit f3, er schaut was mein übergeordneter Ordner ist und geht dann dort hin.

Achja genau, und ich brauch ja gar nicht überprüfen ob ich am Ende bin, hab ja eh eine Schleife. Einfach nachdem die ausgelaufen ist nochmal rekursiv aufrufen :P
Aber da bleibt trotzdem das Problem wie ich höher komme......
 
Inwiefern willst du denn höher kommen?
Wenn du nur zurück zur letzten Rekursion willst, das passiert automatisch, sobald eine Methode zu Ende ist.
Falls es dir um noch nicht erfasste Dateien geht, dann brauchst du das Programm nur zu Beginn auf der obersten Ebene zu starten, für alles weitere musst du dann nur tiefer in die Strukturen rein, aber niemals nach oben.
 
Vielleicht interssiert es dih ja es gibt in der JDK7 eine neue API das package is java.nio.
Kannst ja mal einen Blick drauf werfen.
 
Wie Train Iris schon sagt: einfach nur nach unten gehen, der Rest geht automatisch.

Streiche einfach einen Teil deines Codes. Folgendes sollte reichen:

Code:
public void build(String path) 
{
    dir = new File(path);
    File[] fileList = dir.listFiles();
     
    for (int i = 0; i < fileList.length; i++) {
        // print file or directory name 
        System.out.println(fileList[i]);
        // if subdir call recursive on path
        if (fileList[i].isDirectory()) {
             build(fileList[i].getPath());
    }
}


Grüsse,

Tolotos
 
Wenn wir schon beim posten von Komplettlösungen sind:
Code:
public void build(File dir) {
	File[] fileList = dir.listFiles();
	for (File file : fileList) { // iterate through all files
		// print file or directory name
		System.out.println(file);
		// if subdir call recursive on path
		if (file.isDirectory()) {
			build(file);
		}
	}
}

@Tolotos: In Zeile 3 und 10 (in Verbindung mit 12) sind bei dir Fehler.
 
Soo, gestern bin ich wohl etwas am Schlauch gestanden :evillol:
Hab nach meinem letzen Post den PC ausgemacht und mir das mal auf nem Blatt Papier aufgezeichnet und bin draufgekommen, dass ich ja eh nicht höher gehn muss, weil nachdem die for-schleife durchgelaufen ist endet die Methode eh und wenn dann ein höherer Ordner vorhanden ist geht er in den :P

2) Meistens ist dieses Vorgehen dann praktisch, wenn man nur eine begrenzte Anzahl von Instanzen einer Klasse erlauben will. Nimm beispielsweise eine Klasse, die alle für ein Programm wichtigen Daten speichert, da wäre es nicht sinnvoll, mehrere Instanzen zu erzeugen, sondern alle Methoden sollten die selben Daten zur Verfügung haben. Hast du einen öffentlichen Konstruktor, dann hält niemanden etwas davon ab, gleich mehrere Objekte zu erzeugen. Mit so einer indirekten Erzeugung kannst du beim ersten Aufruf ein Objekt erzeugen, das zurück geben, und im Anschluss bei jedem weiteren Aufruf das schon erzeugte Objekt zurück geben.
Ist natürlich eher dann wichtig, wenn auch andere Personen deine Klasse verwenden können oder du ausschließen willst, dass du selber versehentlich zu viele Objekte erzeugst...

Ich kann mir jetz da ehrlichgesagt nichts vorstellen...
Schließlich kann ich doch die Klasse instanzieren indem ich ne Fremdmethode aufrufe, was hindert mich daran auf diesem Weg 10x ein Objekt zu erzeugen?

@ober mir:
Ist zwar ganz nett, aber bringt mir nicht so viel wenn ich nicht selbst draufkomme :)

Hätte da dann aber doch noch was ;)

Ausgegeben wird es durch eine showData-Methode und die zu suchenden Terms kommen von einer Liste.
Im Moment besteht die Liste nur aus Video mime-types ala .mkv, avi und co.
PHP:
 public void build(String path) {
        dir = new File(path);
       
            File[] fileList = dir.listFiles();

            for (int i = 0; i < fileList.length; i++) {
                System.out.println(fileList[i]);
    if (fileList[i].exists() && fileList[i].canRead()) {
                if (fileList[i].isDirectory()) {
                    File f2 = new File(fileList[i].getPath());
                    build(f2.toString());
                } else {
                    if (fileList[i].isFile()) {
                       // if (isWantedFile(fileList[i], mimeTypeList())) {
                      //      dataList.add(fileList[i]);
                      //  }
                         if (searchByName(fileList[i], nameSearchList())) {
                           dataList.add(fileList[i]);
                         }
                    }
                }
            }
        }
    }

public boolean searchByName(File file, List nameList) {
        String fileName = file.getName().toLowerCase();

        //Iterator iter1 = nameList.iterator();
        
      //  while (iter1.hasNext()) {

            if (nameList.contains(fileName)) {
                return true;
            }
       // }
        return false;
    }

Wenn ich das so durchlaufen lasse bekomme ich bei C:\\ nachdem alle Daten ausgegeben wurden einen nullpointer wobei Netbeans auf
for (int i = 0; i < fileList.length; i++) {
verweist.
Meine gesuchten Daten werden nicht ausgegeben.
Wenn ich jedoch bei D:\\blubb durchlaufen lasse werden mir alle beinhaltenden Dateien/Ordner angezeigt und es kommt kein nullpointer, jedoch werden die gesuchten Daten nicht ausgegeben, auch wenn welche 100%ig im Ordner sind.
Er geht mir dann gar nicht erst in die showData Methode, obwohl eigentlich zumindest "Keine daten gefunden" Ausgabe kommen müsste.

Wenn ich statt der searchbyName die isWanted Methode verwende funktioniert diese, bei der gleichen showData Methode.
Das trifft aber auch nur auf D:\\blubb zu denn bei c:\\ werden alle daten ausgegeben und ich erhalte wieder einen nullpointer.

Stimmt den contains?
Jetzt sind die gesuchten namen ja noch die mime types aber ich möchte ja nach richtigen namen suchen, welche auch mittendrin sein können.
 
Wegen 2.:
Zuerst verhinderst du das Instanzieren per new-Operator mit einem privaten Konstruktor, sodass man nur noch mit der getInstance-Methode eine Ausgabe bekommt. Diese könnte dann, wenn man etwa nur eine Instanz erlauben will, so aussehen:

Code:
public class Blubb{
  private static Blubb myBlubb; //speichert die einzelne Instanz ab

  private Blubb(){} //privater Konstruktor

  public static Blubb getBlubbInstance(){
    if(myBlubb == null){
      myBlubb = new Blubb();
    }
    return myBlubb;
  }
}
Kann sein, dass noch ein Fehler in dem Code ist, habs nicht getestet :)
Siehe auch hier: Singleton-Pattern


Zu deinem Programm muss ich sagen, dass ich deine Beschreibung ziemlich schwer verstehen kann, vielleicht solltest du mal alle relevanten Methoden anfügen?
 
Zuletzt bearbeitet:
Schattenfänger schrieb:
Stimmt den contains?
contains stimmt aber du rufst diese auf dem falschen Objekt auf bzw. übergibst das falsche Objekt. Wenn du contains auf der Liste aufrufst, suchst diese nach dem Vorkommen eines Dateinamens, den du übergibst. Dieser ist natürlich nicht darin enthalten. Du kannst also entweder über die Liste iterieren und contains(filename) auf jedem Element aufrufen oder du rufst auf der Liste contains(dateiendung) auf. Edit: endsWith() wäre in diesem Fall die bessere Wahl.

Schattenfänger schrieb:
Wenn ich das so durchlaufen lasse bekomme ich bei C:\\ nachdem alle Daten ausgegeben wurden einen nullpointer wobei Netbeans auf verweist.
Afaik kann listFiles auch mal null zurück liefern, dass musst du abfangen. Dabei handelt es sich entweder um eine Datei oder du hast keine Rechte den Ordnerinhalt anzuzeigen.

Wegen letzterem solltest du auch canRead() nicht benutzten, da dies auch mal false liefern kann, obwohl der Ordner lesbar ist. Wenn man es ganz richtig machen will, solltest du auch exists() hier nicht nutzen, Stichwort "Race Condition". try-catch ist dir bekannt?
 
Zuletzt bearbeitet:
Train Iris schrieb:
2) Meistens ist dieses Vorgehen dann praktisch, wenn man nur eine begrenzte Anzahl von Instanzen einer Klasse erlauben will. Nimm beispielsweise eine Klasse, die alle für ein Programm wichtigen Daten speichert, da wäre es nicht sinnvoll, mehrere Instanzen zu erzeugen, sondern alle Methoden sollten die selben Daten zur Verfügung haben. Hast du einen öffentlichen Konstruktor, dann hält niemanden etwas davon ab, gleich mehrere Objekte zu erzeugen. Mit so einer indirekten Erzeugung kannst du beim ersten Aufruf ein Objekt erzeugen, das zurück geben, und im Anschluss bei jedem weiteren Aufruf das schon erzeugte Objekt zurück geben.
Ist natürlich eher dann wichtig, wenn auch andere Personen deine Klasse verwenden können oder du ausschließen willst, dass du selber versehentlich zu viele Objekte erzeugst...

Singletons sind die schlechteste Idee seit der Erfindung des Transistors. Wenn du von einer Klasse nur eine Instanz möchtest, dann lege halt nur eine Instanz an, anstatt einen Mechanismus einzuführen, der dir Null Gewinn bringt, dafür aber jeden Ansatz von klar überdachtem Design im Keim erstickt.
 
Singletons haben aber auch den Vorteil, dass du von überall im Code auf das Objekt über die statische Methode getInstance() zugreifen kannst, ohne überall eine Referenz auf das Objekt zu speichern oder zu übergeben.
 
Darlis schrieb:
Singletons haben aber auch den Vorteil, dass du von überall im Code auf das Objekt über die statische Methode getInstance() zugreifen kannst, ohne überall eine Referenz auf das Objekt zu speichern oder zu übergeben.

Das Problem an der Sache ist nur, daß dieser "Vorteil" ein Nachteil ist, da er Abhängigkeiten zu externen Komponenten tief in der Implementierung vergräbt, anstatt sie explizit in der Schnittstelle aufzuzeigen.

Sonst könnte ich auch genauso gut behaupten, der Vorteil an globalen Variablen ist, daß man von überall her ohne viel lästiges Nachdenken und Drumherum auf sie zugreifen kann.
 
Die Singleton-Diskussion hatte ich hier ja auch schon einmal angestoßen. Vielleicht leistet der alte Thread hier etwas Überzeugungsarbeit. ;)
 
@antred: Ich habe zunächst einmal nie gesagt, dass das ein gutes Entwurfsmuster sei; die Frage war lediglich, wann und wo man Methoden anstelle des new-Operators nutzt, um Objekte zu erzeugen.

Allerdings denke ich, dass sich mit diesem Entwurfsmuster "richtiges Verhalten" erzwingen lässt, anstatt es nur innerhalb von etwa einer Klassenbeschreibung zu fordern. Aus dem selben Grund benutzt man immerhin auch private-Variablen und -Methoden, damit niemand von außen Unsinn machen kann.

Btw, persönlich habe ich das selber auch noch nicht angewendet; das liegt aber mehr daran, dass ich noch keinen passenden Anwendungsfall hatte.
 
Zuletzt bearbeitet:
Train Iris schrieb:
@antred: Ich habe zunächst einmal nie gesagt, dass das ein gutes Entwurfsmuster sei; die Frage war lediglich, wann und wo man Methoden anstelle des new-Operators nutzt, um Objekte zu erzeugen.


Ich habe auch nie behauptet, du hättest das behauptet. ;) Aber ... wenn man jemandem, der Programmieren lernt, erklärt was Singletons sind und ihm nicht im gleichen Atemzug empfiehlt, sich von Singletons fernzuhalten, empfinde ich das als grob fahrlässig. ;)
 
Zurück
Oben