Hier ist also die Situation. Ich möchte eine Fallklasse wie folgt definieren:
case class A(val s: String)
und ich möchte ein Objekt definieren, um sicherzustellen, dass beim Erstellen von Instanzen der Klasse der Wert für 's' immer in Großbuchstaben geschrieben wird, wie folgt:
object A {
def apply(s: String) = new A(s.toUpperCase)
}
Dies funktioniert jedoch nicht, da Scala sich darüber beschwert, dass die Methode apply (s: String) zweimal definiert ist. Ich verstehe, dass die Syntax der Fallklasse sie automatisch für mich definiert, aber gibt es keine andere Möglichkeit, dies zu erreichen? Ich möchte bei der Fallklasse bleiben, da ich sie für den Mustervergleich verwenden möchte.
scala
pattern-matching
case-class
John S.
quelle
quelle
Antworten:
Der Grund für den Konflikt ist, dass die Fallklasse genau dieselbe Methode apply () (dieselbe Signatur) bereitstellt.
Zunächst möchte ich vorschlagen, dass Sie Folgendes benötigen:
Dies löst eine Ausnahme aus, wenn der Benutzer versucht, eine Instanz zu erstellen, in der s Kleinbuchstaben enthält. Dies ist eine gute Verwendung von Fallklassen, da Sie in den Konstruktor auch das eingeben, was Sie bei Verwendung von Pattern Matching (
match
) erhalten.Wenn dies nicht das ist, was Sie wollen, würde ich den Konstruktor
private
erstellen und die Benutzer zwingen, nur die Methode apply zu verwenden:Wie Sie sehen, ist A nicht länger a
case class
. Ich bin nicht sicher, ob Fallklassen mit unveränderlichen Feldern zur Änderung der eingehenden Werte gedacht sind, da der Name "Fallklasse" impliziert, dass es möglich sein sollte, die (nicht modifizierten) Konstruktorargumente mit zu extrahierenmatch
.quelle
toCharArray
Anruf ist nicht notwendig, Sie könnten auch schreibens.exists(_.isLower)
.s.forall(_.isUpper)
ist leichter zu verstehen als!s.exists(_.isLower)
.s.forall(_isupper)
das leichter zu lesen ist. Ich werde das in Verbindung mit dem Vorschlag von @ olle verwenden.match
."UPDATE 25.02.2016:
Obwohl die Antwort, die ich unten geschrieben habe, weiterhin ausreichend ist, lohnt es sich, auch auf eine andere verwandte Antwort in Bezug auf das Begleitobjekt der Fallklasse zu verweisen. Das heißt, wie kann man genau den Compiler erzeugte implizite Begleitobjekt reproduzieren , das auftritt , wenn man nur den Fall der Klasse selbst definiert. Für mich erwies es sich als kontraintuitiv.
Zusammenfassung:
Sie können den Wert eines Fallklassenparameters ändern, bevor er in der Fallklasse gespeichert wird, ganz einfach, während er noch ein gültiger (ated) ADT (Abstract Data Type) bleibt. Während die Lösung relativ einfach war, war es etwas schwieriger, die Details zu entdecken.
Details:
Wenn Sie sicherstellen möchten, dass nur gültige Instanzen Ihrer Fallklasse instanziiert werden können, was eine wesentliche Voraussetzung für einen ADT (Abstract Data Type) ist, müssen Sie eine Reihe von Maßnahmen ergreifen.
Beispielsweise
copy
wird standardmäßig eine vom Compiler generierte Methode für eine Fallklasse bereitgestellt. Selbst wenn Sie sehr sorgfältig darauf achten würden, dass nur Instanzen über dieapply
Methode des expliziten Begleitobjekts erstellt werden, die garantiert, dass sie immer nur Großbuchstaben enthalten können, würde der folgende Code eine Fallklasseninstanz mit einem Kleinbuchstabenwert erzeugen:Zusätzlich werden Fallklassen implementiert
java.io.Serializable
. Dies bedeutet, dass Ihre sorgfältige Strategie, nur Großbuchstaben zu verwenden, mit einem einfachen Texteditor und einer Deserialisierung unterlaufen werden kann.Für all die verschiedenen Möglichkeiten, wie Ihre Fallklasse verwendet werden kann (wohlwollend und / oder böswillig), müssen Sie folgende Maßnahmen ergreifen:
apply
Methode mit genau derselben Signatur wie der primäre Konstruktor für Ihre Fallklassenew
Operators eine Instanz der Fallklasse abruft und eine leere Implementierung bereitstellt{}
{}
muss bereitgestellt werden, da die Fallklasse deklariert istabstract
(siehe Schritt 2.1).abstract
apply
Methode im Begleitobjekt generiert, die den Kompilierungsfehler "Methode wird zweimal definiert ..." verursacht hat (Schritt 1.2 oben).private[A]
readResolve
Methodecopy
Methodes: String = s
)Hier ist Ihr Code, der mit den oben genannten Aktionen geändert wurde:
Und hier ist Ihr Code, nachdem Sie die Anforderung implementiert haben (vorgeschlagen in der @ kollekullberg-Antwort) und den idealen Ort für jede Art von Caching identifiziert haben:
Und diese Version ist sicherer / robuster, wenn dieser Code über Java Interop verwendet wird (versteckt die case-Klasse als Implementierung und erstellt eine endgültige Klasse, die Ableitungen verhindert):
Während dies Ihre Frage direkt beantwortet, gibt es noch mehr Möglichkeiten, diesen Pfad um Fallklassen über das Instanz-Caching hinaus zu erweitern. Für meine eigenen Projektanforderungen habe ich eine noch umfassendere Lösung erstellt, die ich auf CodeReview (einer StackOverflow-Schwestersite) dokumentiert habe . Wenn Sie am Ende darüber nachdenken, meine Lösung nutzen oder nutzen, sollten Sie mir Feedback, Vorschläge oder Fragen hinterlassen. Ich werde mein Bestes tun, um innerhalb eines Tages zu antworten.
quelle
Ich weiß nicht, wie ich die
apply
Methode im Begleitobjekt überschreiben soll (falls dies überhaupt möglich ist), aber Sie können auch einen speziellen Typ für Zeichenfolgen in Großbuchstaben verwenden:Der obige Code gibt aus:
Sie sollten sich auch diese Frage und ihre Antworten ansehen: Scala: Ist es möglich, den Standardkonstruktor für Fallklassen zu überschreiben?
quelle
Proxy
! Könnte aber besser sein alss.toUpperCase
einmal .toUpperCase
mehr als einmal angerufen wird.val self
, nichtdef self
. Ich habe gerade C ++ im Gehirn.Für die Personen, die dies nach April 2017 lesen: Ab Scala 2.12.2+ erlaubt Scala standardmäßig das Überschreiben von Anwenden und Nicht-Anwenden . Sie können dieses Verhalten erhalten, indem Sie
-Xsource:2.12
dem Compiler auch in Scala 2.11.11+ eine Option geben .quelle
-Xprint
einematch
Anweisung eingeben, werden Sie feststellen, dass es nicht verwendet wird).Es funktioniert mit var Variablen:
Diese Praxis wird anscheinend in Fallklassen empfohlen, anstatt einen anderen Konstruktor zu definieren. Siehe hier. . Beim Kopieren eines Objekts behalten Sie auch die gleichen Änderungen bei.
quelle
Eine andere Idee, die Fallklasse beizubehalten und keine impliziten Defs oder einen anderen Konstruktor zu haben, besteht darin, die Signatur von
apply
etwas anders, aber aus Benutzersicht gleich zu machen. Irgendwo habe ich den impliziten Trick gesehen, kann mich aber nicht erinnern / finden, welches implizite Argument es war, also habe ichBoolean
hier gewählt. Wenn mir jemand helfen und den Trick beenden kann ...quelle
Ich hatte das gleiche Problem und diese Lösung ist für mich in Ordnung:
Und wenn eine Methode benötigt wird, definieren Sie sie einfach im Merkmal und überschreiben Sie sie in der Fallklasse.
quelle
Wenn Sie mit einer älteren Scala nicht weiterkommen, bei der Sie standardmäßig nicht überschreiben können, oder wenn Sie das Compiler-Flag nicht hinzufügen möchten, wie @ mehmet-emre gezeigt hat, und Sie eine Fallklasse benötigen, können Sie Folgendes tun:
quelle
Ich denke das funktioniert genau so wie du es schon willst. Hier ist meine REPL-Sitzung:
Dies verwendet Scala 2.8.1.final
quelle