Kotlin: Cách làm việc với danh sách cast: Bỏ chọn Cast: kotlin.collections.List <Kotlin.Any?> Thành kotlin.colletions.List <Waypoint>


108

Tôi muốn viết một hàm trả về mọi mục trong một Listkhông phải là mục đầu tiên hoặc cuối cùng (một điểm qua). Hàm nhận một List<*>đầu vào chung chung . Một kết quả sẽ chỉ được trả về nếu các phần tử của danh sách thuộc loại 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}
}

Khi truyền List<*>đến List<Waypoint>, tôi nhận được cảnh báo:

Bỏ chọn Truyền: kotlin.collections.List vào kotlin.colletions.List

Tôi không thể tìm ra cách để thực hiện nó. Cách phù hợp để triển khai chức năng này mà không có cảnh báo này là gì?

Câu trả lời:


190

Trong Kotlin, không có cách nào để kiểm tra các tham số chung trong thời gian chạy (giống như chỉ kiểm tra các mục của a List<T>, đây chỉ là một trường hợp đặc biệt), vì vậy việc truyền một kiểu chung sang một kiểu chung khác với các tham số chung khác sẽ đưa ra cảnh báo trừ khi cast nằm trong giới hạn phương sai .

Tuy nhiên, có nhiều giải pháp khác nhau:

  • Bạn đã kiểm tra loại và bạn khá chắc chắn rằng bó bột an toàn. Do đó, bạn có thể ngăn chặn cảnh báo với @Suppress("UNCHECKED_CAST").

    @Suppress("UNCHECKED_CAST")
    val waypointList = list as? List<Waypoint> ?: return null
    
  • Sử dụng .filterIsInstance<T>()hàm, kiểm tra các loại mục và trả về một danh sách với các mục thuộc loại được truyền:

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

    hoặc giống nhau trong một câu lệnh:

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

    Thao tác này sẽ tạo ra một danh sách mới về kiểu mong muốn (do đó tránh bỏ chọn kiểu truyền bên trong), giới thiệu một chút chi phí, nhưng đồng thời, nó giúp bạn không phải lặp lại listvà kiểm tra các kiểu (trong list.foreach { ... }dòng), vì vậy nó sẽ không đáng chú ý.

  • Viết một hàm tiện ích để kiểm tra kiểu và trả về cùng một danh sách nếu kiểu là đúng, do đó đóng gói ép kiểu (vẫn chưa được kiểm tra theo quan điểm của trình biên dịch) bên trong nó:

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

    Với cách sử dụng:

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

6
Câu trả lời chính xác! Tôi chọn giải pháp list.filterIsInstance <Waypoint> () vì tôi nghĩ đó là giải pháp sạch nhất.
Lukas Lechner

4
Lưu ý rằng nếu bạn sử dụng filterIsInstancevà danh sách ban đầu chứa các phần tử thuộc loại khác, mã của bạn sẽ âm thầm lọc chúng. Đôi khi đây là những gì bạn muốn nhưng đôi khi bạn có thể muốn IllegalStateExceptionném một hoặc tương tự. Nếu sau này là trường hợp sau đó bạn có thể tạo phương pháp riêng của bạn để kiểm tra và sau đó đúc:inline fun <reified R> Iterable<*>.mapAsInstance() = map { it.apply { check(this is R) } as R }
mfulton26

3
Lưu ý rằng .applykhông trả về giá trị trả về của lambda, nó trả về đối tượng nhận. Bạn có thể muốn sử dụng .takeIfnếu bạn muốn tùy chọn trả về giá trị null.
bj0

10

Để cải thiện câu trả lời của @ phím nóng, đây là giải pháp của tôi:

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

Điều này cung cấp cho bạn List<Waypoint>nếu tất cả các mục có thể được đúc, nếu không.


3

Trong trường hợp phôi của các lớp chung không thể được kiểm tra vì thông tin kiểu bị xóa trong thời gian chạy. Nhưng bạn kiểm tra xem tất cả các đối tượng trong danh sách là Waypoints để bạn có thể loại bỏ cảnh báo với @Suppress("UNCHECKED_CAST").

Để tránh những cảnh báo như vậy, bạn phải chuyển một Listtrong các đối tượng có thể chuyển đổi sang Waypoint. Khi bạn đang sử dụng *nhưng cố gắng truy cập danh sách này dưới dạng danh sách đã nhập, bạn sẽ luôn cần truyền và diễn viên này sẽ không được chọn.


1

Tôi đã thực hiện một chút biến thể cho câu trả lời @hotkey khi được sử dụng để kiểm tra các đối tượng Có thể nối tiếp thành danh sách:

    @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

Đang tìm kiếm một giải pháp như thế này, nhưng lỗi này:Cannot access 'Serializable': it is internal in 'kotlin.io'
daviscodesbugs

0

Thay vì

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

Tôi thích làm

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

Không chắc điều này hiệu quả như thế nào, nhưng ít nhất không có cảnh báo.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.