2012-04-15 20 views
10

sono in esercizio 5,7 di "Scala per l'Impaziente", dove ho bisogno di creare una classe Persona che prende il nome di : String il costruttore e dispone di 2 proprietà firstName e lastName riempito dal nome diviso per spazi. Il mio primo processo è stato:variabili Constructor-locali a Scala

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

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

Il problema è che ora nameParts rimane come un campo privato sempre visibile all'interno della classe, quando in realtà dovrebbe esistere solo all'interno dell'ambiente locale del costruttore. L'equivalente Java di ciò che voglio sarebbe:

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

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

Qui, nameParts esiste solo withing il costruttore, che è quello che sto puntando. Qualche suggerimento su come questo può essere fatto in Scala?

NOTA: ho finito per trovare un modo più "Scalesque":

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

ma ho ancora vorrebbe ottenere una risposta alla mia domanda.

+0

Esempi su come utilizzare le variabili temporanee durante istanze di oggetti sul [blog scala giornaliera] (http://daily-scala.blogspot.hu/2010/02/temporary-variables -durante-object.html). –

+0

possibile duplicato di [Come si definisce una var/val locale nel costruttore principale in Scala?] (Http://stackoverflow.com/questions/1118669/how-do-you-define-a-local-var-val -in-the-primary-constructor-in-scala) –

risposta

10

C'è un modo per evitare il private val. Basta usare l'estrattore di Array:

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

edit:

Che cosa si vuole fare può essere raggiunto attraverso un metodo factory sul compagno e un costruttore di default che prende prima e l'ultima come param:

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 

Ma per questo semplice caso preferirei il mio primo suggerimento.

L'esempio nel collegamento funzionerebbe anche, ma è lo stesso del mio primo esempio. Afaik non c'è modo di creare una variabile temporanea nel costruttore.

+0

Questa è una soluzione davvero accurata, ma intendevo sapere se esiste un modo 'sintetico' di definire una variabile in modo che esista solo nel costrutto- re principale. Ho anche controllato [link] (http://stackoverflow.com/questions/1218872/avoiding-scala-memory-leaks-scala-constructors) e sembrano anche funzionare in qualche modo. Forse ho solo bisogno di abituarmi al pensiero funzionale :) – Chirlo

+0

aggiornato la mia risposta – drexin

3

Solo un'aggiunta alla risposta di @drexin. Nel tuo esempio class Person(name:String) il parametro del costruttore è ancora memorizzato come private[this] val name: String e si può accedere all'interno della classe, ad esempio:

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

Se davvero si vuole evitare questo, è possibile creare un oggetto associato e fare il costruttore principale privata :

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) 
    } 
} 

un altro modo è quello di creare un tratto Person con un oggetto associato:

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

buon punto, in realtà finisco con quattro campi: nome, nameParts, first Name e lastName. Non capisco davvero perché Scala lo faccia, se avessi una collezione con 2000 persone, ognuna delle quali memorizza due riferimenti indesiderati, è una perdita di memoria. – Chirlo

+0

In realtà, il nome rimarrà un privato [this] val solo se è accessibile ovunque al di fuori del costruttore. Altrimenti verrebbe rimosso. Puoi testarlo usando javap -private Person.class –

5

Quello che avevo in mente era semplicemente

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

Se si vuole fattorizzare il codice comune, allora la risposta di drexin con l'array è molto bello. Ma richiede conoscenza che il lettore non avrebbe avuto nel capitolo 5.Tuttavia, var con tuple era coperto nel capitolo 4, quindi il seguente è a portata:

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

In realtà ciò creerebbe una scala finale privata.Tuple2 x $ 1; o un membro simile. Prova a fare javap -private Person.class –

1

Un approccio che evita estrattori definiscono o la necessità di un metodo guidata oggetto è

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

Un'espressione scala racchiuso in {..} ha il valore dell'ultima sottoespressione all'interno di

+1

In realtà ciò creerebbe una scala finale privata.Tuple2 x $ 1; o un membro simile. Prova a fare javap -private Person.class –

+0

Vero che sembra. Ho imparato qualcosa: 'public class testdata.Person { scala finale privata.Tuplo2 x $ 1; private final java.lang.String firstName; privato finale java.lang.String lastName; ... ' –

1

Il modo comune di farlo è utilizzare l'oggetto associato (come descritto nelle risposte precedenti).

Tuttavia, ci sono casi in cui questo può essere un problema (ad es. Se ereditiamo la classe e vogliamo chiamare il costruttore rilevante senza conoscere la logica interna o se vogliamo istanziare la classe attraverso la riflessione).

L'unico modo che conosco se farlo direttamente all'interno della classe è un po 'contorto:

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

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

L'idea è che x (lo stesso nome) non hanno var o val e così vengono convertiti in privato [questo] val.

L'ottimizzatore sa che poiché non vengono utilizzati al di fuori del costruttore, può usarli come parametro di funzione normale e non vengono salvati. La chiamata interna è ciò che lo rende possibile.

Risultato di javap Privata 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); 
} 

La soluzione menzionato più volte, che assomiglia a questo:

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

in realtà non funziona. Crea una tupla temporanea che viene salvata. Risultato di javap Privata 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); 
}