18
votes

I'm on exercise 5.7 of "Scala for the Impatient", where i need to create a class Person that takes a name:String on constructor and has 2 properties firstName and lastName filled from name split by whitespace. My first trial was :

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

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

The problem is, that now nameParts remains as a private field always visible within the class, when in fact should only exist within the constructor's local environment. The Java equivalent of what I want would be:

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

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

Here, nameParts exists only withing the constructor, which is what i'm aiming for. Any hints on how this can be done in Scala?

NOTE: I ended up finding a more "Scalesque" way:

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

but I still would like to get an answer to my question.

5
Examples on using temporary variables during object instantiation on the daily scala blog.Norbert Madarász

5 Answers

14
votes

There is a way to avoid the private val. Just use the extractor of Array:

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

edit:

What you want to do can be achieved through a factory method on the companion and a default constructor that takes first and last as 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 = Person@37e79b10

scala> res6.first 
res7: String = Foo

scala> res6.last
res8: String = Bar

But for this simple case I would prefer my first suggestion.

The example in your link would also work, but it's kind of the same as my first example. Afaik there is no way to create a temporary variable in the constructor.

5
votes

What I had in mind was simply

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

If you want to factor out the common code, then drexin's answer with the array is very nice. But it requires knowledge that the reader wouldn't have had in chapter 5. However, var with tuples was covered in chapter 4, so the following is within reach:

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

Just an addition to @drexin answer. In your example class Person(name:String) the constructor parameter is still stored as private[this] val name: String and can be accessed within the class, for example:

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

If you really want to avoid this, you can create a companion object and make the primary constructor private:

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

Another way is to create a trait Person with a companion object:

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

An approach that avoids defining Extractors or the need for a companion object method is

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 expression enclosed in {..} has the value of the last sub-expression within

1
votes

The common scala way of doing this is using companion object (as described in previous answers).

However, there are cases where this can be an issue (e.g. if we inherit the class and want to call the relevant constructor without knowing the internal logic or if we want to instantiate the class through reflect).

The only way I know if doing it directly inside the class is a little convoluted:

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

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

The idea is that x (the same as name) have no var or val and so are converted to private[this] val.

The optimizer knows that since they are not used outside the constructor it can use them as normal function parameter and they are not saved. The internal calling is what makes this possible.

Result of 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);
}

The solution mentioned several times which looks like this:

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

does not actually work. It creates a temporary tuple which is saved. Result of 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);
}