Использование дженериков Java и C # для имитации утиной печати

http://nullprogram.com/blog/2014/04/01/ пытается чтобы объяснить, что универсальные шаблоны Java не могут имитировать утиную печать, на примере:

class Caller<T> {
    final T callee;
    Caller(T callee) {
        this.callee = callee;
    }
    public void go() {
        callee.call();  // compiler error: cannot find symbol call
    }
}

class Foo {
    public void call() { System.out.print("Foo"); }
}

class Bar {
    public void call() { System.out.print("Bar"); }
}

public class Main {
    public static void main(String args[]) {
        Caller<Foo> f = new Caller<>(new Foo());
        Caller<Bar> b = new Caller<>(new Bar());
        f.go();
        b.go();
        System.out.println();
    }
}

Программа выйдет из строя из-за ошибки времени компиляции. Это результат стирания типа. В отличие от шаблонов C ++, будет только одна скомпилированная версия Caller, а T станет Object. Поскольку Object не имеет метода call(), компиляция не выполняется.

Означает ли это, что в дженериках Java методы параметра типа ограничиваются методами класса java.lang.Object?

Дженерики C # реализованы в терминах реификации вместо стирания типов. У дженериков C # нет вышеуказанного ограничения, как у дженериков Java? Так могут ли универсальные шаблоны C # достичь того же, что и утиная печать?

Спасибо.


person Tim    schedule 18.09.2017    source источник


Ответы (3)


могут ли универсальные шаблоны C # достичь того же, что и утиная печать?

Нет. Но универсальные шаблоны C # могут включать ограничение, в котором параметр типа ограничен наследованием или реализацией определенного типа. Когда это будет сделано, любое выражение типа этого параметра типа будет разрешено, поскольку будет доступен ограниченный тип и члены этого типа.

Это похоже на ограничение extends, описанное в статье, которую вы читаете.

Единственная поддержка «утки» в C # - это ключевое слово dynamic, при котором окончательная компиляция выражений, содержащих значения dynamic, откладывается до времени выполнения, когда известен фактический тип среды выполнения.

Связанное чтение:

Тривиальный класс C # с универсальным параметром не компилируется без видимой причины
Вызов метода параметра типа

person Peter Duniho    schedule 18.09.2017
comment
Спасибо. Универсальные шаблоны C # могут включать ограничение, в котором параметр типа ограничен наследованием или реализацией определенного типа. Разрешено ли генерикам C # не иметь такого ограничения? Если да, то разве дженерики C # без такого ограничения не достигают того же, что и утка? - person Tim; 18.09.2017
comment
Разве универсальные шаблоны C # без такого ограничения не достигают того же, что и утка? - нет. Без ограничения (что разрешено ... параметр типа может быть полностью открыт, если хотите), тип неявно ограничен значением System.Object, а код, использующий этот универсальный тип, может использовать только члены из System.Object (т.е. Equals() и GetHashCode()). - person Peter Duniho; 18.09.2017
comment
Почему без ограничения (что разрешено ... параметр типа может быть полностью открыт, если хотите), тип неявно ограничивается значением System.Object? Вы имеете в виду, что невозможно, чтобы параметр типа мог быть полностью открыт, если хотите? - person Tim; 18.09.2017
comment
Между прочим, ИМХО, упомянутый автор блога неправильно объединяет шаблоны C ++ с утиным вводом. То есть да ... шаблоны в C ++ имеют аналогичный эффект. Но по-прежнему существует безопасность типов во время компиляции; шаблон применяется во время компиляции, и в это время должен быть известен тип объектов, используемых в шаблоне. Это немного похоже на утиную типизацию, но IMHO утиная типизация более правильно применяется к поведению на динамически типизированных языках. - person Peter Duniho; 18.09.2017
comment
Вы имеете в виду, что невозможно, чтобы параметр типа мог быть полностью открытым, если хотите - я полагаю, это зависит от того, что вы подразумеваете под полностью открытым. Каждый объект в C # наследует System.Object. Невозможно написать код на C #, где вы не можете предполагать по крайней мере так много. Для меня код, который знает только тип System.Object, - это код, в котором тип полностью открыт. Но если вы имеете в виду, что вообще не знаете, что это за тип, то в этом смысле невозможно, чтобы тип был полностью открытым. ИМХО, это бесполезное определение полностью открытого. - person Peter Duniho; 18.09.2017

Означает ли это, что в дженериках Java методы параметра типа ограничены методами класса java.lang.Object?

Не совсем. Хотя большинство универсальных шаблонов стираются, в Java можно включить ограничение, при котором параметр типа должен быть определенного типа. Что фактически делает его не Object.

Не проверено, но это должно быть близко.

class Caller<T extends CallMe> {
    final T callee;
    Caller(T callee) {
        this.callee = callee;
    }
    public void go() {
        callee.call();  // should work now
    }
}

interface CallMe {
    void call();
}

class Foo implements CallMe {
    public void call() { System.out.print("Foo"); }
}

class Bar implements CallMe {
    public void call() { System.out.print("Bar"); }
}

public class Main {
    public static void main(String args[]) {
        Caller<Foo> f = new Caller<>(new Foo());
        Caller<Bar> b = new Caller<>(new Bar());
        f.go();
        b.go();
        System.out.println();
    }
}
person markspace    schedule 18.09.2017
comment
Я думаю, что @ Antonín пытается сказать, что OP (должен) уже знать информацию в вашем ответе, потому что это полностью рассмотрено на примере в статье блога, которую они читают и на которую ссылаются в начале своего вопроса. - person Peter Duniho; 18.09.2017
comment
Спасибо. @Peter: (1) правильно ли, что причина того, что дженерики Java не могут имитировать утиную типизацию, не из-за стирания типа, а потому, что все классы наследуют java.lang.Object (по той же причине, что и дженерики C # не могут имитировать утиную типизацию)? (2) Почему информация T extends CallMe не теряется во время стирания типа? Если CallMe не интерфейс, а класс, не будет ли информация T extends CallMe потеряна во время стирания типа? - person Tim; 18.09.2017
comment
@Tim: основная причина, по которой дженерики Java и C # не могут имитировать утиную типизацию (не уверен, что симуляция имеет отношение к чему-то здесь, но что угодно ...), заключается в том, что утиная типизация действительно не имеет ничего общего с дженериками, и очень мало общего с шаблонами C ++ в этом отношении (хотя я согласен, что шаблоны C ++ ведут себя немного как утиная печать). Ограничение Java действительно теряется во время стирания типа. Он используется только во время компиляции. Во время выполнения нет возможности восстановить ограничение. И не имеет значения, является ли CallMe классом или интерфейсом; они работают одинаково. - person Peter Duniho; 18.09.2017
comment
@Peter: вы имеете в виду, что во время компиляции информация T extends CallMe используется для проверки того, что callee имеет метод call(), а затем стирается во время стирания типа? - person Tim; 18.09.2017
comment
@ Тим: да, именно так. Использование универсального типа необходимо для соответствия ограничению во время компиляции. Во время выполнения нет необходимости выполнять проверку типа, поэтому информация о типе стирается. - person Peter Duniho; 18.09.2017
comment
@Peter: Означает ли class Caller<T extends CallMe> в Java, что T является подклассом java.lang.Object помимо реализации интерфейса CallMe? Аналогично в C #, означает ли class Caller<T> where T: CallMe, что T является подклассом System.Object помимо реализации интерфейса CallMe? - person Tim; 18.09.2017
comment
@ Тим: extends подразумевает это? Нет. Тот факт, что объект является объектом, подразумевает это. Каждый объект наследует общий базовый класс System.Object (C #) или java.lang.Object (Java). - person Peter Duniho; 18.09.2017

C # имеет те же ограничения.

Кроме dynamic, в C # нигде нет произвольной утиной печати; даже с универсальными шаблонами вы можете вызывать только методы, определенные типом (в частности, ограничением (ограничениями) для параметра универсального типа, который по умолчанию равен object).

person SLaks    schedule 18.09.2017
comment
Спасибо. даже с универсальными шаблонами вы можете вызывать только методы, определенные типом (в частности, ограничение (ограничения) для параметра универсального типа, который по умолчанию имеет значение object. Разрешено ли универсальным шаблонам C # не иметь такого ограничения ? Если да, то разве обобщения C # без такого ограничения не достигают того же, что и утка? - person Tim; 18.09.2017
comment
@Tim: Если нет ограничений, вы не можете предполагать ничего о параметре типа, кроме object. - person SLaks; 18.09.2017