Kotlin:リストキャストの操作方法:未チェックのキャスト:kotlin.collections.List <Kotlin.Any?>からkotlin.colletions.List <Waypoint>


108

List最初または最後のアイテム(経由ポイント)ではないaのすべてのアイテムを返す関数を書きたいのですが。関数はList<*>入力としてジェネリックを取得します。結果は、リストの要素が次のタイプの場合にのみ返されますWaypoint

fun getViaPoints(list: List<*>): List<Waypoint>? {

    list.forEach { if(it !is Waypoint ) return null }

    val waypointList = list as? List<Waypoint> ?: return null

    return waypointList.filter{ waypointList.indexOf(it) != 0 && waypointList.indexOf(it) != waypointList.lastIndex}
}

をにキャストするList<*>List<Waypoint>、次の警告が表示されます。

未チェックのキャスト:kotlin.collections.Listからkotlin.colletions.List

それ以外の場合の実装方法がわかりません。この警告なしでこの関数を実装する正しい方法は何ですか?

回答:


190

Kotlinでは、一般的なケースで実行時にジェネリックパラメーターをチェックする方法はありません(List<T>特殊なケースであるの項目をチェックするだけなど)。そのため、ジェネリックパラメーターを異なるジェネリックパラメーターにキャストすると、キャストは分散範囲内にあります。

ただし、さまざまな解決策があります。

  • タイプを確認しました。キャストが安全であることを確信しています。そのため、で警告抑制できます@Suppress("UNCHECKED_CAST")

    @Suppress("UNCHECKED_CAST")
    val waypointList = list as? List<Waypoint> ?: return null
  • .filterIsInstance<T>()アイテムタイプをチェックし、渡されたタイプのアイテムのリストを返す関数を使用します。

    val waypointList: List<Waypoint> = list.filterIsInstance<Waypoint>()
    
    if (waypointList.size != list.size)
        return null

    または1つのステートメントで同じ:

    val waypointList = list.filterIsInstance<Waypoint>()
        .apply { if (size != list.size) return null }

    これにより、目的のタイプの新しいリストが作成され(内部でチェックされないキャストが回避されます)、オーバーヘッドが少し発生しますが、同時に、を繰り返し処理しlistてタイプを(list.foreach { ... }インラインで)チェックする必要がなくなるため、目立つ。

  • タイプをチェックし、タイプが正しい場合は同じリストを返すユーティリティ関数を記述します。これにより、キャストをカプセル化します(コンパイラの観点からはまだチェックされていません)。

    @Suppress("UNCHECKED_CAST")
    inline fun <reified T : Any> List<*>.checkItemsAre() =
            if (all { it is T })
                this as List<T>
            else null

    使用法:

    val waypointList = list.checkItemsAre<Waypoint>() ?: return null

6
すばらしい答えです!最もクリーンなソリューションだと思うので、list.filterIsInstance <Waypoint>()ソリューションを選択します。
Lukas Lechner

4
使用filterIsInstanceして、元のリストに異なるタイプの要素が含まれている場合、コードはそれらを黙ってフィルタリングして除外することに注意してください。場合によってはこれが必要なこともありますが、場合によっては、IllegalStateExceptionまたは同様のものがスローされることがあります。後者の場合であれば、あなたはチェックするために、独自のメソッドを作成し、キャストすることができます:inline fun <reified R> Iterable<*>.mapAsInstance() = map { it.apply { check(this is R) } as R }
mfulton26

3
.applyラムダの戻り値を返さないことに注意してください、それは受信オブジェクトを返します。.takeIfオプションがnullを返すようにする場合は、おそらく使用します。
bj0 2018年

10

@hotkeyの答えを改善するために、ここに私の解決策があります:

val waypointList = list.filterIsInstance<Waypoint>().takeIf { it.size == list.size }

これにより、List<Waypoint>すべてのアイテムをキャストできる場合はnullを、それ以外の場合はnullを取得できます。


3

ジェネリッククラスの場合、型情報は実行時に消去されるため、キャストをチェックできません。ただし、リスト内のすべてのオブジェクトがWaypoints であることを確認して、で警告を抑制できます@Suppress("UNCHECKED_CAST")

このような警告を回避するには、Listに変換可能なオブジェクトのを渡す必要がありますWaypoint。使用している*が、このリストを型付きリストとしてアクセスしようとすると、常にキャストが必要になり、このキャストはオフになります。


1

Serializable to Listオブジェクトをチェックするために使用したとき、@ hotkeyの回答に少し変更を加えました。

    @Suppress("UNCHECKED_CAST")
    inline fun <reified T : Any> Serializable.checkSerializableIsListOf() =
        if (this is List<*> && this.all { it is T })
          this as List<T>
        else null

このような解決策を探していますが、このエラーが発生したCannot access 'Serializable': it is internal in 'kotlin.io'
daviscodesbugs

0

の代わりに

myGenericList.filter { it is AbstractRobotTurn } as List<AbstractRobotTurn>

するのが好き

myGenericList.filter { it is AbstractRobotTurn }.map { it as AbstractRobotTurn }

これがどれほど効果的かはわかりませんが、少なくとも警告はありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.