What's the behavior of Iterable#all & Why did

2019-07-15 08:48发布

问题:

I'm trying an example in kotlin, like:

fun test(){    
    val harfler = listOf("a","b",'c','d')
    println(harfler.all { 
           it.javaClass == String::class.java || it.javaClass == Char::class.java 
    })
}

List contains Char or String but all function in this expression returns false,Why return false?

Can anybody explain it?

Edit for @JBNizet

回答1:

As @JB Nizet has already told you how to analyze the problem.

According to the Mapped Types, The Kotlin Char will be mapped to Java Type decide on its declaration.

  • when declare as a non-nullable type Char it is a primitive Java type char.
  • when declare as a nullable type Char? it is a Java wrapper type Character.
  • when declare as a type argument List<Char> it is a Java wrapper type Character.

    val it = 'a'
    //                v--- it should be `Any`
    val array: Array<Any> = arrayOf('a')
    
    //          v--- char
    println(it.javaClass)
    
    //             v--- print [java.lang.Character]  
    println(array.map { it.javaClass })
    

But I want to say that there is a different between the usage and the declaration. For example, the parameter type it is a java.lang.Character, but its javaClass is char.

fun typeOf(it: Char?) = it?.javaClass

fun test() {
    //                                          v--- java.lang.Character
    println(::typeOf.javaMethod!!.parameterTypes[0])
    //       v--- but it return `char` rather than `java.lang.Character`
    println(typeOf('a'))
}

And the example below show the different as further, this is why I declare the array type to Array<Any> rather than Array<Char> in preceding example:

//                v--- uses `java.lang.Character` instead
val array: Array<Char> = arrayOf('a')

//                       v--- java.lang.Character
println(array.javaClass.componentType)
//             v--- [char]
println(array.map { it.javaClass })

Why did the strange behavior occurs in Koltin?

This is because Kotlin Char and other wrapper classes represent 2 roles. one is a Java primitive type char, another is a Java wrapper class java.lang.Character. However, Kotlin Char is statically which means you can't change its type in runtime. and a Char should be mapped to a char by default in Kotlin.

IF you want get the wrapper type every time, you should use KClass.javaObjectType instead, for example:

//                  v--- char 
println(Char::class.java)
//                  v--- java.lang.Character
println(Char::class.javaObjectType)

The Iterable#all operation is a short-circuiting operation, which means if any first element didn't satisfied will return false immediately.

inline fun <T> Iterable<T>.all(predicate: (T) -> Boolean): Boolean {
    //   return `false` immediately the condition didn't satisfied
    //                        v                                            
    for (element in this) if (!predicate(element)) return false
    return true
}

When checking a Kotlin class like as Char and others. you should use the Kotlin type checking mechanism rather than traditional comparing approach, it helps you avoid such a confusion. for example:

val anything: Array<Any> = arrayOf('a')
val chars: Array<Char> = arrayOf('a')

println(chars.all { it is Char }) // print true
println(anything.all { it is Char }) // print true

So your code can replace with type checking as below:

fun test() {
    val harfler = listOf("a", "b", 'c', 'd')

    //                       v---------------v--- use type checking here 
    println(harfler.all { it is String || it is Char }) // print true
}