Java Switch...Case Alternativen?

Vulpecula

Commander
Registriert
Nov. 2007
Beiträge
2.241
Moin moin!

Ich beschäftige mich momentan damit, Daten aus einer ASCII-Datei zu parsen. Ein Bestandteil dessen ist, dass ich prüfen möchte, ob bestimmte "Blöcke" geschlossen sind. Sieht in der Datei ungefähr so aus:

Code:
objektStart
    	aussenStart
        		innenStart
            Daten A1 A2 A3
            Daten B1 B2 B3
            Daten C1 C2 C3
        		innenEnde
    	aussenEnde
    	aussenStart
        		innenStart
            Daten A1 A2 A3
            Daten B1 B2 B3
            Daten C1 C2 C3
        		innenEnde
    	aussenEnde
    ...usw...
objektEnde

Die Datei wird zunächst mittels Scanner eingelesen und jedes "Wort" in einer ArrayList abgelegt, die dann an den Parser übergeben wird. Als "Werkzeug" möchte ich einen Stack benutzen, um zu kontrollieren, ob alle "Blöcke" richtig geschlossen sind. Hier mal der Code:

Code:
    private static boolean areAllBlocksClosed(String part[])
    {
        List<String> stack = new ArrayList<>();
        String parts[];

        for (int i = 0; i < part.length; i++)
        {
            parts = part[i].trim().split(" ");
            switch (parts[0].toLowerCase())
            {
                case "objektStart":
                    stack.add("objektStart");
                    break;
                    
                case "aussenStart":
                    stack.add("aussenStart");
                    break;

                case "innenStart":
                    stack.add("innenStart");
                    break;

                case "Daten":
                    if (!stack.get(stack.size() - 1).equals("innenStart"))
                    {
                        return false;
                    }
                    break;

                case "innenEnde":
                    if (!stack.get(stack.size() - 1).equals("innenStart"))
                    {
                        return false;
                    }
                    else
                    {
                        stack.remove(stack.size() - 1);
                    }
                    break;

                case "aussenEnde":
                    if (!stack.get(stack.size() - 1).equals("aussenStart"))
                    {
                        return false;
                    }
                    else
                    {
                        stack.remove(stack.size() - 1);
                    }
                    break;

                case "objektEnde":;
                    if (!stack.get(stack.size() - 1).equals("objektStart"))
                    {
                        return false;
                    } else
                    {
                        stack.remove(stack.size() - 1);
                    }
                    break;

                default:
                    return false;
                }
        }
        return stack.isEmpty();
    }


Es funktioniert soweit, nur frage ich mich, ob man das Ganze auch etwas eleganter - ohne Switch...Case - umsetzen könnte. Das einzige, was mir auf Anhieb einfällt, wäre ein riesen haufen If-Statements... :freak:

Vielleicht hat ja jemand ein paar Hinweise für mich.

MfG - Vulpecula
 
Habe es eben mal aus dem Stehgreif bissle optimiert- so hoffe ich. ;)

Zu deiner Frage. Manchmal ist switch-Case auch eine Option, wenn es um eine Unterscheidung gilt. An sich ist dein Code in Ordnung, nur es könnte "sauberer" sein, d.h. Auslagerung von Vorgängen.

