Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Секреты программирования для Internet на Java

.pdf
Скачиваний:
181
Добавлен:
02.05.2014
Размер:
3.59 Mб
Скачать

Рис. 6.5.

Первый шаг довольно очевиден - мы сначала выясняем в интерактивной документации, вызывает ли наш метод исключение. Если ключевое слово throw присутствует в методе или объявлении конструктора, мы выполняем второй шаг, чтобы понять, какое исключение вызывается. Самый простой способ сделать это состоит в том, чтобы щелкнуть по имени исключения (в нашем случае для конструктора URL - MalformedURLException). Так как исключения являются классами, они имеют собственные Web-страницы.

Добравшись до Web-страницы исключения, мы определяем, что это за исключение, то есть проверяем, подклассом какого класса оно является. Если оно является потомком RuntimeException или Error, нам не нужно использовать блок обработки. Затем мы должны решить, нужно ли перехватить это исключение. Обычно нет никаких причин перехватывать исключение - оно вызывается, когда что-то идет действительно неправильно, например, управление передается за пределы памяти. Но бывают случаи, когда возникает необходимость перехватывать исключения во время выполнения апплета.

Зачем перехватывать исключения?

В API есть много методов, которые вызывают исключения во время выполнения программы, которые имеет смысл перехватывать. Ниже в этой главе мы рассмотрим методы, преобразующие строки в числовые значения типа float и int. Если строка задана некорректно, произойдет исключение NumberFormatException. Если мы попробуем преобразовать строку, не имеющую числового смысла, в число и не перехватим это, наша программа сработает неправильно.

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

import java.applet.*;

class badApplet extends Applet { Image img;

// описание переменных

public void init() { URL imgURL;

// Плохая идея! Должно быть в блоке обработки исключений. try {

Ⱦɚɧɧɚɹ ɜɟɪɫɢɹ ɤɧɢɝɢ ɜɵɩɭɳɟɧɚ ɷɥɟɤɬɪɨɧɧɵɦ ɢɡɞɚɬɟɥɶɫɬɜɨɦ %RRNV VKRS Ɋɚɫɩɪɨɫɬɪɚɧɟɧɢɟ ɩɪɨɞɚɠɚ ɩɟɪɟɡɚɩɢɫɶ ɞɚɧɧɨɣ ɤɧɢɝɢ ɢɥɢ ɟɟ ɱɚɫɬɟɣ ɁȺɉɊȿɓȿɇɕ Ɉ ɜɫɟɯ ɧɚɪɭɲɟɧɢɹɯ ɩɪɨɫɶɛɚ ɫɨɨɛɳɚɬɶ ɩɨ ɚɞɪɟɫɭ piracy@books-shop.com

imgURL=new URL(getParam)("image"));

}

catch (MalformedURLException e) {}

// Плохая идея! Мы ничего не делаем в случае исключения. img=getlmage(imgURL);

}

// другие методы

}

Рассмотрим, что произойдет, если параметр "image" не будет верным URL-адресом. Наш блок проверки даже не сообщит пользователям, что что-то неправильно! Еще хуже то, что мы использовали imgURL вне блока обработки. Это означает, что наша программа даст сбой, когда мы попытаемся отыскать изображение.

Фрагмент кода, приведенный выше, - результат нежелания попробовать обрабатывать исключения в программе. Так как обработка исключений часто непонятна начинающему программисту Java, он может отнестись к ней, как к ненужным сложностям, и делать только то, что абсолютно необходимо. Однако, как мы увидим из главы 10, "Структура программы", обработка исключений чрезвычайно важна для написания надежных Java-программ. Мы не будем обсуждать это глубже, но, поскольку вы начинаете использовать API, попробуйте не приобретать привычки плохо обрабатывать исключения. Если вы пишете блоки обработки, берите за основу следующие советы:

Если автор метода считает, что надо вызывать исключение, то это, вероятно, уже является хорошей причиной обработки исключения.

