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

  • // Сравниваем коды зон int areaCodeDiff = areaCode - pn.areaCode; if (areaCodeDiff 1= О) return areaCodeDiff; // Коды зон равны, сравниваем номера АТС

  • // Коды зон и номера АТС равны, сравниваем номера абонентов

  • // Методы доступа без соответствующих мутаторо

  • // почему используется // метод floatTolntBits // см. статью 7.

  • 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
    страница8 из 25
    1   ...   4   5   6   7   8   9   10   11   ...   25

    // Сравниваем коды зон
    if (areaCode < pn.areaCode)
    return -1; if (areaCode > pn.areaCode) return 1;
    // Коды зон равны, сравниваем номера АТС if (exchange < pn.exchange) г -1; if (exchange > pn.exchange) г 1;
    // Коды зон и номера АТС равны, сравниваем номера абонентов if (extension < pn.extension) return -1; if (extension > pn.extension)
    return 1; return о // Все поля равны Этот метод работает прекрасно, но его можно улучшить. Напомним, что в соглашениях для метода соmрагеТо величина возвращаемого значения не конкретизируется, только знак. Вы можете извлечь из этого пользу, упростив программу и, возможно, заставив ее работать немного быстрее public int compareTo(Object о) {
    PhoneNumber рп = (PhoneNumber)o;
    // Сравниваем коды зон
    int areaCodeDiff = areaCode - pn.areaCode; if (areaCodeDiff 1= О) return areaCodeDiff;
    // Коды зон равны, сравниваем номера АТС
    int exchangeDiff = exchange - pn.exchange; if (exchangeDiff != 0) return exchangeDiff;
    // Коды зон и номера АТС равны, сравниваем номера абонентов
    return extension - pn.extension;
    }
    Такая уловка работает прекрасно, но применять ее следует крайне осторожно. Не пользуйтесь ею, если у вас нет уверенности в том, что рассматриваемое полене может иметь отрицательное значение или, что бывает чаще, разность между наименьшими наибольшим возможными значениями поля меньше или равна значению
    IN T E G E R . M A X _V A LU E
    (231
    -1). Причина, по которой этот прием не работает, обычно заключается в том, что битовое целое число со знаком является недостаточно большим, чтобы показать разность двух битовых целых чисел с произвольным знаком. Если i - большое положительное целое число, а j - большое отрицательное целое число, то при вычислении разности
    (i-j) произойдет переполнение и будет возвращено отрицательное значение. Следовательно, полученный нами метод соmрагеТо работать не будет для некоторых аргументов будет выдаваться бессмысленный результат, тем самым будут нарушены первое и второе условия соглашения для метода соmрагеТо. И эта проблема не является чисто теоретической, она уже вызывала сбои в реальных системах. Выявить причину подобных отказов бывает крайне трудно, поскольку неправильный метод соmрагеТо работает правильно со многими входными значениями. Глава Классы и интерфейсы bКлассы и интерфейсы занимают в языке программирования Java центральное положение. Они являются основными элементами абстракции. Язык Java содержит множество мощных элементов, которые можно использовать при построении классов и интерфейсов. В этой главе даются рекомендации, которые помогут вам наилучшим образом применять эти элементы, чтобы ваши классы и интерфейсы были удобными, надежными и гибкими. Сводите км и ним уму доступность классов и членов Единственный чрезвычайно важный фактор, отличающий хорошо спроектированный модуль от неудачного- степень сокрытия его внутренних данных и иных деталей реализации от других модулей. Хорошо спроектированный модуль скрывает все детали реализации, четко разделяя свой АР и реализацию. Модули взаимодействуют друг с другом только через свои API, и ни один из них не знает, какая обработка происходит внутри другого модуля. Эта концепция, называемая сокрытием информации (information hiding) или инкапсуляцией (encapsulatiori), представляет собой один из фундаментальных принципов разработки программного обеспечения [Parnas72]. Сокрытие информации важно по многим причинам, большинство из которых Связано стем обстоятельством, что этот механизм эффективно изолирует друг от друга модули, составляющие систему, позволяя разрабатывать, тестировать, оптимизировать, использовать, исследовать и обновлять их по отдельности. Благодаря этому ускоряется разработка системы, так как различные модули могут создаваться параллельно. Кроме того, уменьшаются расходы на сопровождение приложения, поскольку каждый модуль можно быстро изучить и отладить, минимально рискуя навредить остальным модулям. Само по себе сокрытие информации не может обеспечить
    хорошей производительности, но оно создает условия для эффективного управления производительностью. Когда разработка системы завершена и процедура ее профилирования показала, работа каких модулей вызывает падение производительности (статья
    37), можно заняться их оптимизацией, не нарушая функционирования остальных модулей. Сокрытие информации повышает возможность повторного использования программ, поскольку каждый отдельно взятый модуль независим от остальных модулей и часто оказывается полезен в иных контекстах, чем тот, для которого он разрабатывался. Наконец, сокрытие информации уменьшает риски при построении больших систем удачными могут оказаться отдельные модули, даже если в целом система не будет пользоваться успехом. Язык программирования java имеет множество возможностей для сокрытия информации. Одна из них - механизм управления доступом control) [JLS, 6.6], задающий степень доступности
    (accessibility) для интерфейсов, классов и членов классов. Доступность любой сущности определяется тем, в каком месте она была декларирована и какие модификаторы доступа, если таковые есть, присутствуют в ее декларации (pr1vate, protected или public). Правильное использование этих модификаторов имеет большое значение для сокрытия информации. Главное правило заключается в том, что вы должны сделать каждый класс или член максимально недоступным. Другими словами, вы должны использовать самый низший из возможных уровней доступа, который еще допускает правильное функционирование создаваемой программы. Для классов и интерфейсов верхнего уровня (не являющихся вложенными) существуют лишь два возможных уровня доступа доступный только в пределах пакета
    (package-private) и открытый
    (public). Если вы объявляете класс или интерфейс верхнего уровня с модификатором public, он будет открытым, в противном случае он будет доступен только в пределах пакета. Если класс или интерфейс верхнего уровня можно сделать доступным только в пакете, таки нужно поступать. При этом класс или интерфейс становится частью реализации этого пакета, а не частью его внешнего
    API. Вы можете модифицировать его, заменить или исключить из пакета, не опасаясь нанести вред клиентам. Если же выделаете класс или интерфейс открытым, на вас возлагается обязанность всегда поддерживать его с целью сохранения совместимости. Если класс или" интерфейс верхнего уровня, доступный лишь в пределах пакета, используется только водном классе, вы должны рассмотреть возможность превращения его в закрытый класс (или интерфейс, который будет вложен именно в тот класс, где он используется (статья
    18). Тем самым вы еще более уменьшите его доступность. Однако это уже не так важно, как сделать необоснованно открытый класс доступным только в пределах пакета, поскольку класс, доступный лишь в пакете, уже является частью реализации этого пакета, а не его внешнего
    API. Для членов класса (полей, методов, вложенных классов и вложенных интерфейсов) существуют четыре возможных уровня доступа, которые перечислены здесь в порядке увеличения доступности Закрытый (private) - данный член доступен лишь в пределах того класса верхнего уровня, где он был объявлен.
    Доступный лишь в пределах пакета (package-private) - член доступен из любого класса в пределах того пакета, где он был объявлен. Формально этот уровень называется доступом по умолчанию
    (default access), и именно этот уровень доступа вы получаете, если небыли указаны модификаторы доступа. Защищенный (protected) - член доступен для подклассов того класса, "где этот член был объявлен (с небольшими ограничениями [jLS, 6.6.2]); доступ к члену можно получить из любого класса в пакете, где этот член был объявлен. Открытый (public) - член доступен отовсюду. После того как для вашего класса тщательно спроектирован открытый
    API, вам следует сделать все остальные члены класса закрытыми. И только если другому классу из того же пакета действительно необходим доступ к какому-то члену, вы можете убрать модификатор private и сделать этот член доступным в пределах всего пакета. Если вы обнаружите, что таких членов слишком много, еще раз проверьте модель вашей системы и попытайтесь найти другой вариант разбиения на классы, при котором они были бы лучше изолированы друг от друга. Как было сказано, и закрытый член, и член, доступный только в пределах пакета, являются частью реализации класса и обычно не оказывают воздействия на его внешний
    API. Однако они могут "просочиться" во внешний
    API, если класс реализует интерфейс Serializable (статьи
    54 и
    55). Если уровень доступа для члена открытого класса меняется с доступного в пакете на защищенный, уровень доступности данного члена резко возрастает. Для этого класса защищенный член является частью внешнего
    API, а потому ему навсегда должна быть обеспечена поддержка. Более того, наличие защищенного члена в классе, передаваемом за пределы пакета, представляет собой открытую передачу деталей реализации (статья
    15). Потребность в использовании защищенных членов должна возникать сравнительно редко. Существует одно правило, ограничивающее ваши возможности по уменьшению доступности методов. Если какой-либо метод переопределяет метод супер класса, то методу в подклассе не разрешается иметь более низкий уровень доступа, чем был у метода в суперклассе [JLS, 8.4.6.3]. Это необходимо для гарантии того, что экземпляр подкласса можно будет использовать повсюду, где можно было использовать экземпляр суперкласса. Если вы нарушите это правило, то когда попытаетесь скомпилировать этот подкласс, компилятор сгенерирует сообщение об ошибке. Частный случай правила если класс реализует некий интерфейс, то все методы класса, представленные в этом интерфейсе, должны быть объявлены как открытые (public). Это объясняется тем, что в интерфейсе все методы неявно подразумеваются открытыми. Открытые поля (в отличие от открытых методов) в открытых классах должны быть редким явлением если вообще должны ПОЯВЛЯТЬСЯ. Если полене имеет модификатора final или имеет модификатор и ссылается на изменяемый объект, то, делая его открытым, вы упускаете возможность наложения ограничений назначения которые могут быть записаны в этом поле. Вы также лишаетесь возможности предпринимать какие- либо действия в ответ на изменение этого поля. Отсюда простой вывод классы с открытыми изменяемыми полями небезопасны в системе с несколькими потоками
    (not thread-safe). Даже если поле имеет модификатор final и не ссылается на изменяемый объект, объявляя его открытым, вы отказываетесь от возможности гибкого перехода на новое представление внутренних данных, в котором это поле будет отсутствовать. Из правила, запрещающего открытым классам иметь открытые поля, есть одно исключение. С помощью полей public static final классы могут предоставлять вовне константы. Согласно договоренности, названия таких полей состоят из прописных букв, слова в названии разделяются символом подчеркивания (статья
    38). Крайне важно, чтобы эти поля содержали либо простые значения, либо ссылки на неизменяемые объекты (статья
    13). Поле с модификатором final, содержащее ссылку на изменяемый объект, обладает всеми недостатками поля без модификатора final: хотя саму ссылку изменить нельзя, объект, на который она указывает, может быть изменен - с роковыми последствиями. Заметим, что массив ненулевой длины всегда является изменяемым. Поэтому практически никогда нельзя декларировать поле массива как public static final. Если в классе будет такое поле, клиенты получат возможность менять содержимое этого массива. Часто это является причиной появления дыр в системе безопасности.
    // Потенциальная дыра в системе безопасности public static final Туре VALUES = { ... } ; Открытый массив следует заменить закрытым массивом и открытым неизменяемым списком private static final Туре PRIVATE_VALUES = { ... }; public static final List VALUES =
    Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES)); Другой способ если на этапе компиляции вам необходима проверка типов ивы готовы пожертвовать производительностью, то можете заменить открытое поле массива открытым методом, который возвращает копию закрытого массива. private static final Туре PRIVATE_VALUES = { .. . } ; private static final Туре values() { return ( Туре ) PRIVATE_VALUES.clone(); Подведем итоги. Всегда следует снижать уровень доступа, насколько это возможно. Тщательно разработав наименьший открытый
    API, вы должны не дать возможности каким-либо случайным классам, интерфейсами членам стать частью этого
    API. За исключением полей типа public static final, других открытых полей в открытом классе быть не должно. Убедитесь в том, что объекты, на которые есть ссылки в полях типа public static final, не являются изменяемыми.
    Предпочитайте постоянство bНеизменяемый класс - это такой класс, экземпляры которого нельзя поменять. Вся информация, содержащаяся в любом его экземпляре, записывается в момент его создания и остается неизменной в течение всего времени существования этого объекта. В библиотеках для платформы ]ауа имеется целый ряд неизменяемых классов в том числе String, простые классы- оболочки, Biglnteger и BigDecimal. На это есть много веских причин по сравнению с изменяемыми классами, их проще проектировать, разрабатывать и и<;пользовать. Они менее подвержены ошибками более надежны. Делая класс неизменяемым, выполняйте следующие пять правил Не создавайте каких-либо методов, которые модифицируют представленный объект эти методы называются мутаторами (mutator)).
    2. Убедитесь в том, что ни один метод класса не может быть переопределен. Это предотвратит потерю свойства неизменяемости данного класса в небрежном или умышленно плохо написанном подклассе. Защита методов от переопределения обычно осуществляется путем объявления класса в качестве окончательного, однако есть и другие способы (см. ниже. Сделайте все поля окончательными
    (final). Это ясно выразит ваши намерения, причем в некоторой степени их будет поддерживать сама система. Это может понадобиться и для обеспечения правильного поведения программы в том случае, когда ссылка на вновь созданный экземпляр передается из одного потока в другой без выполнения синхронизации
    [Pugh01a] как результат ведущихся работ по ис

    равлению модели памяти в ]ауа). Сделайте все поля закрытыми
    (private). Это не позволит клиентам непосредственно менять значение полей. Хотя формально неизменяемые классы и могут иметь открытые поля с модификатором final, которые содержат либо значения простого типа, либо ссылки на неизменяемые объекты, делать это не рекомендуется, поскольку они будут препятствовать изменению в последующих версиях внутреннего представления класса (статья 12). Убедитесь в монопольном доступе ко всем изменяемым компонентам. Если в вашем классе есть какие-либо поля, содержащие ссылки на изменяемые объекты, удостоверьтесь в том, что клиенты этого класса не смогут получить ссылок на эти объекты. Никогда не инициализируйте такое поле ссылкой на объект, полученной от клиента, метод доступа не должен возвращать хранящейся в этом поле ссылки на объект. При использовании конструкторов, методов доступа к полями методов readObject (статья 56) создавайте резервные копии
    (defensive copies) (статья
    24).
    В примерах из предыдущих статей многие классы были неизменяемыми. Так, класс PhoneNumber статья 8) имеет метод доступа для каждого атрибута, ноне имеет соответствующего мутатора.
    ГIредставим более сложный примере г final float im; public Complex(float ге, float im) {
    this. ге = ге; this.im = im;
    }
    // Методы доступа без соответствующих мутаторо
    public float realPart() { return ге; } public float imaginaryPart() { return im; } public Complex add(Complex с) { return new Complex(re + С.ге, im + c,im);
    }
    public Complex subtract(Complex с) { return new Complex(re - С.ге, im - c.im);
    }
    public Complex multiply(Complex с) {
    return new г - im*c.im, re*c.im + im*c. ге);
    }
    public Complex divide(Complex с) { float tmp = с. ге*с. ге + c.im*c.im; return new Complex((re*c. ге + im*c.im)/tmp, (im*c.re - re*c.im)/tmp);
    }
    public boolean equals(Object о) {
    if (о == this) return true; if ос Чтобы понять,

    // почему используется
    // метод floatTolntBits
    // см. статью 7.
    public int hashCode() { int result = 17 + Float.floatTolntBits(re); result = 37*result + Float.floatTolntBits(im); return result;
    }
    public String toString() { return "(" + ге + " + " + im + "i)";
    }
    }
    Данный класс представляет комплексное число число с действительной и мнимой частями. Помимо обычных методов класса Object, он реализует методы доступа к действительной и мнимой частям числа, а также четыре основные арифметические операции сложение, вычитание, умножение и деление. Обратите внимание на то, что представленные арифметические операции вместо того, чтобы менять данный экземпляр, генерируют и передают новый экземпляр класса Complex. Такой подход используется для большинства сложных неизменяемых классов. Называется это функциональным подходом (functiona! approach), поскольку рассматриваемые методы возвращают результат применения некоей функции к своему операнду, не изменяя при этом сам операнд. Альтернативой является более распространенный процедурный подход (procedura! approach), при котором метод выполняет для своего операнда некую процедуру, которая меняет его состояние. При первом знакомстве функциональный подход может показаться искусственным, однако он создает условия для неизменяемости объектов, а это имеет множество преимуществ. Неизменяемые объекты просты. Неизменяемый объект может находиться только водном состоянии - в том, с которым он был создан. Если вы удостоверитесь, что каждый конструктор класса устанавливает требуемые инварианты, это будет гарантией того, что данные инварианты будут оставаться действительными всегда, без каких-либо дополнительных усилий с вашей стороны и со стороны программиста, использующего этот класс. Что же касается изменяемого объекта, то он может иметь относительно сложное пространство состояний. Если в документации не представлено точного описания смены состояний, осуществляемой методами-мутаторами, надежное использование изменяемого' класса может оказаться сложной или даже невыполнимой задачей.
    Неизменяемые объекты по своей сути безопасны при работе с потоками (thread-safe): им ненужна синхронизация. Они не могут быть разрушены только из-за того, что одновременно к ним обращается несколько потоков. Несомненно, это самый простой способ добиться безопасности при работе с потоками. Действительно, ни один поток никогда не сможет обнаружить какого-либо воздействия со стороны другого потока через неизменяемый объект. По этой причине неизменяемые объекты можно свободно использовать для совместного доступа. Неизменяемые классы должны задействовать это преимущество, заставляя клиентов везде, где возможно, применять ж существующие экземпляры. Один из простых приемов, позволяющих достичь этого для часто используемых значений создавать константы типа publiC static final. Например, в классе Соmрех можно представить следующие константы publiC static final Complex ZERO = new Complex(0, 0);
    public static final Complex ONE = new Complex(1, 0);
    public static final Complex I = new Complex(0, 1);
    Mожно сделать еще один шаг в этом направлении. В неизменяемом классе MOHO предусмотреть статические методы генерации, которые кэшируют часто запрашиваемые экземпляры вместо того, чтобы при каждом запросе создавать новые экземпляры, дублирующие уже имеющиеся. Подобные статические методы генерации есть в классах 8iglnteger и 8oo1ean. Применение статических методов генерации заставляет клиентов совместно использовать уже имеющиеся экземпляры, а не создавать новые. Это снижает расход памяти и сокращает работу по ее освобождению. Благодаря тому, что неизменяемые объекты можно свободно предоставлять для 'совместного доступа, не требуется создавать для них резервные копии copies) (статья
    24). В действительности вам вообще ненужно делать никаких копий, поскольку они всегда будут идентичны оригиналу. Соответственно, для неизменяемого класса не надо, да и не следует создавать метод clone и конструктор копии сору constructor) (статья
    10). Когда платформа Java только появилась, еще не было четкого понимания этого обстоятельства, и потому класс String имеет конструктор копий. Лучше им не пользоваться (статья
    4). Можно совместно использовать не только неизменяемый объектно и его содержимое. Например, класс 8iglnteger применяет внутреннее представление знак/модуль (sign/magnitude). Знак числа задается полем типа int, его модуль массивом int. Метод инвертирования negate создает новый экземпляр 8iglnteger стем же модулем и с противоположным знаком. При этом нет необходимости копировать массив, поскольку вновь созданный экземпляр 8iglnteger имеет внутри ссылку на тот же самый массив, что и исходный экземпляр. Неизменяемые объекты образуют крупные строительные блоки для остальных объектов, как изменяемых, таки неизменяемых. Гораздо легче обеспечивать поддержку инвариантов сложного объекта, если известно, что составляющие его объекты не будут менять его "снизу. Частный случай данного принципа неизменяемый объект формирует большую схему соответствия между ключами и набором элементов.
    При этом вас не должно беспокоить то, что значения, однажды записанные в эту схему или набор, вдруг поменяются, и это приведет к разрушению инвариантов схемы или набора. Единственный настоящий недостаток неизменяемых классов заключается в том, что для каждого уникального значения им нужен отдельный объект. Создание таких объектов может потребовать больших ресурсов, особенно если они имеют значительные размеры. Предположим, что у вас есть объект Biglnteger размером в миллион битов ивы хотите логически дополнить его младший бит
    Biglnteger mоbу = ...;
    Mоbу = moby.flipBit(0); Метод flip8it создает новый экземпляр класса 8iglnteger длиной также в миллион битов, который отличается от своего оригинала только одним битом. Этой операции требуются время и место, пропорциональные размеру экземпляра 8iglnteger. Противоположный подход использует java.util.BitSet. Как и Biglnteger, BitSet представляет последовательность битов произвольной длины, однако, в отличие от BigInteger, BitSet является изменяемым классом. В классе BitSet предусмотрен метод, позволяющий в экземпляре, содержащем миллионы битов, менять значение отдельного бита в течение фиксированного времени. Проблема производительности усугубляется, когда вы выполняете многошаговую операцию, генерируя на каждом этапе новый объекта в конце отбрасываете все эти объекты, оставляя только окончательный результат. Справиться с этой проблемой можно двумя способами. Во-первых, можно догадаться, какие многошаговые операции будут требоваться чаще всего, и представить их в качестве элементарных. Если многошаговая операция реализована как элементарная (primitive), неизменяемый класс уже не обязан на каждом шаге создавать отдельный объект. Изнутри неизменяемый класс может быть сколь угодно хитроумным. Например, у класса Biglnteger есть изменяемый "класс-компаньон", который доступен только в пределах пакета и применяется для ускорения многошаговых операций, таких как возведение в степень по модулю. По всем перечисленным выше причинам использовать изменяемый класс-компаньон гораздо сложнее. Однако делать этого вам, к счастью, не надо. Разработчики класса Biglnteger уже выполнили за вас всю тяжелую работу. Описанный прием будет работать превосходно, если вам удастся точно предсказать, какие именно сложные многошаговые операции с вашим неизменяемым классом будут нужны клиентам. Если сделать это невозможно, самый лучший вариант создание
    открытого
    изменяемого класса- компаньона. В библиотеках для платформы Java такой подход демонстрирует класс String, для которого изменяемым классом Компаньоном является StringBuffer. В силу ряда причин BitSet вряд ли играет роль Изменяемого компаньона для Biglnteger. Теперь, когда вызнаете, как создавать неизменяемый класс и каковы доводы за и против неизменяемости, обсудим несколько альтернативных вариантов. Напомним, что для гарантии неизменяемости класс должен запретить любое переопределение своих методов

    65
    Помимо возможности сделать класс окончательным
    (final), есть еще два способа решения этой проблемы. Первый способ вместо того, чтобы делать окончательным сам класс, сделать окончательными все его методы, пометив как final. Единственное преимущество данного подхода заключается в том, что он позволяет программистам расширять класс, добавляя к нему новые методы, выстроенные поверх старых. По эффективности это тоже самое, что вводить новые методы как статические в отдельном вспомогательном классе, не создающем экземпляров (статья
    3), а потому использовать такой подход не рекомендуется. Второй прием заключается в том, чтобы сделать все конструкторы неизменяемого класса закрытыми либо доступными только в пакете и вместо открытых конструкторов использовать открытые статические методы генерации статья 1). Для пояснения представим, как бы выглядел класс
    Complex, если бы применялся такой подход
    1   ...   4   5   6   7   8   9   10   11   ...   25


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