попробуйте заблокировать область - PullRequest
17 голосов
/ 28 августа 2010

Я недоволен правилом, что переменная область действия в блоке try не используется совместно с соответствующими блоками catch и finally. В частности, это приводит к коду, подобному следующему:

var v: VType = null

try {
  v = new VType()
}
catch {
  case e => // handle VType constructor failure (can reference v)
}
finally {
  // can reference v.
}

В отличие от:

try {
  val v = new VType()
}
catch {
  case e => // handle VType constructor failure (can reference v)
}
finally {
  // can reference v.
}

Может ли кто-нибудь объяснить или обосновать, почему это правило из Java сохраняется?

и / или есть ли надежда, что это может измениться?

Спасибо!

UPDATE

Большое спасибо за все ответы на сегодняшний день.

Консенсус, по-видимому, подразумевает "просто продолжай в том же духе", и я начинаю заключать, что, возможно, технически то, что я хочу, либо несостоятельно, не стоит усилий или труднодостижимо.

Мне нравится ответ Рекса Керра, но каким образом приведенный выше исходный код будет заключен в вызов метода без введения локальной переменной в теле метода?

Мои собственные усилия были не слишком хороши, я использовал параметр по имени, чтобы задержать конструкцию, пока безопасно в блоке try не сработает, но все равно не даст мне доступ к построенному (или нет) объекту в блоках catch или finally .

Ответы [ 7 ]

20 голосов
/ 06 января 2011

Просто «попробуйте»;)

val v = try { new VType() } catch { case e: Exception => /* ... */ }

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

14 голосов
/ 28 августа 2010

Возможно, вы думаете о проблеме неправильно. Почему вы хотите так много вещей в своем блоке try / catch / finally? В вашем коде

try { val v = new VType() }

исключение может быть выдано , прежде чем вы получите v обратно , поэтому вы не можете безопасно ссылаться на v. Но если вы не можете сослаться на v, то что вы можете сделать на стороне finally, которая не сломает, не сгенерирует свое собственное исключение или не имеет другого плохо определенного поведения? Что делать, если вы создаете v, но не можете создать w, но для удаления требуется также w? (Или нет?) Это заканчивается беспорядок.

Но если вы пришли из Java, есть несколько вещей, которые могут помочь вам написать блоки try / catch / finally разумным способом.

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

def s2a(s: String) = try { Some(s.toInt) } catch { case nfe: NumberFormatException => None}

Еще одна вещь, которую вы можете сделать, это создать свой собственный менеджер ресурсов