Удостоверьтесь, что весь код, который зависит от метода или конструктора, вызывающего исключение, содержится в блоке обработки. Самый простой способ гарантировать это состоит в том, чтобы объявлять любые переменные, которые назначены специфическим методом или конструктором, внутри этого блока.

Всегда используйте свои блоки обработки исключений, чтобы разумно отвечать на обстоятельства, вызывающие исключения. В нашем примере плохого апплета мы должны объяснить пользователю тот факт, что параметр "image" является недопустимым. В критических случаях вы можете завершить выполнение апплета в блоке обработки.

Класс java.lang.Object

Класс java.lang.Object - это фундаментальный класс Java. Помните ли вы из главы 3, "Объектная ориентация в Java", что все классы происходят из одного класса? И это - тот самый класс! Наиболее важное замечание относительно класса Object состоит в том, что он находится наверху иерархии. Однако он не является просто абстракцией - этот класс содержит несколько методов и может подвергаться обработке подобно другим классам. Так как все классы происходят из класса Object, его методы присутствуют в любом другом классе. Большинство (если не все) базовых методов, приведенных в табл. 6-2, обычно отменяются в подклассах.

 

Таблица 6-2. Базовые методы класса Object

Метод

Описание

Исключения

boolean equals

Возвращает true, если эквивалентен

Нет

(Object o)

заданному объекту.

 

String toString()

Возвращает строку с информацией о

Нет

 

заданном объекте.

 

Object clone()

Клонирует заданный объект.

CloneNotSupportedException

void finalize()

Вызывается, когда заданный объект уже

Throwable

 

больше не нужен.

 

Хотя в классе Object есть и другие методы, здесь приведены наиболее важные. Первые два отменяются любым классом в API. Метод clone - наш секрет для создания новых копий объектов. Этот метод возвращает нас к обсуждению оператора присваивания в главе 4. Давайте вспомним наш пример:

public void sampleMethod(someClass anObject) { someClass anotherObject=anObject; }

www.books-shop.com

Здесь anotherObject и anObject в действительности являются одним и тем же классом - присваивание не создавало новую копию. Если метод clone не изменялся, мы можем использовать его, чтобы сделать копию образца:

SomeClass aCopy=anObject.clone();

Здесь мы считаем, что автор someClass определил, как он хочет проклонировать экземпляр someClass. Однако определение способа клонировать себя для объектов - не всегда хорошая идея для класса (см. главу 10). Авторы специфических классов решают, могут ли клонироваться экземпляры этих классов, и многие из классов в API не клонируемы. Этот метод можно использовать, когда класс или один из его суперклассов осуществляет интерфейс клонирования и явно заменяет метод.

Но что можно сказать относительно других методов в классе Object? Их изучение выходит за рамки этой главы. В табл. 6-3 описано, что они делают и где они обсуждаются впоследствии.

 

Таблица 6-3. Другие методы класса Object

Метод

Описание

Где описан

wait, notify,

Позволяет объекту общаться с потоками Java.

Глава 11,

notifyAll

 

"Многопотоковость".

hashCode

Позволяет объекту использоваться совместно с

Глава 10, "Структура

 

java.util.Hashtabfe.

программы".

getClass

Используется для получения информации об этом

Глава 10, "Структура

 

объекте во время выполнения.

программы".

Работа со строками

Как вы знаете, строка - это просто последовательность символов. Фактически все языки программирования так или иначе поддерживают строки, и Java не является исключением. Класс String, чья Web-страница с документацией показана на рис. 6-6, находится в пакете java.lang и используется для представления строк. Этот класс обеспечивает такие функциональные возможности, как преобразование к числовым типам, поиск и замена подстроки, обращение к конкретным символам и незаполненному пространству. Вполне вероятно, что вы будете использовать класс String больше, чем любой другой класс в API, и вы должны хорошо знать входящие в него методы. В этом разделе мы исследуем класс String. Мы также рассмотрим некоторые другие классы в API, которые помогут вам манипулировать со строками.

