Begründung dafür, dass Matcher IllegalStateException austriggers, wenn keine “Matching” -Methode aufgerufen wird

TL; DR

Was sind die Designentscheidungen hinter der API von Matcher ?

Hintergrund

Matcher hat ein Verhalten, das ich nicht erwartet habe und für das ich keinen guten Grund finde. Die API-Dokumentation sagt:

Einmal erstellt, kann ein Matcher verwendet werden, um drei verschiedene Arten von Übereinstimmungsoperationen auszuführen: […] Jede dieser Methoden gibt einen booleschen Wert zurück, der Erfolg oder Fehlschlag anzeigt. Weitere Informationen über eine erfolgreiche Übereinstimmung erhalten Sie, indem Sie den Status des Matchers abfragen.

Was die API-Dokumentation weiter sagt, ist:

Der explizite Status eines Matcher ist zunächst undefiniert. Wenn Sie versuchen, einen Teil davon vor einer erfolgreichen Übereinstimmung abzufragen, wird eine IllegalStateException ausgetriggers.

Beispiel

 String s = "foo=23,bar=42"; Pattern p = Pattern.compile("foo=(?[0-9]*),bar=(?[0-9]*)"); Matcher matcher = p.matcher(s); System.out.println(matcher.group("foo")); // (1) System.out.println(matcher.group("bar")); 

Dieser Code triggers einen aus

 java.lang.IllegalStateException: No match found 

um (1) . Um dies zu Matcher , müssen Sie matches() oder andere Methoden Matcher , die den Matcher in einen Zustand bringen, der group() erlaubt. Die folgenden Arbeiten:

 String s = "foo=23,bar=42"; Pattern p = Pattern.compile("foo=(?[0-9]*),bar=(?[0-9]*)"); Matcher matcher = p.matcher(s); matcher.matches(); // (2) System.out.println(matcher.group("foo")); System.out.println(matcher.group("bar")); 

Das Hinzufügen des Aufrufs zu matches() bei (2) den Matcher in den richtigen Zustand, um group() aufzurufen.

Frage, wahrscheinlich nicht konstruktiv

Warum ist diese API so gestaltet? Warum nicht automatisch übereinstimmen, wenn der Matcher mit Patter.matcher(String) ?

   

Eigentlich hast du die Dokumentation falsch verstanden. Werfen Sie einen zweiten Blick auf die Aussage, die Sie zitiert haben: –

Wenn Sie versuchen, einen Teil davon vor einer erfolgreichen Übereinstimmung abzufragen, wird eine IllegalStateException ausgetriggers.

Ein Matcher kann IllegalStateException beim Zugriff auf matcher.group() wenn keine Übereinstimmung gefunden wurde.

Also, Sie müssen folgenden Test verwenden, um den Abgleichsprozess tatsächlich zu initiieren:

  - matcher.matches() //Or - matcher.find() 

Der folgende Code: –

 Matcher matcher = pattern.matcher(); 

