avva: (Default)
[personal profile] avva

Это будет интересно только программистам.

Мне пришло в голову пару дней назад, что нет собственно причины, по которой в Джаве должно быть ключевое слово implements. Наверное, это тривиальная мысль, но мне до сих пор не приходила в голову.

Предположим, у нас есть интерфейс Foo и класс Bar. Предположим, в классе есть определения всех методов, которые перечислены в интерфейсе, с правильными аргументами, типами возврата и другими атрибутами. Значит, компилятор может этот факт в любой момент проверить напрямую, сверив определения класса и интерфейса (причем класс может быть в скомпилированной форме). В любом месте, где мы хотим использовать объект класса Bar (или его подкласса), а ожидают интерфейс Foo - например, в списке аргументов какого-то метода - сейчас компилятор проверяет правильность подстановки, основываясь на том, что в определении Bar написано "implements Foo". Но если бы не было написано, он все равно мог бы проверить правильность подстановки, просто напрямую сравнив типы методов класса и интерфейса.

(Я что-то упускаю тут или правильно все описал?)

Если это ключевое слово не нужно, зачем же оно есть? Зачем дизайнеры языка заставляют программиста специально написать, что класс Bar воплощает интерфейс Foo, если это не дает компилятору новой информации? Я бы сказал - по двум причинам; во-первых, из идеологических соображений, чтобы заставить программиста иметь в голове стройную картинку того, кто что воплощает. Во-вторых, чтобы была дополнительная проверка совместимости класса с интерфейсом в момент определения класса, а не только в момент его использования в качестве интерфейса. Без этой проверки, скажем, ошибка в определении класса, делающая его несовместимым с интерфейсом, обнаружилась бы только в момент использования класса там, где требуется интерфейс, а не во время компиляции самого класса с интерфейсом.

Что было бы лучше, если бы не было слова implements? Предположим, у меня есть некий достаточно простой интерфейс с одним-двумя методами, воплощающий некую идею; и класс, который написал не я и контролирую не я (возможно, только в скомпилированном виде), который воплощает эту идею, с ровно теми же именами методов и типами, но его создатели ничего не знали про мой интерфейс. Сейчас, чтобы их состыковать, мне надо делать дополнительный класс посредине, который прячет в себе объект настоящего класса и делегирует ему все, что нужно. Если бы компилятор не опирался на implements, а проверял настоящие типы, мне бы это не нужно было делать. Оно бы просто работало само по себе.

См. также интересную статью JavaGI: Generalized Interfaces for Java, которая натолкнула меня на эту нехитрую мысль. JavaGI - интересная попытка внести в Джаву идею type classes из Haskell. Предположим, у вас есть интерфейс и класс, скорее всего из разных источников (возможно, только в скомпилированном виде), причем класс как бы воплощает все, что нужно интерфейсу, но не под теми же именами, и возможно с некоторыми тривиальными изменениями. Обычное решение, опять-таки - промежуточный класс, Adapter pattern. Вместо этого предлагается новый синтаксис, который совмещает класс и интерфейс вместе, "объясняя" компилятору, как воплотить методы интерфейса в терминах методов класса, так, что после этого компилятор может подставлять объект класса туда, где нужен интерфейс, как обычно. Нет умножения сущностей в виде ненужного по сути дела промежуточного класса; вместе с тем сохраняются все проверки типизации, обычные для Джавы. Довольно любопытная идея.

Page 1 of 4 << [1] [2] [3] [4] >>

Date: 2007-01-02 11:54 am (UTC)
From: [identity profile] 0bs3rv3r.livejournal.com
как насчет разных интерфейсов с одинаковыми методами?

Date: 2007-01-02 11:56 am (UTC)
From: [identity profile] avva.livejournal.com
А в чем проблема? Если у класса есть эти методы, он может притвориться любым из них.

Date: 2007-01-02 12:02 pm (UTC)
From: [identity profile] 0bs3rv3r.livejournal.com
Да, но ничего хорошего в этом нет - возникает неоднозначность. С implements мы всегда уверены, что класс будет приведен к нужному интерфейсу, а без он может привестись к чему угодно, и мы не сможем это контролировать.

Date: 2007-01-02 12:06 pm (UTC)
From: [identity profile] http://users.livejournal.com/_kir/
Третья причина - невозможность неявной имплементации интерфейса во избежание ошибочной идентификации обьектов класса как обьектов интерфейса.
Самый простой пример - интерфейс, не содержащий методов, как, например, java.io.Serializable (http://java.sun.com/j2se/1.4.2/docs/api/java/io/Serializable.html) (обратите внимание на его отличие от java.io.Externalizable (http://java.sun.com/j2se/1.4.2/docs/api/java/io/Externalizable.html)) и механизм сериализации, основанный на явной имплементации этих интерфейсов.
Другой пример - имплементация механизмов instanceof (а также всего, что связано с reflection).

Date: 2007-01-02 12:16 pm (UTC)
From: [identity profile] dimrub.livejournal.com
Кстати, использование пустых интерфейсов для декларации определенного поведения объектов класса - это хэк, как раз-таки и решаемый использованием type classes Хаскелля, как мне кажется. А instanceof это вещь сугубо противоречащая философии Джавы (как подмножества философии OO), как я ее понимаю (хотя зачастую без него ну совершенно не обойтись, и это, видимо, проблема языка - хотя во многих случаях дженерики, видимо, ее решают).

Date: 2007-01-02 12:23 pm (UTC)
From: [identity profile] dejavit.livejournal.com
Насколько я понимаю, без implements совмещение интерфейсов и имплементаций будет стоить вам O(кол-во интерфейсов * кол-во классов) проверок.
То есть, сделать matching можно, но в большой системе это может оказаться запредельно дорого.

Date: 2007-01-02 12:37 pm (UTC)
From: [identity profile] arbat.livejournal.com
проверьте - сделайте ошибку. Если это слово позволяет Java не проверять - то она ошибку не заметит.

Date: 2007-01-02 12:54 pm (UTC)
From: [identity profile] faceted-jacinth.livejournal.com
> Зачем дизайнеры языка заставляют программиста специально написать, что класс Bar воплощает интерфейс Foo

Для того, чтобы компилятор мог проверить, что класс Bar действительно воплощает интерфейс Foo.

В реальности-то проверка типизации как правило оказывается реалтаймовой: есть у вас какая-то дырка, откуда лезут объекты, вы каждому из них делаете IFoo foo = someObject as IFoo (в шарповом синтаксисе) и работаете с теми, которые на самом деле поддерживают интерфейс Foo.
Соответственно, как разработчик того места, откуда в дырке появляются объекты, вы хотите защититься от случайного (ошибочного) изменения сигнатуры какого-нибудь из типов, в результате которого он перестанет поддерживать интерфейс. С явным указанием implements вы получаете ошибку на этапе компиляции, с гипотетическим неявным - в лучшем случае инвалид каст при исполнении, в худшем - непонятное поведение программы, у которой вдруг начинает что-то куда-то исчезать.

Ещё техническая причина, о которой вам пытались сказать выше: сигнатура метода void Close(); (включая название) может использоваться в дофига разных интерфейсах и есть неиллюзорная вероятность того, что в какой-то момент нужно будет ручками разруливать что, типа, вот это - IStream.Close - имплементация для интерфейса IStream.

Опять-таки возвращаясь к сценарию "лезут объекты" -- нам, фактически, нужно выделять объекты по некоему признаку (допустим, "являются потоками") и потом вызывать у них какие-то методы характерные для потоков, причём само по себе наличие похожих методов вовсе не гарантирует, что мы имеем дело с потоком, гораздо приятней, когда это в явном виде указано.

Ну и последнее, наверное: если б этого не было в синтаксисе, правильным программистам пришлось бы писать "implements IDisposable" в комментариях =)

Date: 2007-01-02 01:01 pm (UTC)
nine_k: A stream of colors expanding from brain (Default)
From: [personal profile] nine_k
+1
в java 5 для этого ввели пояснялки через @.

Date: 2007-01-02 01:08 pm (UTC)
From: [identity profile] faceted-jacinth.livejournal.com
> использование пустых интерфейсов для декларации определенного поведения объектов класса - это хэк
Да нет, почему? В C# можно вместо этого лепить атрибуты, но в некоторых случаях -- когда речь идёт именно о _поведении_, с намёком на то, что потом в этом интерфейсе могут и методы появиться -- почему нет?

> А instanceof это вещь сугубо противоречащая философии Джавы (как подмножества философии OO)
А это-то почему? "Нажать на все кнопки на форме" - вполне ООП задача, без instanceof (или аналогичного механизма) не решающаяся.

Date: 2007-01-02 01:40 pm (UTC)
From: [identity profile] gde-moi-17-let.livejournal.com
Даже если проверяет - так нужно проверить один интерфейс, а так - все.

Date: 2007-01-02 01:42 pm (UTC)
From: [identity profile] http://users.livejournal.com/_rowan_tree_/
1. Static compilation, type safety - выявить ошибки типов как можно раньше, т.е. на стадии компиляции.
2. Декларация типов - важный элемент software design, он позволяет программисту объяснить место каждого типа и их взаимоотношение друг с другом. Подробнее см., например, в "Agile software development: Principles, patterns, and
practices" by Robert C. Martin
(http://search.barnesandnoble.com/bookSearch/isbnInquiry.asp?r=1&isbn=0135974445)

Date: 2007-01-02 01:53 pm (UTC)
From: [identity profile] avva.livejournal.com
1. Мне кажется, вы не поняли. Все проверки проводятся ровно в то же время, во время компиляции. Просто компилятору не нужно знать, что класс специально заявил о том, что воплощает некий интерфейс - он может проверить это (во время компиляции) пройдясь по спискам методов интерфейса и класса.
2. Это да, это подпадает под "идеологию".

Date: 2007-01-02 02:19 pm (UTC)
From: [identity profile] http://users.livejournal.com/_rowan_tree_/
Можно во время компиляции, но тогда будут проблемы с методами, у которых случайно оказалось то же имя и параметры. И поддерживать такой код будет pain in the neck - методы, которые нельзя переименовывать, но об этом никто не знает, скажем. Ну и весь software design летит в трубу, потому что, например, если я сучайно назвала мой метод compareTo, то мой класс вдруг implements Comaprabale, но я же при этом не озаботилась следовать правилам написания этого метода!

Date: 2007-01-02 02:20 pm (UTC)
From: [identity profile] http://users.livejournal.com/_rowan_tree_/
implements Comaprabale -> implements Comparable. Кофе, кофе!

Date: 2007-01-02 02:38 pm (UTC)
From: [identity profile] avva.livejournal.com
Можно во время компиляции, но тогда будут проблемы с методами, у которых случайно оказалось то же имя и параметры.

Какие проблемы?

Ну и весь software design летит в трубу, потому что, например, если я сучайно назвала мой метод compareTo, то мой класс вдруг implements Comaprabale, но я же при этом не озаботилась следовать правилам написания этого метода!

А чем это вам мешает? Никто же не требует от вас использовать объекты этого класса там, где кто-то хочет иметь Comparable. Собственно, сейчас никто бы и не смог этого сделать, если вы не написали implements.

Date: 2007-01-02 02:41 pm (UTC)
From: [identity profile] avva.livejournal.com
1. Не знаю, как насчет Java, а во многих других языках интерфейсы - это не только компиляторная фишка, но и определенная бинарная совместимость. Бинарная совместимость нужна там, где никакими компиляторами уже давно и не пахнет.

Это да. В C++ это важно. В Джаве, если я правильно понимаю - нет.
Там классы "тяжелые", они и во время выполнения тащат за собой много всякой информации, и организованы и так посложнее, чем строгий vtable а-ля C++, где вызов метода - одна инструкция косвенного вызова.

Date: 2007-01-02 02:44 pm (UTC)
From: [identity profile] madfire.livejournal.com
duck typing

Date: 2007-01-02 02:45 pm (UTC)
From: [identity profile] avva.livejournal.com
Нет. Все проверки остаются в compile time.

Date: 2007-01-02 02:45 pm (UTC)
From: [identity profile] avva.livejournal.com
Не надо путать duck typing и type inference!

Date: 2007-01-02 02:47 pm (UTC)
From: [identity profile] madfire.livejournal.com
> "Нажать на все кнопки на форме" - вполне ООП задача, без instanceof (или аналогичного механизма) не решающаяся.

@form_objects.each { |obj| obj.push if obj.respond_to? :push }

Не вижу instanceof ни одного.

+1

Date: 2007-01-02 02:48 pm (UTC)
From: [identity profile] yurilax.livejournal.com
да, только хотел написать: всё проверять - дороговато будет для компилятора.

Date: 2007-01-02 02:48 pm (UTC)
From: [identity profile] faceted-jacinth.livejournal.com
Так а с проверками на этапе исполнения что делать?

Постепенно просыпаясь...

Date: 2007-01-02 02:49 pm (UTC)
From: [identity profile] http://users.livejournal.com/_rowan_tree_/
Не, не будет работать. Представьте себе, что у вас есть интерфейсы Foo и Bar, оба декларирующие метод name. Представьте, что в некотором классе есть два метода:
m(Foo f)
m(Bar b).
Теперь представьте, что у вас есть класс с методом name и вызовите метод m с этим объектом в качестве параметра. Если у вас объявлено, который из интерфейсов он implements, то вызовется правильный m, а если не объявлено, то плохо дело.

Подозреваю также, что будут проблемы с bounded generics, но пример пока не приходит в голову.

Date: 2007-01-02 02:51 pm (UTC)
From: [identity profile] madfire.livejournal.com
Молчу, молчу.
Page 1 of 4 << [1] [2] [3] [4] >>

January 2026

S M T W T F S
    1 2 3
4 5 6 7 8 910
11 121314151617
18192021222324
25262728293031

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jan. 12th, 2026 03:35 pm
Powered by Dreamwidth Studios