2012-04-15 18 views
10

Estoy en el ejercicio de 5,7 "Scala para impacientes", donde i necesidad de crear una clase Persona que lleva un nombre : String el constructor y tiene 2 propiedades primerNombre y lastName relleno del nombre dividido por espacios en blanco. Mi primera prueba fue:las variables Constructor-locales en Scala

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

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

El problema es que ahora nameParts permanece como un campo privado siempre visible dentro de la clase, cuando en realidad sólo debe existir en el entorno local del constructor. El equivalente de Java de lo que quiero sería:

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

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

Aquí, nameParts existe sólo en el canto del constructor, que es lo que estoy buscando. ¿Algún indicio de cómo se puede hacer esto en Scala?

NOTA: Me terminó encontrando una manera más "Scalesque":

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

pero todavía le gustaría obtener una respuesta a mi pregunta.

+0

ejemplos sobre el uso de variables temporales durante la instanciación de objetos en el [Blog Scala diaria] (http://daily-scala.blogspot.hu/2010/02/temporary-variables -during-object.html). –

+0

posible duplicado de [¿Cómo se define un var/val local en el constructor primario en Scala?] (Http://stackoverflow.com/questions/1118669/how-do-you-define-a-local-var-val -in-the-primary-constructor-in-scala) –

Respuesta

10

Hay una forma de evitar el private val. Sólo tiene que utilizar el extractor de Array:

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

edición:

Lo que quiere hacer se puede lograr a través de un método de fábrica en el compañero y un constructor por defecto que toma primero y el último como 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 

Pero en este caso simple, preferiría mi primera sugerencia.

El ejemplo en su enlace también funcionaría, pero es similar a mi primer ejemplo. Afaik no hay forma de crear una variable temporal en el constructor.

+0

Esa es una solución muy clara, pero quise saber si hay una forma 'sintética' de definir una variable para que solo exista en la constructora primaria. También revisé [link] (http://stackoverflow.com/questions/1218872/avoiding-scala-memory-leaks-scala-constructors) y también parecen solucionarlo de alguna manera. Tal vez solo necesito acostumbrarme al pensamiento funcional :) – Chirlo

+0

actualicé mi respuesta – drexin

3

Solo una adición a @drexin respuesta. En su ejemplo class Person(name:String) el parámetro constructor se sigue almacena como private[this] val name: String y se puede acceder dentro de la clase, por ejemplo:

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

Si realmente quiere evitar esto, se puede crear un objeto de compañía y hacer que el constructor primaria privada :

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

Otra forma es crear un rasgo Person con un objeto acompañante:

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

buen punto, en realidad termino con cuatro campos: nombre, nombrePartes, nombre y apellido. Realmente no entiendo por qué Scala hace esto, si tuviera una colección con 2000 personas, cada una almacenando dos referencias no deseadas, eso es un desperdicio de memoria. – Chirlo

+0

En realidad, el nombre se mantendría como privado [este] val solo si se accede a él fuera del constructor. De lo contrario, se eliminaría. Puede probar esto usando javap -private Person.class –

5

Lo que tenía en mente era simplemente

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

Si desea factorizar el código común, entonces la respuesta de drexin con la matriz es muy agradable. Pero requiere conocimiento que el lector no habría tenido en el capítulo 5.Sin embargo, var con tuplas estaba cubierta en el capítulo 4, por lo que la siguiente se encuentra dentro de alcance:

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

. De hecho, esto crearía un scala.Tuple2 x $ 1 final privado; o miembro similar. Intenta hacer javap -private Person.class –

1

Un enfoque que evita Extractores la definición o la necesidad de un método de objeto compañero es

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

Una expresión Scala encerrado en {..} tiene el valor de la última sub-expresión dentro de

+1

En realidad, esto crearía un scala final privado.Tuple2 x $ 1; o miembro similar. Intenta hacer javap -private Person.class –

+0

Cierto, parece. He aprendido algo: 'public class testdata.Person { private final scala.Tuple2 x $ 1; final privado java.lang.String firstName; final privado java.lang.String lastName; ... ' –

1

La forma más común de hacer esto es usar un objeto complementario (como se describe en las respuestas anteriores).

Sin embargo, hay casos en que esto puede ser un problema (por ejemplo, si heredamos la clase y queremos llamar al constructor relevante sin conocer la lógica interna o si queremos crear una instancia de la clase a través del reflejo).

La única manera que sé si hacerlo directamente dentro de la clase es un poco más complicado:

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

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

La idea es que x (el mismo que el nombre) no tienen var o Val y así se convierten en privado [este] val.

El optimizador sabe que, dado que no se utilizan fuera del constructor, puede usarlos como un parámetro de función normal y no se guardan. La llamada interna es lo que hace esto posible.

Resultado de javap -private 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 solución mencionado varias veces con la apariencia siguiente:

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

no funciona realmente. Crea una tupla temporal que se guarda. Resultado de la javap -private 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); 
} 
Cuestiones relacionadas