область действия блока try

Я недоволен правилом области видимости переменной в блоке 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 сохраняется?

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

Спасибо !

ОБНОВЛЕНИЕ

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

Похоже, что консенсус подразумевает «просто продолжай в том же духе», и я ' Я начинаю делать вывод, что, возможно, технически то, что я хочу, является либо несостоятельным, не стоящим усилий или трудным для достижения.

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

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

18
задан Don Mackenzie 29 August 2010 в 00:51
поделиться

5 ответов

Возможно, вы неправильно думаете о проблеме. Зачем вам столько всего в блоке 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...
}

Или вы можете Метод -escape-safely в другом методе:

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

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

15
ответ дан 30 November 2019 в 06:14
поделиться

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

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

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

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

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

4
ответ дан 30 November 2019 в 06:14
поделиться

Если вас больше всего беспокоит то, что 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(), но, насколько мне известно, нет механизма для безопасного определения того, был ли инициализирован lazy val.

3
ответ дан 30 November 2019 в 06:14
поделиться

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

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 failed

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

3
ответ дан 30 November 2019 в 06:14
поделиться

Ваш пример не конкретизирует, зачем вам нужно предложение 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)
}

В любом случае вы избавитесь от var.

2
ответ дан 30 November 2019 в 06:14
поделиться
Другие вопросы по тегам:

Похожие вопросы: