2012-04-15 13 views
10

Ich bin auf Übung 5.7 von „Scala für Ungeduldige“, wo ich brauche eine Klasse Person zu erstellen, die einen Namen gab: String auf Konstruktor und hat zwei Eigenschaften vorName und lastName gefüllt von Name geteilt durch whitespace. Mein erster Versuch war:Constructor-lokale Variablen in Scala

class Person(name:String) { 
    private val nameParts = name.split(" ") 

    val firstName = nameParts(0) 
    val lastName = nameParts(1) 
} 

Das Problem ist, dass jetzt nameParts bleibt als privates Feld immer sichtbar innerhalb der Klasse, wenn in der Tat nur in dem Konstruktor des lokalen Umgebung existieren sollte. Der Java-Äquivalent, was ich will wäre:

class Person{ 
    private final String firstName; 
    private final String lastName; 

    Person(String name){ 
     final String[] nameParts = name.split(" "); 
     firstName = nameParts[0]; 
     lastName = nameParts[1]; 
    } 
} 

Hier nameParts existiert den Konstruktor nur withing, das ist das, was ich bin mit dem Ziel. Irgendwelche Hinweise, wie das in Scala gemacht werden kann?

HINWEIS: Ich landete einen „Scalesque“ Weg zu finden:

class Person(name:String) { 
    val firstName::lastName::_ = name.split(" ").toList 
} 

aber ich würde immer noch gerne eine Antwort auf meine Frage bekommen.

+0

