Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


Изучите библиотеки и пользуйтесь ими



 

Предположим, что нужно генерировать случайные целые числа в диапазоне от нуля до некоторой верхней границы. Столкнувшись с такой распространенной задачей, многие программисты написали бы небольшой метод примерно следующего содер­жания:

 

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; Просмотров: 212; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.048 с.)
Главная | Случайная страница | Обратная связь