Лучшая имитация Scala сейфа Groovy - разыменовывает оператор (?).?

Я перепробовал большинство опций в этой теме (и многие другие), но у меня ничего не получалось. Наконец-то это сработало, и я подумал поделиться им со всеми ...

У меня есть несколько версий Eclipse IDE на моей машине (потому что я также работаю над проектами, не относящимися к Android). Случайно я создал свой проект Android, используя рабочее пространство, которое использовалось другой (не Android) версией Eclipse. Из-за этого я предполагаю, что происходило что-то странное, и моя R.java никогда не генерировалась!

Чтобы исправить это, все, что я сделал, это создал новое рабочее пространство, и волшебным образом R.java начал генерироваться, и все прошло гладко ...

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

25
задан Community 23 May 2017 в 12:32
поделиться

5 ответов

Как насчет этого?

def ?[A](block: => A) =
  try { block } catch {
    case e: NullPointerException if e.getStackTrace()(2).getMethodName == "$qmark" => null
    case e => throw e
  }

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

val a = ?(b.c.d.e)

a == null, если b, bc, или bcd, или bcde. null, в противном случае a == bcde

Я думаю, что значение оператора безопасного разыменования уменьшается, когда вы используете такой язык, как Scala, который имеет такие возможности, как вызов по имени и имплициты.

ps: Я изменяю приведенный выше код немного в свете одного из комментариев ниже, чтобы обработать случай, когда NullPointerException фактически брошен внутри вызываемой функции.

Кстати, я думаю, что использование приведенной ниже функции является более идиоматическим способом написания Scala:

def ??[A](block: => A): Option[A] = ?(block) match {
    case a: A => Some(a)
    case _ => None
  }

вот так:

??(a.b.c.d) match {
    case Some(result) => // do more things with result
    case None => // handle "null" case
  }
8
ответ дан 28 November 2019 в 20:51
поделиться

There are two things that need to be considered here.

First, there is the problem of the "nothing". How do you chain things when a part of the chain may not return anything? The answer is using Option and for comprehensions. For example:

scala> case class Address(city: Option[String] = None, street: Option[String] = None, number: Option[Int] = None)
defined class Address

scala> case class Contact(name: String, phone: Option[String] = None, address: Option[Address] = None)
defined class Contact

scala> case class ContactDetails(phone: Option[String] = None, address: Option[Address] = None)
defined class ContactDetails

scala> case class Contact(phone: Option[String] = None, address: Option[Address] = None)
defined class Contact

scala> case class Person(name: String, contactDetails: Option[Contact] = None)
defined class Person

scala> case class Company(name: String, contactPerson: Option[Person] = None)
defined class Company

scala> val p1 = Company("ABC", Some(Person("Dean", Some(Contact(None, Some(Address(city = Some("New England"))))))))
p1: Company = Company(ABC,Some(Person(Dean,Some(Contact(None,Some(Address(Some(New England),None,None)))))))

scala> val p2 = Company("Finnicky", Some(Person("Gimli", None)))
p2: Company = Company(Finnicky,Some(Person(Gimli,None)))

scala> for(company <- List(p1, p2);
     | contactPerson <- company.contactPerson;
     | contactDetails <- contactPerson.contactDetails;
     | address <- contactDetails.address;
     | city <- address.city) yield city
res28: List[String] = List(New England)

This is how you are supposed to write code which may return something or not in Scala.

The second problem, of course, is that sometimes you may not have access to the source code to do the proper convertion. In this case, there is some additional syntax overhead to be head, unless an implicit can be used. I'll give an example below, in which I use an "toOption" function -- there is such a thing on Scala 2.8, of which I'll talk about below.

scala> def toOption[T](t: T): Option[T] = if (t == null) None else Some(t)
toOption: [T](t: T)Option[T]

scala> case class Address(city: String = null, street: String = null, number: Int = 0)
defined class Address

scala> case class Contact(phone: String = null, address: Address = null)
defined class Contact

scala> case class Person(name: String, contactDetails: Contact = null)
defined class Person

scala> case class Company(name: String, contactPerson: Person = null)
defined class Company

scala> val p1 = Company("ABC", Person("Dean", Contact(null, Address(city = "New England"))))
p1: Company = Company(ABC,Person(Dean,Contact(null,Address(New England,null,0))))

scala> val p2 = Company("Finnicky", Person("Gimli"))
p2: Company = Company(Finnicky,Person(Gimli,null))

scala> for(company <- List(p1, p2);
     | contactPerson <- toOption(company.contactPerson);
     | contactDetails <- toOption(contactPerson.contactDetails);
     | address <- toOption(contactDetails.address);
     | city <- toOption(address.city)) yield city
res30: List[String] = List(New England)

Remember that you can be quite creative in naming a function. So, instead of "toOption", I might have named it "?", in which case I'd write things like "?(address.city)".

Thanks to nuttycom for reminding me, on Scala 2.8 there is an Option factory on the object Option, so I can just write Option(something). In effect, you can replace "toOption" above with "Option". And if you prefer using ?, you can just use import with rename.

15
ответ дан 28 November 2019 в 20:51
поделиться

Монадическое связывание (flatMap / map) с типом scala.Option. Поддержка также обеспечивается for-complation. Scalaz предоставляет, если хотите, стиль аппликативного функтора.

Это не эквивалент, но гораздо лучшее решение, чем оператор Groovy по многим причинам.

6
ответ дан 28 November 2019 в 20:51
поделиться

Поскольку это выглядело бы ужасно как комментарий, вот комментированная версия кода Уолтера:

/**
 * Safe dereference operator. E.g. ?(a.b.c.null.dd)
 */
def ?[A](block: => A) = {
  try { block } catch {
    // checks to see if the 3rd to last method called in the stack, is the ?() function, 
    // which means the null pointer exception was actually due to a null object, 
    // otherwise the ?() function would be further down the stack.
    case e: NullPointerException if e.getStackTrace()(2).getMethodName == "$qmark" => {null}
    // for any other NullPointerException, or otherwise, re-throw the exception.
    case e => throw e
  }

И спецификация, которая проходит:

case class Company(employee:Employee)
case class Employee(address:Address){
  def lookupAddressFromDb:Address = throw new NullPointerException("db error")
}
case class Address(city:String)

"NullSafe operater" should {
  "return the leaf value when working with non-null tree" in {
    val company = Company(Employee(Address("Auckland")))
    val result = ?( company.employee.address.city )
    result mustEq "Auckland"
  }
  "return null when working with a null element at some point in the tree" in {
    val company = Company(null)
    val result = ?( company.employee.address.city )
    result must beNull
  }
  "re-throw the NPE when working with a method which actually throws a NullPointerException" in {
    val company = Company(Employee(Address("Auckland")))
    ?( company.employee.lookupAddressFromDb.city ) aka "the null-safe lookup method" must throwA[NullPointerException]
  }   
}
2
ответ дан 28 November 2019 в 20:51
поделиться

Не мой, а коллега

class NullCoalescer[T <: AnyRef](target: T) {
    def ?? (other: T) =
        if(target == null) other else target
}
object NullCoalescerConversions {
    implicit def toNullCoalescer[T <: AnyRef](target: T): NullCoalescer[T] = 
        new NullCoalescer(target)
}

println (System.getProperty("maybe") ?? "definitely")
4
ответ дан 28 November 2019 в 20:51
поделиться
Другие вопросы по тегам:

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