LINUX.ORG.RU

Про final в Java замолвите слово

 , , ,


0

1

Последнее время у меня появились сильные загоны на перфекционизм. Глядя на продакшен код проектов понимаешь, что он говно, и хочется хотя-бы в своей уютной репе всё делать по канонам. Большинство вопросов у меня возникло по final в разных контекстах его применения, которые хотелось бы тут обсудить (без срача, пожалуйста, только аргументы и факты).

Первое - классы. Если опираться на трактовку Open Closed принципа, то мы должны иметь возможность отнаследоваться от класса, чтобы расширить его функциональность, потому final классами должны быть как правило только утилитные классы, либо то, что логически по тем или иным причинам не должно использоваться как родитель чего-либо.

Методы - а вот тут уже интереснее, опять же возвращаемся к трактовке Open Closed, правильно ли я понимаю, что все методы класса должны быть помечены как final? Т.е. даже если кто-то отнаследуется от класса, он сможет только докинуть своих методов, но не сможет переопределить то, что уже имеется. Такой же вопрос к методам-реализациям. Если класс, допустим, является реализацией какого-либо интерфейса, должны ли Override методы из этого интерфейса быть final?

Переменные: Локальные - да, если не меняются на протяжении области видимости, в сигнатуре функции судя по всему тоже да, чтобы не было возможности сделать вот так

public void foo(String s) {
    s = "New String";
}

Соб-но, дискасс, а где и как ты, ЛОРовец, используешь final и как видишь данную ситуацию?

★★★★

Если класс, допустим, является реализацией какого-либо интерфейса, должны ли Override методы из этого интерфейса быть final?

С чего бы?

Tanger ★★★★★ ()
Ответ на: комментарий от Jefail

Ну, например с того, что другой класс может отнасдедоваться от него и переопределить метод-реализацию.

Чем это плохо? Зачем это запрещать? В большинстве случаев тебе не final нужно, а private.

Tanger ★★★★★ ()

Последнее время у меня появились сильные загоны на перфекционизм. Глядя на продакшен код проектов понимаешь, что он говно

Такие вопросы поднимаются в команде, вырабатывается коллегиальное решение и новый кодстайл начинается использоваться во всем коде, получается единообразие в коде. В ходе общих дискуссий обычно решаются проблемы неприятия того или иного решения оформления кода, если кто-то резко против то эта вещь просто не применяется. Иначе тихая агрессия в коллективе, лор это не то место где нужно решать как оформлять код. А партизанить (делать «как нужно» ни с кем не посоветовавшись) только тратить время, никто твои решения просто так не примет, более того каждый может начать выпендриваться по своему, в общем тогда это не разработка а детский сад...

Следить за стилем можно посредством статических анализаторов кода вроде checkstyle или findbug, есть плагины в ide, можно еще sonar использовать и бить по рукам в случае залития ворнингов.

Aber ★★★ ()

Так это не только про java, в крестах те же вопросы можно поднимать.

Члены класса - всегда по возможности const/final, локальные - чаще нет, у них контекст виден. Зачем нужен final у классов/методов я не знаю, разве что в редких случаях вызов метода получится быстрее.

anonymous ()
public void foo(String s) {
    s = "New String";
}

Кажется это статическим анализатором можно отловить, используйте например checkstyle, следите чтоб в коде не было ворнингов, когда добавляете новые чеки(правила) выделяйте время (создавайте баг на трекере) на приведение кода в порядок.

Aber ★★★ ()
Последнее исправление: Aber (всего исправлений: 1)

Методы - а вот тут уже интереснее, опять же возвращаемся к трактовке Open Closed, правильно ли я понимаю, что все методы класса должны быть помечены как final?

Переопределять метод из неабстрактного класса уже code smell, дискасс.

spec_po_kiskam ★★★ ()
Ответ на: комментарий от spec_po_kiskam

Во, удваиваю. Я сторонник идеи, что если какое-то поведение должно быть гибким, то сделать интерфейс и протащить его через конструктор, а там уже пускай фабрика / DI Framework решает, что должно быть.

Jefail ★★★★ ()

вот пример класс структурка

public class Point {

