Kotlin Class Cast Cast Exception - PullRequest
       0

Kotlin Class Cast Cast Exception

0 голосов
/ 27 декабря 2018

Я новичок в разработке для Android, и я увидел этот фрагмент кода в учебнике

class MainActivity : AppCompatActivity() {
    private val newNumber by lazy(LazyThreadSafetyMode.NONE) { 
        findViewById<EditText>(R.id.newNumber) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val listener = View.OnClickListener {v ->
            val b = v as Button
            newNumber.append(v.text)
        }

    }
}

Я попытался понять оператор "как", поэтому я сделал этот код:

fun main(args: Array<String>) {
    open class View {
        fun a() {
            println("This is the View method")
        }

    }
    open class TextView: View() {
        fun b() {
            println("This is the TextView method")
        }
    }

    open class Button: TextView() {
        fun c() {
            println("This is the Button method")
        }
    }

    var v = View()

    var b = v as Button

    b.c()
}

Но я получаю эту ошибку:

Exception in thread "main" java.lang.ClassCastException: Simplest_versionKt$main$View cannot be cast to Simplest_versionKt$main$Button
    at Simplest_versionKt.main(Simplest version.kt:28)"

Почему это происходит?

Ответы [ 4 ]

0 голосов
/ 31 декабря 2018

Поскольку эта проблема не относится к Android, давайте создадим минимальный пример.

Рассмотрим следующую иерархию наследования, в которой у нас есть Fruit с двумя подклассами Apple и Banana:

open class Fruit
class Apple: Fruit()
class Banana: Fruit()

Давайте проведем некоторое тестирование с оператором safe cast как? , который возвращает null в случае неудачного приведения:

val fruit = Fruit()
fruit as? Apple // returns null - fruit is not of type Apple

val apple = Apple()
apple as? Fruit // apple is a Fruit
apple as? Banana // returns null - apple is not a Banana

Если вы создаете Fruit, этони Apple, ни Banana.Просто фрукт в целом.

Если вы создаете Apple, это Fruit, потому что Fruit - это его суперкласс, но Apple не имеет отношения к Banana.

0 голосов
/ 27 декабря 2018

Это базовая концепция Java.Если вы внимательно прочитали документацию по исключениям.

Брошено, чтобы указать, что код попытался привести объект к подклассу, экземпляром которого он не является.

Проще говоря, объект родительского класса(v) может быть приведено только к типу дочернего класса (Button), только если он содержит экземпляр объекта дочернего класса.

, поэтому правильный код будет

val v: View = Button()
val b = v as Button
b.c()
0 голосов
/ 27 декабря 2018

as - ключевое слово для кастинга в Котлине.Пример: someInstance as CastTarget.Эквивалент Java равен (CastTarget) someInstance.Обычно они зависят от языка, но некоторые языки имеют такой же синтаксис.С ++ имеет тот же синтаксис, что и Java (хотя он и имеет дополнительный, но это не относится к делу).

Кнопки расширяют вид.Это означает, что кнопка является представлением. Однако , это не означает, что View является кнопкой.Представлением также может быть TextView, ListView, RecyclerView и т. Д. Существует длинный список представлений, а также есть библиотеки, которые добавляют больше.

Это означает, что это действительно:

val view: View = Button(...)
val btn = view as Button

Это работает, потому что в данном случае представление представляет собой кнопку.Однако, если у вас есть:

val view: View = RecyclerView(...)
val btn = view as Button

, это не удастся.Это происходит потому, что по довольно очевидным причинам в этом случае RecyclerView не является кнопкой.Причина сбоя View(...) as Button в том, что View тоже не кнопка.Когда вы приводите, вы можете использовать только экземпляр как самого себя или как родительский, но не дочерний класс.Вот фактический пример:

interface Base 
class Parent : Base 
class Child1 : Parent()
class Child11 : Child1()
class Child2 : Parent()

Теперь, в этом случае, классы бесполезны.Они ничего не делают, но их все еще можно использовать для демонстрации наследования и приведения.

Теперь, скажем, у вас есть это:

val base = getRandomBaseChild()

Означает ли это, что у вас есть Child2?Предполагаемый тип здесь будет Base, что означает, что это может быть любой класс (или интерфейс, поскольку Base является интерфейсом), который расширяет / реализует Base.Это не имеет , чтобы быть Child2, но это может быть.Так как метод в этом случае будет случайным, это может произойти несколько раз, но не всегда:

val child2 = base as Child2

Это связано с тем, что в некоторых случаях основой будет Child2.Но для любого другого экземпляра это не Child2.

Скажем, мы взяли Child1 вместо:

val child1 = base as Child1

Это фактически имеет две действительные цели: Child1 и Child11.Вы можете всегда понижать, но никогда не повышать, если тип не соответствует.Теперь вы знаете, что это всегда будет успешным:

val obj = base as Any

Потому что все Any (/ Object в Java).Но обновление не обязательно будет успешным, пока тип не будет правильным.

Теперь, если вы находитесь в таком случае, когда тип действительно меняется, самый простой способ - использовать is:

if(base is Child2) // cast and do something 

В качестве альтернативы, есть более тяжелый подход с использованием as?.Обратите внимание, что это добавит обнуляемый тип;если произойдет сбой приведения, вы получите null:

val child2 = base as? Child2 ?: TODO("Cast failed");

Вы также добавили некоторый код;в ваших примерах вы всегда сможете использовать Button как TextView или View, а TextView можно преобразовать как View.Однако, если вы приведете View как TextView или Button, произойдет сбой, потому что тип не совпадает.

TL; DR:

Вид не является кнопкой.Чтобы ваш код работал, используйте val v: View = Button(), а затем приведите.v может быть приведен как дочерний, только если экземпляр, объявленный как родительский тип, фактически является указанным дочерним.Вы также можете использовать is, чтобы проверить, является ли тип совпадающим, прежде чем разыграть, или использовать as?, чтобы получить ноль, если он потерпит неудачу.


Вы также можете взглянуть на эту статью из Oracle о типах и наследовании.

0 голосов
/ 27 декабря 2018

В Kotlin as является оператором приведения типа. Эквивалент

val b = v as Button

в Java (игнорируйте нулевую проверку) равен

Button b = (Button) v;

Кроме того, listenerв первом фрагменте кода не используется.

Для вашего второго фрагмента кода Button, безусловно, является View, но то, что является View, может не быть Button.Если вы попытаетесь разыграть View, который на самом деле не является Button, вы получите это исключение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...