Beispiele zur Verwendung von temporären Variablen während Objektinstanziierung auf [täglich scala Blog] (http://daily-scala.blogspot.hu/2010/02/temporary-variables -during-object.html). –

+0

mögliches Duplikat von [Wie definierst du ein lokales var/val im primären Konstruktor in Scala?] (Http://stackoverflow.com/questions/1118669/how-do-you-define-a-local-var-val -in-the-primary-Konstruktor-in-scala) –

Antwort

10

Es gibt eine Möglichkeit, die private val zu vermeiden. Verwenden Sie einfach den Extraktor von Array:

class Person(name: String) { 
    val Array(first, last) = name.split(" ") 
} 

edit:

Was möchten Sie durch eine Factory-Methode auf der Begleit-und ein Standard-Konstruktor, der erste und der letzte als param nimmt erreicht zu tun ist:

class Person(val first: String, val last: String) 

object Person { 
    def apply(name: String) = { 
    val splitted = name.split(" ") 
    new Person(splitted(0), splitted(1)) 
    } 
} 

scala> Person("Foo Bar") 
res6: Person = [email protected] 

scala> res6.first 
res7: String = Foo 

scala> res6.last 
res8: String = Bar 

Aber für diesen einfachen Fall würde ich meinen ersten Vorschlag bevorzugen.

Das Beispiel in Ihrem Link würde auch funktionieren, aber es ist irgendwie das gleiche wie mein erstes Beispiel. Afaik gibt es keine Möglichkeit, eine temporäre Variable im Konstruktor zu erstellen.

+0

Das ist eine wirklich nette Abhilfe, aber ich wollte wissen, ob es eine 'syntaktische' Möglichkeit gibt, eine Variable so zu definieren, dass sie nur im primären Konstrukt existiert. Ich habe auch [link] (http://stackoverflow.com/questions/1218872/avoiding-scala-memory-leaks-scala-constructors) überprüft und sie scheinen es auch irgendwie zu arbeiten. Vielleicht muss ich mich nur an den funktionalen Gedanken gewöhnen :) – Chirlo

+0

meine Antwort aktualisiert – drexin

3

Nur eine Ergänzung zu @drexin Antwort. In Ihrem Beispiel class Person(name:String) wird der Konstruktor Parameter noch als private[this] val name: String gespeichert und kann innerhalb der Klasse zugegriffen werden, zum Beispiel:

class Person(name:String) { 
    def tellMeYourName = name 
} 

Wenn Sie wirklich, dies vermeiden mögen, können Sie ein Begleiter-Objekt erstellen und das primäre Konstruktor machen privat :

class Person private (val fName: String, val lName: String) 

object Person { 
    def apply(name: String) = { 
    val Array(fName, lName) = name split " " 
    new Person(fName, lName) 
    } 
} 

eine andere Möglichkeit ist ein Merkmal Person mit einem Begleiter-Objekt zu erstellen:

trait Person { 
    val lName: String 
    val fName: String 
} 
object Person { 
    def apply(name: String) = new Person { 
    val Array(lName, fName) = name split " " 
    } 
} 
+1

guten Punkt, ich tatsächlich am Ende mit vier Felder: Name, nameParts, Vorname und Nachname. Ich verstehe nicht wirklich, warum Scala das macht, wenn ich eine Sammlung mit 2000 Leuten hätte, von denen jede zwei unerwünschte Referenzen speichert, ist das eine Verschwendung von Speicher. – Chirlo

+0

Eigentlich würde name nur dann ein privates [this] val bleiben, wenn auf ihn irgendwo außerhalb des Konstruktors zugegriffen wird. Sonst würde es entfernt werden. Sie können dies testen, indem Sie javap -private Person.class verwenden –

5

Was ich im Sinn hatte, war einfach

class Person(n: String) { 
    val firstName = n.split(" ")(0) 
    val lastName = n.split(" ")(1) 
} 

Wenn Sie den gemeinsamen Code ausklammern wollen, dann Antwort des drexin mit dem Array ist sehr schön. Aber es erfordert Wissen, das der Leser in Kapitel 5 nicht gehabt hätte.Allerdings var mit Tupeln in Kapitel behandelt wurde 4, so dass die folgende ist in Reichweite:

class Person(n: String) { 
    val (firstName, lastName) = { val ns = n.split(" "); (ns(0), ns(1)) } 
} 
+1

Eigentlich würde dies eine private endgültige scala.Tuple2 x $ 1; oder ähnliches Mitglied. Versuchen Sie javap -private Person.class –

1

Ein Ansatz, der Definition von Extraktoren oder die Notwendigkeit einer Begleitobjekt Methode vermeidet, ist

class Person(name: String) {  
    val (firstName, lastName) = { 
    val nameParts = name.split(" ") 
    (nameParts(0), nameParts(1)) 
    } 
} 
object Example { 
    println(new Person("John Doe").firstName) 
} 

A scala Ausdruck eingeschlossen in {..} hat den Wert der letzten Unterausdruck innerhalb

+1

Eigentlich würde dies eine private endgültige scala.Tuple2 x $ 1; oder ähnliches Mitglied. Versuchen Sie es mit javap -private Person.class –

+0

Wahr, es scheint. Ich habe etwas gelernt: 'öffentliche Klasse testdata.Person { private final scala.Tuple2 x $ 1; private endgültige java.lang.String Vorname; private final java.lang.String lastName; ... ' –

1

Die übliche scala Weg dies zu tun ist mit Companion-Objekt (wie in früheren Antworten beschrieben).

Es gibt jedoch Fälle, in denen dies ein Problem sein kann (z. B. wenn wir die Klasse erben und den relevanten Konstruktor aufrufen möchten, ohne die interne Logik zu kennen oder die Klasse durch Reflektion instanziieren).

Der einzige Weg, ich weiß, wenn es direkt in der Klasse zu tun ist ein wenig verworren:

class Person(name: String, x: Seq[String]) { 
    val firstName = x(0) 
    val lastName = x(1) 

    def this(name: String) { 
    this(name, name.split(" ")) 
    } 
} 

Die Idee ist, dass x (die gleiche wie Name) keine var oder val und werden privat so umgewandelt [dieses] val.

Der Optimierer weiß, dass er als normaler Funktionsparameter verwendet werden kann, da er außerhalb des Konstruktors nicht verwendet wird und nicht gespeichert wird. Der interne Aufruf macht das möglich.

Ergebnis javap -privater Person.class:

public class com.rsa.nw_spark.Person { 
    private final java.lang.String firstName; 
    private final java.lang.String lastName; 
    public java.lang.String firstName(); 
    public java.lang.String lastName(); 
    public com.rsa.nw_spark.Person(java.lang.String, scala.collection.Seq<java.lang.String>); 
    public com.rsa.nw_spark.Person(java.lang.String); 
} 

Die Lösung mehrmals erwähnt, die wie folgt aussieht:

class Person(name: String) {  
    val (firstName, lastName) = { 
    val nameParts = name.split(" ") 
    (nameParts(0), nameParts(1)) 
    } 
} 

nicht wirklich funktionieren. Es erstellt ein temporäres Tupel, das gespeichert wird. Ergebnis javap -privater Person.class:

public class com.rsa.nw_spark.Person { 
    private final scala.Tuple2 x$1; 
    private final java.lang.String firstName; 
    private final java.lang.String lastName; 
    public java.lang.String firstName(); 
    public java.lang.String lastName(); 
    public com.rsa.nw_spark.Person(java.lang.String); 
} 
Verwandte Themen