Попробуйте блокировать область
Я недоволен правилом об области видимости переменной в блоке 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.
Ответы
Ответ 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.