smaricevic
smaricevic

Reputation: 3

Kotlin Generic problem, UNCHECKED_CAST , required:Nothing

@file:Suppress("UNCHECKED_CAST")

data class Element<T>(
    val key: String,
    val valueOne: T,
    val valueTwo: T,
    val comparator: Comparator<T>,
    val comparatorValue: CompareResult
)

enum class CompareResult(
    val value: Int
) {
    LESS(-1),
    EQUAL(0),
    GREATER_THAN(1)
}

fun <T> matchesComparison(list:Collection<Element<T>>): Pair<Boolean, List<String>> {
    val failedComparisons = mutableListOf<String>()
    for (element in list) {
        val compareValue = element.comparator.compare(element.valueOne, element.valueTwo)
        if (element.comparatorValue.value != compareValue) {
            failedComparisons.add(element.key)
        }
    }
    return Pair(failedComparisons.isEmpty(), failedComparisons)
}

val stringComparator = Comparator.comparing(String::toString)
val intComparator = Comparator.comparing(Int::toInt)

val elementsToCompare = listOf(
    Element("number", 1, 2, intComparator, CompareResult.LESS),
    Element("first name", "a", "a", stringComparator, CompareResult.EQUAL),
    Element("last name", "a", "b", stringComparator, CompareResult.EQUAL)
)

matchesComparison(elementsToCompare).second.joinToString(", ","Failed elements: \"","\"")

I often get faced with comparing two different object properties with the same values. As an example object A has props number,firstname,lastname. What i want to do is create a list have and have a function which goes over these Elements and returns which props have failed the comparison. I've managed to use generics for both the object and the matchesComparison function which returns the failed comparisons. The problem begins when i want to pass this list which is of type Collection<Element<out Any>> to this function is i get a type missmatch. instead of using unchecked casts to force the Comparator to be of type Any i would like to do this

val stringComparator = Comparator.comparing(String::toString) 
val intComparator = Comparator.comparing(Int::toInt)

The result value that of the script above should be Failed elements: "last name"

I tried changing the signature of the function to out any but then the comparator.compare method has both params as of type Nothing. I really want to avoid unsing unchecked casts.

Upvotes: 0

Views: 101

Answers (1)

broot
broot

Reputation: 28322

matchesComparison() doesn't need to be generic in this case. It doesn't really care what is the type of the whole input collection, so we can simply use * here.

Then we have another problem. The compiler isn't smart enough to notice that while we perform operations on a single element, all its properties are of matching types. As a result, it doesn't allow to use element.comparator on element.valueOne and element.valueTwo. To fix this problem, we simply need to create a separate function which works on a single Element, so it understand the type for all properties is the same:

fun matchesComparison(list:Collection<Element<*>>): Pair<Boolean, List<String>> {
    fun <T> Element<T>.matches() = comparatorValue.value == comparator.compare(valueOne, valueTwo)

    val failedComparisons = mutableListOf<String>()
    for (element in list) {
        if (!element.matches()) {
            failedComparisons.add(element.key)
        }
    }
    return Pair(failedComparisons.isEmpty(), failedComparisons)
}

Also, I believe such matches() function should be actually a member function of Element. It seems strange that while Element is pretty independent and it contains everything that is needed to perform a comparison, it still requires to use external code for this. If it would have a matches() function then we wouldn't need to care about its T. matches() would work with any Element.

Upvotes: 0

Related Questions