2

I need to create a data class:

data class MyClass(val b: Boolean,
               val s: String,
               val flags: Map<Key<T>, T>)

This does not compile due to "Unresolved reference: T"

In the flags map, I need to have Key<String>, String and Key<Int>, Int pairs, but NOT Key<String>, Int

Any ideas on how I can achieve this?

Mugur
  • 1,019
  • 9
  • 21

3 Answers3

4

You must define the generic for the class:

data class MyClass<T>(val b: Boolean,
               val s: String,
               val flags: Map<Key<T>, T>)
guenhter
  • 11,255
  • 3
  • 35
  • 66
  • I feel like that's not going to work. If I'll have MyClass, wouldn't that allow a `Key, Int` pair? – Mugur Jul 20 '17 at 11:50
  • @Mugur `T` is invariant, so no... Besides, can't you test it? That would only work if you had `flags: Map, T>` – Salem Jul 20 '17 at 12:05
3

You can't do this directly through the type system, you will have to use encapsulation to ensure the types of key and value are consistent. This problem exists in Java as well.

The solution given by others doesn't work, since you'd only be able to put one type of key/value into the map (the idea of having a Map<Key<T>, T> is somewhat flawed):

val map = mutableMapOf<Key<String>, String>() // what to put as generic params here?
map[Key<String>()] = "blah" // works
map[Key<Int>()] = 3 // doesn't work!
map[Key<Int>()] = "blah" // compiler error (as intended)

or:

val map = mutableMapOf<Key<*>, Any?>()
map[Key<String>()] = "blah" // works
map[Key<Int>()] = 3 // works
map[Key<Int>()] = "blah" // works, but is wrong.

You'd have to for instance write your own encapsulation around a map that ensures a key always matches a value:

class MutableKeyMap {
    private val map = mutableMapOf<Key<*>, Any?>() // private / encapsulated

    operator fun <T> get(key: Key<T>): T {
        return map[key] as T
    }

    operator fun <T> set(key: Key<T>, value: T): Unit { // Key's T must match value's type.
        map[key] = value
    }
}

(You could add an immutable super-interface if you'd like)

And then use that as a field:

data class MyClass(val b: Boolean,
               val s: String,
               val flags: MutableKeyMap)

...

val map = MutableKeyMap()
map[Key<String>()] = "blah" // works
map[Key<Int>()] = 3 // works
map[Key<Int>()] = "blah" // compiler error (as intended)
Jorn Vernee
  • 31,735
  • 4
  • 76
  • 93
1

You've forgotten about this:

 data class MyClass<T>(val b: Boolean,
 // ----------------^
               val s: String,
               val flags: Map<Key<T>, T>)

For more info: Generics

Alex Romanov
  • 11,453
  • 6
  • 48
  • 51