GeekFactory

int128.hatenablog.com

Use trait with generics in Groovy

Groovyでジェネリクス付きのtraitを使う場合にちょっとハマったのでメモを残します。

前提

  • Groovy 2.3.6
  • JDK 7u72

下記のコードを実行してみます。

@groovy.transform.Immutable class Person {
  final String name
  final int age
}

trait NamedMap<T> {
  boolean add(T item) {
    put(item.name, item) ? false : true
  }
}

def peoples = [:] as NamedMap<Person>
assert peoples.add(new Person('Taro', 30))
assert peoples.Taro.age == 30

このコードを実行すると、以下の実行時エラーが発生します。

org.codehaus.groovy.runtime.typehandling.GroovyCastException: Error casting map to NamedMap, Reason: startup failed: NamedMap$TraitAdapterwrapper: -1: A transform used a generics containing ClassNode NamedMap <T extends java.lang.Object -> java.lang.Object> for the super class NamedMap$TraitAdapter directly. You are not supposed to do this. Please create a new ClassNode referring to the old ClassNode and use the new ClassNode instead of the old one. Otherwise the compiler will create wrong descriptors and a potential NullPointerException in TypeResolver in the OpenJDK. If this is not your own doing, please report this bug to the writer of the transform. @ line -1, column -1. 1 error

対処

下記のようにジェネリクス付きtraitを継承したtraitを定義したら解決しました。これが本格対処になるのかは分かりません。

@groovy.transform.Immutable class Person {
  final String name
  final int age
}

trait NamedMap<T> {
  boolean add(T item) {
    put(item.name, item) ? false : true
  }
}

trait PersonMap implements NamedMap<Person> {}

def peoples = [:] as PersonMap
assert peoples.add(new Person('Taro', 30))
assert peoples.Taro.age == 30

参考文献