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

  • // Ошибка использование плавающей точки для денежных расчетов

  • // Неуместное объединение строк - плохая производительность

  • // Хорошо указывается тип интерфейса.

  • 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
    страница17 из 25
    1   ...   13   14   15   16   17   18   19   20   ...   25

    // Неправильно, хотя встречается часто
    static int random(int n) { return Math.abs(rnd.nextlnt()) % n; Неплохой метод, но он несовершенен у него есть три недостатка. Первый состоит в том, что если n
    - это небольшая степень числа два, то последовательность генерируемых случайных чисел через очень короткий период начнет повторяться. Второй заключается в том, что если n не является степенью числа два, тов среднем некоторые Числа будут получаться гораздо чаще других. Если n большое, указанный недостаток может проявляться довольно четко. Графически это демонстрируется следующей программой, которая генерирует миллион случайных чисел в тщательно подобранном диапазоне и затем печатает, сколько всего чисел попало в нижнюю половину этого диапазона public static void main(String[] args) {
    int n = 2 * (Integer.MAX_VALUE / 3);
    int low = 0 ; for (int i = 0; i < 1000000; i++) if (random (n)
    < n/2) low++;
    System.out.println(low); Если бы метод random работал правильно, программа печатала бы число, близкое к полумиллиону, однако, запустив эту программу, вы обнаружите, что она печатает число, близкое к ббб ббб. Две трети чисел, сгенерированных методом random, попадает в нижнюю половину диапазона Третий недостаток представленного метода random заключается в том, что он может, хотя и редко, потерпеть полны фиаско, выдавая результат, выходящий за пределы указанного диапазона.

    TO происходит потому, что метод пытается преобразовать значение, возвращенное мет дом rnd.nextlnt (), в неотрицательное целое число, используя метод Math.abs. Если nextlnt() вернул Integer.MIN_VALUE,
    то Math.abs также возвратит Intege г. MIN_ VALUE. Затем, если n не является степенью числа два, оператор остатка
    (%) вернет отрицательное число. Это почти наверняка вызовет сбой в вашей программе, и воспроизвести обстоятельства этого сбоя будет трудно. Чтобы написать такой вариант метода random, в котором были бы исправлены все эти три недостатка, необходимо изучить генераторы линейных конгруэнтных псевдослучайных чисел, теорию чисел и арифметику дополнения до двух. К счастью, делать это вам ненужно, все это уже сделано 'для вас. Необходимый метод называется Random. nextlnt(int), он был добавлен в пакет java. util стандартной библиотеки в версии 1.2. Нет нужды вдаваться в подробности, каким образом метод nextlnt(int) выполняет свою работу хотя любопытные личности могут изучить документацию или исходный текст метода. Старший инженер с подготовкой в области алгоритмов провел много времени за разработкой, реализацией и тестированием этого метода, а затем показал метод экспертам в данной области стем, чтобы убедиться в его правильности. После этого библиотека прошла стадию предварительного тестирования и была опубликована, тысячи программистов широко пользуются ею в течение нескольких лет. До сих пор ошибок в указанном методе найдено не было. Но если какой-либо дефект обнаружится, он будет исправлен в следующей же версии. Обращаясь к стандартной библиотеке, вы используете знания написавших ее экспертов, а также опыт тех, кто работал с нею до вас. Второе преимущество от применения библиотек заключается в том, что вам ненужно терять время на решение специальных задач, имеющих лишь косвенное отношение к вашей работе. Как и большинство программистов, вы должны тратить время на разработку своего приложения, а не на подготовку его фундамента. Третье преимущество от использования стандартных библиотек заключается в том, что их производительность имеет тенденцию повышаться со временем, причем без каких-либо усилий с вашей стороны. Множество людей пользуется библиотеками, они применяются в стандартных промышленных тестах, поэтому организация, которая осуществляет поддержку этих библиотек, заинтересована в том, чтобы заставить их работать быстрее. Например, стандартная библиотека арифметических операций с многократно увеличенной точностью java.math была переписана в версии
    1.3, что привело к впечатляющему росту ее производительности. Со временем библиотеки приобретают новые функциональные возможности. Если в каком-либо классе библиотеки не хватает важной функции, сообщество разработчиков даст знать об этом недостатке. Платформа Java всегда развивалась при серьезной поддержке со стороны сообщества разработчиков. Прежде этот процесс был неформальным, сейчас же существует официальное движение, называемое Java и Process ОСР). Так или иначе, библиотеки пополняются недостающими функциями. Последнее преимущество от применения стандартных библиотек заключается в том, что ваш код соответствует господствующим в данный момент тенденциям. Такой код намного легче читать и сопровождать, его могут использовать множество разработчиков.
    Учитывая 'все эти преимущества, логичным казалось бы применение библиотека не частных разработок, однако многие программисты этого не делают. Но почему Может быть, потому, что они не знают о возможностях имеющихся библиотек. С
    каждой следующей версией в библиотеки включается множество новых функций, и стоит быть в курсе этих новшеств. Вы можете внимательно изучать соответствующую документацию в режиме online либо прочесть о новых библиотеках в самых разных книгах АР, ChanOO, Flanagan99, Chan98]. Библиотеки слишком объемны, чтобы просматривать всю документацию, однако каждый программист должен хорошо знать java.lang, java. util ив меньшей степени java.io. Остальные библиотеки изучаются по мере необходимости. Обзор всех возможностей библиотек выходит за рамки данной статьи, однако некоторые из них заслуживают особого упоминания. В версии 1.2 в пакет java.util была добавлена архитектура п
    Frатешоrk. Она должна входить в основной набор инструментов каждого программиста. Collections F ramework - унифицированная архитектура, предназначенная для представления и управления коллекциями и позволяющая манипулировать коллекциями независимо от деталей представления. Она сокращает объемы работ по программированию ив тоже время повышает производительность. Эта архитектура позволяет достичь унифицированности несвязанных API, упрощает проектирование и освоение новых API, способствует повторному использованию программного обеспечения. Указанная архитектура базируется на шести интерфейсах коллекций (Collection, Set, List, Мари. Она включает в себя реализацию этих интерфейсов и алгоритмы работы сними. Наследуемые от коллекций классы Vector и Hashtable были перестроены под эту архитектуру, и потому, чтобы воспользоваться преимуществами Collections Framework, вам не придется отказываться от этих классов.
    Collections Framework существенно уменьшает объем программного кода, необходимого для решения многих скучных задач. Например, у вас есть вектор строки вы хотите отсортировать его в алфавитном порядке. Эту работу выполняет всего одна строка
    Collections.sort(v); Если нужно сделать тоже самое, но игнорируя регистр, воспользуйтесь конструкцией
    Collections.sort(v, String.CASE_INSENSITIVE_ORDER); Предположим, вы хотите напечатать все элементы массива. Многие программисты в таких случаях пользуются циклом for, нов этом нет необходимости, если применить следующую идиому
    System.out.println(Arrays.asList(a)); Наконец, предположим, что вам необходимо узнать все ключи, для' которых два экземпляра класса Hashtable - h1 и h2 - имеют одинаковые значения. До появления архитектуры Collections
    Framework это потребовало бы большого количества программного кода, сейчас же решение занимает всего три строки
    Мар tmp = new HashMap(h1);
    tmp.entrySet().retainAll(h2.entrySet());
    Set result = tmp.keySet(); Приведенные примеры затрагивают лишь немногие из возможностей, которые предлагает
    Collections Framework. Если вы хотите узнать больше, обратитесь к документации на узле компании Sun [Collections] либо прочтите учебник В. Среди библиотек от третьих компаний упоминания заслуживает пакет util, сопcurrent Дага Ли (Doug
    Lea) [Lea01], в котором представлены утилиты высокого уровня, упрощающие программирование параллельных потоков. В версии 1.4 в библиотеке появилось много дополнений. Самыми примечательными являются следующие

    jаvа.util.regex - полноценная функция для регулярных выражений в духе Perl.

    java.util.prefs - функция для сохранения пользовательских предпочтений и сведений о конфигурации программы.

    java.nio - высокопроизводительная функция ввода-вывода, обеспечивающая масштабируемый ввод-вывод
    (scalable 1/0), похожий на вызов poll в Unix, и ввод-вывод, отображаемый в памяти (memory-mapped 1/0), похожий на вызов nmap в Unix.
    138

    jаvа.util.LinkedHashSet, LinkedHashMap, IdentityHashMap новые реализации коллекций. Иногда функция, заложенная в библиотеке, не отвечает вашим потребностям. И чем специфичнее ваши запросы, тем это вероятнее. Если вы изучили возможности, предлагаемые библиотеками в некоей области, и обнаружили, что они не соответствуют вашим потребностям, используйте альтернативную реализацию. В функциональности, предоставляемой любым конечным набором библиотек, всегда найдутся пробелы. И если необходимая вам функция отсутствует, у вас нет иного выбора, как реализовать ее самостоятельно. Подведем итоги. Не изобретайте колесо. Если вам нужно сделать нечто, что кажется вполне обычным, в библиотеках уже может быть класс, который делает это. Вообще говоря, программный код в библиотеке наверняка окажется лучше кода, который вы напишете сами, а со временем он может стать еще лучше. Мы не ставим под сомнение ваши способности как программиста, однако библиотечному коду уделяется гораздо больше внимания, чем может позволить себе средний разработчик при реализации тех же самых функций. Если требуются точные ответы, избегайте использования типов и d o u b Ie Типы float ив первую очередь предназначены для научных и инженерных расчетов. ни реализуют бинарную арифметику с плавающей точкой (binary f!oating-point arithmetic), которая была тщательно выстроена стем, чтобы быстро получать правильное приближение для широкого диапазона значений. Однако эти типы не дают точного результата, ив ряде случаев их нельзя использовать. Типы float и double не подходят для денежных расчетов, поскольку сих помощью
    невозможно представить число 0.1 (или любую другую отрицательную степень числа десять. Например, у вас в кармане лежит $1.03, ивы тратите 42 цента. Сколько денег у вас осталось Приведем фрагмент наивной программы, которая пытается ответить на этот вопрос:
    Sуstеm.оut.рrintln(1.0З - .42); Как ни печально, программа выводит 0.6100000000000001. И это не единственный случай. Предположим, что у вас в кармане есть доллар, ивы покупаете девять прокладок для крана по десять центов за каждую. Какую сдачу вы получите
    System.out.println(1.00 - 9*.10); Если верить этому фрагменту программы, то вы получите $0.09999999999999995. Может быть, проблему можно решить, округлив результаты перед печатью К сожалению, это срабатывает не всегда. Например, у вас в кармане есть доллар, ивы видите полку, где выстроены вряд вкусные конфеты за 10, 20, 30 центов итак далее вплоть до доллара. Вы покупаете по одной конфете каждого вида, начиная стой, что стоит 10 центов, итак далее, пока у вас еще есть возможность взять следующую конфету. Сколько конфет выкупите и сколько получите сдачи Решим эту задачу следующим образом
    // Ошибка использование плавающей точки для денежных расчетов
    public static void main(String[] args) { double funds = 1.00; int itemsBought = 0 ; for (double price = .10; funds >= p rice; price += .10) { funds -= price; itemsBought++; }
    System.out.printl n(itemsBought + " items bought.");
    System.out.println("Change: $" + funds); Запустив программу, вы выясните, что можете позволить себе три конфеты и у вас останется еще $0.39999999999999999. Но это неправильный ответ Правильный путь решения задачи заключается в применении для денежных расчетов типов BigDecima l, int или long. Представ м простое преобразование предыдущей программы, которое позволяет использовать тип BigDecima l вместо double: public static void main(String[] args) { final BigDecimal TEN_CENTS = new BigDecimal( ".10”); int itemsBought = 0 ;
    BigDecima l funds = new BigDecimal("1.00");
    for (BigDecimal price = TEN_CENTS; funds.compareTo(price) >= 0 ; price = price.add(TEN_CENTS)) { itemsBought++; funds = fu nds.subtract(price); }
    System.out.print1n(itemsBought + " items bought.");
    System.out.print1n("Money left over: $" + funds); Запустив исправленную программу, вы обнаружите, что можете позволить себе четыре конфеты и у вас останется $0.00. Это верный ответ. Однако тип BigDecima l имеет два недостатка он не столь удобен и медленнее, чем простой арифметический тип. Последнее можно считать несущественным, если вы решаете единственную маленькую задачу, а вот неудобство может раздражать. Вместо BigDecima l можно использовать int или long (в зависимости от обрабатываемых величин) и самостоятельно отслеживать положение десятичной точки. В нашем примере расчеты лучше производить не в долларах, а в центах. Продемонстрируем этот подход public static void main(String[] args) {
    int itemsBought = 0 ; int funds = 100; for (int price = 10; funds >= price; price += 10) { itemsBought++; funds - = price;
    }
    System.out.println(itemsBought + " items bought.");
    System.out.println("Money left over: " + funds + " cents"); Подведем итоги. Для вычислений, требующих точного результата, не используйте типы float и double. Если вы хотите, чтобы система сама отслеживала положение десятичной точки, и вас не пугают неудобства, связанные с отказом от простого типа, используйте BigDecima l. Применение этого класса имеет еще то преимущество, что он дает вам полный контроль над округлением для любой операции, завершающейся округлением, предоставляется на выбор восемь режимов округления. Это пригодится, если выбудете выполнять экономические расчеты с жестко заданным алгоритмом округления. Если для вас важна производительность, вас не пугает необходимость самостоятельно отслеживать положение десятичной точки, а обрабатываемые значения не слишком велики, используйте тип int или long. Если значения содержат не более девяти десятичных цифр, можете применить тип int. Если в значении не больше восемнадцати десятичных цифр, используйте тип long. Если же в значении более восемнадцати цифр, вам придется работать с BigDecima l.
    Не используйте строку там , где более уместен иной тип Тип String создавался для того, чтобы представлять текст, и делает он это прекрасно. Поскольку строки широко распространены и имеют хорошую поддержку в языке Java, возникает естественное желание использовать строки для решения тех задач, для которых они не предназначались. В этой статье обсуждается несколько операций, которые не следует проделывать со строками. Строки - плохая замена другим типам значений. Когда данные попадают в программу из файла, сети или с клавиатуры, они часто имеют вид строки. Естественным является стремление оставить их в том же виде, однако это оправданно лишь тогда, когда данные по своей сути являются текстом. Если получены числовые данные, При конкатенации строк опасайтесь потери производительности конкатенации строк (+) - удобный способ объединения нескольких строк в одну. Он превосходно справляется с генерацией отдельной строки для вывода и с созданием строкового представления для небольшого объекта с фиксированным размером, ноне допускает масштабирования. Время, которое необходимо оператору конкатенации для последовательного объединения n строк, пропорционально квадрату числа n. К сожалению, это следствие того факта, что строки являются неизменяемыми (статья 13). При объединении двух строк копируется содержимое обеих строк. Например, рассмотрим метод, который создает строковое представление для выписываемого счета, последовательно объединяя строки для каждого пункта в счете
    // Неуместное объединение строк - плохая производительность
    public String statement() {
    String s = “ ” ; for (int i = 0 ; i < numItems(); i++) s += lineForItem(i);
    // Объединение строк
    return s; Если количество пунктов велико, этот метод работает очень медленно. Чтобы добиться приемлемой производительности, создаваемое представление счета должно храниться в классе
    StringBuffer, а не String: public String statement() {

    StringBuffer s = new StringBuffer(numItems()
    * LINE_W IDTH); for (int i = 0; i < numItems(); i++) s.append(lineForItem(i)); return s.toString(); Изменение производительности впечатляет. Если число пунктов и Item s) равно 100, а длина строки (lineForItem ) постоянна.и равна 80, тона моей машине второй метод работает в девяносто раз быстрее первого. Поскольку первый метод демонстрирует квадратичную зависимость от количества пунктов, а второй - линейную, разница в производительности при большем количестве пунктов становится еще более разительной. Заметим, что второй метод начинается с предварительного размещения в памяти объекта StringBuffer, достаточно крупного, чтобы в нем поместился результат вычислений. Даже если отказаться от этого и создать StringBuffer, имеющий размер по умолчанию, он будет работать в сорок пять раз быстрее, чем первый метод. Мораль проста не пользуйтесь оператором конкатенации для объединения большого Числа строк, если производительность имеет важное значение. Лучше применять метод append из Класса
    Str1ngBuffer. В качестве альтернативы можно использовать массив символов или обрабатывать строки по одной, не объединяя их. Для с с ы п к и на объект используйте его интерфейс В статье 25 дается совет в качестве типа параметра указывать интерфейса не класс. В более общей формулировке ссылаясь на объект, вы должны отдавать предпочтение не классу, а интерфейсу. Если есть подходящие типы интерфейсов, то параметры, возвращаемые значения, переменные и поля следует декларировать, указывая интерфейс. Единственный случай, когда вам нужно ссылаться на класс объекта- при его создании. Для пояснения рассмотрим случай с классом
    Vector, который является реализацией интерфейса List. Возьмите заправило писать так
    // Хорошо указывается тип интерфейса.
    List subscribers = new Vector(); а не так
    // Плохо в качестве типа указан класс subscribers = new Vector(); Если вы выработаете привычку указывать в качестве типа интерфейс, ваша программа будет более гибкой. Когда вы решите поменять реализацию, все, что для этого потребуется- изменить название класса в конструкторе (или использовать другой статической метод генерации. Например, первую из представленных деклараций можно переписать следующим образом
    List subscribers = new ArrayList(); И весь окружающий код сможет продолжить работу. Код, окружающий эту декларацию, ничего не знало прежнем типе, который реализовывал интерфейс. Поэтому он не должен заметить подмену декларации.
    Однако нельзя упускать из виду следующее если первоначальная реализация интерфейса выполняла некие особые функции, непредусмотренные общими соглашениями для этого интерфейса, и программный код зависел от этих функций, крайне важно, чтобы новая реализация интерфейса обеспечивала те же функции. Например, если программный код, окружавший первую декларацию, зависел оттого обстоятельства, что Vector синхронизирован по отношению к потокам, то замена класса
    Vector на ArrayList будет некорректной. Есть лишь несколько сложных приложений, которым необходим механизм отражения. В их число входят визуализаторы классов, инспекторы объектов, анализаторы программного кода и интерпретирующие встроенные системы. Отражение можно также использовать в системах вызова удаленных процедур
    (RPC system) с целью снижения потребности в компиляторах запросов. Если у вас есть сомнения, подпадает ли ваше приложение в одну из этих категорий, вероятнее всего, оно к ним не относится. Вы можете без больших затрат использовать многие преимущества механизма отражения, если будете применять его в усеченном виде. Во многих программах, которым нужен класс, отсутствовавший на момент компиляции, для ссылки на него можно использовать соответствующий интерфейс или суперкласс (статья
    34). Если это то, что вам нужно, вы можете сначала опосредованно создать экземпляры, а затем обращаться к ним обычным образом. используя их интерфейс или суперкласс. Если соответствующий конструктор, как часто бывает, не имеет параметров, вам даже ненужно обращаться к пакету java.lang.ref1ect - требуемые функции предоставит метод C1ass. newlnstance. В качестве примера приведем программу, которая создает экземпляр интерфейса Set, чей класс задан первым аргументом командной строки. Остальные аргументы командной строки программа вставляет в полученный набор и затем распечатывает его. При выводе аргументов программа уничтожает дубликаты (первый аргумент игнорируется. Порядок печати аргументов зависит оттого, какой класс указан в первом аргументе. Если вы указываете "java.util.HashSet", аргументы выводятся в произвольном порядке, если - "java.uti1.TreeSet", они печатаются в алфавитном порядке, поскольку элементы в наборе TreeSet сортируются.
    // Опосредованное создание экземпляра с доступом public static void main(String[] args) {
    1   ...   13   14   15   16   17   18   19   20   ...   25


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