www.books-shop.com

Рис. 6.6.

Создание строк

Строки можно создавать одним из двух способов:

использованием одного из семи конструкторов класса String;

использованием знака операции присваивания (=).

До сих пор для создания строк мы применяли знак операции присваивания. Оператор

String S="My string,'';

является самым простым способом создать строку. Знак операции + также может использоваться со строками для простой конкатенации строк, как показано в примере 6-1.

Пример 6-1a. Формирование строк из простых типов.

String S2="another string";

String S3=S+S2+"and a primitive type:" int i=4;

S3=S3+i

S3 будет теперь иметь значение "My string, another string and a primitive type: 4". Знак операции + работает со всеми примитивными типами. Однако знак операции = этого не делает. Следующий фрагмент кода не будет компилироваться из-за последних двух операторов:

int i=4; String SI=i;

//Не будет компилироваться! String S2=4;

//Не будет компилироваться!

Это ограничение неприятно, но его можно легко, хотя и не очень изящно, обойти. Надо создать пустую строку и добавить к ней нужное значение с помощью знака операции +.

www.books-shop.com

Пример 6-1b. Присваивание значений примитивных типов непосредственно строкам. int i=4;

String S1=""+i;

String S2=""+4;

Знак операции присваивания работает только со строковыми константами (то есть текстом, содержащимся внутри кавычек) и другими строками. Знак операции + будет работать со строковыми константами, другими строками и всеми примитивными типами. Кроме того, строки могут быть созданы с помощью конструкторов, описанных в табл. 6-4.

Таблица 6-4. Конструкторы строк

Конструктор

Объект, который он создает

String()

Пустая строка.

String (String S)

Новая строка, которая является копией S.

String(char charArray[])

Строка, основанная на массиве charArray.

String(char charArray[], int offset, Строка, основанная на подмассиве, начинающаяся со

int len)

смещения ang и длиной len символов.

String(byte byteArray[], int hibyte) Строка, основанная на массиве byteArray, где hibyte описывает

 

старший байт Unicode каждого символа.

String(char charArray[], int hibyte,

Строка, основанная на подмассиве, начинающаяся по

int offset, int len)

смещению offset и длиной len байт.

Хотя знак операции присваивания не совместим с примитивными типами, это действительно единственное, что является интуитивно неясным при создании строк в Java. Как только мы проберемся через причуды сравнения строк, вы будете готовы спокойно использовать строки.

Сравнение строк

Теперь, умея создавать строки, мы должны научиться их сравнивать. В классе String есть два метода сравнения - equals и compareTo. Метод equals заменяет метод equals в классе Object и возвращает true, когда строки совпадают, в то время как метод compareTo при эквивалентности строк возвращает 0.

Пример 6-2a. Сравнение строк с помощью методов compareTo и equals. if (S1.compareTo(S2)!=0)

System.out.println("S1 is equivalent to the String S2"); if (S1.equals(S2))

System.out.println("S1 is equivalent to the String S2");

Значение, возвращаемое методом compareTo, равно -1, если S1 - лексически меньше, чем S2, и 1, если S1 лексически больше, чем S2. Как можно видеть из нижеприведенного примера, "лексическое" сравнение по существу означает сравнение в алфавитном порядке. Различие состоит в том, что этот порядок простирается на цифры и другие небуквенные символы так, что "a113" оказывается лексически больше, чем "a112". Упорядочение определено значениями

Unicode.

Пример 6-2b. Сравнение строк с помощью метода compareTo.

String S1="alpha";

String S2="alpha";

if (S1.compareTo(S2)==-1)

System.out.println("this will always be printed"); if (S2.compareTo(S1)==1)

System.out.println("this will always be printed");

Лексическое упорядочение получено из целочисленных значений символов Unicode. Метод compareTo последовательно сравнивает каждый символ строк, основываясь на его целочисленном значении. Когда он находит различие, то возвращает 1 или -1, как описано выше. Для цифр и букв упорядочение интуитивно понятно и просто для запоминания (рис. 6-7).

