Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
Изучите библиотеки и пользуйтесь ими
Предположим, что нужно генерировать случайные целые числа в диапазоне от нуля до некоторой верхней границы. Столкнувшись с такой распространенной задачей, многие программисты написали бы небольшой метод примерно следующего содержания:
static Random rnd = new Random(); // Неправильно, хотя встречается часто static int random(int n) { return Math.abs(rnd.nextlnt()) % n; }
Неплохой метод, но он несовершенен: у него есть три недостатка. Первый состоит в том, что если n - это небольшая степень числа два, то последовательность генерируемых случайных чисел через очень короткий период начнет повторяться. Второй заключается в том, что если n не является степенью числа два', то в среднем некоторые Числа будут получаться гораздо чаще других. Если n большое, указанный недостаток
135
может проявляться довольно четко. Графически это демонстрируется следующей программой, которая генерирует миллион случайных чисел в тщательно подобранном диапазоне и затем печатает, сколько всего чисел попало в нижнюю половину этого диапазона: 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) выполняет свою работу (хотя любопытные личности могут изучить документацию или исходный текст метода). Старший инженер с подготовкой в области алгоритмов провел много времени за разработкой, реализацией и тестированием этого метода, а затем показал метод экспертам в данной области с тем, чтобы убедиться в его правильности. После этого библиотека прошла стадию предварительного тестирования и была опубликована, тысячи программистов широко пользуются ею в течение нескольких лет. До сих пор ошибок в указанном методе найдено не было. Но если какой-либо дефект обнаружится, он будет исправлен в следующей же версии. Обращаясь к стандартной библиотеке, вы используете знания написавших ее экспертов, а также опыт тех, кто работал с нею до вас.
136
Второе преимущество от применения библиотек заключается в том, что вам не нужно терять время на решение специальных задач, имеющих лишь косвенное отношение к вашей работе. Как и большинство программистов, вы должны тратить время на разработку своего приложения, а не на подготовку его фундамента. Третье преимущество от использования стандартных библиотек заключается в том, что их производительность имеет тенденцию повышаться со временем, причем без каких-либо усилий с вашей стороны. Множество людей пользуется библиотеками, они применяются в стандартных промышленных тестах, поэтому организация, которая осуществляет поддержку этих библиотек, заинтересована в том, чтобы заставить их работать быстрее. Например, стандартная библиотека арифметических операций с многократно увеличенной точностью java.math была переписана в версии 1.3, что привело к впечатляющему росту ее производительности. Со временем библиотеки приобретают новые функциональные возможности. Если в каком-либо классе библиотеки не хватает важной функции, сообщество разработчиков даст знать об этом недостатке. Платформа Java всегда развивалась при серьезной поддержке со стороны сообщества разработчиков. Прежде этот процесс был неформальным, сейчас же существует официальное движение, называемое Java Commиnity Process ОСР). Так или иначе, библиотеки пополняются недостающими функциями. Последнее преимущество от применения стандартных библиотек заключается в том, что ваш код соответствует господствующим в данный момент тенденциям. Такой код намного легче читать и сопровождать, его могут использовать множество разработчиков. Учитывая 'все эти преимущества, логичным казалось бы применение библиотек, а не частных разработок, однако многие программисты этого не делают. Но почему? Может быть, потому, что они не знают о возможностях имеющихся библиотек. С каждой следующей версией в библиотеки включается множество новых функций, и стоит быть в курсе этих новшеств. Вы можете внимательно изучать соответствующую документацию в режиме online либо прочесть о новых библиотеках в самых разных книгах [J2SE-АРIs, ChanOO, Flanagan99, Chan98]. Библиотеки слишком объемны, чтобы просматривать всю документацию, однако каждый программист должен хорошо знать java.lang, java. util и в меньшей степени java.io. Остальные библиотеки изучаются по мере необходимости. Обзор всех возможностей библиотек выходит за рамки данной статьи, однако некоторые из них заслуживают особого упоминания. В версии 1.2 в пакет java.util была добавлена архитектура Collectioпs Frатешоrk. Она должна входить в основной набор инструментов каждого программиста. Collections F ramework - унифицированная архитектура, предназначенная для представления и управления коллекциями и позволяющая манипулировать коллекциями независимо от деталей представления. Она сокращает объемы работ по программированию и в то же время повышает производительность. Эта архитектура позволяет достичь унифицированности несвязанных API, упрощает проектирование и освоение новых API, способствует повторному использованию программного обеспечения.
137
Указанная архитектура базируется на шести интерфейсах коллекций (Collection, Set, List, Мар, SortedSet и SortedMap). Она включает в себя реализацию этих интерфейсов и алгоритмы работы с ними. Наследуемые от коллекций классы 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. Если вы хотите узнать больше, обратитесь к документации на web-узле компании Sun [Collections] либо прочтите учебник [Вloch99]. Среди библиотек от третьих компаний упоминания заслуживает пакет 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 новые реализации коллекций.
Иногда функция, заложенная в библиотеке, не отвечает вашим потребностям. И чем специфичнее ваши запросы, тем это вероятнее. Если вы изучили возможности, предлагаемые библиотеками в некоей области, и обнаружили, что они не соответствуют вашим потребностям, используйте альтернативную реализацию. В функциональности, предоставляемой любым конечным набором библиотек, всегда найдутся пробелы. И если необходимая вам функция отсутствует, у вас нет иного выбора, как реализовать ее самостоятельно. Подведем итоги. Не изобретайте колесо. Если вам нужно сделать нечто, что кажется вполне обычным, в библиотеках уже может быть класс, который делает это. Вообще говоря, программный код в библиотеке наверняка окажется лучше кода, который вы напишете сами, а со временем он может стать еще лучше. Мы не ставим под сомнение ваши способности как программиста, однако библиотечному коду уделяется гораздо больше внимания, чем может позволить себе средний разработчик при реализации тех же самых функций.
Если требуются точные ответы, избегайте использования типов float и doubIe
Типы float и double в первую очередь предназначены для научных и инженерных расчетов. Oни реализуют бинарную арифметику с плавающей точкой (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. Может быть, проблему можно решить, округлив результаты перед печатью? К сожалению, это срабатывает не всегда. Например, у вас в кармане есть доллар, и вы видите
139
полку, где выстроены в ряд вкусные конфеты за 10, 20, 30 центов и так далее вплоть до доллара. Вы покупаете по одной конфете каждого вида, начиная с той, что стоит 10 центов, и так далее, пока у вас еще есть возможность взять следующую конфету. Сколько конфет вы купите и сколько получите сдачи? Решим эту задачу следующим образом:
// Ошибка: использование плавающей точки для денежных расчетов! public static void main(String[] args) { double funds = 1.00; int itemsBought = 0; for (double price = .10; funds >= price; price += .10) { funds -= price; itemsBought++; } System.out.println(itemsBought + " items bought."); System.out.println("Change: $" + funds); } Запустив программу, вы выясните, что можете позволить себе .три конфеты и у вас останется еще $0.39999999999999999. Но это неправильный ответ! Правильный путь решения задачи заключается в применении для денежных расчетов типов BigDecimal, int или long. Представ м простое преобразование предыдущей программы, которое позволяет использовать тип BigDecimal вместо double: public static void main(String[] args) { final BigDecimal TEN_CENTS = new BigDecimal( ".10”); int itemsBought = 0; BigDecimal funds = new BigDecimal("1.00"); for (BigDecimal price = TEN_CENTS; funds.compareTo(price) >= 0; price = price.add(TEN_CENTS)) { itemsBought++; funds = funds.subtract(price); } System.out.print1n(itemsBought + " items bought."); System.out.print1n("Money left over: $" + funds); } Запустив исправленную программу, вы обнаружите, что можете позволить себе четыре конфеты и у вас останется $0.00. Это верный ответ. Однако тип BigDecimal имеет два недостатка: он не столь удобен и медленнее, чем простой арифметический тип. Последнее можно считать несущественным, если вы решаете единственную маленькую задачу, а вот неудобство может раздражать.
140
Вместо BigDecimal можно использовать 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. Если вы хотите, чтобы система сама отслеживала положение десятичной точки, и вас не пугают неудобства, связанные с отказом от простого типа, используйте BigDecimal. Применение этого класса имеет еще то преимущество, что он дает вам полный контроль над округлением: для любой операции, завершающейся округлением, предоставляется на выбор восемь режимов округления. Это пригодится, если вы будете выполнять экономические расчеты с жестко заданным алгоритмом округления. Если для вас важна производительность, вас не пугает необходимость самостоятельно отслеживать положение десятичной точки, а обрабатываемые значения не слишком велики, используйте тип int или long. Если значения содержат не более девяти десятичных цифр, можете применить тип int. Если в значении не больше восемнадцати десятичных цифр, используйте тип long. Если же в значении более восемнадцати цифр, вам придется работать с BigDecimal. |
Последнее изменение этой страницы: 2019-04-11; Просмотров: 233; Нарушение авторского права страницы