Главная страница
Навигация по странице:

  • // Класс утилит, не имеющий экэемпляров public class UtilityClass {// Подавляет появление конструктора по умолчанию, а заодно и создание экземпляров класса

  • // Этот конструктор никогда не будет вызван

  • * Даты начала и конца демографического взрыва

  • Effective Java tmprogramming Language GuideJ o s h u a b lo c h


    Скачать 1.05 Mb.
    НазваниеEffective Java tmprogramming Language GuideJ o s h u a b lo c h
    Дата03.04.2018
    Размер1.05 Mb.
    Формат файлаpdf
    Имя файлаBlokh_Dzh_-_Java_Effektivnoe_programmirovanie.pdf
    ТипДокументы
    #40178
    страница3 из 25
    1   2   3   4   5   6   7   8   9   ...   25

    * Возвращает единственный истинный Elvis и позволяет сборщику мусора разобраться с Еlvis-самозванцем
    */
    return INST ANCE;
    }
    public static void main(String[] args) {
    System.out.println(Elvis.INST ANCE);
    Общая тема связывает эту статью со статьей
    21 , описывающей шаблон для перечисления типов. В обоих случаях используются закрытые конструкторы в сочетании с открытыми статическими членами, и это гарантирует, что для соответствующего класса после инициализации не будет создано никаких новых экземпляров. В настоящей статье для класса создается только один экземпляр, в статье
    21 один экземпляр создается для каждого члена в перечислении. В следующей статье в этом направлении делается еще один шаг отсутствие открытого конструктора является гарантией того, что для класса никогда не будет создано никаких экземпляров. Отсутствие экземпляров обеспечивает закрытый конструктор Время от времени приходится писать класс, который является всего лишь собранием статических методов и статических полей. Такие классы приобрели дурную репутацию, поскольку отдельные личности неправильно пользуются ими с целью написания процедурных программ с помощью объектно- ориентированных языков. Подобные классы требуют правильного применения. Их можно использ

    ать для того, чтобы собирать вместе связанные друг с другом методы обработки простых значений или массивов, как это сделано в библиотеках java.lang.Math и java.util.Arrays, либо чтобы собирать вместе статические методы объектов, которые реализуют определенный интерфейс, как это сделано в j ауа.util.Collections. Можно также собрать Методы в некоем окончательном
    (fina!) классе вместо того, чтобы заниматься расширением Класса. Подобные классы утилит (uti!ity c!ass) разрабатываются не для того, чтобы СОздавать для них экземпляры - такой экземпляр был бы абсурдом. Однако если у Класса нет явных конструкторов, компилятор по умолчанию сам создает для него ОТКРытый конструктор
    (defau!t constructor), не имеющий параметров. Для пользователя этот Конструктор ничем не будет отличаться от любого другого. В опубликованных АР нередко можно встретить классы, непреднамеренно наделенные способностью порождать экземпляры.
    Попытки запретить классу создавать экземпляры, объявив его абстрактным, не работают. Такой класс может иметь подкласс, для которого можно' создавать экземпляры. Более того, это вводит пользователя в заблуждение, заставляя думать, что данный класс был разработан именно для наследования (статья 15). Существует, однако, простая идиома, гарантирующая отсутствие экземпляров. Конструктор по умолчанию создается только тогда, когда у класса нет явных конструкторов, и потому запретить создание экземпляров можно, поместив в класс единственный явный закрытый конструктор
    // Класс утилит, не имеющий экэемпляров
    public class UtilityClass {
    // Подавляет появление конструктора по умолчанию, а заодно и
    создание экземпляров класса
    private UtilityClass() {
    // Этот конструктор никогда не будет вызван
    }
    // ... // Остальное опущено Поскольку явный конструктор заявлен как закрытый (private), за пределами класса он будет недоступен. И если конструктор не вызывается в самом классе, это является гарантией того, что для класса никогда не будет создано никаких экземпляров. Эта идиома несколько алогична, так как конструктор создается здесь именно для того, чтобы им нельзя было пользоваться. Есть смысл поместить в текст программы комментарий, который описывает назначение данного конструктора. Побочным эффектом является то, что данная идиома не позволяет создавать подклассы для этого класса. Явно или неявно, все конструкторы должны вызывать доступный им конструктор суперкласса. Здесь же подкласс лишен доступа к конструктору, к которому можно было бы обратиться. Нес о зд а в ай те дублирующих объектов Вместо того чтобы создавать новый функционально эквивалентный объект всякий раз, когда в нем возникает необходимость, можно, как правило, еще раз использовать тот же объект. Применять что- либо снова - и изящнее, и быстрее. Если объект является неизменяемым (immutable), его всегда можно использовать повторно (статья 13). Рассмотрим оператор, демонстрирующий, как делать не надо
    String s = new String("silly"); / / Никогда не делайте так
    При каждом проходе этот оператор создает новый экземпляр
    String, но ни одна из процедур создания объектов не является необходимой. Аргумент конструктора
    String - "silly" - сам является экземпляром класса
    String и функционально равнозначен всем объектам, создаваемым конструктором. Если этот оператор попадает в цикл или в часто вызываемый метод, без всякой надобности могут создаваться миллионы экземпляров S
    t r
    i ng. Исправленная версия выглядит просто
    String s = "No longer silly"; В этом варианте используется единственный экземпляр
    String вместо создания новых при каждом проходе. Более того, гарантируется, что этот объект будет повторно использоваться любым другим программным кодом, выполняемым на той же виртуальной машине, где содержится эта строка- константа
    [JLS,
    3.10.5]. Создания дублирующих объектов часто можно избежать, если неизменяемом классе, имеющем и конструкторы, и статические методы генерации статья 1), предпочесть вторые первым. Например, статический метод генерации почти всегда предпочтительнее конструктора
    Boolean(String). При каждом вызове конструктор создает новый объект, тогда как от статического метода генерации этого не требуется. Вы можете повторно использовать не только неизменяемые объекты, но и изменяемые, если знаете, что последние уже не будут меняться. Рассмотрим более тонкий и более распространенный пример того, как не надо поступать, в том Числе с изменяемыми объектами, которые, будучи получены один раз, впоследствии остаются без Изменений import java.util.*;
    public class Person {
    private final Date birthDate;
    // Прочие поля опущены public Person(Date birthDate) {
    this.birthDate = birthDate;
    }
    // Никогда не делайте так public boolean isBabyBoomer() {
    Calendar gmtCal =
    Calendar.getInstance(TimeZone.getTimeZone("GMT"));
    gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
    Date boomStart = gmtCal.getTime();
    gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
    Date boomEnd = gmtCal.getTime();
    return birthDate.compareTo(boomStart) >= 0 &&
    birthDate.compareTo(boomEnd) < 0;
    }
    public static void main(String[] args) {
    Person p = new Person(new Date());
    long startTime = System.currentTimeMillis();
    for (int i=0; i<1000000; i++)
    p.isBabyBoomer();
    long endTime = System.currentTimeMillis();
    long time = endTime - startTime;
    System.out.println(time+" ms.");
    }
    }
    Метод isBabyBoomer при каждом вызове создает без всякой надобности новые экземпляры
    Calendar, TimeZone и два экземпляра Date. В следующей версии подобная расточительность пресекается с помощью статического инициализатора import java.util.*;
    class Person {
    private final Date birthDate;
    public Person(Date birthDate) {
    this.birthDate = birthDate;
    }
    /**
    * Даты начала и конца демографического взрыва
    */
    private static final Date BOOM_START;
    private static final Date BOOM_END;
    static {
    Calendar gmtCal =
    Calendar.getInstance(TimeZone.getTimeZone("GMT"));
    gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
    BOOM_START = gmtCal.getTime();
    gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
    BOOM_END = gmtCal.getTime();
    }
    public boolean isBabyBoomer() {
    return birthDate.compareTo(BOOM_START) >= 0 &&
    birthDate.compareTo(BOOM_END) < 0;
    }
    public static void main(String[] args) {
    Person p = new Person(new Date());
    long startTime = System.currentTimeMillis();
    for (int i=0; i<1000000; i++)
    p.isBabyBoomer();
    long endTime = System.currentTimeMillis();
    long time = endTime - startTime;
    System.out.println(time+" ms.");
    } В исправленной версии класса Person экземпляры Calendar, Т и Date создаются только один разв ходе инициализации, а не при каждом вызове метода isBabyBoomer. Если данный метод вызывается часто, это приводит к значительному выигрышу в производительности. На моей машине исходная версия программы тратит на миллион вызовов 36000 мс, улучшенная - 370 мс, те. она работает в сто раз быстрее. Причем повышается не только производительность программы, но и наглядность. Замена локальных переменных boomStart и boomEnd статическими полями типа final показывает, что эти даты рассматриваются как константы, и программный код становится более понятным. Для полной ясности заметим, что экономия от подобной оптимизации не всегда будет столь впечатляющей, просто здесь много ресурсов требует создание экземпляров Calendar. Если метод isBabyBoomer вызываться не будет, инициализация полей BOOM_START ив улучшенной версии класса Person окажется напрасной. Ненужных действий можно избежать, использовав для этих полей отложенную инициализацию (lazily initializing) (статья 48), которая бы выполнялась при первом вызове метода

    14
    isBabyBoomer, однако делать это не рекомендуется. Как часто бывает в случаях с отложенной инициализацией, это усложнит реализацию и вряд ли приведет к заметному повышению производительности (статья 37). Во всех примерах, приведенных в этой статье, было очевидным то, что рассматриваемые объекты можно использовать повторно, поскольку они неизменяемые. Однако в ряде ситуаций это не столь очевидно. Рассмотрим случай с адаптерами (adapter) [Сатта95, стр. 139], известными также как представления (view). Адаптер - это объект, который делегирован нижележащим объектом и который создает для него альтернативный интерфейс. Адаптер не имеет иных состояний, помимо состояния нижележащего объекта, поэтому для адаптера, представляющего данный объект, ненужно создавать более одного экземпляра. Например, в интерфейсе Мар метод keySet возвращает для объекта Мар представление Set, которое содержит все ключи данной схемы. По незнанию можно подумать, что каждый вызов метода keySet должен создавать новый экземпляр Set. Однако в действительности для некоего объекта Мар любые вызовы keySet могут возвращать один и тот же экземпляр Set. И хотя обычно возвращаемый экземпляр Set является изменяемым, все' возвращаемые объекты функционально идентичны когда меняется один из них, тоже самое происходит со всеми остальными экземплярами Set, поскольку за всеми ними стоит один и тот же экземпляр В этой статье отнюдь не утверждается, что создание объектов требует много ресурсов и его нужно избегать. Наоборот, создание и повторное использование небольших объектов, чьи конструкторы выполняют несложную и понятную работу, необременительно, особенно для современных реализаций jVM. Создание дополнительных объектов ради большей наглядности, упрощения и расширения возможностей программы - это обычно хорошая практика. И наоборот, отказ от создания объектов и поддержка собственного пула объектов (object pool) - плохая идея, если только объекты в этом пуле не будут крайне ресурсоемкими. Основной пример объекта, для которого оправданно создание пула, - соединение с базой данных (database connection). Затраты на установление такого соединения высоки, и потому лучше обеспечить многократное использование этого объекта. Однако в общем случае создание собственного пула объектов загромождает. Современные реализации JVM имеют хорошо оптимизированные сборщики мусора, которые при работе с небольшими объектами с легкостью превосходят подобные пулы объектов.
    В противовес этой статье можно привести статью 24, посвященную резервному копированию
    (defensive copying). Если в настоящей статье говориться Не создавайте новый объект, если вы обязаны исполнять имеющийся еще раз, то статья 24 гласит ” Не надо использовать имеющийся объект еще раз, если вы обязаны создать новый. Заметим, что ущерб от повторного применения объекта, когда требуется резервное копирование, значительно превосходит ущерб от бесполезного создания дублирующего объекта. Отсутствие резервных копий там, где они необходимы, может привести к коварным ошибками дырам в системе безопасности, создание же ненужных объектов всего лишь влияет на стиль и производительность программы

    1 Уничтожайте устаревшие с с ы п к и (на объекты) При переходе с языка программирования с ручным управлением памятью, такого как С или Сна язык с автоматической очисткой памяти (garbage-collect - "сбора мусора) ваша работа как программиста существенно упрощается благодаря тому обстоятельству, что ваши объекты автоматически утилизируются, как только вы перестаете их использовать. Когда вы впервые сталкиваетесь с этой особенностью, то воспринимаете ее как волшебство. Легко может создаться впечатление, что вам больше ненужно думать об управлении' памятью, но это не совсем так. Рассмотрим следующую реализацию простого стека import Можете ли вы заметить "утечку памяти

    public class Stack {
    private Object[] elements;
    private int size = 0;
    public Stack(int initialCapacity) {
    this.elements = new Object[initialCapacity];
    }
    public void push(Object e) {
    ensureCapacity();
    elements[size++] = e;
    }
    public Object pop() {
    if (size==0)
    throw new EmptyStackException();
    Object result = elements[--size];
    elements[size] = null; // Eliminate obsolete reference return result;
    }
    /**
    * Убедимся в том, что в стеке есть место хотя бы еще
    * для одного элемента. Каждый раз, когда
    * нужно увеличить массив, удваиваем его емкость.
    */
    private void ensureCapacity() {
    if (elements.length == size) {
    Object[] oldElements = elements;
    elements = new Object[2 * elements.length + 1];
    System.arraycopy(oldElements, 0, elements, 0, size);
    } }
    public static void main(String[] args) {
    Stack s = new Stack(0);
    for (int i=0; i s.push(args[i]);
    for (int i=0; i System.out.println(s.pop());

    } В этой программе нет погрешностей, которые бросались бы в глаза. Вы можете тщательно протестировать ее, любое испытание она пройдет с успехом, нов ней все же скрыта одна проблема. В этой программе имеется "утечка памяти, которая может тихо проявляться в виде снижения производительности в связи с усиленной работой сборщика мусора, либо в виде увеличения размера используемой памяти. В крайнем случае подобная утечка памяти может привести к подкачке страниц с диска и даже к аварийному завершению программы с диагностикой OutOfMemoryError, хотя подобные отказы встречаются относительно редко. Где же происходит утечка Если стек растет, а затем уменьшается, то объекты, которые были вытолкнуты из стека, не могут быть удалены, даже если программа, пользующаяся этим стеком, уже не имеет ссылок на них. Все дело в том, что стек сохраняет устаревшие ссылки
    (obsolete reference) на объекты. Устаревшая ссылка это такая ссылка, которая уже никогда не будет разыменована. В данном случае устаревшими являются любые ссылки, оказавшиеся за пределами активной части массива элементов. Активная же часть стека включает в себя элементы, чей индекс меньше значения переменной size. Утечка памяти в языках с автоматической сборкой мусора (или точнее, непреднамеренное сохранение объектов - unintentional object retention) весьма коварна. Если ссылка на объект была непреднамеренно сохранена, сборщик мусора не сможет удалить не только этот объектно и все объекты, на которые он ссылается, и т. д. Если даже непреднамеренно было сохранено всего несколько объектов, многие и многие объекты могут стать недоступными сборщику мусора, а это может оказать большое влияние на производительность программы. Проблемы такого типа решаются очень просто как только ссылки устаревают, их нужно обнулять. В случае с нашим классом Stack ссылка становится устаревшей, как только ее объект выталкивается из стека. Исправленный вариант метода рор выглядит следующим образом public Object рор() if (size = = О) throw new EmptyStackException(); Object result = elements[--size]; elements[size] = null; Убираем устаревшую ссылку геtuгп result; Обнуление устаревших ссылок дает и другое преимущество если впоследствии кто-то по ошибке попытается разыменовать какую-либо из этих ссылок, программа незамедлительно завершится с диагностикой NullРоiпtегЕхсерtiоп вместо того, чтобы спокойно выполнять неправильную работу. Всегда выгодно обнаруживать ошибки fIрограммирования настолько быстро, насколько это возможно. Когда программисты впервые сталкиваются с подобной проблемой, они начинают перестраховываться, обнуляя все ссылки на объекты, лишь только программа заканчивает работу сними. Это ненужно, поскольку загромождает программу и снижает ее
    производительность. Обнуление ссылок на объект должно быть не нормой, а исключением. Лучший способ избавиться от устаревшей ссылки - вновь использовать переменную, в которой она находилась, либо выйти из области видимости переменной. Это происходит естественным образом, если для каждой переменной вы задаете самую ограниченную область видимости (статья 29). Следует, заметить, что в современных реализациях JVM недостаточно просто выйти из блока, в котором определена переменная. Чтобы переменная пропала, необходимо выйти из соответствующего метода- контейнера. Так когда же следует обнулять ссылку Какая особенность класса Stack сделала его восприимчивым к утечке памяти Класс Stack управляет своей памятью. Пул хранения состоит из массива элементов (причем его ячейками являются ссылки на объекты, а не сами объекты. Как указывалось выше, элементы в активной части массива считаются занятыми, в остальной - свободными. Сборщик мусора этого знать никак не может, и для него все ссылки на объекты, хранящиеся в массиве, в равной' степени действительны. Только программисту известно, что неактивная часть массива ненужна. Сообщить об этом сборщику мусора программист может, лишь вручную обнуляя элементы массива по мере того, как они переходят вне активную часть массива. Вообще говоря, если какой-либо класс начинает управлять своей памятью, программист должен подумать об утечке памяти. Как только элемент массива освобождается, любые ссылки на объекты, имевшиеся в этом элементе, необходимо обнулять. Другим распространенным источником утечки памяти являются КЭШи. Поместив однажды в кэш ссылку на некий объект, легко можно забыть о том, что она там есть, и держать ссылку в КЭШе еще долгое время после того, как она стала недействительной. Возможны два решения этой проблемы. Если вам посчастливилось создать кэш, в котором запись остается значимой ровно до тех пор, показа пределами КЭШа остаются ссылки на ее ключ, представьте этот кэш как W eak HashMa p: когда записи устареют, они будут удалены автоматически. В общем случае время, на протяжении которого запись в
    КЭШе остается значимой. четко не оговаривается. Записи теряют свою значимость стечением времени. В таких обстоятельствах кэш следует время от времени очищать от записей, которыми уже никто не пользуется. Подобную чистку может выполнять фоновый поток (например, через АР java.util.
    Тiтeг), либо это может быть побочным эффектом от добавления в кэш новых записей. При реализации второго подхода применяется метод rem oveEldestEntry из класса java.util.Link edHashMap, включенного в версию 1.4. Поскольку утечка памяти обычно не обнаруживает себя в виде очевидного сбоя, она может оставаться в системе, годами. Как правило, выявляют ее лишь в результате тщательной инспекции программного кода или с помощью инструмента отладки, известного как профилировщик (heap Поэтому очень важно научиться предвидеть проблемы, похожие на эту, до того, как они возникнут, и предупреждать их появление.
    Остерегайтесь методов Методы finalize непредсказуемы, часто опасны и, как правило, ненужны. Их использование может привести к странному поведению программы, низкой производительности и проблемам с переносимостью. Метод finalize имеет лишь несколько областей применения (см. ниже, а главное правило таково следует избегать методов finalize. Программистов, пишущих на С, следует предостеречь в том, что нельзя рассматривать методы finalize как аналог деструкторов в СВ С+
    деструктор _ это обычный способ утилизации ресурсов, связанных с объектом, обязательное дополнение к конструктору. В языке программирования java, когда объект становится недоступен, очистку связанной с ним памяти осуществляет сборщик мусора. Со стороны же программиста никаких специальных действий не требуется. В С+ деструкторы используются для освобождения не только памяти, но и других ресурсов системы. В языке программирования java для этого обычно применяется блок try-finally. Нет гарантии, что метод finalize будут вызван немедленно
    [JLS, 12.6]. С момента, когда объект становится недосТупен, и до момента выполнения метода finalize может пройти сколь угодно длительное время. Это означает, что с помощью метода
    1   2   3   4   5   6   7   8   9   ...   25


    написать администратору сайта