matcher nur eine matcher Instanz. Dies entspricht einer Zeichenfolge nicht. Selbst wenn es ein erfolgreiches Spiel gab. Sie müssen also die folgende Bedingung überprüfen, um nach erfolgreichen Übereinstimmungen zu suchen:

 if (matcher.matches()) { // Then use `matcher.group()` } 

Und wenn die Bedingung in if false zurückgibt, bedeutet das, dass nichts gefunden wurde. Wenn Sie also matcher.group() ohne diese Bedingung zu überprüfen, erhalten Sie IllegalStateException wenn die Übereinstimmung nicht gefunden wurde.


Angenommen, Matcher wurde so entwickelt, wie Sie es nennen, dann müssten Sie einen null Check durchführen, um zu prüfen, ob eine Übereinstimmung gefunden wurde oder nicht, um matcher.group() wie matcher.group() : –

Die Art und Weise, wie Sie denken, hätte getan werden müssen:

 // Suppose this returned the matched string Matcher matcher = pattern.matcher(s); // Need to check whether there was actually a match if (matcher != null) { // Prints only the first match System.out.println(matcher.group()); } 

Aber was ist, wenn Sie weitere Matches drucken möchten, da ein Muster mehrfach in einem String abgeglichen werden kann, sollte es eine Möglichkeit geben, dem Matcher zu sagen, das nächste Match zu finden. Aber der null Check würde das nicht können. Dafür müsstest du deinen Matcher nach vorne bewegen, um ihn dem nächsten String anzupassen. Daher gibt es verschiedene Methoden, die in der Matcher class definiert Matcher , um den Zweck zu erfüllen. Die Methode ” matcher.find() ” stimmt mit der matcher.find() überein, bis alle Übereinstimmungen gefunden wurden.

Es gibt auch andere Methoden, die die Zeichenfolge auf eine andere Art und Weise anpassen, die von Ihnen abhängt, wie Sie übereinstimmen möchten. Also ist es letztendlich am Matcher , den matching gegen die Saite zu machen. Pattern pattern erstellt nur ein pattern mit dem es übereinstimmt. Wenn das Pattern.matcher() mit dem Muster match , dann muss es eine Möglichkeit geben, verschiedene Arten der match zu definieren, da die matching unterschiedlich sein kann. Also, da kommt die Matcher class.

Also, so wie es ist:

 Matcher matcher = pattern.matcher(s); // Finds all the matches until found by moving the `matcher` forward while(matcher.find()) { System.out.println(matcher.group()); } 

Wenn also 4 Übereinstimmungen in der Zeichenfolge gefunden werden, wird beim ersten Weg nur der erste gedruckt, während beim zweiten Weg alle Treffer gedruckt werden, indem der matcher vorwärts bewegt wird, um mit dem nächsten Muster matcher .

Ich hoffe das macht es klar.

Die Dokumentation der Matcher class beschreibt die Verwendung der drei Methoden, die es bietet:

Ein Matcher wird aus einem Muster erstellt, indem die Matcher-Methode des Musters aufgerufen wird. Einmal erstellt, kann ein Matcher verwendet werden, um drei verschiedene Arten von Match-Operationen durchzuführen:

  • Die Übereinstimmungsmethode versucht, die gesamte Eingabesequenz mit dem Muster abzugleichen.

  • Die lookingAt-Methode versucht, die Eingangssequenz beginnend am Anfang mit dem Muster abzugleichen.

  • Die Suchmethode durchsucht die Eingabesequenz nach der nächsten Teilsequenz, die mit dem Muster übereinstimmt.

Leider konnte ich keine anderen offiziellen Quellen finden, die explizit auf Warum und Wie dieses Problems hinweisen.

Meine Antwort ist der von Rohit Jain sehr ähnlich, enthält aber auch einige Gründe, warum der “extra” Schritt notwendig ist.

Implementierung von java.util.regex

Die Linie:

 Pattern p = Pattern.compile("foo=(?[0-9]*),bar=(?[0-9]*)"); 

bewirkt, dass ein neues Musterobjekt zugewiesen wird, und speichert intern eine Struktur, die die RE – Information darstellt, wie eine Auswahl von Zeichen, Gruppen, Sequenzen, gierig gegenüber nicht gierig, Wiederholungen und so weiter.

Dieses Muster ist zustandslos und unveränderlich, so dass es wiederverwendet werden kann, multi-tauglich ist und sich gut optimiert.

Die Linien:

 String s = "foo=23,bar=42"; Matcher matcher = p.matcher(s); 

gibt ein neues Matcher Objekt für das Pattern und den String – einen, der den String noch nicht gelesen hat. Matcher ist wirklich nur der Zustand einer Zustandsmaschine, wo die Zustandsmaschine das Pattern .

Der Abgleich kann ausgeführt werden, indem die Statusmaschine über den Matching-process mithilfe der folgenden API schrittweise ausgeführt wird:

  • lookingAt() : Versucht, die Eingangssequenz beginnend am Anfang mit dem Muster lookingAt()
  • find() : Scannt die Eingabesequenz nach der nächsten Teilsequenz, die mit dem Muster übereinstimmt.

In beiden Fällen kann der Zwischenzustand mit den Methoden start() , end() und group() gelesen werden.

Vorteile dieses Ansatzes

Warum sollte jemand Schritt für Schritt durch das Parsing gehen?

  1. Holen Sie Werte von Gruppen mit einer Quantifizierung größer als 1 (dh Gruppen, die sich wiederholen und am Ende mehr als einmal übereinstimmen). Zum Beispiel im folgenden TR-RE, das Variablenzuweisungen analysiert:

     Pattern p = new Pattern("([az]=([0-9]+);)+"); Matcher m = p.matcher("a=1;b=2;x=3;"); m.matches(); System.out.println(m.group(2)); // Only matches value for x ('3') - not the other values 

    Siehe den Abschnitt “Gruppenname” in “Gruppen und Erfassen” des JavaDoc auf Muster

  2. Der Entwickler kann den RE als Lexer verwenden und der Entwickler kann die lexierten Token an einen Parser binden. In der Praxis würde dies für einfache Domänensprachen funktionieren, aber reguläre Ausdrücke sind wahrscheinlich nicht der richtige Weg für eine vollständige Computersprache. BEARBEITEN Das hängt teilweise mit dem vorherigen Grund zusammen, aber es kann häufig einfacher und effizienter sein, den Syntaxbaum zu erstellen, der den Text verarbeitet, als zuerst alle Eingaben zu lexieren.
  3. (Für Mutige) können Sie REs debuggen und herausfinden, welche Subsequenz nicht übereinstimmt (oder fälschlicherweise übereinstimmt).

In den meisten Fällen müssen Sie den Zustandsautomaten jedoch nicht durch den Abgleich verschieben, daher gibt es eine bequeme Methode ( matches ), die das Musterabgleich zum Abschluss führt.

Wenn ein Matcher die Eingabezeichenfolge automatisch abgleicht, wäre das eine Verschwendung, falls Sie das Muster finden möchten.

Ein Matcher kann verwendet werden, um zu überprüfen, ob das Muster matches() der Eingabezeichenfolge matches() , und es kann verwendet werden, find() das Muster in der Eingabezeichenfolge zu finden (auch wiederholt, um alle übereinstimmenden Teilzeichenfolgen zu finden). Bis Sie eine dieser beiden Methoden aufrufen, weiß der Matcher nicht, welchen Test Sie durchführen möchten. Daher können Ihnen keine übereinstimmenden Gruppen zugeordnet werden. Selbst wenn Sie eine dieser Methoden aufrufen, kann der Aufruf fehlschlagen – das Muster wird nicht gefunden – und in diesem Fall muss auch ein Aufruf der group fehlschlagen.

Dies wird erwartet und dokumentiert.

Der Grund dafür ist, dass .matches() einen booleschen .matches() zurückgibt, der angibt, ob eine Übereinstimmung vorhanden war. Wenn es eine Übereinstimmung gab, können Sie .group(...) sinnvoll aufrufen. Andernfalls, wenn es keine Übereinstimmung gibt, macht ein Aufruf von .group(...) keinen Sinn. Daher sollte es Ihnen nicht erlaubt sein, .group(...) aufzurufen, bevor Sie matches() aufrufen.

Der richtige Weg, einen Matcher zu verwenden, ist ungefähr wie folgt:

 Matcher m = p.matcher(s); if (m.matches()) { ...println(matcher.group("foo")); ... } 

Meine Vermutung ist, dass die Designentscheidung auf Abfragen mit einer klaren, gut definierten Semantik basierte, die die Existenz nicht mit Übereinstimmungseigenschaften in Verbindung brachte.

Bedenken Sie Folgendes: Was würden Sie von Matcher-Abfragen erwarten, wenn der Matcher nicht erfolgreich etwas gefunden hat?

Betrachten wir zuerst group() . Wenn wir etwas nicht gefunden haben, sollte Matcher die leere Zeichenfolge nicht zurückgeben, da sie nicht mit der leeren Zeichenfolge übereinstimmt. Wir könnten an diesem Punkt null .

Ok, jetzt betrachten wir start() und end() . Jede Rückkehr int . Welcher int Wert wäre in diesem Fall gültig? Sicherlich keine positive Zahl. Welche negative Zahl wäre angemessen? -1?

Angesichts all dessen muss ein Benutzer immer noch die Rückgabewerte für jede Abfrage überprüfen, um zu überprüfen, ob eine Übereinstimmung aufgetreten ist oder nicht. Alternativ können Sie überprüfen, ob es erfolgreich direkt übereinstimmt, und bei Erfolg haben die Abfragesemantik alle eine wohldefinierte Bedeutung. Wenn nicht, erhält der Benutzer konsistentes Verhalten, unabhängig davon, welcher angular abgefragt wird.

Ich gebe zu, dass die erneute Verwendung von IllegalStateException möglicherweise nicht zur besten Beschreibung der Fehlerbedingung geführt hat. Wenn wir jedoch IllegalStateException in NoSuccessfulMatchException umbenennen / IllegalStateException NoSuccessfulMatchException , sollte man sich IllegalStateException im NoSuccessfulMatchException sein können, wie das aktuelle Design die NoSuccessfulMatchException erzwingt und den Benutzer dazu ermutigt, Abfragen zu verwenden, deren Semantik zum Zeitpunkt der Anfrage bekannt ist.

TL; DR : Was ist der Wert der Frage nach der spezifischen Todesursache eines lebenden Organismus?

Sie müssen den Rückgabewert von matcher.matches() überprüfen. Es wird true wenn eine Übereinstimmung gefunden wurde, sonst false .

 if (matcher.matches()) { System.out.println(matcher.group("foo")); System.out.println(matcher.group("bar")); } 

Wenn matcher.matches() keine Übereinstimmung findet und Sie matcher.group(...) aufrufen, erhalten Sie immer noch eine IllegalStateException . Genau das sagt die Dokumentation:

Der explizite Status eines Matcher ist zunächst undefiniert. Wenn Sie versuchen, einen Teil davon vor einer erfolgreichen Übereinstimmung abzufragen, wird eine IllegalStateException ausgetriggers.

Wenn ” matcher.match() false zurückgibt, wurde keine erfolgreiche Übereinstimmung gefunden und es ist nicht sinnvoll, Informationen über die Übereinstimmung zu erhalten, indem Sie beispielsweise ” group() aufrufen.