简体   繁体   中英

How to call constructor from companion apply method in scala case class?

case class Host(
                name: String,
                http: String
                )

I want to make all given names to lower case in the class.
When I try to override apply like this:

case class Host(
                name: String,
                http: String
                )

object Host{
  def apply(
            name: String,
            http: String
           ): Host= {
    Host(name.toLowerCase, http)
  }
}

then it begins never ending recursion.

If you wish to enforce the rule for all instances of Host then consider making the primary constructor private like so

case class Host private (
  name: String,
  http: String
)

and follow the rest of @user's answer. If you do not make it private then the rule can be easily circumvented by invoking the primary constructor via new instead of companion's apply factory method

Host("PICARD", "starfleet.org")            // OK     
// res0: Host = Host(picard,starfleet.org)

new Host("PICARD", "starfleet.org")        // oops!
// res1: Host = Host(PICARD,starfleet.org)


This is because the compiler thinks you're calling the apply method in your companion object. You need to use the new keyword to make sure the constructor gets called. I would suggest this:

case class Host(name: String, http: String)

object Host{
  def apply(name: String, http: String): Host = {
    new Host(name.toLowerCase, http)
  }
}

Then, when you do this

val host = Host("BLAH", "bleh")
println(host)

the output is Host(blah, bleh) because your companion object's apply method is called. If you didn't make a companion object, a companion object with the apply method (and unapply method) would have been generated for you. Since you've made your own companion object, the compiler just uses that instead of making its own for the generated companion object.


Another way to tackle it would be with an implicit class that wraps around a String and makes it lowercase:

case class Host(name: LowerCase, val http: String)

implicit class LowerCase(str: String) {
  val lowerCase: String = str.toLowerCase
}

implicit def toString(lower: LowerCase): String = lower.lowerCase

And this works perfectly:

val host = Host("BLAH", "bleh")
host match {
  case Host(name, http) => print(name + http)
}

EDIT : If you don't care about having a case class, you can always do this too:

class Host(nom: String, val http: String) {
  val name: String = nom.toLowerCase
}

If you want to be able to not use new, you can always add an apply method, and if you want to be able to use pattern matching, you can add an unapply method.

object Host{
  def apply(name: String, http: String): Host = new Host(name.toLowerCase, http)
  def unapply(arg: Host): Option[(String, String)] = Some((arg.name, arg.http))
}

And then you'll have to override toString, equals, and hashCode if you want them, but that's probably not worth it, unless you have a very specific implementation of each. Hence, I'd suggest Mario Galic's answer of making your primary constructor private if you don't want to accidentally make your name uppercase.

I assume CountryHost is a typo?

You're recursively calling apply instead of calling the constructor, try this:

object Host{
  def apply(
            name: String,
            http: String
           ): Host= {
    new Host(name.toLowerCase, http)
  }
}

To enforce this rule you need to make the constructor private:

case class Host private(
  name: String,
  http: String
)

To avoid recursion, use new :

object Host {
  def apply(name: String, http: String): Host =
    new Host(name.toLowerCase, http)
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM