java에서는 당연히 가능하고, 아주 쉽습니다. java 의 generic이 . NET과는 달리 JVM 상에서는 타입을 지워버리는 (erasure) 특성때문에 적응하는데 좀 애를 먹었습니다. ㅋㅋ
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static <T> T createInstance(Class<T> clazz) { | |
Guard.shouldNotBeNull(clazz, "clazz"); | |
if (log.isDebugEnabled()) | |
log.debug("수형 [{}] 의 새로운 인스턴스를 생성합니다...", clazz.getName()); | |
try { | |
return (T) clazz.newInstance(); | |
} catch (Exception e) { | |
if (log.isWarnEnabled()) | |
log.warn(clazz.getName() + " 수형을 생성하는데 실패했습니다.", e); | |
return null; | |
} | |
} |
그럼 Scala에서는? 다행히 Scala 2.10.0 부터는 scala.reflect.runtime.unverse.TypeTag 로 더 많은 기능을 제공하지만, 기존 2.9.2 버전에서도 지원하는 scala.reflect.ClassTag 를 이용하면 java와는 달리 .NET처럼 수형을 제공하지 않아도 동적으로 수형을 알아낼 수 있더군요.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Generic 수형의 클래스에 대해 기본 생성자를 통한 인스턴스를 생성합니다. | |
*/ | |
def newInstance[T: ClassTag](): T = { | |
classTag[T].runtimeClass.newInstance().asInstanceOf[T] | |
} |
좀 더 나가서 기본 생성자 이외에 인자가 있는 클래스의 경우 인자를 주고 생성하는 경우는 어떨까? 제작해 보았습니다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Generic 수형의 클래스에 대해 지정된 생성자 인자에 해당하는 생성자를 통해 인스턴스를 생성합니다. | |
*/ | |
def newInstance[T: ClassTag](initArgs: Any*): T = { | |
if (initArgs == null || initArgs.length == 0) | |
return newInstance[T]() | |
val parameterTypes = initArgs.map(getClass(_)).toArray | |
val constructor = classTag[T].runtimeClass.getConstructor(parameterTypes: _*) | |
constructor.newInstance(initArgs.map(_.asInstanceOf[AnyRef]): _*).asInstanceOf[T] | |
} |
추출한 생성자에게 인자들을 제공하여 생성하면 됩니다.
여기서 문제가 발생했습니다... java의 primitive type인 boolean, char, byte, short, int, long, float, double 이 문제였습니다. Scala가 boxing, unboxing 을 최소화하기위해 scala.Int, scala.Long 등의 수형을 정의하여 자동으로 (implicit) 하게 변환이 되도록 하였습니다. 이러한 기능으로 JVM 에서 구현될 때 scala.Int, scala.Long 등을 java 의 primitive type 으로 매칭시켜줘야 합니다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def asJavaClass(x: Any): Class[_] = x match { | |
case x: scala.Boolean => java.lang.Boolean.TYPE | |
case x: scala.Char => java.lang.Character.TYPE | |
case x: scala.Byte => java.lang.Byte.TYPE | |
case x: scala.Short => java.lang.Short.TYPE | |
case x: scala.Int => java.lang.Integer.TYPE | |
case x: scala.Long => java.lang.Long.TYPE | |
case x: scala.Float => java.lang.Float.TYPE | |
case x: scala.Double => java.lang.Double.TYPE | |
case _ => x.getClass | |
} |
이 변환 메소드를 써야 제대로 실행됩니다.
마지막으로, 아예 생성자의 수형까지 지정해서 생성자를 찾을 수 있도록 하면 다음과 같습니다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def newInstanceWithTypes[T: ClassTag](initArgs: Any*)(initArgsTypes: Class[_]*): T = { | |
if (initArgs == null || initArgs.length == 0) | |
return newInstance[T]() | |
val parameterTypes = | |
if (initArgsTypes != null) initArgsTypes.toArray | |
else initArgs.map(asJavaClass(_)).toArray | |
val constructor = classTag[T].runtimeClass.getConstructor(parameterTypes: _*) | |
constructor.newInstance(initArgs.map(_.asInstanceOf[AnyRef]): _*).asInstanceOf[T] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class MyClass(var id: Int, var name: String) { | |
def this() { | |
this(0, "Unknown") | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Test | |
def instancingByDefaultContructor() { | |
val instance = ScalaReflects.newInstance[MyClass]() | |
Assert.assertNotNull(instance) | |
Assert.assertTrue(instance.isInstanceOf[MyClass]) | |
} | |
@Test | |
def instancingByParameterizedContructor() { | |
val instance = ScalaReflects.newInstance[MyClass](100, "Dynamic") | |
Assert.assertNotNull(instance) | |
Assert.assertTrue(instance.isInstanceOf[MyClass]) | |
} | |
@Test | |
def instancingByParameterizedContructorWithTypes() { | |
val instance = ScalaReflects.newInstanceWithTypes[MyClass](100, "Dynamic")(classOf[Int], classOf[String]) | |
Assert.assertNotNull(instance) | |
Assert.assertTrue(instance.isInstanceOf[MyClass]) | |
} |
Scala가 Generic 에서는 Java 보다 .NET에 유사하여 이해하기도 쉽고, 더 쉽게 적용이 가능하다고 생각되네요.
위에서 사용한 ClassTag[T] 의 단점은 Nested Class 에 대해서는 지원하지 않습니다. 내부 Class 도 지원하려면 TypeTag[T] 로 해야 합니다.
댓글 없음:
댓글 쓰기