21

I'm trying to extend enum classes of type String with the following function but am unable to use it at the call site like so:

fun <T: Enum<String>> Class<T>.join(skipFirst: Int = 0, skipLast: Int = 0): String {
    return this.enumConstants
        .drop(skipFirst)
        .dropLast(skipLast)
        .map { e -> e.name }
        .joinToString()
}

MyStringEnum.join(1, 1);

What am I doing wrong here?

geg
  • 4,399
  • 4
  • 34
  • 35

4 Answers4

22

I suggest following solution:

fun <T : Enum<*>> KClass<T>.join(skipFirst: Int = 0, skipLast: Int = 0): String {
    return this.java
            .enumConstants
            .drop(skipFirst)
            .dropLast(skipLast)
            .map { e -> e.name }
            .joinToString()
}

Instead of attaching extension function to Class, i attached it to KotlinClass.

Now, you can simply use it:

enum class Test {ONE, TWO, THREE }

fun main(args: Array<String>) {
    println(Test::class.join())
}
// ONE, TWO, THREE
Ruslan
  • 14,229
  • 8
  • 49
  • 67
  • Good call, I'm relatively new to Kotlin and forgot about KClass – geg Mar 13 '16 at 05:49
  • This is not clean solution.. better to not write code like this... Who will know that you should look for functions in class of enum ? And you will forget too... – Renetik Jul 15 '21 at 19:14
10

Use of ::class is a nasty workaround. I suggest you to look at enumValues<E> and enumValueOf<E> from stdlib and do the same way:

inline fun <reified E : Enum<E>> joinValuesOf(skipFirst: Int = 0, skipLast: Int = 0): String =
        enumValues<E>().join(skipFirst, skipLast)

@PublishedApi
internal fun Array<out Enum<*>>.join(skipFirst: Int, skipLast: Int): String =
        asList()
                .subList(skipFirst, size - skipLast)
                .joinToString(transform = Enum<*>::name)

Usage: joinValuesOf<Thread.State>()

Miha_x64
  • 5,973
  • 1
  • 41
  • 63
  • 1
    why is `::class` bad? – geg Oct 30 '19 at 20:07
  • 7
    @geg, there's too much indirection. (1) `::class` allocates a special `KClass` object (which is, of course, not free), (2) `class.java` is JVM-only, and (3) `Class.enumConstants` uses reflection, thus, first call is slow, and it could fail with obfuscated code. – Miha_x64 Apr 15 '20 at 17:38
8

@IRus' answer is correct but you don't have to use reflection. For every enum class, a values() method is automatically generated by the compiler. This method returns an array containing all the entries. We can make the extension function operate directly on this array like this:

fun <T : Enum<*>> Array<T>.join(skipFirst: Int = 0, skipLast: Int = 0)
        = drop(skipFirst)
        .dropLast(skipLast)
        .map { e -> e.name }
        .joinToString()

And call it like this:

fun main(args: Array<String>) {
    Test.values().join()
}
Kirill Rakhman
  • 42,195
  • 18
  • 124
  • 148
4

I'll rewrite your join slightly like this with a wildcard:

fun <T: Enum<*>> Class<T>.join(skipFirst: Int = 0, skipLast: Int = 0): String {
    return this.enumConstants
            .drop(skipFirst)
            .dropLast(skipLast)
            .map { e -> e.name }
            .joinToString()
}

Then, assuming your MyStringEnum is defined like this:

enum class MyStringEnum { FOO, BAR, BAZ }

You can call it like this:

println(MyStringEnum.values()[0].javaClass.join())

to get output "FOO, BAR, BAZ"

Since you're defining join on Class, you need an actual Class object to call it on. Enum classes apparently don't work like that, but its defined enums can yield a Class with javaClass. So this is the best I could come up with that I think meets the general spirit of your request. I don't know if there is a more elegant way to achieve what you're trying to do for all enum classes like this.

EDIT: You can tighten this up a little bit more with this:

fun Enum<*>.join(skipFirst: Int = 0, skipLast: Int = 0): String {
    return this.javaClass.join(skipFirst, skipLast)
}

Which lets you call like this:

println(MyStringEnum.values()[0].join())
Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • Aha, you moved me in the right direction. Just tried `MyStringEnum::class.java.join()` without any complaints. – geg Mar 13 '16 at 05:37