PHP:
	private static boolean areAllBlocksClosed(String part[])
    {
        List<String> stack = new ArrayList<>();
        String parts[];
 
        for (int i = 0; i < part.length; i++)
        {
			parts = part[i].trim().split(" ");
			// Anmerkung: lowercase aber als Bedingungen mit groß und kleinschreibung?
            switch (parts[0].toLowerCase())
            {
                case "objektStart":
                case "aussenStart":
                case "innenStart":
                    addStatement(parts[0]);
                    break;
                case "Daten":
                case "innenEnde":
                case "aussenEnde":
                case "objektEnde":
                    return irgendwas(stack.get(stack.size() - 1))
                default:
                    return false;
                }
        }
        return stack.isEmpty();
    }
	
	private void addStatement(final String statement) {
		stack.add(statement);
	}
	
	private boolean irgendwas(final String position) {
		switch(position) {
			case "innenstart":
			case "aussenStart":
			case "objektStart":
				stack.remove(stack.size() - 1);
				return true;
			default: return false;
	}
 
Zuletzt bearbeitet:
Danke erstmal für den Input.

@capilano: Sorry für die Verwirrung mit dem .toLowerCase() :)
Ich habe das .toLowerCase() in den Part des Auslesens ausgelagert und nur vergessen, das zu löschen. Dazu kommt, dass ich gar keinen echten Stack verwende, sondern eine ArrayList... :freak: Muss auch noch geändert werden. Aber Dein Ansatz sieht schonmal viel sauberer aus und lässt sich besser verstehen.

@capilano: Ich gehe den Code gerade durch und mir fällt auf, dass eine Bedingung fehlt und er deswegen immer FALSE rausschmeißen wird ;)
 
Zuletzt bearbeitet:
@antred: Das Video find ich zwar recht interessant, aber ich komme nicht umher zu sehen, dass er in dem Beispiel eine einzige (relativ übersichtlichen) 10 Zeilen Funktion in ein Konstrukt mit 5 Objekten und 3 Funktionen überführt hat.
Irgendwie fällt es mir schwer, das wirklich als Verbesserung anzusehen. Bei einem realistischeren Beispiel, mit mehr (und komplexeren) Kommandos sähe es allerdings vielleicht schon wieder anders aus.
 
Zuletzt bearbeitet:
Ich habe noch ein kleines Problem mit IF-Statements... Und zwar folgender Code dazu:

Code:
for(int i=0; i<fileContent.size(); i++)
        {
            if(fileContent.get(i)==OBJEKT) {
                System.out.println("Objektanfang erkannt!");
            } 
            if(fileContent.get(i)==AUSSEN) {
                System.out.println("Äußere Schleife erkannt!");
            } 
            if(fileContent.get(i)==INNEN) {
                System.out.println("Innere Schleife erkannt!");
            } 
            if(fileContent.get(i)==ENDEINNEN) {
                System.out.println("Ende der inneren Schleife!");
            } 
            if(fileContent.get(i)==ENDEAUSSEN) {
                System.out.println("Ende der äußeren Schleife!");
            } 
            if(fileContent.get(i)==ENDEOBJEKT) {
                System.out.println("Ende des Objektes!");
            } 
        }

"fileContent.get(i)" liefert mir definitiv einen String zurück, aber die IF-Statements bleiben unangetastet bzw. werden nicht ausgeführt. Ich frage mich, woran das liegen kann... "fileContent" ist übrigens eine ArrayList<String>. Selbst, wenn ich die Liste zu einem String-Array (String[]) mache, hilft es nicht. Keine Ahnung warum, aber ich seh das Problem gerade nicht.

P.S.: OBJEKT, AUSSEN, INNEN, ENDEINNEN, ENDEAUSSEN und ENDEOBJEKT sind Strings, die ich als Konstanten definiert habe (private static final String OBJEKT = "objekt"). Damit bin ich flexibler, falls sich die Argumente in der ASCII-Datei evtl. mal ändern sollten.
 
Zuletzt bearbeitet:
Strings müssen mit equals und nicht mit == verglichen werden.

Davon abgesehen, weis ich nicht warum du ein neues Format entwirfst, wenn es bereits einige existierende gibt, für die es auch fertige parser gibt.
z.B.: XML, JSON, YAML, .....
Ansonsten, lässt sich das Problem mit OOP konstrukten lösen. Da müsste ich jetzt aber das Ganze Beispiel kennen um einen sinnvollen Vorschlag geben zu können.
 
Zuletzt bearbeitet:
Moin! Ist mir gerade wie Schuppen von den Augen gefallen... War einfach zu spät gestern. :( Aber trotzdem Danke für die Antwort. :daumen:
 
@Vulpecula, wie ich geschrieben hatte, war kurzerhand aus den Stehgreif. ;)