def enclosed[C <: { def close() }](c: C)(f: C => Unit) {
  try { f(c) } finally { c.close() }
}
enclosed(new FileInputStream(myFile))(fis => {
  fis.read...
}

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

val r = valuableOpenResource()
def attempt[F](f: => F) = {
  try { f } catch { case re: ReasonableException => r.close() throw re }
}
doSomethingSafe()
attempt( doSomethingDangerous() )
doSomethingElseSafe()
r.close()

Между этими различными способами обработки вещей у меня не было особой необходимости создавать переменные для хранения переменных, которые я хочу очистить позже или иным образом обрабатывать в блоках catch или finally.

6 голосов
/ 28 августа 2010

Как будет работать этот код?

try
{
    int i = 0;

    // Do stuff...

    Foo x = new Foo();

    // Do more stuff...

    Bar y = new Bar();
}
catch
{
    // Print the values of i, x, and y.
}

Каковы значения i, x и y?Ты даже был объявлен до того, как мы приземлились в блоке улова?

4 голосов
/ 28 августа 2010

Концепция исключения не является подпрограммой блока try, это альтернативный поток кода.Это делает в блоке управления try-catch больше похожим на «если случится что-то плохое», а затем вставьте эти (catch) строки в текущую позицию блока try, если это необходимо.

Имея это в виду, это не 't ясно, будет ли определено Val v = Type(); или нет, потому что исключение может (теоретически) быть сгенерировано до оценки Val v = Type();.Да, Val v - это первая строка в блоке, но есть ошибки JVM, которые могут быть выданы до нее.

Наконец, еще одна конструкция кода, которая добавляет и чередует, но требуется, поток кода в конце завершенияконструкция try-catch.Опять же, мы не знаем, сколько (если таковые имеются) блока try было вычислено до вызова блока finally, поэтому мы не можем зависеть от объявленных переменных в этом блоке.

Единственная оставшаяся альтернатива (теперь, когда мы не можем использовать переменные блока try из-за их неопределенности существования), это использовать переменную вне всей конструкции try-catch-finally для связи между отдельными блоками кода.

Это отстой?Может немного.У нас есть что-нибудь лучше?Возможно нет.Размещение объявлений переменных вне блока делает очевидным, что переменные будут определены до какой-либо структуры управления, которую вы обрабатываете в сценарии try-catch-finally.

3 голосов
/ 29 августа 2010

Вот еще один вариант:

object Guard {
    type Closing = {def close:Unit}

    var guarded: Stack[Set[Closing]] = Stack()
    def unapply(c: Closing) = {
      guarded.push(guarded.pop + c)
      Some(c)
    }

    private def close {println("Closing"); guarded.head.foreach{c => c.close}}
    private def down {println("Adding Set"); guarded.push(Set())}
    private def up {println("Removing Set"); guarded.pop}

    def carefully(f: => Unit) {
      down
      try {f}
      finally {close; up}
    }
}

Вы можете использовать его следующим образом:

import Guard.carefully

class File {def close {println("Closed File")}}
class BadFile {def close {println("Closed Bad File")}; throw new Exception("BadFile failed")}

carefully {
  val Guard(f) = new File
  val Guard(g) = new File
  val Guard(h) = new BadFile
}

, что приводит к

Добавление набора

Закрытие

Закрытый файл

Закрытый файл

java.lang.Exception: BadFile не удалось

Итак, первые два файла созданы,затем, когда третий конструктор выходит из строя, первые два автоматически закрываются.Все файлы являются значениями.

3 голосов
/ 28 августа 2010

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

case class VType(name: String) { 
   // ... maybe throw an exception ...
}

val v = LazyVal(() => new VType())
try {
   // do stuff with v
   println(v.name) // implicitly converts LazyVal[VType] to VType

   // do other unsafe stuff
} catch {
   case e => // handle VType constructor failure
   // can reference v after verifying v.isInitialized
} finally {
   // can reference v after verifying v.isInitialized
   if (v.isInitialized) v.safelyReleaseResources
}

, где LazyVal определяется как

/**
 * Based on DelayedLazyVal in the standard library
 */
class LazyVal[T](f: () => T) {
   @volatile private[this] var _inited = false
   private[this] lazy val complete = {
      val v = f()
      _inited = true
      v
   }

   /** Whether the computation is complete.
    *
    *  @return true if the computation is complete.
    */
   def isInitialized = _inited

   /** The result of f().
    *
    *  @return the result
    */
   def apply(): T = complete
}

object LazyVal {
   def apply[T](f: () => T) = new LazyVal(f)
   implicit def lazyval2val[T](l: LazyVal[T]): T = l()
}

Было бы хорошо, если бы мы могли использовать lazy val v = new VType(), но AFAIK не существует механизма, позволяющего безопасно определить, была ли инициализирована lazy val.

2 голосов
/ 29 августа 2010

Ваш пример не конкретизирует, зачем вам нужен пункт finally. Если VType, например, ресурс, который нужно закрыть, можно сделать одним из следующих способов.

1) Вы хотите сослаться на v после того, как оно выдает исключение:

try {
  val v = new VType // may throw
  try {
    v.someThing  // may throw
  }
  catch {
    case ex => println("Error on doing something with v :" + v + ex) // or whatever
  }
  finally {
    v.close()
  }
}
catch {
  case ex => println("Error on getting or closing v: " + ex)  // v might not be constructed
}

2) Вас не волнует v в предложении catch:

try {
  val v = new VType // may throw
  try {
    v.someThing  // may throw
  }
  finally {
    v.close()
  }
}
catch {
  case ex => println("Error on either operation: " + ex)
}

В любом случае вы избавляетесь от переменной.

...