2013-01-18 4 views
2

Я знаю, что есть несколько вопросов, касающихся связанных проблем, но я не уверен, что он атакует именно то, что я ищу. Я еще не знаком с Scala после нескольких лет разработки Java. Я ищу лучший способ проверить, был ли объект инициализирован, а если нет, тогда инициализируйте его. Например, в Java:Scala - обработка инициализации объектов (_ vs Option [T])

private MyObject myObj = null; 

и в какой-то момент в будущем:

public void initMyObj(){ 
    if (myObj == null){ 
     myObj = new MyObj(); 
    } 
    // do something with myObj 
} 

После этого я мог бы передать myObj на другой объект, но это маловероятно. В Scala, у меня есть это:

class Test { 
    var myObj: MyObj = _ 
} 

Я прочитал, что я мог бы использовать вариант вместо этого, что-то вроде:

var myObj = None : Option[MyObj] 

, а затем мой чек:

myObj match { 
    case None => ... 
    case Some(value) => ... 
} 

но он чувствует ackward, чтобы использовать этот шаблон, когда я, возможно, не сделаю такого рода проверку где-либо еще в любое другое время - хотя, будучи настолько новым для Scala, я могу ошибаться. Это лучший способ достичь того, чего я хочу, или есть ли другой вариант, не связанный с Option?

ответ

4

Практически не существует идеальной практики в Скале, чтобы оставить частично построенные объекты, лежащие вокруг. Обычно вы переосмысливаете, как ваши объекты создавались, чтобы увидеть, не можете ли вы использовать другой шаблон, который менее хрупкий. Например, вместо установки неинициализированных переменных в методах:

class Foo { var a: String = null; var b: String = null } 
def initFooA(s: String, f: Foo) { if (f.a == null) f.a = s } 
def initFooB(s: String, f: Foo) { if (f.b == null) f.b = s } 
f 
initFooA("salmon", f) 
// Do stuff 
initFooB("herring", f) 

вы пытаетесь перестроить свой код, чтобы сгенерировать значения, нужно по требованию, а не задерживать экземпляра обув до тех пор:

case class Bar(a: String, b: String) {} 
def initBarA(s: String) = s 
def initBarB(s: String) = s 
val iba = initBarA("halibut") 
// Do stuff 
val ibb = initBarB("cod") 
Bar(iba, ibb) 

Поскольку Scala имеет легкий доступ к кортежам (и выводит тип), это может быть намного менее болезненным, чем в Java.

Другая вещь, которую вы можете сделать, - отложить позднюю инициализацию кому-то еще.

case class Baz(a: String)(bMaker: => String) { 
    lazy val b = bMaker 
} 

Теперь вы передаете то, что сделает параметр Ь, и организовать для того, чтобы справиться с любым поздним материалом инициализации, который должен быть обработан. Это не всегда не нужно устанавливать vars, но это может помочь вытолкнуть его из вашего кода класса в вашу логику инициализации (что обычно лучше для него).

Выполнение этого с помощью vars немного менее прямолинейно. Реально, вам, вероятно, лучше всего посвятить класс этому, например. по:

class LazyVar[A](initial: => A) { 
    private[this] var loaded = false 
    private[this] var variable: A = _ 
    def apply() = { if (!loaded) { loaded = true; variable = initial }; variable } 
    def update(a: A) { loaded = true; variable = a } 
} 

где то (к сожалению) придется использовать () на каждый читать и писать.

scala> val lv = new LazyVar({ println("Hi!"); 5 }) 
lv: LazyVar[Int] = [email protected] 

scala> lv() 
Hi! 
res2: Int = 5 

scala> lv() = 7 

scala> lv() 
res4: Int = 7 

Затем вы используете экземпляр этого класса вместо фактического var и пройти через ленивый инициализатор. (lazy val очень похож на это под капотом, компилятор просто защищает вас замечать.)

Наконец, если вы хотите иметь полностью функциональный объект, который иногда отсутствующее значение, var x: Option[X] является конструкцией вы хотите использование; если вы не можете найти способ создания стандартных шаблонов Java (и вы не хотите попробовать что-то более экзотическое, как объекты, которые создают друг друга с большей и большей информацией, либо потому, что производительность критическая, и вы не можете себе это позволить , или вам не нравится писать много шаблонов, чтобы проверить тип, чтобы убедиться, что ваш объект правильно создан), но вы в противном случае хотите его использовать, var x: X = null - это то, что я бы выбрал, а не _. Если X является примитивным, вам, вероятно, нужно будет правильно выбрать правильное значение (например, Double.NaN вместо 0.0, -1, а не 0 для Int), чтобы указать I-am-not-initialized. Если это общий код, и вы хотите, чтобы Any вместо AnyRef, asInstanceOf -от взад и вперед между Any и AnyRef, вероятно, лучший выход из плохо типизированной ситуации (если вы действительно не можете использовать Option, что в этот момент много понятнее).

+0

Спасибо Rex. Мне нравится поздняя отсрочка инициализации. В моем случае мне нужно инициализировать переменную. Можно ли использовать ленивую инициализацию каким-то другим способом? – Dan

+0

@ Дан - вид; Я исправил свой ответ. –

5

Возможно, ленивая переменная - это то, что вам нужно.

lazy val myObj: MyObj = //here you put the object creation code 

Таким образом, создание объекта переносится в первый раз, когда код пытается получить к нему доступ.

+0

не является ленивым только для определения значений? не варны? – Dan

+0

@ Dan: Правильно. Существует только «ленивый вал», а не «ленивый вар». –

+0

Моя вина, извините за опечатку –