Das was das Video zeigt, kann man machen, aber Frage ist, ob es sich lohnt. Nach meiner Ansicht nicht.
Was noch möglich ist, die einzelnen Elemente als Objekte ablegen und somit eine instanceOf versuchen, welche auf weitere Anweisung verweist.. Aber spontan würde ich sagen, switch-Case ist die Wahl hier.
Ich muss auch gestehen, das was im Video passiert, ist mir in der Praxis auch nie unterkommen...
 
Ich frage mich, warum ich solche Schwierigkeiten habe, die Statements auf Abgeschlossenheit zu prüfen. Wenn die Datei der Konformität entspricht geht alles glatt, aber nicht alle der Tests mit fehlenden Statements werden verlaufen mit dem richtigen Ergebnis. :mad:


Edit:

Ich habe jetzt mal einen ganz anderen Ansatz verfolgt und das Ganze aus einer Kombination von States (DEA-like...) und switch...cases gemacht:

Code:
        int state = 0;
        
        for(int i=0; i<fileContent.size(); i++)
        {
            
            switch (fileContent.get(i))
            {
                case objektStart:
                case aussenStart:
                case innenStart:
                case innenEnde:
                    state += 1;
                    break;
                case aussenEnde:
                    state -= 3;
                    break;
                case aussenEnde:
                    state -= 1;
                    break;
            default:
                break;
            }
        }
        if(state==0) {
            return true;
        }
        return false;

Bisher hat die Prüfung keine meiner präparieten, inkonsistenten Dateien druchgelassen. :D:daumen:
 
Zuletzt bearbeitet:
Irgendwie ergibt das für mich nicht den geringsten Sinn. Ich glaube da sind einige Copy-Past-Fehler dabei.
 
Hm... Ich könnte versuchen, Dir zu erklären warum es so funktioniert, aber einfacher ist es, wenn du Dir das selbst aufzeichnest und nachverfolgst. Im Prinzip wird hier je nach CASE ein anderer Zustand erreicht. Nur, wenn am Ende der Datei der Ausgangszustand (0) erreicht ist, sind alle Statements in der Datei geschlossen.
 
Das stimmt doch nie im Leben

- aussenEnde ist doppelt
- bei innen Ende addierst du
- objektEnde gibts gar nicht
 
Ließ dir bitte nochmal deinen geposteten Code durch:
Du
  • verwendest zweimal das gleiche Label (aussenEnde)
  • ignorierst objektEnde völlig
  • führst für innenStart und innenEnde die gleiche Aktion durch ( state += 1; )
  • überprüfst nicht die Reihenfolge der tags
Und ob
Code:
state -= 3;
wirklich so gedacht ist sei mal dahingestellt.

Wenn das dein echter Code ist dürfte er noch nicht einmal compilieren, geschweige denn korrupten Files erkennen.
 
Sorry, mein (Copy-Paste-)Fehler. Der gepostete Code war tatsächlich nicht richtig. Ich war gezwungen noch mehr einzudeutschen (eigentlich benenne ich alles auf Englisch) und dabei sind ein paar Fehler passiert. Hier mal der korrigierte Code, der so bei mir auch kompiliert und jetzt sämtliche erdenklichen Fehler abgefangen hat.

Code:
private static boolean alleStatementsGeschlossen(ArrayList<String> dateiInhalt) {
        int state = 0;
        for(int i=0; i<dateiInhalt.size(); i++)
        {
            switch (dateiInhalt.get(i))
            {
                case objektStart:
                case aussenStart:
                case innenStart:
                case innenEnde:
                    state += 1;
                    break;
                case aussenEnde:
                    state -= 3;
                    break;
                case objektEnde:
                    state -= 1;
                    break;
            default:
                break;
            }
        }
        if(state==0) {
            return true;
        }
        return false;
    }


