Как реализовать промежуточные типы для неявных методов?
Предположим, я хочу предложить метод foo
по существующему типу A
вне моего контроля. Насколько я знаю, канонический способ сделать это в Scala - реализовать неявное преобразование из A
к какому-то типу, который реализует foo
, Сейчас я в основном вижу два варианта.
Определите отдельный, возможно, даже скрытый класс для этой цели:
protected class Fooable(a : A) { def foo(...) = { ... } } implicit def a2fooable(a : A) = new Fooable(a)
Определите анонимный класс в строке:
implicit def a2fooable(a : A) = new { def foo(...) = { ... } }
Вариант 2), безусловно, менее шаблонный, особенно когда встречается много параметров типа. С другой стороны, я думаю, что это должно создать больше накладных расходов, поскольку (концептуально) создается один класс на преобразование, а не один класс в глобальном масштабе в 1).
Есть ли общие рекомендации? Разницы нет, потому что компилятор / ВМ избавляются от накладных расходов 2)?
2 ответа
Я считаю, что 1 и 2 скомпилированы в один и тот же байт-код (за исключением имени класса, которое генерируется в случае 2). Если Fooable существует только для того, чтобы вы могли неявно преобразовать A в Fooable (и вы никогда не собираетесь напрямую создавать и использовать Fooable), то я бы выбрал вариант 2.
Однако, если вы управляете A (имеется в виду, что A не является классом библиотеки Java, который не может быть подклассом), я бы рассмотрел использование черты вместо неявных преобразований для добавления поведения в A.
ОБНОВЛЕНИЕ: я должен пересмотреть свой ответ. Я бы использовал вариант 1 вашего кода, потому что вариант 2, как оказалось, использует отражение (scala 2.8.1 в Linux).
Я скомпилировал эти две версии одного и того же кода, декомпилировал их в java с помощью jd-gui, и вот результаты:
исходный код с именованным классом
class NamedClass { def Foo : String = "foo" }
object test {
implicit def StrToFooable(a: String) = new NamedClass
def main(args: Array[String]) { println("bar".Foo) }
}
исходный код с анонимным классом
object test {
implicit def StrToFooable(a: String) = new { def Foo : String = "foo" }
def main(args: Array[String]) { println("bar".Foo) }
}
скомпилирован и декомпилирован в Java с помощью Java-GUI. "Именованная" версия генерирует NamedClass.class, который декомпилируется в эту Java:
public class NamedClass
implements ScalaObject
{
public String Foo()
{
return "foo";
}
}
Аноним генерирует тестовый класс $$anon$1, который декомпилируется в следующую Java
public final class test$$anon$1
{
public String Foo()
{
return "foo";
}
}
так что почти идентично, за исключением того, что аноним "финальный" (очевидно, они хотят убедиться, что вы не сделаете все возможное, чтобы попытаться создать подкласс анонимного класса...)
однако на сайте вызова я получаю эту Java для "именованной" версии
public void main(String[] args)
{
Predef..MODULE$.println(StrToFooable("bar").Foo());
}
а это для анонима
public void main(String[] args) {
Object qual1 = StrToFooable("bar"); Object exceptionResult1 = null;
try {
exceptionResult1 = reflMethod$Method1(qual1.getClass()).invoke(qual1, new Object[0]);
Predef..MODULE$.println((String)exceptionResult1);
return;
} catch (InvocationTargetException localInvocationTargetException) {
throw localInvocationTargetException.getCause();
}
}
Я немного погуглил и обнаружил, что другие сообщали о том же самом, но я не нашел больше понимания того, почему это так.
Использование отдельного класса лучше для производительности, так как альтернатива использует отражение.
Считают, что
new { def foo(...) = { ... } }
действительно
new AnyRef { def foo(...) = { ... } }
Сейчас, AnyRef
нет метода foo
, В Scala этот тип на самом деле AnyRef { def foo(...): ... }
, который, если вы удалите AnyRef
следует признать структурным типом.
Во время компиляции это время можно передавать взад и вперед, и везде будет известно, что метод foo
вызывается. Однако в JVM нет структурного типа, и для добавления интерфейса потребуется прокси-объект, что может вызвать некоторые проблемы, такие как нарушение ссылочного равенства (т. Е. Объект не будет равен версии самого структурного типа).
Был найден способ использовать вызовы кэшированных отражений для структурных типов.
Итак, если вы хотите использовать шаблон Pimp My Library для любого чувствительного к производительности приложения, объявите класс.