Initializing a val lately

前端 未结 3 1359
旧时难觅i
旧时难觅i 2021-01-23 17:51

Is it possible to do that in Scala using only val:

class MyClass {
  private val myVal1: MyClass2 //.....????? what should be here?

  def myMethod1(param1: Int)         


        
相关标签:
3条回答
  • 2021-01-23 18:27

    To imitate a lazy "value" whose initial value might not be retrieved until after instance initialization completes (btw, there is nothing special about such objects, e.g. Swift have lazy properties that are even recommended to be declared as variables), you can introduce a wrapper to repeat the same logic that the Scala compiler generates internally for lazy values in Scala:

    class LazyVar[T] {
    
      private[this] var value$compute: () => T = () => null.asInstanceOf[T]
      @volatile private[this] var value$: T = null.asInstanceOf[T]
      @volatile private[this] var isInitialized$ = false
      @volatile private[this] var isComputed$ = false
    
      def value_=(value: T) = this.synchronized {
        if(!isInitialized$) {
          value$compute = () => value
          isInitialized$ = true
        }
        else throw new IllegalStateException("Already initialized")
      }
    
      def value: T = this.synchronized {
        if(!isInitialized$) throw new IllegalStateException("Not yet initialized")
        else if(isComputed$) value$
        else {
          value$ = value$compute()
          isComputed$ = true
          value$
        }
      }
    }
    

    Now you just have to change MyClass2 to LazyVar[MyClass2] keeping tha val keyword as you wanted:

    case class MyClass2(param: Int)
    
    class MyClass {
      private val myVal1: LazyVar[MyClass2] = new LazyVar[MyClass2]
    
      def this(param: Int) {
        this()
        println("Storing the result of an expensive function...")
        myVal1.value = new MyClass2(param)
      }
    
      def debug() = println(myVal1.value)
    }
    

    Now, if you write something like

    val myClass = new MyClass(42)
    myClass.debug
    myClass.debug
    

    you'll see the value is only computed once:

    Storing the result of an expensive function...
    MyClass2(42)
    MyClass2(42)
    
    0 讨论(0)
  • 2021-01-23 18:48

    No, it isn't possible to do in the way you want. Consider, what would be the result of

    val mc = new MyClass
    mc.method1(0)
    mc.method1(1)
    

    ? An exception thrown for setting myVal1 twice? Or should it keep the first value?

    0 讨论(0)
  • 2021-01-23 18:49

    This is not possible, but there are some ways (in addition to using param1 as a constructor parameter)

    1. Change the var into an Option; the setter myMethod1 returns a new instance of the same class with the Option set to the value.
    2. Create a separate mutable Builder class with a var, and turn it into an immutable one later, when all data has been collected
    3. If you are dealing with forward or cyclic references, consider using call-by-name and lazy vals (example 1, example 2)

    Update: Example for 1:

    class MyClass(val myVal1: Option[Int]) {
      def myMethod1(param1: Int): MyClass = {
        new MyClass(Some(param1))
      }
    }
    
    object MyClass {
      def apply() = new MyClass(None)
      def apply(i: Int) = new MyClass(Some(i))
    }
    

    This pattern is used by immutable.Queue for example.


    Update: Example for 3 (cyclic reference):

    // ref ... call by name
    class MyClass(val id: Int, ref: => MyClass) {
      lazy val myVal1 = ref
    
      override def toString: String = s"$id -> ${myVal1.id}"
    }
    

    to be used like this:

    val a: MyClass = new MyClass(1, b)
    val b: MyClass = new MyClass(2, a)
    println(a)
    println(b)
    

    Update: Example for 3 (forward reference):

    class MyClass2(val id: Int)
    
    // ref ... call by name
    class MyClass(val id: Int, ref: => MyClass2) {
      lazy val myVal1 = ref
    
      override def toString: String = s"$id -> ${myVal1.id}"
    }
    

    to be used with

    val a = new MyClass(1, x)
    println(a.id) // You can use a.id, but not yet the lazy val
    val x = new MyClass2(10)
    println(a)
    
    0 讨论(0)
提交回复
热议问题