    public final int x;
    public final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

final еще и помогает не забыть поле инициализировать.

barberry ★★ ()
Ответ на: комментарий от spec_po_kiskam

Если у кого-то возникла ситуация, когда корова наследуется от жирафа, ему следует менять профессию. А посмотреть массу примеров переопределения методов родительского не абстрактного класса можно в Android SDK и понять когда и где это может быть нужно

r0ck3r ★★★★★ ()
Ответ на: комментарий от Jefail

Равняться, может, и не стоит, но зато он наглядно показывает с чем придется столкнуться разработчику, если проект, над которым он работает, долгосрочный

r0ck3r ★★★★★ ()

Вообще, по уму, final надо ставить везде, где только можно. Наследование — это вообще зло, и использовать его надо аккуратно и изредка. Наследование от неабстрактных классов — зло полнейшее, его использовать нельзя никогда. Поэтому ВСЕ неабстрактные классы надо пометить как final. А если кто хочет расширить функциональность твоего класса, пусть использует композицию. Меньше проблем огребет.

Про локальные переменные я не понял если честно.

morse ★★★★★ ()
Ответ на: комментарий от trex6

В академической среде жавы нет, незачем она там. А с жавой я работаю в самых что ни на есть реальных проектах, я уже давно не ученый, а унылый жава-программер.

И качество кода в реальных проектах, надо сказать, тоже атас. Но человек спрашивает как оно там в мире розовых пони и идеальных проектов. А там оно вот так.

morse ★★★★★ ()
Ответ на: комментарий от t184256

Полиморфизм? Ну что ж, рад за Вас. Вообще переопределение родительских не абстрактных методов в Java - норма.

Стоит отметить, что все классы автоматически наследуются от Object, который также не является абстрактным. Также и с JFrame, который расширяют и переопределяют конструктор

r0ck3r ★★★★★ ()
Последнее исправление: r0ck3r (всего исправлений: 1)
Ответ на: комментарий от r0ck3r

может это и не является best practices и лучше наследоваться только от абстрактных классов, но на практике это не работает, так часто бывает нужно расширить функционал какой-то части приложения без переписывания старого кода и с минимумом затрат. Здесь выходом является расширение класса с переопределением «проблемных» методов. В связи с этим я и привел в пример Android SDK

r0ck3r ★★★★★ ()
Последнее исправление: r0ck3r (всего исправлений: 1)
Ответ на: комментарий от r0ck3r

В Android чаще всего переопределение является делегированием, изначально в архитектуру заложенным (тот же onCreate в Activity), это немного другое. Если посмотреть на исходники OkHttp / Retrofit, то хрен там получится от чего-то отнаследоваться и что-то проблемное переопределить, руки разработчика в конечном API весьма сильно связаны.

Jefail ★★★★ ()
Ответ на: комментарий от r0ck3r

И это, кстати, очень дерьмовый подход. Например, вот дефолтная реализация метода onCreateView у фрагмента

    @Nullable
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
            @Nullable Bundle savedInstanceState) {
        return null;
    }

Это просто пустышка, но от того, что будет возвращего переопределеним очень многое меняется в поведении, ссылка на вьюху где-то неявно сохраняется, но, самое главное, родитель явно закладывается на наследника, хотя он вообще о нём ничего знать не должен.

Jefail ★★★★ ()

Возьми pdf-ку «Bruce Eckel. Thinking in Java» и тупо просканируй на слово «final» (хотя там есть отдельный раздел на эту тему). Сомневаюсь, что к этому можно будет еще что-то существенное добавить. Разве что, конкретные примеры из личной практики...

vinvlad ()
Ответ на: комментарий от r0ck3r

суть ООП именно в том, что дочерний класс может переопределять методы родителя. man полиморфизм

Суть ООП в том что это ООП. man понты.

А если откинуть все эти понты, то метод, это обычная функция с this в первом аргументе. Тогда this это просто набор данных и никакого полиморфизма нахер не нужно, ведь любые функции и так доступны.

deep-purple ★★★★★ ()

потому final классами должны быть как правило только утилитные классы, либо то, что логически по тем или иным причинам не должно использоваться как родитель чего-либо.

А как же классы которые собой представляют просто данные и ничего больше? Также почитай JSR133 (к слову об оптимизации и потоко безопасности)

Int64 ★★★ ()
Ответ на: комментарий от r0ck3r

Полиморфизм исключительно через щель копирования реализации с расширением. Это ни в коем случае не противоречит ООП, это даже, как ты правильно отметил, много где разрешено. Но «сутью ООП» это, слава богам, не является.

t184256 ★★★★★ ()
Ответ на: комментарий от barberry

Странно, я, в общем, так до сих пор делаю, что на Java, что на C. Правда в C, это действительно микрооптимизация, особенно, если многопоток.

DarkAmateur ★★ ()
Последнее исправление: DarkAmateur (всего исправлений: 1)

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

Но если нужны простые установки. Классы и методы финализируются только в крайнем случае с ясным объяснением зачем. переменный наоборот по умолчанию финализируются и без него с явным объяснением зачем.

vtVitus ★★★★★ ()
Ответ на: комментарий от morse

Наследование от неабстрактных классов — зло полнейшее, его использовать нельзя никогда

Это ты какой-то совсем идеальный случай описываешь.

В том же Android часто бывает нужно отнаследоваться от какого-нибудь конкретного View, чтобы добавить или переопределить некий метод. В том, что это не очень красиво – да, но что совсем «никогда нельзя» – слишком резко.

mono ★★★★★ ()