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

  • * Если какая-либо из трех частей телефонного номера мала и * не заполняет свое поле, последнее дополняется ведущими нулями. * Например, если значение номера абонента в АТС равно З, то

  • * последними четырьмя символами в строковом представлении будут "0123". *

  • // Убираем устаревшую ссылку return result; } // Убедимся в том, что в стеке есть место хотя бы // еще для одного элемента

  • 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
    страница6 из 25
    1   2   3   4   5   6   7   8   9   ...   25
    // Отложенная инициализация, кэшируемый hashCode
    private volatile int hashCode = о ;
    // (см. статью 48)
    public int hashCode() { if (hashCode == о) { int result = 17; result = 37*result + areaCode; result = 37*result + exchange; result = 37*result + extension; hashCode = result;
    } return hashCode;
    }
    Рецепт, изложенный в этой статье, позволяет создавать довольно хорошие хэш-функции, однако он не соответствует ни современным хэш-функциям, ни хэш-функциям из библиотек для платформы Java, которые реализованы в версии
    1.4. Разработка подобных хэш-функций является предметом активных исследований, которые лучше оставить математиками ученым, работающим в области теории вычислительных машин. Возможно, в последней версии платформы Java для библиотечных классов будут представлены современные хэш-функции, а также методы-утилиты, которые позволят рядовым программистам самостоятельно создавать такие хэш-функции. Пока же описанные в этой статье приемы применяются для большинства приложений. Повышение производительности не стоит того, чтобы при вычислении хэш-кода игнорировать значимые части объекта. Хотя хэш-функция способна работать быстрее, ее качество может ухудшиться до такой степени, что обработка хэш-таБлицы будет производиться слишком медленно. В частности, не исключено, что фэш-функция столкнется с большим количеством экземпляров, которые существенно разнятся как разв тех частях, которые вы решили игнорировать. В этом случае хэш-функция сопоставит всем этим экземплярам всего лишь несколько значений хэш-кода. Соответственно, коллекция, основанная на хэш- функциях, будет вызывать падение производительности в квадратичной зависимости от числа элементов. Это непросто теоретическая проблема. Хэш-функция класса String, реализованная во всех версиях платформы
    Java до номера
    1,2, проверялась самое большее для строк с
    16 символами и равномерным распределением пробелов по всей строке, начиная с первого символа. для больших коллекций иерархических имен, таких как
    U RL, эта хэшфую(ция демонстрировала то патологическое поведение, о котором здесь говорилось. у многих классов в библиотеках для платформы
    Java, таких как Str1ng, Integer и Date, конкретное значение, возвращаемое методом hashCode, определяется как функция от значения экземпляра. Вообще говоря, это не слишком хорошая идея, поскольку она серьезно ограничивает возможности по улучшению хэш-функций в будущих версиях. Если бы вы оставили детали реализации фэш-йункции не конкретизированными ив ней обнаружился бы изъян, вы бы могли исправить эту хэш-функцию в следующей версии, не опасаясь утратить совместимость с теми клиентами, работа которых зависит оттого, какое конкретное значение возвращает хэш-функция. Всегда переоп редел яй тем етод toS в классе java.lang.Object предусмотрена реализация метода toString, однако возвращаемая им строка, как правило, совсем не та, которую желает видеть пользователь вашего класса. Она состоит из названия класса, за которым следуют символ "коммерческого at" (@) и его хэш-код в виде беззнакового шестнадцатеричного числа, например "PhoneNumbeг@16ЗЬ91". Общее соглашение для метода toString: возвращаемая строка должна быть "лаконичным, но информативным, легко читаемым
    представлением объекта. Хотя можно поспорить, является ли лаконичной и легко читаемой строка "РhопеNumЬег@16ЗЬ91", она не столь информативна, как, например, такая строка "( 408) 867 -З. Далее в соглашении для метода toSt ring говорится Рекомендуется во всех подклассах переопределять этот метод. Хороший совет, ничего не скажешь. Эти соглашения не столь строги, как соглашения для методов equals и hashCode (статьи
    7 и
    8), однако, качественно реализовав метод toString, вы сделаете свой класс более приятным в использовании. Метод toSt ring вызывается автоматически, когда ваш объект передается методу println, оператору сцепления строк
    (+) или assert (в версии
    1.4). Если вы создали хороший метод toString, получить удобное диагностическое сообщение можно простым способом
    System.out.println("Failed to connect: "+ phoneNumber); Программисты все равно будут строить такие диагностические сообщения, переопределите вы метод toString или нет, но сообщения не станут понятней, если не сделать этого. Преимущества от реализации удачного метода toString передаются не только экземплярам этого класса, но и объектам, которые содержат ссылки на эти экземпляры, особенно это касается коллекций. Чтобы вы хотели увидеть
    "{Jеппу=РhопеNumЬег@16ЗЬ91}" или же "{Jenny=(408) З Будучи переопределен, метод toString должен передавать всю полезную информацию, которая содержится в объекте, как это было показано в примере с телефонными номерами. Однако это не годится для больших объектов или объектов, состояние которых трудно представить в виде строки. В подобных случаях метод toString возвращает такое резюме, как "Manhattan white pages (1487536 listings)" или "Thread
    [main, 5, main]". В идеале полученная строка не должна требовать разъяснений. (Последний пример сне отвечает этому требованию) При реализации метода toString вы должны принять одно важное решение будете ли вы описывать в документации формат возвращаемого значения. Это желательно делать для
    классов-значении (value class), таких как телефонные номера и таблицы. Задав определенный формат, вы получите то преимущество, что он будет стандартным, однозначными удобным для чтения представлением соответствующего объекта. Это представление можно использовать для ввода, вывода, а также для создания удобных для прочтения записей в фиксируемых объектах, например в документах
    XML. При задании определенного формата, как правило, полезно бывает создать соответствующий конструктор объектов типа String (или статический метод генерации, см. статью 1), что позволит программистам с легкостью осуществлять преобразование между объектом и его строковым представлением. Такой подход используется в 'библиотеках платформы Java для многих классов-значений, включая Biglnteger, BigDecimal и большинство примитивных классов-оболочек
    (wrapper). Неудобство от конкретизации формата значения, возвращаемого методом toString, заключается в том, что если ваш класс используется широко, то, задав формат, вы оказываетесь привязаны к нему навсегда. Другие программисты будут писать код, который анализирует данное представление, генерирует и использует его при записи
    объектов в базу данных (persistent data). Если в будущих версиях вы поменяете формат представления, они будут очень недовольны, поскольку вы разрушите созданные ими код и данные. Отказавшись от спецификации формата, вы сохраняете возможность внесения в него новой информации него совершенствования в последующих версиях. Будете вы объявлять формат или нет, вы должны четко обозначить ваши намерения. Если вы описываете формат, то обязаны сделать это пунктуально. В качестве примера представим метод toString, который должен сопровождать класс PhoneNumber (статья 8):
    /**
    * Возвращает представление данного телефонного номера в виде строки.
    * Строка состоит из четырнадцати символов, имеющих формат
    * "(ХХХ) YYY-ZZZZ" , где ХХХ - код зоны, У У У - номер АТС,
    * ZZZZ - номер абонента в АТС. (Каждая прописная буква представляет * одну десятичную цифру)
    *
    * Если какая-либо из трех частей телефонного номера мала и
    * не заполняет свое поле, последнее дополняется ведущими нулями. * Например, если значение номера абонента в АТС равно З, то
    * последними четырьмя символами в строковом представлении будут "0123".
    *
    * Заметим, что закрывающую скобку, следующую за кодом зоны, и первую * цифру номера АТС разделяет один пробел.
    */
    public Stгiпg tоStгiпg() { геtuгп "(" + tоРаddеdStгiпg(агеаСоdе, З) + ") " +
    tоРаddеdStгiпg(ехсhапgе, 3) + "-" + toPaddedString(extension, 4);
    /**
    * Преобразует значение типа п в строку указанной длины, дополненную
    * ведущими нулями. Предполагается. что i >= О ,
    * 1 <= 1епgth <= 10, а Integer.toString(i) <= 1ength.
    */
    private static String toPaddedString(int i, int length)
    String s = Integer.toString(i); return ZEROS[length - s.length()] + s;
    }
    private static String[] ZEROS =
    { “”, "0", "00", "000", "0000",
    "00000". "000000", "0000000". "00000000", "ООООООООО"};
    Если вы решили не конкретизировать формат, соответствующий комментарий к документации должен выглядеть примерно так
    /**
    * Возвращает краткое описание зелья. Точные детали представления
    * не конкретизированы и могут меняться, однако следующее представление . * может рассматриваться в качестве типичного
    *
    * "Зелье #9: тип=любовный, аромат=скипидар, вид=тушь]"
    */
    public п toString () { ... } Прочитав этот комментарий, программисты, разрабатывающие объекты, сохраняемые в базе данных, или программный код, который зависит от особенностей формата, уже не смогут винить никого, кроме самих себя, если формат однажды поменяется. Вне зависимости оттого, описываете вы формат или нет, всегда полезно предоставлять альтернативный программный доступ ко всей информации, которая содержится в значении, возвращаемом методом toString. Например, класс PhoneNumber должен включать в себя методы доступа к коду зоны, номеру А те и номеру абонента в А те. В противном случае программистам, которым нужна эта информация, придется делать разбор данной строки. Помимо того, что вы снижаете производительность приложения и заставляете программистов выполнять ненужную работу, это чревато ошибками и приводит к созданию ненадежной системы, которая перестает работать, как только выменяете формат. Не предоставив альтернативных методов доступа, вы де-факто превращаете формат строки в элемент API, даже если в документации и указали, что он может быть изменен. Соблюдайте осторожность припер е определении метода Сое Интерфейс Сlonеаblе проектировался в качестве дополнительного интерфейса
    (mixin) (статья
    16), позволяющего объектам объявлять о том, что они могут быть клонированы. К сожалению, он не может использоваться для этой цели. Его основной недостаток - отсутствие метода clone; в самом же классе Object метод clone является закрытым. Вы не можете, не обращаясь к механизму отражения свойств
    (reflection) (статья 35), вызывать для объекта метод clone лишь на том основании, что он реализует интерфейс Сlоnеаblе. Даже отражение может завершиться неудачей, поскольку нет гарантии, что у данного объекта есть доступный метод clone. Несмотря на этот и другие. недочеты, данный механизм используется настолько широко, что Имеет смысл с ним разобраться. В этой статье рассказывается о том, каким образом создать хороший метод clone, обсуждается, когда имеет смысл это делать, а также кратко описываются альтернативные подходы.
    Что же делает интерфейс
    Cloneable, который, как оказалось, не имеет методов Он определяет поведение закрытого метода clone в классе
    Object: если какой-либо класс реализует интерфейс
    Cloneable, то метод clone, реализованный в классе
    Object, возвратит его копию с воспроизведением всех полей, в противном случае будет инициирована исключительная ситуация
    CloneNotSupportedException. Это совершенно нетипичный способ использования интерфейсов, и ему не следует подражать. Обычно факт реализации некоего интерфейса говорит кое-что о том, что этот класс может делать для своих клиентов. Интерфейс же
    Cloneable лишь меняет поведение некоего защищенного метода в суперклассе. для того чтобы реализация интерфейса
    Cloneable могла оказывать какое-либо воздействие на класс, он сами все его суперклассы должны следовать довольно сложному, трудновыполнимому ив значительной степени недокументированному протоколу. Получающийся механизм не укладывается в рамки языка Java: объект создается без использования конструктора. Общие соглашения для метода clone довольно свободны. Они описаны в спецификации класса java.lang.Object: Метод создает и возвращает копию объекта. Точное значение термина копия" может зависеть от класса этого объекта. Общая задача ставится так, чтобы для любого объектах оба выражениях и x.clone().getClass() == x.getClass() возвращали true, однако эти требования не являются безусловными. Обычно условие состоит в том, чтобы выражение x.clone().equals(x) возвращало true, однако и это требование не является безусловным копирование объекта обычно приводит к созданию нового экземпляра соответствующего класса, при этом может потребоваться также копирование внутренних структур данных. Никакие конструкторы не вызываются. Такое соглашение создает множество проблем. Условие "никакие конструкторы не вызываются" является слишком строгим. Правильно работающий метод clone может воспользоваться конструкторами для создания внутренних объектов клона. Если же класс является окончательным
    (final), метод clone может просто вернуть объект, СОЗДанный конструктором. Условие, что x.clone().getClaSs() должно быть тождественно x.getClass(), является слишком слабым. Как правило, программисты полагают, что если они расширяют класс и вызывают в полученном подклассе метод super. clone, то получаемый в результате объект будет экземпляром этого подкласса. Реализовать такую схему суперкласс может только одним способом - вернуть объект, полученный в результате вызова метода supe г. Если метод clone возвращает объект, созданный конструктором, это будет экземпляр другого класса. Поэтому, если в расширяемом
    классе вы переопределяете метод clone, то возвращаемый объект вы должны получать вызовом super. clone. Если все суперклассы данного класса выполняют это условие, рекурсивный вызов метода supe г. clone в конечном счете приведет к вызову метода clone из класса
    Object и к созданию экземпляра именно того класса, который нужен. Этот механизм отдаленно напоминает автоматическое сцепление конструкторов, за исключением того, что оно не является принудительным. В версии 1.3 интерфейс
    Cloneable не раскрывает, какие обязанности берет на себя класс, реализуя этот интерфейс. В спецификации не говорится ничего, помимо того, что реализация данного интерфейса влияет на реализацию метода clone в классе
    Object. На практике же требования сводятся к тому, что в классе, реализующем интерфейс
    Cloneable, должен быть представлен правильно работающий открытый метод clone. Вообще же, выполнить это условие невозможно, если только все суперклассы этого класса не будут иметь правильную реализацию метода clone, открытую или защищенную. Предположим, что вы хотите реализовать интерфейс
    Cloneable с помощью класса, чьи суперклассы имеют правильно работающие методы clone. В зависимости от природы этого класса объект, который вы получите после вызова super.clone(), может быть, а может и не быть похож на тот, что выбудете иметь в итоге. Сточки зрения любого суперкласса этот объект будет полнофункциональным клоном исходного объекта. Поля, объявленные в вашем классе (если таковые имеются, будут иметь те же значения, что и поля в клонируемом объекте. Если все поля объекта содержат значения простого типа или ссылки на неизменяемые объекты, то возвращаться будет именно тот объект, который вам нужен, и дальнейшая обработка в этом случае не требуется. Такой вариант демонстрирует, например, класс
    PhoneNumbe r из статьи
    8. Все, что здесь нужно- это обеспечить в классе
    Object открытый доступ к защищенному методу clone: public Object clone() { try { return super,clone(); catch(CloneNotSupportedException е) { throw new Error("Assertion failure"); // Этого не может быть Однако если ваш объект содержит поля, имеющие ссылки на изменяемые объекты, такая реализация метода clone может иметь катастрофические последствия. Рассмотрим класс
    Stack' из статьи 5: public class Stack { private Object[] elements; private int size = о public Stack(int initialCapacity) { this.elements = new Object[initialCapacity];
    }

    45
    public void push(Object ее рор() {
    if (size == О) throw new EmptyStackException();
    Object result = elements[- size]; elements[size] = null;
    // Убираем устаревшую ссылку
    return result;
    }
    // Убедимся в том, что в стеке есть место хотя бы // еще для одного элемента
    private void ensureCapacity() { if (elements.length == size) {
    Object oldElements[] = elements; elements = new Object[2 * elements.length + 1];
    System.arraycopy(oldElements, о, elements, О, size); Предположим, что вы хотите сделать этот класс клонируемым. Если его метод clone просто вернет результат вызова super.clone(), полученные экземпляр Stack будет иметь правильное значение в поле size, однако его поле elements будет ссылаться на тот же самый массив, что и исходный экземпляр
    Stack. Следовательно, изменение в оригинале будет нарушать инварианты клона, и наоборот. Вы быстро обнаружите, что ваша программа выдает бессмысленные результаты либо инициирует исключительную ситуацию Подобная ситуация не могла бы возникнуть, если бы использовался основной конструктор класса
    Stack. Метод clone фактически работает как еще один конструктор, ивам необходимо убедиться в том, что он не вредит оригинальному объекту и правильно устанавливает инварианты клона. Чтобы метод clone в классе Stack работал правильно, он должен копировать содержимое стека. Проще всего это сделать путем рекурсивного вызова метода clone для массива elements: public Object clone() throws CloneNotSupportedException {
    Stack result = (Stack) super.clone(); result.elements = (Object[]) elements.clone(); return result;
    }
    Заметим, что такое решение не будет работать, если поле elements имеет модификатор final, поскольку тогда методу clone запрещено помещать туда новое значение. Это фундаментальная проблема архитектура клона несовместима с обычным применением полей final, содержащих ссылки на изменяемые объекты. Исключение составляют случаи, когда изменяемые объекты могут безопасно использовать сразу и объект, и его клон. Чтобы сделать класс клонируемым, возможно, потребуется убрать' у некоторых полей модификатор final. Не всегда бывает достаточно рекурсивного вызова метода clone. Предположим, что выпишите метод clone для хэш-таблицы, состоящей из набора сегментов (buckets), каждый из которых содержит либо ссылку напер выи элемент в связном списке, имеющем несколько пар ключ/значение, либо null, если сегмент пуст. для лучшей производительности в этом классе вместо java.util.LinkedList используется собственный упрощенный связный список public class HashTable implements Cloneable{
    private'Entry[] buckets = ... ; private static class Entry {
    Object key;
    Object value;
    Entry next;
    Entry(Object key, Object value, Entry next) { this.key
    = key; this.value = value;
    this.next = next;
    }
    }
    // Остальное опущено Предположим, что вы рекурсивно клонируете массив buckets, как это делалось для класса Stack:
    1   2   3   4   5   6   7   8   9   ...   25


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