www.books-shop.com

Рис. 6.7.

Более сложным является сравнение строк при использовании символов "неанглийского" алфавита и служебных символов типа %, ! или *. Самый простой способ узнать численные значения подобных символов, не показанных на рис. 6-7, - привести тип char к int и напечатать его значение:

public void strangeCharacter(char Strange) { int val =( int) Strange;

System.out.println ("One strange character!''); System.out.println ( "The char " +Strange+" = " +val); }

Знак операции == и сравнение строк

Вам может встретиться Java-код, который сравнивает строки с помощью знака операции ==. В простых случаях знак операции == работает для строк так же, как для примитивных типов. Но такое использование не является частью спецификации языка - оно зависит от компилятора и может не работать в некоторых случаях. Так что лучше им не пользоваться, чтобы не допустить неприятную и труднонаходимую ошибку, и всегда применять методы compareTo или equals.

В дополнение к двум методам сравнения, которые мы только что рассмотрели, есть еще шесть других, сравнивающих две строки различными способами. Все они помещены в табл. 6-5 в порядке увеличения сложности.

 

Таблица 6-5. Методы сравнения строк

Метод

Возвращает true, когда

equalsIgnoreCase(String S)

Строка равняется S, независимо от регистра символов.

beginsWith(String S)

Строка начинается с S.

beginsWith(String S,int len)

Строка начинается с первых len символов S.

endsWith(String S)

Строка заканчивается S.

regionMatches(int toffset, String S,int Подстрока в строке, начиная с позиции со смещением ooffset, int len) toffset, соответствует подстроке, начинающейся по

смещению ooffset, для len символов.

regionMatches(boolean ignoreCase, int Тот же, что и предыдущий метод, но игнорирует регистр toffset, String S, int ooffset, int len) символов, когда ignoreCase == true.

Большинство методов сравнения строк интуитивно понятны, но методы regionMatches нуждаются в некотором объяснении. Они позволяют сравнивать любую из подстрок в S1 и S2. Параметр first определяет позицию в строке образца, c которой вы хотите начать сравнение. Второй параметр - некоторая другая строка с позицией, определенной в третьем параметре, с которой вы хотите начать сравнение в этой строке. Последний параметр - длина области сравнения. Использование методов regionMatches проиллюстрировано на рис. 6-8.

www.books-shop.com

Рис. 6.8.

Когда мы начнем анализировать строки, вы увидите, что эти два метода сравнения чрезвычайно полезны. Их правые целые числа можно передавать как параметры. К примеру, их можно использовать для эмуляции методов endsWith и startsWith. Ниже приведен фрагмент кода, который всегда возвращает true.

Пример 6-3a. Подражание методу startsWith с помощью метода regionMatches. int lengthB=B.length();

if (A.startsWith(B)==A.regionMatches(0,B,0,lengthB)) // всегда true

Еще лучше проиллюстрировать, как добиться правильных параметров, можно, сделав эквивалент метода endsWith при помощи метода regionMatches.

Пример 6-3b. Подражание методу endsWith с помощью метода regionMatches. int lengthB=B.length();

int startA=A.length()-B.length();

if (A.endsWith(B)== A. regionMatches( startA, B, 0, IengthB)) DealingWith Substrings

Работа с подстроками

Мы только что видели, как класс String позволяет сравнивать подстроки. Существует также множество методов для извлечения подстрок. Давайте начнем с простых методов работы с подстроками:

public String substring(int start)

public String substring(int start, int len)

Первый метод из показанных выше возвращает новую строку, которая начинается с первой позиции в символьном массиве и содержит все символы после этой позиции. Второй метод возвращает только len символов после начала. Первый символ строки находится в нулевой позиции. Рассмотрим некоторые примеры:

String S="0123456789";

String S1=S.substring(0);

String S2=S;

//строки SI, S2 и S3 эквивалентны. String S4=S.substrilng(4);

//S4 имеет значение "456789"

www.books-shop.com

String S5=S.substring(4,3);

// S5 имеет значение "456"

Методы работы с подстроками неудобны единственно тем, что им всегда нужно передавать параметры. Это тот случай, когда можно применить методы indexOf. Методы indexOf позволяют вам исследовать класс String на индивидуальные символы или строки и возвращать индекс массива. Возвращенное целочисленное значение может затем быть передано методам работы с подстроками для извлечения или к методам regionMatches для сравнения. В табл. 6-6 описаны различные методы indexOf.

 

Таблица 6-6. Методы indexOf класса String

Объявление

Параметры

Возвращаемое значение

метода

 

 

lilt indexOf(char a)

a - символ для поиска.

Индекс первого местонахождения.

IndexOf(char a, int

a - символ для поиска, start -

Индекс первого местонахож-дения от

start)

начальная позиция для поиска.

начальной позиции.

IndexOf(String S)

S - строка для поиска.

Индекс первого местонахождения S.

indexOf(String S, int

S - строка для поиска, start -

Индекс первого местонахождения от

start)

начальная позиция для поиска.

начальной позиции.

Метод lastIndexOf()

Для каждого метода indexOf имеется соответствующий метод lastlndexOf. Как и обязывает его имя,, метод lastlndexOf начинает поиск от конца строки.

Изменение строк

На протяжении этой главы мы изменяли строки следующими операторами:

String S="some string"; S=S+", more of the string", S=S.substring(14,4);

// присвоение подстроки первоначальной строке

Обратите внимание, что, когда мы делаем эти перестановки, мы должны присвоить результат первоначальной строке. Этот шаг необходим, потому что, однажды созданный, экземпляр строки не может быть изменен. Так что мы создаем новую копию строки, вызывая один из методов класса String, и затем присваиваем новую копию обратно первоначальной переменной.

Кроме методов работы с подстроками, есть еще четыре других метода, которые мы можем использовать для перестановок. Эти методы приведены в табл. 6-7. Не забудьте, что в каждом случае создана копия строки, перестановки сделаны на этой копии и копия возвращена обратно. Ни один из методов в классе String фактически не изменяет строки непосредственно - взамен вы присваиваете новую копию или той же самой, или другой переменной.

Таблица 6-7. Методы изменения строк

 

Методы, используемые со

Параметры

Возвращаемое значение

строкой S1

 

 

 

S1.concat(S2)

S2 - строка, которая будет

SI+S2

 

конкатенирована к S1.

 

 

S1.replace(a,b)

Символ b заменяет символ a.

S1

с замененным символом b.

S1.toLowerCase()

нет

S1

без символов верхнего

 

 

регистра.

S1.toUpperCase()

нет

S1

без символов нижнего

 

 

регистра.

S1.trim()

нет

S1

без начальных или

 

 

конечных пробелов.

Разбор строк

Теперь у нас есть все инструменты для анализа строк. Мы можем сравнивать и, используя методы indexOf, отыскивать подстроки. Но давайте допустим, что строка может иметь несколько

www.books-shop.com

вхождений одной и той же подстроки. Например, математическое выражение может иметь несколько вхождений знака +. Компьютерные специалисты отнеслись бы к такой подстроке как к разделителю. Единицы между разделителями называются токенами.

Токены, разделители и компиляция

Слово "токен" может встретиться вам в литературе, связанной с компьютерными языками. Компиляторы должны разбить файл исходного текста, используя разделители, запятые и незаполненное пространство. Простой пример - процесс разбора списка параметров. Компилятор сначала изолирует символы внутри круглых скобок. Затем, используя запятую в качестве разделителя, он может идентифицировать токены - индивидуальные объявления переменных. После этого он может начинать компилировать остальную часть подпрограммы.

С помощью методов indexOf и методов работы с подстроками, о которых мы уже говорили, можно было бы написать некоторый код, который бы анализировал токены, основываясь на специфическом разделителе. Но это было бы нелегкой работой. К счастью, пакет java.util содержит класс StringTokenizer, который разобьет данную строку, основываясь на данном разделителе. На рис. 6-9 показана интерактивная документация для StringTokenizer.

Рис. 6.9.

Предположим, что у нас есть строка "3 + 4 + 2 + 7" и нам нужно вычислить сумму целых чисел. Класс StringTokenizer расчленит строку, основываясь на знаках +, начиная слева.

Пример 6-4. Разбор строки с использованием класса StringTokenizer. public int getSum(String S) {

int runningSum=0;

StringTokenizer tok=new StringTokenizer(S,"+");

//S - строка для разбора

//"+" - разделитель

while (tok.hasMoreTokens()) { String thisToken=tok.getNextToken(); runningSum=Add(thisToken);

}

return runningSum;}

www.books-shop.com

private int Add(String S) {

//мы должны знать преобразование примитивного типа

//прежде, чем сможем выполнить его

}

Как вы можете видеть, с помощью методов indexOf и regionMatches сделать то же самое было бы намного труднее. Мы можем резервировать использование методов indexOf и regionMatches, если мы должны расчленить строку только один раз. В табл. 6-8 описаны конструкторы, а в табл. 6-9 показаны основные методы, которые вы должны знать, чтобы эффективно использовать этот класс.

Таблица 6-8. Конструкторы класса StringTokenizer

Конструктор

 

Описание

StringTokenizer(String S, String delim)

Разбивает S, основываясь на delim.

StringTokenizer(String S, String delim,

Как и выше, но если delims == true, разделители

boolean returnDetims)

 

возвращаются как токены.

StringTokenizer(String S)

 

Разбивает строку, основываясь на незаполненном

 

 

пространстве ("\t\n\r").

Таблица 6-9. Методы класса StringTokenizer

Метод

 

Описание

String nextToken()

Возвращает следующий токен.

String nextToken(String

Возвращает следующий токен после переключения разделителя

newDelim)

на newDelim.

 

boolean hasMoreTokens()

Возвращает true, если имеются токены, которые не были

 

возвращены.

 

int countTokens()

Возвращает число токенов в строке.

Преобразование строк в другие типы данных

Программистам часто приходится делать преобразования между строками и примитивными типами данных, например целыми числами. Чтобы преобразовывать примитивные типы данных в строки, используются методы valueOf. Конкретный метод valueOf имеется для каждого из примитивных типов данных, и все они работают сходным образом. Давайте покажем преобразование значений типов int, float, double, boolean и char.

Пример 6-5a. Преобразование примитивных типов данных в строки. int someInt=1;

String StringInt=String.valueOf(someInt); float someFloat=9.99f;

String StringAsFloat=String.valueOf(someFloat);

//StringAsFloat имеет значение 9.99

//Обратите внимание на конечный f,

//отличающий его от типа double. double someDouble=99999999.99;

String StringAsDouble=String.valueOf(someDouble);

//StringAsDouble имеет значение 999999999.99 boolean someBoolean=true;

String StringAsBoolean=String.valueOf(someBoolean);

//StringAsBoolean имеет значение true

char someChar='a';

String StringAsChar=String.valueOf(someChar); // StringAsChar имеет значение "a"

Эти методы не отличаются от того обходного пути, который мы использовали раньше, чтобы присвоить строке значение примитивного типа с помощью пустой строки и знака операции +. Возможно, вы найдете эти методы полезными, чтобы сделать свой код немного более понятным. Но как преобразовать строки в примитивные типы? Как и можно было ожидать, для этого в API есть специальные статические методы - но они не находятся в классе String. Они содержатся в классах-упаковщиках (wrapper) примитивных типов, которые мы обсудим после. К примеру, рассмотрим преобразования для числовых примитивных типов, исключая short и byte.

www.books-shop.com