Как получить интерфейс null vs throw в чистом виде Kotlin без Java - PullRequest
0 голосов
/ 25 февраля 2020

Рассмотрим этот пример:


CommonHandler. java:

Общий обработчик с дополнительными логами c. Здесь все очень просто:

import kotlin.jvm.functions.Function0;

public class CommonHandler {

  private void doCoolStuffBefore() { /* do some cool stuff */ }
  private void doCoolStuffAfter() { /* do some cool stuff */ }

  public <R> R getResult(Function0<R> provider) {
    R result;
    doCoolStuffBefore();
    try {
      result = provider.invoke();
    } finally {
      doCoolStuffAfter();
    }
    return result;
  }

}

NullableHandler.kt :

Версия обработчика, которая возвращает null, если операция вызывает исключение. Результат имеет тип R?

class NullableHandler : CommonHandler() {
  override fun <R> getResult(provider: Function0<R>): R? {
    return try {
      super.getResult(provider)
    } catch(ex: Throwable) {
      null
    }
  }
}

ThrowingHandler.kt :

Версия обработчика, которая включает ошибки в свой внутренний тип исключения. Тип результата: R.

class ThrowingHandler : CommonHandler() {

  class WrappedException(message: String, cause: Throwable?): Exception(message, cause)

  override fun <R> getResult(provider: Function0<R>): R {
    return try {
      super.getResult(provider)
    } catch(ex: Throwable) {
      throw WrappedException("Throwing handler failed with exception: ${ex.javaClass.name}", ex)
    }
  }

}

Api.kt :

В основном любой API, которым мы не владеем и не можем изменять.

object Api {

  fun find(query: String): Int =
      if (query.length > 3) 42
      else throw NoSuchElementException("Not found for $query")

  fun select(query: String): String =
      if (query.count { it == 'x' } > 2) "Selected"
      else throw NoSuchElementException("Not found for $query")

}

Теперь, имея все вышеперечисленные классы, мы можем реализовать API-оболочку:

object ApiProxy {

  private val throwingHandler = ThrowingHandler()
  private val nullableHandler = NullableHandler()

  fun find(query: String): Int = throwingHandler.getResult { Api.find(query) }
  fun findOrNull(query: String): Int? = nullableHandler.getResult { Api.find(query) }

  fun select(query: String): String = throwingHandler.getResult { Api.select(query) }
  fun selectOrNull(query: String): String? = nullableHandler.getResult { Api.select(query) }

}

У меня вопрос, как я могу реализовать подобную иерархию , не падая обратно к Java, так что существует один класс / интерфейс с методом, который может возвращать тип R или тип R?. Насколько я знаю, мы не можем объявить явно типы платформ в Kotlin с синтаксисом R!.

Ответы [ 2 ]

1 голос
/ 25 февраля 2020

Если вы определяете тип возврата как обнуляемый R:

open class CommonHandler {
    private fun doCoolStuffBefore() { /* do some cool stuff */
    }

    private fun doCoolStuffAfter() { /* do some cool stuff */
    }

    open fun <R> getResult(provider: () -> R): R? {
        doCoolStuffBefore()
        return try {
            provider.invoke()
        } finally {
            doCoolStuffAfter()
        }
    }

}

Тогда вы можете сузить область видимости до необнуляемого R в подклассе.

class ThrowingHandler : CommonHandler() {

    class WrappedException(message: String, cause: Throwable?): Exception(message, cause)

    override fun <R> getResult(provider: () -> R): R {
        return try {
            super.getResult(provider)!!
        } catch(ex: Throwable) {
            throw WrappedException("Throwing handler failed with exception: ${ex.javaClass.name}", ex)
        }
    }
}

Ваш ThrowableHandler предполагает, что API не может вернуть ноль, поэтому вполне разумно использовать здесь оператор !! и позволить любому KotlinNPE быть заключенным в ваше исключение WrappedException. Это на самом деле лучше, чем то, что у вас уже есть, потому что непредвиденное исключение содержится в вашей оболочке, а не всплывает в Java NPE где-либо еще в вашем коде. Если вам нужно обернуть API, который должен возвращать значение null, то я думаю, что вам также нужен NullableThrowingHandler.

1 голос
/ 25 февраля 2020

Я думаю, что простым решением было бы заставить getResult из CommonHandler возвращать обнуляемые результаты, а затем обрабатывать нулевой возврат, поскольку это было исключение. По крайней мере, это то, что пришло мне в голову вначале.

, поэтому, следуя подсказке, у нас будет общий обработчик, определенный как

open class CommonHandler {
    private fun doCoolStuffBefore() { /* do some cool stuff */
    }

    private fun doCoolStuffAfter() { /* do some cool stuff */
    }

    open fun <R> getResult(provider: Function0<R>): R? {
        doCoolStuffBefore()
        return try {
            provider.invoke()
        } finally {
            doCoolStuffAfter()
        }
    }
}

, NullableHandler не меняется, но он меняется ThrowingHandler

class ThrowingHandler : CommonHandler() {

    class WrappedException(message: String, cause: Throwable?): Exception(message, cause)

    override fun <R> getResult(provider: Function0<R>): R {
        return try {
            super.getResult(provider) ?: throw AnotherWrappedException("whooops")
        } catch(ex: Throwable) {
            throw WrappedException("Throwing handler failed with exception: ${ex.javaClass.name}", ex)
        }
    }

}

, где вы используете оператор elvis для возврата значения, если оно не равно нулю, или выбрасывает AnotherWrappedException("whooops"), если есть нулевое значение.

Что ты думаешь? Может ли это работать на вас?

...