Использование Option повсюду кажется немного неудобным.Я делаю что-то неправильно?
-
21-09-2019 - |
Вопрос
Благодаря статьям, которые я прочитал о Option
класс, который помогает вам избежать исключений NullPointerException, я начал использовать его повсюду.Представьте себе что-то вроде этого:
var file:Option[File] = None
и позже, когда я его использую:
val actualFile = file.getOrElse(new File("nonexisting"))
if(actualFile.getName.equals("nonexisting")) { // instead of null checking
}
else { // value of file was good
}
Делать подобные вещи мне кажется не совсем «правильным».Я также заметил, что .get
стал устаревшим..Ребята, вы тоже делаете что-то подобное с Option, или я иду неправильным путем?
Решение
Обычно возвращаться не лучшая идея. Option
а затем использовать getOrElse
для получения некоторого контрольного значения, которое означает «не найдено».Это то что Option
предназначен для:чтобы указать, что значение не найдено!
Option
действительно демонстрирует свою мощь при использовании в сочетании с конструкциями функционального программирования, такими как map
и foreach
.Это наиболее эффективно при работе с несколькими вариантами.Например, предположим, что я пишу метод, который принимает строку и возвращает мне файл, но только если файл существует и является файлом, а не каталогом:
import java.io._;
def niceFile1(s: String): File = {
val f = new File(s);
if (f.exists && !f.isDirectory) f else null
}
def niceFile2(s: String): Option[File] = {
val f = new File(s);
if (f.exists && !f.isDirectory) Some(f) else None
}
До сих пор, используя null
проще - по крайней мере, пока вы не забудете, что это может дать вам null
и вы получите NPE.В любом случае, давайте теперь попробуем его использовать.
def niceFopen1(s: String) = {
val f = niceFile1(s);
if (f!=null) new FileInputStream(f) else null;
}
def niceFopen2(s: String) = niceFile2(s).map(f => new FileInputStream(f))
Посмотрите, что произошло!В первом случае нам пришлось вручную выполнять логические тесты и создавать временные переменные.Фу!Во втором случае map
сделал за нас всю грязную работу:None было сопоставлено с None, и Some(file)
был сопоставлен с Some(fileinputstream)
.Легкий!
Но дальше становится лучше.Возможно, мы хотим узнать размер целой группы файлов:
def totalSize2(ss: Seq[String]) = {
(0L /: ss.flatMap(niceFile2)){(sum,f) => sum+f.length}
}
Подождите, что здесь происходит? А как насчет всех этих None
?Разве нам не нужно обращать на них внимание и как-то справляться с ними?Ну, вот где flatMap
приходит в:он объединяет все ответы в один список. None
является ответом нулевой длины, поэтому он игнорирует его. Some(f)
есть один ответ--f
--поэтому он помещает его в список.Затем мы используем складку, чтобы сложить все длины — теперь, когда все элементы в списке действительны.Довольно приятно!
Другие советы
Это хорошая идея не решить ценность Option
, но применять логика к тому, что она содержит:
findFile.foreach(process(_))
В основном это обрабатывает File
если кто-то найден и ничего не делает иначе (и это эквивалентно первому Томасу for
понимание, потому что for
компилируется в вызов foreach
).Это более краткая версия:
findFile match {
case Some(f) => process(f)
case None =>
}
Более того, самое замечательное в этом то, что вы можете цепь операции, что-то вроде:
(findLiveFile orElse fileBackupFile orElse findTempFile).foreach(process(_)
В большинстве случаев вы будете использовать сопоставление с образцом
file match {
case Some(f) => { .. } //file is there
case _ => { .. } //file is not there
}
Если вас интересует только файл, если он есть, вы можете использовать выражение for
for(f <- file) { //file is there
}
Затем вы можете создать цепочку выражений для работы на нескольких уровнях контейнеров.
for{
option <- List(Some(1), None, Some(2))
f <- option
} yield f
res0: List[Int] = List(1, 2)
Альтернативно вы можете использовать isDefined и получить:
if(option.isDefined) {
val x = option.get;
} else {
}
get не устарел в Scala 2.8.0.Beta-1.
Вот альтернатива:
var file:Option[File] = None
// ...
file map (new File(_)) foreach { fh =>
// ...
}
Однако если вам нужно что-то сделать, если файл существует, и что-то еще, если его нет, match
утверждение более уместно:
var file:Option[File] = None
// ...
file map (new File(_)) match {
case Some(fh) =>
// ...
case None =>
// ...
}
Это почти то же самое, что if
утверждение, но мне нравится, что оно лучше связывает природу для таких вещей, как Option
, где я также хочу извлечь значение.
В основном повторяю то, что все говорят, но я думаю, что есть как минимум 4 различные ситуации, с которыми вы столкнетесь, когда у вас будет такая переменная, допускающая значение NULL:
1.Файл не должен быть нулевым
load(file.get())
Вызовет исключение, когда вы попытаетесь использовать файл.Потерпите неудачу быстро, ура!
2.Файл имеет значение null, но в данном случае у нас есть разумное значение по умолчанию:
load(file.getOrElse(new File("/defaultFile")))
3.Две ветви логики в зависимости от существования файла:
file match {
case Some(f) => { .. } //file is there
case _ => { .. } //file is not there
}
4.Нет операции, если файл имеет значение null, то есть молчаливый сбой.Я не думаю, что этот вариант часто предпочтительнее для меня в реальной жизни:
for (f <- file) {
//do some stuff
}
Или, может быть, более четко?
if (f.isDefined) {
//do some stuff
}