Как избежать явного приведения - PullRequest
0 голосов
/ 23 апреля 2020

У меня проблема с написанием функции, которая принимает аргумент типа Any? и должна передать его методу CriteriaBuilder, который ожидает Comparable типов.

val arg : Any? = args.first()
return when (operator) {
            EQUAL -> criteriaBuilder.equalPredicate(arg, root)
            GREATER_THAN -> criteriaBuilder.greaterThanPredicate<Comparable<Comparable<*>>>(arg, root)
            else -> throw IllegalStateException("Unknown comparison operator: $operator")
        }


private fun <Y : Comparable<Y>> CriteriaBuilder.greaterThanPredicate(arg: Any?, root: Root<T>) : Predicate {
        check(arg != null, { "Cannot compare null arguments" })
        return when (arg) {
            is Comparable<*> -> greaterThan(root.get(property), arg as Y)
            else -> throw UnsupportedOperationException("Cannot process comparation of: $arg of class ${arg.javaClass}")
        }
    }

Это решение на самом деле " работает ", но у него есть следующие проблемы:

1) Я должен привести arg as Y, не зная, является ли arg на самом деле типом Comparable, который учитывает утверждение:" Я - объект типа A, который является Comparable<A> ".

2) Поскольку невозможно вывести параметр типа Y, я должен явно установить его во время вызова метода. Кстати, я должен вставить уродливый Comparable<Comparable<*>>, который не соблюдает контракт, который я хочу утверждать. data class A : Comparable<B> передает это утверждение, но мне нужен класс, подобный data class A: Comparable<A>.

Может ли кто-нибудь помочь мне понять, как разблокировать эту ситуацию? :)

Ответы [ 2 ]

0 голосов
/ 26 апреля 2020

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

data class ClassInterfaces(val clazz: Class<*>, val interfaces: List<Class<*>>)

private fun extractClassTree(arg: Any): List<Class<*>> =
    extractClassTree(arg::class.java, mutableListOf())

private fun extractClassTree(clazz: Class<*>, seen: MutableList<Class<*>>): List<Class<*>> {
    val classesToCheck = listOf(*clazz.interfaces, clazz.superclass).filterNotNull()
    return listOf(clazz,
        *classesToCheck
            .filter { seen.contains(it).not() }
            .onEach { seen.add(it) }
            .flatMap { extractClassTree(it, seen) }
            .toTypedArray()
    )
}

fun checkIfComparableOfSameType(arg: Any) = extractClassTree(arg)
    .any { checkIfcomparableOfSameType(it) }


fun checkIfcomparableOfSameType(clazz: Class<*>): Boolean {
    val comparableType = clazz
        .annotatedInterfaces
        .map { it.type as? ParameterizedType }.filterNotNull()
        .find { it.rawType == Comparable::class.java }
        ?.actualTypeArguments
        ?.first()
    return when (comparableType) {
        is ParameterizedType -> comparableType.rawType == clazz
        else -> comparableType == clazz
    }
}

Тогда я могу смело использовать свой метод:

private inline fun <reified Y : Comparable<Y>> CriteriaBuilder.comparablePredicate(
        operator: ComparisonOperator, arg: Any?, root: Root<T>
    ): Predicate {
        check(arg != null) { "Cannot compare null arguments" }
        check(checkIfComparableOfSameType(arg)) { "${arg.javaClass} is not a comparable of his same type" }

        val castedArg = arg as Y
        return when (operator) {
            GREATER_THAN -> greaterThan(root.get(property), castedArg)
            GREATER_THAN_OR_EQUAL -> greaterThanOrEqualTo(root.get(property), castedArg)
            LESS_THAN -> lessThan(root.get(property), castedArg)
            LESS_THAN_OR_EQUAL -> lessThanOrEqualTo(root.get(property), castedArg)
            else -> throw IllegalArgumentException("Unexpected operator")
        }
    }
0 голосов
/ 23 апреля 2020

Похоже, вам нужно объявить тип arg как Y. Тогда вы знаете, что arg не является ненулевым Comparable<Y>, поэтому вам не нужно проверять, является ли оно нулевым или сопоставимым, и тип вашей функции может быть выведен из аргумента, который вы передаете при его вызове.

fun <Y : Comparable<Y>> CriteriaBuilder.greaterThanPredicate(arg: Y, root: Root<T>) : Predicate {
    return greaterThan(root.get(property), arg)
}

Вам все равно придется выполнить приведение, поскольку вы делаете предположение, основанное на значениях времени выполнения, которые компилятор не может проверить. Но вы можете, по крайней мере, переместить приведение к тому месту, где вы делаете предположение, и выполнить там безопасную проверку.

val arg : Any? = args.first()
return when (operator) {
            EQUAL -> criteriaBuilder.equalPredicate(arg, root)
            GREATER_THAN -> {
                (arg as? Comparable<Int>) ?: throw IllegalStateException("Unsupported or missing argument")
                criteriaBuilder.greaterThanPredicate(arg, root)
            }
            else -> throw IllegalStateException("Unknown comparison operator: $operator")
        }

Если вы поддерживаете несколько сопоставимых типов, вам может потребоваться перечислить их:

val arg : Any? = args.first()
return when (operator) {
            EQUAL -> criteriaBuilder.equalPredicate(arg, root)
            GREATER_THAN -> when {
                arg is Int -> criteriaBuilder.greaterThanPredicate(arg, root)
                arg is Double -> criteriaBuilder.greaterThanPredicate(arg, root)
                else -> throw IllegalStateException("Unsupported or missing argument")
            }
            else -> throw IllegalStateException("Unknown comparison operator: $operator")
        }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...