Можем ли мы достичь безопасности типа компиляции для объединения типов, которые мы не можем контролировать?

Допустим, у меня есть функция:

fun doSomething(vararg pairs: Pair<String, *>) { // Do things with the pairs } 

Проблема с этим подходом заключается в том, что он позволяет использовать любой тип для второй половины Pair (например, Pair<String, CustomType1> ).

Что, если я хочу только разрешить конечное число типов, как бы я это достиг?

Если функция имела более простую подпись, я мог бы получить ограничение через перегрузку, например:

 fun doSomethingSimpler(param: Boolean) { // Boolean implementation } fun doSomethingSimpler(param: Int) { // Int implementation } // etc. 

Если ограниченный тип «set» был в моем распоряжении, я мог бы использовать интерфейс или закрытый класс для достижения этого. Например

 sealed class Root class Child1 : Root() class Child2 : Root() fun doSomethingICanControl(param: Root) { // Root implementation } 

Но что, если у меня нет контроля над типами, или они примитивны, как я не могу позволить себе все пропускать?

Я знаю, что могу использовать smart-casts для обеспечения безопасности во время выполнения, но можно ли это сделать во время компиляции?

Или язык запрещает?

Редактировать 1

Я знаю, что могу создавать свои собственные типы ящиков (например, MyBoolean ) и использовать общий интерфейс или закрытый класс, но это будет шаблон, который каждый должен будет писать каждый раз, когда ему это нужно.

Изменить 2

Чтобы быть ясным, я хотел бы сделать так:

 doSomething( "key1" to false, "key2" to "value2", "key3" to 86 ) 

… Т.е. есть смешанный набор «вторых» ( Pair ) типов.

Итак, чтобы быстро это вычислить:

Вы хотите вызвать методы из библиотеки, которая ожидает Pair<String, *> , но ограничить возможные значения, которые могут быть * .

TL; DR: То, что вы пытаетесь выполнить, невозможно без какой-либо обертки, потому что

  1. Мы не имеем Sum-Types в Kotlin, поэтому не можем сказать компилятору, что вы ожидаете Int или Double или Float и ничего больше
  2. Если библиотечный метод ожидает, что что-то будет Pair<String, *> , нам нельзя сказать компилятору, что мы просто хотим дать ему String вместо *

Один из способов получить это поведение – создать Decorator ( Decorator Pattern ), например создать собственные методы расширения, которые позволяют использовать только подмножество

 class Foo { //Allows everything fun doSomething(param: Pair<String, *>) } //Now lets create our own extension methods fun Foo.doSomethingWithInt(param: Pair<String, Int>) fun Foo.doSomethingWithBoolean(param: Pair<String, Boolean>) fun Foo.doSomethingWithString(param: Pair<String, String>) 

Или, если вы не хотите, чтобы вы могли вызвать Foo.doSomething() вы можете создать класс Decoratoror:

 class FooDecorator { val foo = Foo() fun doSomething(param: Pair<String, Int>) { } } 

И следующий пример невозможен без какого-либо Wrapper, потому что в Котлине нет Sum-Types:

 doSomething( "key1" to false, "key2" to "value2", "key3" to 86 ) 

Что вы можете сделать, это что-то вроде:

Сначала создайте свой собственный тип JSONItem и добавьте методы расширения в типы, которые можно использовать как один

 class JSONItem<T> private constructor (item: T) fun Int.asJSONItem() = JSONItem(this) fun String.asJSONItem() = JSONItem(this) fun Boolean.asJSONItem() = JSONItem(this) 

Тогда вы можете сделать что-то вроде этого:

 //Your own personal doSomething fun doSomething(varargs: param: Pair<String, JSONItem>) { //Call the real doSomething() doSomething(param.map { Pair(it.first, it.second.item) }} } doSomething( "key1" to false.asJSONItem(), "key2" to "value2".asJSONItem(), "key3" to 86.asJSONItem() ) 

Обозначенные типы соединений и пересечений в настоящее время не поддерживаются в Котлине (начиная с 1.1.x).

Это актуальная проблема.