Подтвердить что ты не робот

Попробуйте блокировать область

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

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

Спасибо!

ОБНОВЛЕНИЕ

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

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

Мне нравится Rex Kerr ответ, но как бы исходный код выше быть обернут в вызове метода без введения локального var в тело метода?

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

4b9b3361

Ответ 1

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

try { val v = new VType() }

исключение можно было бы выбросить, прежде чем вы получите v назад, поэтому вы не можете безопасно ссылаться на v. Но если вы не можете ссылаться на v, то что вы можете сделать на последней стороне, которая не сломает или не выбросит свое собственное исключение или не будет иметь какое-то другое нечеткое поведение? Что делать, если вы создаете 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...
}

Или вы можете создать свой собственный метод shut-down-and-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.

Ответ 2

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

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

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

Ответ 3

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

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? Вы даже объявили, прежде чем мы приземлились в блоке catch?

Ответ 4

Концепция исключения не является подпрограммой блока 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.

Ответ 5

Если ваша главная проблема заключается в том, что 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.

Ответ 6

Здесь другая альтернатива:

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

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

Ответ 7

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