1

I am in a process of converting my app from Java to Kotlin and I am getting a ClassCastException while executing the code below. Thoughts?

private fun getImageFragments(imagesList: MutableList<Restaurant?>?) {

   if (imagesList != null) {

   val restaurant: Restaurant? = imagesList[0] 
/*exception: java.lang.ClassCastException:
  java.lang.String cannot be cast to com.example.misc.Restaurant */    

   }
}

Not sure if the implementation of the Restaurant Class is of any importance in this case? https://pastebin.com/q5z76exh

While inspecting the contents of imagesList[0] with debugger I am seeing this:

ArrayList size = 1

    https://domainName.com/files/app/banners/1550449003515.png
    count=116
    hash:0
    shadow$_klass_=class java.lang.String
    shadow$_monitor_

Moreover, when I try to rewrite the problematic line to

val restaurant: String? = imagesList[0]

I am getting:

Type mismatch: inferred type is Restaurant? but String? was expected

EDIT:

So as per requests I am posting some more code, be warned though, it's not going to be pretty. Trust me, I would gladly show you the whole thing if it wasn't closed but the main reason is I don't want you to suffer. Ask if you want more. Here is the code of the function that is calling getImageFragments:

getDrawerBanners(App.preference.getString("language", "ENG"),
object : OnAPIRequestListResultListener<Restaurant?> {

    override fun onListResult(list: MutableList<Restaurant?>?) {
        bannerViewPager.adapter = fragmentManager?.
        let{ DrawerBannersAdapter(it, getImageFragments(list)) }
    }
})

And here is OnAPIRequestListResultListener interface, I have a strong feeling that error might be hidden smowewhere here, if you think otherwise read on further.

interface OnAPIRequestListResultListener<T> {
                fun onListResult(list: MutableList<T>?)
            }

Original signature + body of getDrawerBanners function

 // Downloading banners  from server
    @JvmStatic
fun getDrawerBanners(languageCode: String?, listener:
OnAPIRequestListResultListener<Restaurant?>) {
    val async = GetMenuBannersAsync(listener)
    async.execute(languageCode)
}

GetMenuBannersAsync

private class GetMenuBannersAsync(var listener: 
OnAPIRequestListResultListener<Restaurant?>) : AsyncTask<String?, Void?, Void?>() {
        var bannersUrls: MutableList<Restaurant?> = ArrayList()

        override fun doInBackground(vararg params: String?): Void? {
            val url = "https://api.smartapp.com/App/Language/" + params[0]
            val client: HttpClient = DefaultHttpClient()
            val httpGet = HttpGet(url)
                httpGet.addHeader(AUTHORIZATION, AUTHORIZATION_STRING)
            var httpResponse: HttpResponse? = null
            var httpEntity: HttpEntity? = null
                httpResponse = client.execute(httpGet)
                httpEntity = httpResponse.entity
            val response = EntityUtils.toString(httpEntity)
            val json = JSONObject(response)
                // Getting drawer banner
            val bannersArray = json.getJSONArray("mainMenuBanners")
                bannersUrls = ArrayList()

            for (i in 0 until bannersArray.length()) {
                val bannerObj = bannersArray.getJSONObject(i)
                val imageUrl = bannerObj.getString("image")
                    (bannersUrls as ArrayList<String>).add(imageUrl)
            }

            return null
        }

        override fun onPostExecute(result: Void?) {
            listener.onListResult(bannersUrls)
            super.onPostExecute(result)
        }

    }
10
  • It seems like your list contains a String object instead of a Restaurant. This can happen if you use reflection or for example your serialization library that uses reflection under the hood. Have you checked the actual content of the list with a debugger? Commented May 31, 2020 at 9:52
  • 1
    Show the code where You prepare paremeter imageList before call function getImageFragments. Commented May 31, 2020 at 10:12
  • 1
    Surely this is a bug, I have reproduced it pl.kotl.in/Gi028_MdC this might be due to the type-erasure, I'm going to file an issue about this. Commented May 31, 2020 at 10:32
  • 1
    @Jannik it is indeed a bug, you can see in Java you cannot cast an List<Integer> to List<Object> because they are invariant imgur.com/a/PV0KWKQ You can read more about covariance, invariance and contravariance here. The KDoc of kotlin already suggests that MutableList is invariant by default that means there is no relation between MutableList<Int> MutableList<Any>, that's why the cast shouldn't be successful. Commented May 31, 2020 at 14:10
  • 1
    @AmonChepri problem is indeed in that line (bannersUrls as ArrayList<String>).add(imageUrl), you should take care of the list and never put anything else than Restaurant? but as I said that code should never be compiled hence it is a bug. Commented May 31, 2020 at 14:16

1 Answer 1

3

The problem lies in your GetMenuBannersAsync function. There you create the bannersUrls list with type MutableList<Restaurant?> but the you put values into the list with (bannersUrls as ArrayList<String>).add(imageUrl). The cast expression tells the compiler that the conversion you do is save, which it is not in your case.

So always remember that you are on your own if your use a cast and the type system will no longer help you to detect problems. You need to make sure that the conversion is safe do do.

To solve the actual problem you need to create the Restaurant objects and put them into the list instead of urls.

Sign up to request clarification or add additional context in comments.

6 Comments

Problem is indeed in that line bannersUrls as ArrayList<String>, but it should never be successful because MutableLists and ArrayList both are invariant.
I don't see why the code should not compile. The variance does not matter for a cast. If the types are reference types you can cast them as you please. The compiler might warn you about an unchecked cast but that is the programmers problem not the compilers.
Ok, now I see where you where going. The part about the ClassCastException threw me off a bit :). But have a look at the slightly modified code: pl.kotl.in/aORaW-CWd The CCE was thrown only because the type of the destination of the value was Int and not Any. You probably meant that the cast in line 3 should fail at compile time as in the java version the second line. Thats a fair point for sure. But at least kotlinc tells you about the unchecked cast that is taking place. Also, in java you can provoke this behavior such that the compiler allows it (see the other lines).
true, fair point JVM type erasures are so bad, jetbrains issue tracker got a comment with same thing(due to erased generics). :)
Thank you so much! I've been fighting this issue for weeks...
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.