如何将 class 的实例与一种接口类型进行比较?

How can I compare an instance of a class to a type of interface?

我正在尝试基于 this article 在 Kotlin 中实现一个简单的服务定位器,但我想使用泛型类型参数。我还试图避免使用具体化的类型参数内联函数,因为这要求所有内容都是 public.

这个class负责缓存定位服务的实例:

class Cache {
    private val services: MutableList<Any> = mutableListOf()

    fun <T> getService(serviceClass: Class<T>): T? {
        val service = services.firstOrNull { s -> s::class.java == serviceClass }

        if (service != null) {
            return service as T
        }

        return null
    }

    fun addService(service: Any) {
        services.add(service)
    }
}

这是调用缓存的方式:

cache.getService(IMyService::class)

它 returns 每次都为 null,无论它是否包含 MyService 的实例。问题出在 s::class.java == serviceClass,因为在运行时缓存包含一个实例 MyService,并且 MyService::class.java 不等同于 IMyService::class(也不等同于 MyService::class - 我试过了那也是)。

我试过像这样修改 getService 方法:

fun <T> getService(): T? {
    val service = services.firstOrNull { s -> s is T }

    if (service != null) {
        return service as T
    }

    return null
}

s is T 上,编译器抱怨 "Cannot check for instance of erased type: T"。如何在没有内联的情况下完成这项工作,这需要制作服务列表 public?

如果您同意反射,您可以使用 isAssignableFrom 来检查所请求的 Class 是否是您拥有的给定 Class 的 superclass/superinterface缓存:

fun <T> getService(serviceClass: Class<T>): T? {
    return services.firstOrNull { s -> serviceClass.isAssignableFrom(s::class.java) } as T?
}

只是为了回答关于内联问题的另一半,您可以通过使用 @PublishedApi 注释和标记字段 internal 来避免制作服务映射 public。例如:

class Cache {
    @PublishedApi internal val services: MutableList<Any> = mutableListOf()

    inline fun <reified T> getService(serviceClass: Class<T>): T? {
        return T::class.java.let { it.cast(services.firstOrNull(it::isInstance)) }
    }

    fun addService(service: Any) {
        services.add(service)
    }
}

感谢 kcoppock 的 @PublishedApi 注释的好主意,您也可以使用 is,避免反射并使函数非常简洁:

class Cache {
    @PublishedApi internal val services: MutableList<Any> = mutableListOf()

    inline fun <reified T> getService() = services.firstOrNull { it is T } as T?

    // ...
}

如果你想保持 services 私有(或使 getService 在 Java 中可用),并且仍然保持 Kotlin 更好的可用性,只需添加一个重载:

fun <T> getService(serviceClass: Class<T>): T? { ... }

inline fun <reified T> getService(): T? = getService(T::class.java)