Hier mal eine schnelle Zeichnung dazu, die das ganze verdeutlicht:

zeichnung1w9uo9.png
 
Zuletzt bearbeitet:
Funktioniert immer noch nicht. Überleg dir mal, was bei folgender Abfolge passiert:
- objektStart
- aussenStrart
- innenStart
- aussenEnde

Das ist offensichtlich keine gültige Folge von Tags, aber es würde trotzdem akzeptiert werden. Wenn du mit Zustandsautomaten arbeiten willst, dann musst du auch immer überprüfen ob der Übergang im derzeitigen Zustand überhaupt erlaubt ist und nicht einfach nur stur Werte addieren und subtrahieren.

Übrigens spricht nichts dagegen hier englischen code zu posten. Wenn dir jemand bei deinem Code helfen kann, dann kann er zu 99% auch gut genug English um englische Variablennamen zu verstehen.
 
Habe es gerade mit Deiner Reihenfolge versucht, aber selbst da geh ich mir FALSE aus der Methode raus, folglich macht die Methode doch alles richtig? :(

Edit: Natürlich ist es kein richtiger Automat, sondern nur etwas, das entfernt daran angelehnt ist. Ziel ist hier, mit einem Wert von 0 aus dem Switch...Case rauszukommen, was bisher immer geklappt hat.
 
Zuletzt bearbeitet:
Also entweder ist das immer noch nicht der richtige Code, oder ich steh auf dem Schlauch:
- objektStart (+1)
- aussenStrart (+1)
- innenStart (+1)
- aussenEnde (-3)
Ergibt bei mir 0 und müsste daher akzeptiert werden
 
Das fängt ja auch sonst nicht alle Fehler ab.

Wieso addiert innenEnde dazu? müsste ja auch subtrahieren, es schliesst ja.

Auch sonst hats noch viel das nicht geht.

innenStart
objektEnde

wäre ja z.b. OK. Rein mit addieren ist das mMn auch gar nicht sinnvoll umzusetzen, da du so die Statusübergänge nicht korrekt prüfen kannst.
 
Die bekannten Tags kannst übrigens auch in eine Liste packen. Z.b eine Liste mit Array[2] worin dann jeweils zusammengehörige start und end tags drin stehen. Laufzeit kannst optimieren indem die häufigen Tags vorne in der Liste stehen, oder du sowas wie einen Cache baust.

Zählen musst beim Stack auch nix, Variablen sind überflüssig. Hast du ja an sich korrekt angefangen.
Java hat übrigens eine Stack Klasse, da gibt es push, pop und peek was du oben recht umständlich selbst machst :) Ist ja auch nur eine Liste mit speziellen Operationen...
http://docs.oracle.com/javase/7/docs/api/java/util/Stack.html

Du könntest auch den Stack erst aufbauen mit Start und End, dann müssen immer oberstes und unterstes tag zusammenpassen, ansonsten syntax error :) Wenn du die abgebauten Tags zählst, kannst dann auch sagen wo genau der Error liegt.

Noch eine Alternative: Compiler Style aka Bäume :)
Noch welche wären DOM (XML style) oder JDOM

@an die Zählervariablen fans
Du gehst ja von oben nach unten die Tags durch, wenn du den Text auslässt und nur Starttags auf den Stack einfügst, dann kannst es entsprechend rückwärts auslesen. Immer wenn du ein Enttag triffst, muss das oberste auf dem Stack der passende Starttag sein. Stack pop, nächstes endtag suchen usw.

Der Sinn vom Stack ist ja das man nix zählen muss, sondern die Sortierung ausnutzt ;)



/edit
Bedenke: Lesbarkeit == Easy Wartung == billiger == Besser als schneller kryptischer Code
 
Zuletzt bearbeitet:

Ähnliche Themen

W
  • Gesperrt
  • Frage
2 3
Antworten
45
Aufrufe
2.363
Antworten
10
Aufrufe
747
Zurück
Oben