Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
1. Процедурная организация программного кода. Методология ООП и ее отличительные особенности. Объекты и их свойства. Классы
Процедура представляет собой законченную последовательность действий или операций, направленных на решение отдельной задачи. В языках программирования появилась специальная синтаксическая конструкция, которая также получила название процедуры. Например, на языке Pascal описание процедуры выглядит следующим образом:
Procedure printGreeting(name: String)
Begin
Write("Hello, ");
WriteLn(name);
End;
Назначение данной процедуры - вывести на экран приветствие Hello, Name, где Name передается в процедуру в качестве входного параметра.
Объектно-ориентированный подход обладает такими преимуществами, как:
По определению будем называть объектом понятие, абстракцию или любой предмет с четко очерченными границами, имеющий смысл в контексте рассматриваемой прикладной проблемы. Введение объектов преследует две цели:
Примеры объектов: форточка, Банк "Империал", Петр Сидоров, дело № 7461, сберкнижка и т.д.
Состояние (state) - совокупный результат поведения объекта: одно из стабильных условий, в которых объект может существовать, охарактеризованных количественно; в любой момент времени состояние объекта включает в себя перечень (обычно статический) свойств объекта и текущие значения (обычно динамические) этих свойств .
Поведение (behavior) - действия и реакции объекта, выраженные в терминах передачи сообщений и изменения состояния ; видимая извне и воспроизводимая активность объекта .
Уникальность (identity) - свойство объекта; то, что отличает его от других объектов (автор не согласен с переводом русского издания , поэтому здесь приводится авторский перевод).
Класс изображается в виде прямоугольника, состоящего из трех частей. В верхней части помещается название класса, в средней - свойства объектов класса, в нижней - действия, которые можно выполнять с объектами данного класса (методы).
Каждый класс также может иметь специальные методы, которые автоматически вызываются при создании и уничтожении объектов этого класса:
Обычно конструктор и деструктор имеют специальный синтаксис, который может отличаться от синтаксиса, используемого для написания обычных методов класса.
2. Наследование, инкапсуляция и полиморфизм. Типы отношений между классами. Основные понятия UML.
Инкапсуляция
Инкапсуляция (encapsulation) - это сокрытие реализации класса и отделение его внутреннего представления от внешнего (интерфейса). При использовании объектно-ориентированного подхода не принято применять прямой доступ к свойствам какого-либо класса из методов других классов. Для доступа к свойствам класса принято задействовать специальные методы этого класса для получения и изменения его свойств.
Наследование
Наследование (inheritance) - это отношение между классами, при котором класс использует структуру или поведение другого класса (одиночное наследование ), или других (множественное наследование ) классов. Наследование вводит иерархию "общее/частное", в которой подкласс наследует от одного или нескольких более общих супер-классов. Подклассы обычно дополняют или переопределяют унаследованную структуру и поведение.
Полиморфизм (polymorphism) - положение теории типов, согласно которому имена (например, переменных) могут обозначать объекты разных (но имеющих общего родителя) классов. Следовательно, любой объект, обозначаемый полиморфным именем, может по-своему реагировать на некий общий набор операций
Предположим, мы хотим создать векторный графический редактор, в котором нам нужно описать в виде классов набор графических примитивов - Point, Line, Circle, Box и т.д. У каждого из этих классов определим метод draw для отображения соответствующего примитива на экране.
Для описанной выше иерархии классов, используя полиморфизм, можно написать следующий код:
Point p[] = new Point[1000];
p[0] = new Circle();
p[1] = new Point();
p[2] = new Box();
p[3] = new Line();
...
for(int i = 0; i < p.length;i++) {
if(p[i]!=null) p[i].draw();
}
В описанном выше примере массив p[] может содержать любые объекты, порожденные от наследников класса Point. При вызове какого-либо метода у любого из элементов этого массива будет выполнен метод того объекта, который содержится в ячейке массива. Например, если в ячейке p[0] находится объект Circle, то при вызове метода draw следующим образом:
p[0].draw()
Агрегация
Отношение между классами типа "содержит" (contain) или "состоит из" называется агрегацией, или включением. Например, если аквариум наполнен водой и в нем плавают рыбки, то можно сказать, что аквариум агрегирует в себе воду и рыбок.
Ассоциация
Если объекты одного класса ссылаются на один или более объектов другого класса, но ни в ту, ни в другую сторону отношение между объектами не носит характера "владения", или контейнеризации, такое отношение называют ассоциацией (association). Отношение ассоциации изображается так же, как и отношение агрегации, но линия, связывающая классы,- простая, без ромбика.
3.Достоинства и недостатки ООП
Достоинства ООП
ООП дает возможность создавать расширяемые системы. Это одно из основных достоинств ООП, и именно оно отличает данный подход от традиционных методов программирования. Расширяемость означает, что существующую систему можно заставить работать с новыми компонентами, причем без внесения в нее каких-либо изменений. Компоненты могут быть добавлены на этапе исполнения программы.
Полиморфизм оказывается полезным преимущественно в следующих ситуациях.
Недостатки ООП
Инкапсуляцией данных не следует злоупотреблять. Чем больше логики и данных скрыто в недрах класса, тем сложнее его расширять. Отправной точкой здесь должно быть не то, что клиентам не разрешается знать о тех или иных данных, а то, что клиентам для работы с классом этих данных знать не требуется.
1.Неэффективность в смысле распределения памяти. Динамическое связывание и проверка типа на этапе выполнения требуют по ходу работы информации о типе объекта. Такая информация хранится в дескрипторе типа и он выделяется один на класс. Каждый объект имеет невидимый указатель на дескриптор типа для своего класса. Таким образом, в объектно-ориентированных программах необходимая дополнительная память выражается в одном указателе для объекта и в одном дескрипторе типа для класса.
2.Излишняя универсальность. Неэффективность также может означать, что в программе реализованы избыточные возможности. В библиотечном классе часто содержится больше методов, чем это реально необходимо. А поскольку лишние методы не могут быть удалены, они становятся мертвым грузом. Это не влияет на время выполнения, но сказывается на размере кода.
4. Составные элементы программы на Java. Кодировка, комментарии, типы лексем.
Кодировка
Как известно, Unicode UTF-16 представляет символы кодом из 2 байт, описывая, таким образом, 65535 символов. Это позволяет поддерживать практически все распространенные языки мира. Первые 128 символов совпадают с набором ASCII. Однако понятно, что требуется некоторое специальное обозначение, чтобы иметь возможность задавать в программе любой символ Unicode, ведь никакая клавиатура не позволяет вводить более 65 тысяч различных знаков. Эта конструкция представляет символ Unicode, используя только символы ASCII. Таким образом можно закодировать все символы Unicode от \u0000 до \uFFFF. Буквы русского алфавита начинаются с \u0410 (только буква Ё имеет код \u0401 ) по \u044F (код буквы ё \u0451 ). В последних версияхJDK в состав демонстрационных приложений и апплетов входит небольшая программа SymbolTest, позволяющая просматривать весь набор символов Unicode. Ее аналог несложно написать самостоятельно. Для перекодирования больших текстов служит утилита native2ascii, также входящая в JDK. Она может работать как в прямом режиме переводить из разнообразных кодировок в Unicode, записанный ASCII -символами, так и в обратном (опция -reverse ) из Unicode в стандартную кодировку операционной системы.
Комментарии
В Java комментарии бывают двух видов:
Строчные например:
int y=1970; // год рождения
Блочные /* и */, могут занимать произвольное количество строк
Лексемы
Виды лексем
Ключевые слова: abstract double int strictfp boolean else interface super break extends long switch
byte final native synchronized case finally new this catch float package throw char for private throws class goto protected transient const if public trycontinue implements return void default import short volatile do instanceof static while
Литералы
Null-литерал
Null- литерал может принимать всего одно значение: null. Это литерал ссылочного типа, причем эта ссылка никуда не ссылается, объект отсутствует. Разумеется, его можно применять к ссылкам любого объектного типа данных.
Разделители
( ) [ ] { } ; . ,
Операторы
= > < ! ~ ? : == <= >= != && || ++ --
+ - * / & | ^ % << >> >>>
+= -= *= /= &= |=
5. Ключевые слова Java и их назначение. Типы литералов и способы их записи.
Ключевые слова
Ключевые слова это зарезервированные слова, состоящие из ASCII -символов и выполняющие различные задачи языка. Вот их полный список (48 слов):
abstract double int strictfp boolean else interface super break extends long switch byte final native synchronized case finally new this
catch float package throw char for private throws class goto protected transient const if public try continue implements return void default import short volatile do instanceof static while
Ключевые слова goto и const зарезервированы, но не используются. Это сделано для того, чтобы компилятор мог правильно отреагировать на их использование в других языках.
Литералы
Литералы позволяют задать в программе значения для числовых, символьных и строковых выражений, а также null- литералов . Целочисленные литералы (integer); Целочисленные литералы позволяют задавать целочисленные значения в десятичном, восьмеричном и шестнадцатеричном виде. Десятичный формат традиционен и ничем не отличается от правил, принятых в других языках. Значения в восьмеричном виде начинаются с нуля, и, конечно, использование цифр 8 и 9 запрещено. Запись шестнадцатеричных чисел начинается с 0x или0X .
Дробные литералы (floating-point); Дробные литералы представляют собой числа с плавающей десятичной точкой. Правила записи таких чисел такие же, как и в большинстве современных языков программирования.
Логические литералы (boolean);
Логические литералы имеют два возможных значения true и false. Эти два зарезервированных слова не являются ключевыми, но также не могут использоваться в качестве идентификатора.
Символьные литералы (character);
Символьные литералы описывают один символ из набора Unicode, заключенный в одиночные кавычки, или апострофы ( ASCII -символ single quote, \u0027 ). Например:
'a' // латинская буква а
Символьный литерал должен содержать строго один символ.
Строковые литералы (string);
Строковые литералы состоят из набора символов и записываются в двойных кавычках. Длина может быть нулевой или сколь угодно большой. Любой символ может быть представлен специальной последовательностью, начинающейся с \ (см. "Символьные литералы ").
"" // литерал нулевой длины
Каждый строковый литерал является экземпляром класса String
Null-литерал (null-literal).
Null- литерал может принимать всего одно значение: null. Это литерал ссылочного типа, причем эта ссылка никуда не ссылается, объект отсутствует. Разумеется, его можно применять к ссылкам любого объектного типа данных. Типы данных подробно рассматриваются в следующей лекции.
Разделители
Разделители это специальные символы, которые используются в служебных целях языка. Назначение каждого из них будет рассмотрено по ходу изложения курса. Вот их полный список:
( ) [ ] { } ; . ,
Операторы
Операторы используются в различных операциях арифметических, логических, битовых, операциях сравнения и присваивания. Следующие 37 лексем (все состоят только из ASCII -символов) являются операторами языка Java:
= > < ! ~ ? :
6. Операторы Java. Арифметические, логические и битовые операции.
Операторы присваивания и сравнения
Во-первых, конечно же, различаются оператор присваивания = и оператор сравнения ==.
x = 1; // присваиваем переменной x значение 1
x == 1 // сравниваем значение переменной x с единицей
Оператор сравнения всегда возвращает булевское значение true или false. Оператор присваивания возвращает значение правого операнда. Поэтому обычная опечатка в языке С, когда эти операторы путают:
Условие "не равно" записывается как !=.
Арифметические операции
+, -, *, /, %
Унарные операторы инкрементации ++ и декрементации --
Логические операторы
Логические операторы "и" и "или" ( && и || )
Логический оператор отрицания "не" записывается как !
Оператор с условием ?: состоит из трех частей условия и двух выражений x>0 ? x : -x
Битовые операции
Как известно, битовые операции "и", "или", "исключающее или" принимают два аргумента и выполняют логическое действие попарно над соответствующими битами аргументов. При этом используются те же обозначения, что и для логических операторов, но, конечно, только в первом (одиночном) варианте. Например, вычислим выражение 5&6=4
При сдвиге влево оператором << все биты числа смещаются на указанное количество позиций влево, причем освободившиеся справа позиции заполняются нулями. Эта операция аналогична умножению на 2n и действует вполне предсказуемо, как при положительных, так и при отрицательных аргументах.
Рассмотрим примеры применения операторов сдвига для значений типа int, т.е. 32-битных чисел. Пусть положительным аргументом будет число 20, а отрицательным -21.
// Сдвиг влево для положительного числа 20
20 << 00 = 00000000000000000000000000010100 = 20
20 << 01 = 00000000000000000000000000101000 = 40
20 << 02 = 00000000000000000000000001010000 = 80
20 << 03 = 00000000000000000000000010100000 = 160
20 << 04 = 00000000000000000000000101000000 = 320
7. Типы данных в Java. Переменные. Примитивные и ссылочные типы.
Все типы данных разделяются на две группы. Первую составляют 8 простых, или примитивных
Они подразделяются на три подгруппы:
Целочисленные byte short int long char
Дробные float double
Булевые true false
Переменные
Переменные используются в программе для хранения данных. Любая переменная имеет три базовых характеристики:
Работа с переменной всегда начинается с ее объявления (declaration). Конечно, оно должно включать в себя имя объявляемой переменной. Как было сказано, в Java любая переменная имеет строгий тип, который также задается при объявлении и никогда не меняется. Значение может быть указано сразу (это называется инициализацией), а в большинстве случаев задание начальной величины можно и отложить. Тип +имя = инициализация(значение).
Кроме того, при объявлении переменной может быть использовано ключевое слово final. Его указывают перед типом переменной, и тогда ее необходимо сразу инициализировать и уже больше никогда не менять ее значение. Таким образом,final -переменные становятся чем-то вроде констант. Простейший пример: final double pi=3.1415;
Примитивные и ссылочные типы данных
Целочисленные типы это byte, short, int, long, также к ним относят и char . Первые четыре типа имеют длину 1, 2, 4 и 8 байт соответственно, длина char 2 байта, это непосредственно следует из того, что все символы Java описываются стандартом Unicode.
char единственный беззнаковый тип. Инициализировать его можно как символьным, так и целочисленным литералом. Во всем остальном char полноценный числовой тип данных.
Для каждого примитивного типа существуют специальные вспомогательные классы-обертки byte,short, int, long, char это Byte, Short, Integer, Long, Character. Эти классы содержат многие полезные методы для работы с целочисленными значениями. Например, преобразование из текста в число. Кроме того, есть класс Math.
Дробные типы это float и double . Их длина - 4 и 8 байт, соответственно. Оба типа знаковые. Для дробных типов добавляется еще одно ограничение -- каково наименьшее положительное ненулевое значение. Таким образом, нельзя задать литерал заведомо больший, чем позволяет соответствующий тип данных. И нельзя задать литерал, значение которого по модулю слишком мало для данного типа.
Теперь перейдем к преобразованию типов. Если хотя бы один аргумент имеет тип double, то значения всех аргументов приводятся к этому типу и результат операции также будет иметь тип double. Вычисление будет произведено с точностью в 64 бита.
Если же аргументов типа double нет, а хотя бы один аргумент имеет тип float, то все аргументы приводятся к float, вычисление производится с точностью в 32 бита и результат имеет тип float.
Булев тип представлен всего одним типом boolean, который может хранить всего два возможных значения true и false . Величины именно этого типа получаются в результате операций сравнения.
Ссылочные типы
Итак, выражение ссылочного типа имеет значение либо null, либо ссылку, указывающую на некоторый объект в виртуальной памяти .Объект (object) это экземпляр некоторого класса, или экземпляр массива. Объекты всегда создаются с использованием ключевого слова new, причем одно слово new порождает строго один объект. После ключевого слова указывается имя класса, от которого мы собираемся породить объект. Создание объекта всегда происходит через вызов одного из конструкторов класса, поэтому в заключение ставятся скобки, в которых перечислены значения аргументов, передаваемых выбранному конструктору.
8. Операции с численными типами данных, приведение численных типов данных. Специфика поведения операций с численными типами данных.
Над целочисленными аргументами можно производить следующие операции:
Операторы сравнения всегда булевого типа ( true или false ).
Результатом операции с целочисленными аргументами всегда является целое число. А значит, в следующем примере
double x = 1/2;
переменной x будет присвоено значение 0, а не 0.5, как можно было бы ожидать.
byte x=-128;
print(-x);
byte y=127;
print(++y);
Результатом будет:
128
-128
Этот пример иллюстрирует вопросы преобразования типов при вычислениях и случаи переполнения.
Вторым исключением является оператор с условием ?:. Если второй и третий операнды имеют одинаковый тип, то и результат операции будет такого же типа.
byte x=2;
byte y=3;
byte z=(x>y) ? x : y;
// верно, x и y одинакового типа
byte abs=(x>0) ? x : -x;
// неверно!
Последняя строка неверна, так как третий аргумент содержит числовую операцию, стало быть, его тип int, а значит, и тип всей операции будет int, и присвоение некорректно. Даже если второй аргумент имеет тип byte, а третий short, значение будет типа int.
Наконец, рассмотрим оператор конкатенации со строкой. Оператор + может принимать в качестве аргумента строковые величины. Если одним из аргументов является строка, а вторым целое число, то число будет преобразовано в текст и строки объединятся.
int x=1;
print("x="+x);
Результатом будет:
x=1
9. Объекты и правила работы с ними. Класс Object и его методы. Класс String
Объекты и правила работы с ними
Объект (object) это экземпляр некоторого класса, или экземпляр массива. Класс это описание объектов одинаковой структуры, и если в программе такой класс используется, то описание присутствует в единственном экземпляре. Объектов этого класса может не быть вовсе, а может быть создано сколь угодно много.
Объекты всегда создаются с использованием ключевого слова new, причем одно слово new порождает строго один объект. После ключевого слова указывается имя класса, от которого мы собираемся породить объект. Создание объекта всегда происходит через вызов одного из конструкторов, поэтому в заключение ставятся скобки, в которых перечислены значения аргументов. Приведем правильное определение Point:
class Point {
int x, y;
/**
* Конструктор принимает 2 аргумента,
* которыми инициализирует поля объекта.
*/
Point (int newx, int newy){
x=newx;
y=newy;
}
}
Если конструктор отработал успешно, то выражение new возвращает ссылку на созданный объект. Эту ссылку можно сохранить в переменной, передать в качестве аргумента в какой-либо метод или использовать другим способом.
Объект всегда "помнит", от какого класса он был порожден.
Класс Object
Это класс от которого наследуются все классы, в объявлении которых явно не указан другой родительский класс. А значит, любой класс напрямую, или через своих родителей, является наследником Object. Отсюда следует, что методы этого класса есть любого объекта. (поля в Object отсутствуют)
Рассмотрим основные из них.
getClass()
Этот метод возвращает объект класса Class, который описывает класс, от которого был порожден этот объект. У него есть метод getName(), возвращающий имя класса:
String s = "abc";
Class cl=s.getClass();
System.out.println(cl.getName());
Результатом будет строка:
java.lang.String
finalize()
Данный метод вызывается при уничтожении объекта автоматическим сборщиком мусора (garbage collector). В классе Object он ничего не делает, однако в классе-наследнике позволяет описать все действия, необходимые для корректного удаления объекта, такие как закрытие соединений с БД, сетевых соединений, снятие блокировок на файлы
Класс String
Экземпляры только этого класса можно создавать без использования ключевого слова new. Каждый строковый литерал порождает экземпляр String, и это единственный литерал (кроме null ), имеющий объектный тип. Породив объект, содержащий некое значение-строку, мы уже не можем изменить данное значение необходимо создать новый объект.
В классе String определен метод intern(), который возвращает один и тот же объект-строку для всех экземпляров, равных по значению. То есть если для ссылок s1 и s2 верно выражение s1.equals(s2), то верно и s1.intern()==s2.intern().
Разумеется, в классе переопределены методы equals() и hashCode(). Метод toString() также переопределен и возвращает он сам объект-строку, то есть для любой ссылки s типа String, не равной null, верно выражение s==s.toString().
10. Система именования в Java. Имена и идентификаторы. Область видимости. Соглашения по именованию
Имена бывают простыми (simple), состоящими из одного идентификатора и составными (qualified), состоящими из последовательности идентификаторов, разделенных точкой. У пакетов и ссылочных типов (классов, интерфейсов, массивов) есть элементы (members). Доступ к элементам осуществляется с помощью выражения, состоящего из имен, например, пакета и класса, разделенных точкой.
Элементами пакета являются содержащиеся в нем классы и интерфейсы, а также вложенные пакеты. Чтобы получить составное имя пакета, необходимо к полному имени пакета, в котором он располагается, добавить точку, а затем его собственное простое имя.
Простое имя классов и интерфейсов дается при объявлении, например, Object, String, Point. Чтобы получить составное имя таких типов, надо к составному имени пакета, в котором находится тип, через точку добавить простое имя типа. Например,java.lang.Object, java.lang.reflect.Method или com.myfirm.MainClass.
Для ссылочных типов элементами являются поля и методы, а также внутренние типы (классы и интерфейсы). Элементы могут быть как непосредственно объявлены в классе, так и получены по наследству от родительских классов и интерфейсов. Простое имя элементов также дается при инициализации. Например, toString(), PI, InnerClass. Составное имя получается путем объединения простого или составного имени типа, или переменной объектного типа с именем элемента. Например, ref.toString(), java.lang.Math.PI, OuterClass.InnerClass. Другие обращения к элементам ссылочных типов уже неоднократно применялись в предыдущих главах.
Имена и идентификаторы
Понятно, что простое имя состоит из одного идентификатора, а составное - из нескольких. Однако не всякий идентификатор входит в состав имени.
Во-первых, в выражении объявления идентификатор еще не является именем. Он становится именем после первого появления в коде в месте объявления. Во-вторых, существует возможность обращаться к полям и методам объектного типа не через имя типа или объектной переменной, а через ссылку на объект, полученную в результате выполнения выражения.
Наконец, идентификаторы также используются для названий меток.
Область видимости
у каждого имени есть область видимости (scope). Если обращение, например, к полю, идет из части кода, попадающей в область видимости его имени, то можно пользоваться простым именем, если нет необходимо применять составное.
Область видимости имен
Областью видимости объявления некоторого элемента языка называется часть программы, откуда допускается обращение к этомуэлементу по простому имени.
Область видимости доступного пакета вся программа, то есть любой класс может использовать доступный пакет. Однако необходимо помнить, что обращаться к пакету можно только по его полному составному имени.
Областью видимости типа верхнего уровня является пакет, в котором он объявлен. Из других пакетовдоступ возможен либо по составному имени.
Область видимости элементов это все тело типа, в котором они объявлены. Если обращение к этимэлементам происходит из другого типа, необходимо воспользоваться составным именем.
Соглашения по именованию
Соглашения регулируют именование следующих конструкций:
11. Пакеты Java. Модуль компиляции. Импортирование пакетов, уникальность имён.
Простейшим способом организации пакетов и типов является обычная файловая структура. Java использует специальную переменную окружения, которая называется classpath. эта переменная помогает работать с Java-классами. Ее значение должно состоять из путей к каталогам или архивам, разделенных точкой с запятой. Например, переменная classpath может иметь такое значение:
.;c:\java\classes;d:\lib\3Dengine.zip; d:\lib\fire.jar
Модуль компиляции хранится в текстовом .java -файле и является единичной порцией входных данных для компилятора. Он состоит из трех частей:
class Simple {public static void main(String s[])
{System.out.println("Hello!");}}
Импортируется один тип выражение записывается с помощью ключевого слова import и полного имени типа. import java.net.URL; Выражение, импортирующее пакет, включает в себя полное имя пакета следующим образом : import java.awt.*;
package first;
class FirstClass { }
interface MyInterface { }
Область видимости типа - пакет, в котором он описан. Из других пакетов к типу можно обращаться либо по составному имени, либо с помощью импортирующих выражений.
Однако, кроме области видимости, в Java также есть средства разграничения доступа. Чтобы другие пакеты также могли использовать его, можно указать ключевое слово public:
package second;
public class OpenClass { }
public interface PublicInterface { }
Такие типы доступны для всех пакетов.
Уникальность имен пакетов
Если программа создается разработчиком, у которого есть Internet-сайт, либо же он работает на организацию, у которой имеется сайт, и доменное имя такого сайта, например, company.com, то имена пакетов в обратном порядке: com.company. Дальнейшие вложенные пакеты могут носить названия подразделений компании, пакетов, фамилии разработчиков и т.д.
Таким образом, пакет верхнего уровня всегда записывается ASCII-буквами в нижнем регистре и может иметь имена:
Если имя сайта противоречит требованиям к идентификаторам Java, то предпринимаются следующие шаги по добавлению/замене на знак подчеркивания на необходимой позиции.
Примеры имен пакетов, составленных по таким правилам:
com.sun.image.codec.jpeg
org.omg.CORBA.ORBPackage
oracle.jdbc.driver.OracleDriver
12.Область видимости имён. Затеняющее, заслоняющее объявление.
Областью видимости объявления некоторого элемента языка называется часть программы, откуда допускается обращение к этому элементу по простому имени.
Область видимости доступного пакета вся программа, то есть любой класс может использовать доступный пакет.
Областью видимости импортированного типа являются все объявления верхнего уровня в этом модуле компиляции.
Областью видимости типа (класса или интерфейса) верхнего уровня является пакет, в котором он объявлен.
Область видимости элементов классов или интерфейсов это все тело типа, в котором они объявлены.
Область видимости локальных переменных начинается с момента их инициализации и до конца блока, в котором они объявлены. В отличие от полей типов, локальные переменные не имеют значений по умолчанию и должны инициализироваться явно.
int x; for (int i=0; i<10; i++) {int t=5+i;}
// здесь переменная t уже недоступна, так как блок, в котором она была объявлена, уже завершен, а переменная x еще недоступна, так как пока не была инициализирована//
"Затеняющее" объявление
Перейдем к проблеме перекрытия имен полей класса и локальных переменных. Пример:
class Human { int age; // возраст
int getAge( ) { return age; }
void setAge(int age) {
age=age; }} // ???
В классе Human объявлено поле age. Удобно определить также метод setAge( ), который должен устанавливать новое значение возраста для человека. Вполне логично сделать у метода setAge( ) один входной аргумент, который также будет называться age (ведь в качестве этого аргумента будет передаваться новое значение возраста). Получается, что в реализации метода setAge() нужно написать age=age, в первом случае подразумевая поле класса, во втором - параметр метода. Во-первых, рассмотрим, из-за чего возникла конфликтная ситуация. Есть два элемента языка аргумент метода и поле класса, области видимости которых пересеклись. Область видимости поля класса больше, она охватывает все тело класса, в то время как область видимости аргумента метода включает только сам метод. В таком случае внутри области пересечения по простому имени доступен именно аргумент метода, а поле класса "затеняется" (shadowing) объявлением параметра метода.
Остается вопрос, как в такой ситуации все же обратиться к полю класса. Если вызвать метод setAge() у объекта класса Human и использовать в этом методе слово this, то его значение будет ссылкой на данный объект.
Исправленный вариант примера:
class Human { int age; // возраст
void setAge(int age) { this.age=age; }}
"Заслоняющее" объявление
Может возникнуть ситуация, когда простое имя может быть одновременно рассмотрено как имя переменной, типа или пакета.
Приведем пример, который частично иллюстрирует такой случай:
import java.awt.*;
public class Obscuring {
static Point Test = new Point(3,2);
public static void main (String s[]) { print(Test.x);}}
class Test {static int x = -5;}
В методе main() простое имя Test одновременно обозначает имя поля класса Obscuring и имя другого типа, находящегося в том же пакете, Test. С помощью этого имени происходит обращение к полю x, которое определено и в классеjava.awt.Point и Test.
Результатом этого примера станет 3, то есть переменная имеет более высокий приоритет. В свою очередь, тип имеет более высокий приоритет, чем пакет. Таким образом, обращение к доступному в обычных условиях типу или пакету может оказаться невозможным, если есть объявление одноименной переменной или типа, имеющее более высокий приоритет. Такое объявление называется "заслоняющим" (obscuring).
Эта проблема скорее всего не возникнет, если следовать соглашениям по именованию элементов языка Java.
13. Объявление классов. Модификаторы доступа и их предназначение
Предназначение модификаторов доступа
Функциональность класса необходимо разделять на открытый интерфейс, описывающий действия, которые будут использовать внешние типы, и на внутреннюю реализацию, которая применяется только внутри самого класса. Внешний интерфейс в дальнейшем модифицировать невозможно, или очень сложно, для больших систем, поэтому его требуется продумывать особенно тщательно. Детали внутренней реализации могут быть изменены на любом этапе, если они не меняют логику работы всего класса. Благодаря такому подходу реализуется одна из базовых характеристик объектной модели инкапсуляция, и обеспечивается важное преимущество технологии ООП модульность.
Таким образом, модификаторы доступа вводятся не для защиты типа от внешнего пользователя, а, напротив, для защиты, или избавления, пользователя от излишних зависимостей от деталей внутренней реализации. Что же касается неправильного применения класса, то его создателям нужно стремиться к тому, чтобы класс был прост в применении, тогда таких проблем не возникнет, ведь программист не станет намеренно писать код, который порождает ошибки в его программе.
Конечно, такое разбиение на внешний интерфейс и внутреннюю реализацию не всегда очевидно, часто условно. Для облегчения задачи технических дизайнеров классов в Java введено не два ( public и private ), а четыре уровня доступаУровень доступа элемента языка является статическим свойством, задается на уровне кода и всегда проверяется во время компиляции. Попытка обратиться к закрытому элементу напрямую вызовет ошибку.
В Java модификаторы доступа указываются для:
Как следствие, массив также может быть недоступен в том случае, если недоступен тип, на основе которого он объявлен.
Все четыре уровня доступа имеют только элементы типов и конструкторы. Это:
protected дает доступ наследникам класса. Понятно, что наследникам может потребоваться доступ к некоторым элементам родителя, с которыми не приходится иметь дело внешним классам.
Модификатор protected может быть указан для наследника из другого пакета, а доступ по умолчанию допускает обращения из классов-ненаследников, если они находятся в том же пакете. По этой причине возможности protected были расширены таким образом, что он включает в себя доступ внутри пакета. Итак, модификаторы доступа упорядочиваются от менее открытых к более открытым: private= >(none) default=>(none) default=> protected=> public
Модификаторы доступа возможны для различных элементов :
Разграничения сказываются на обращении к элементам объектных типов, пакетов,также при вызове конструкторов, наследовании, приведении типов. Импортировать недоступные типы запрещается.
Проверка уровня доступа проводится компилятором.
14. Конструкторы и инициализаторы классов. Инициализаторы объектов. Порядок инициализации
Объявление конструкторов
Формат объявления конструкторов : выделяют заголовок и тело конструктора. Заголовок состоит, из модификаторов доступа. Указывается имя класса, которое можно расценивать двояко. Можно считать, что имя конструктора совпадает с именем класса. Тело конструктора пустым быть не может и поэтому всегда описывается в фигурных .В отсутствие имени сигнатура конструктора определяется только набором входных параметров по тем же правилам, что и для методов. Допускается любое количество конструкторов, если у них различные сигнатуры.
Тело конструктора может содержать любое количество return -выражений без аргументов. Если процесс исполнения дойдет до такого выражения, то на этом месте выполнение конструктора будет завершено.
Поскольку при их вызове осуществляется создание и инициализация объекта, становится понятно, что такой процесс не может происходить без обращения к конструкторам всех родительских классов. Поэтому вводится обязательное правило первой строкой в конструкторе должно быть обращение к родительскому классу, которое записывается с помощью ключевого слова super.
Конструкторы не имеют имени и их нельзя вызвать явно, только через выражение создания объекта. Конструкторы не передаются по наследству. Если в родительском классе объявлено пять разных полезных конструкторов и требуется, чтобы класс-наследник имел аналогичный набор, необходимо все их описать заново.
Класс обязательно должен иметь конструктор, иначе невозможно порождать объекты ни от него, ни от его наследников. Поэтому если в классе не объявлен ни один конструктор, компилятор добавляет один по умолчанию. Это public -конструктор без параметров и с телом.
Инициализаторы
Последней допустимой конструкцией в теле класса является объявление инициализаторов. Записываются объектные инициализаторы внутри фигурных скобок.
Инициализаторы не имеют имен, исполняются при создании объектов, не могут быть вызваны явно, не передаются по наследству.Было указано уже три вида инициализирующего кода в классах конструкторы, инициализаторы переменных, а теперь добавились объектные инициализаторы.При создании экземпляра класса вызванный конструктор выполняется следующим образом:
Теперь можно сформулировать наиболее гибкий подход к инициализации final -полей. Главное требование чтобы такие поля были проинициализированы ровно один раз. Это можно обеспечить в следующих случаях:
15. Заголовок и тело класса. Объявление полей и методов класса. Модификаторы объявлений.
Заголовок класса
Вначале указываются модификаторы класса. Допустимым является public, либо его отсутствие доступ по умолчанию.
Класс может быть объявлен как final. В этом случае не допускается создание наследников такого класса. На своей ветке наследования он является последним. Класс String и классы-обертки, например, представляют собой final -классы.
После списка модификаторов указывается ключевое слово class, а затем имя класса корректный Java-идентификатор.
Полное составное имя класса строится из полного составного имени пакета, в котором он объявлен, и простого имени класса, разделенных точкой. Область видимости класса, где он может быть доступен по своему простому имени, его пакет.
Далее заголовок может содержать ключевое слово extends, после которого должно быть указано имя доступного не- final класса. В этом случае объявляемый класс наследуется от указанного класса. Если выражение extends не применяется, то класс наследуется напрямую от Object.
Попытка расширить final -класс приведет к ошибке компиляции.
Класс A считается наследником класса B, если:
Таким образом можно проследить цепочки наследования на несколько уровней вверх.
Также в заголовке может быть указано ключевое слово implements, за которым должно следовать перечисление через запятую имен доступных интерфейсов.В этом случае говорят, что класс реализует перечисленные интерфейсы. Класс может реализовывать любое количество интерфейсов. Если выражение implements отсутствует, то класс действительно не реализует никаких интерфейсов, здесь значений по умолчанию нет.Далее следует пара фигурных скобок, которые могут быть пустыми или содержать описание тела класса.
Тело класса может содержать объявление элементов класса:
Элементы класса имеют имена и передаются по наследству, не-элементы нет. Для элементов простые имена указываются при объявлении, составные формируются из имени класса, или имени переменной объектного типа, и простого имени элемента. Областью видимости является все объявление тела класса.
Не-элементы не обладают именами, а потому не могут быть вызваны явно. Их вызывает сама виртуальная машина.
Элементами класса являются элементы, описанные в объявлении тела класса и переданные по наследству от класса-родителя и всех реализуемых интерфейсов при условии достаточного уровня доступа. Классы из того же пакета могут пользоваться полным набором элементов, а из других пакетов только protected и public. private -элементы по наследству не передаются.
Поля и методы могут иметь одинаковые имена, поскольку обращение к полям всегда записывается без скобок, а к методам всегда со скобками.
Объявление полей
Объявление полей начинается с перечисления модификаторов. Возможно применение любого из трех модификаторов доступа, либо никакого вовсе. Поле может быть объявлено как final, это означает, что оно инициализируется один раз и больше не будет менять своего значения. После списка модификаторов указывается тип поля. Затем идет перечисление одного или нескольких имен полей.
Объявление метода состоит из заголовка и тела метода. Заголовок состоит из:
16. Дополнительные свойства классов. Параметры методов и их передача. Перегруженные методы.
Дополнительные свойства классов
Рассмотрим в этом разделе некоторые особенности работы с классами в Java.
Программа, написанная на Java, является набором классов. Понятно, что требуется некая входная точка, с которой должно начинаться выполнение приложения.
Такой входной точкой, по аналогии с языками C/C++, является метод main(). Пример его объявления:
public static void main(String[] args) { }
Метод не возвращает никакого значения, хотя в C есть возможность указать код возврата из программы. В Java для этой цели существует метод System.exit(), который закрывает виртуальную машину и имеет аргумент типа int.
Аргументом метода main() является массив строк. Он заполняется дополнительными параметрами, которые были указаны при вызове метода.
Для вызова программы виртуальной машине передается в качестве параметра имя класса, у которого объявлен метод main(). Поскольку это имя класса, а не имя файла, то не должно указываться никакого расширения ( .class или .java ) и расположение класса записывается через точку (разделитель имен пакетов), а не с помощью файлового разделителя. Компилятору же, напротив, передается имя и путь к файлу.
Если приведенный выше модуль компиляции сохранен в файле Test.java, который лежит в каталоге test\first, то вызов компилятора записывается следующим образом:
javac test\first\Test.java
А вызов виртуальной машины:
java test.first.Test
Параметры методов
Теперь можно уточнить, что означает возможность объявлять параметры методов и конструкторов как final. Поскольку изменения значений параметров (но не объектов, на которые они ссылаются) никак не сказываются на переменных вне метода, модификатор final говорит лишь о том, что значение этого параметра не будет меняться на протяжении работы метода.
Перегруженными (overloaded) методами называются методы одного класса с одинаковыми именами. Сигнатуры у них должны быть различными и различие может быть только в наборе аргументов.
Если в классе параметры перегруженных методов заметно различаются: например, у одного метода один параметр, у другого два, то для Java это совершенно независимые методы и совпадение их имен может служить только для повышения наглядности работы класса. Каждый вызов, в зависимости от количества параметров, однозначно адресуется тому или иному методу.
Однако если количество параметров одинаковое, а типы их различаются незначительно, при вызове может сложиться двойственная ситуация, когда несколько перегруженных методов одинаково хорошо подходят для использования.
17. Преобразование типов в Java. Виды приведений. Расширение и сужение примитивных типов.
Java является строго типизированным языком, а это означает, что каждое выражение и каждая переменнаяимеет строго определенный тип уже на момент компиляции.
Виды приведений. В Java предусмотрено семь видов приведений:
Преобразование примитивных типов (расширение и сужение)
Для простых типов расширение означает, что осуществляется переход от менее емкого типа к более емкому. Например, от типа byte (длина 1 байт) к типу int (длина 4 байта). Такие преобразования безопасны в том смысле, что новый тип всегда гарантированно вмещает в себя все данные, которые хранились в старом типе.
Следующие 19 преобразований являются расширяющими:
Обратите внимание, что нельзя провести преобразование к типу char от типов меньшей или равной длины ( byte, short ), или, наоборот-- char является беззнаковым.
Сужение - означает, что переход осуществляется от более емкого типа к менее емкому. При таком преобразовании есть риск потерять данные. В Java такое преобразование должно совершаться явным образом .Следующие 23 преобразования являются сужающими:
При сужении целочисленного типа к более узкому целочисленному все старшие биты, не попадающие в новый тип, просто отбрасываются.
Сужение дробного типа до целочисленного является более сложной процедурой. Она проводится в два этапа. 1)На первом шаге дробное значение преобразуется в long, если целевым типом является long, или в int - в противном случае .Для этого исходное дробное число сначала математически округляется в сторону нуля, то есть дробная часть просто отбрасывается.2) На втором шаге производится дальнейшее сужение от выбранного целочисленного типа к целевому, если таковое требуется, то есть может иметь место дополнительное преобразование от int к byte, short или char.
Понятно, что преобразование от char к int не приводит к потере точности, но позволяет распечатывать не символ, а его числовой код, что более удобно для анализа.В заключение еще раз обратим внимание на то, что примитивные значения типа boolean могут участвовать только в тождественных преобразованиях.
18.Преобразование ссылочных типов, расширение и сужение. Преобразование к строке.
Преобразование ссылочных типов (расширение и сужение) Переходим к ссылочным типам. Преобразование объектных типов лучше всего иллюстрируется с помощью дерева наследования. Рассмотрим небольшой пример наследования:
// Объявляем класс Parent
class Parent {
int x;}
// Объявляем класс Child и наследуем
// его от класса Parent
class Child extends Parent {
int y;}
// Объявляем второго наследника
// класса Parent - класс Child2
class Child2 extends Parent {
int z;} В каждом классе объявлено поле с уникальным именем. Три объявленных класса могут порождать три вида объектов. Объекты класса Parent обладают только одним полем x, а значит, только ссылки типа Parent могут ссылаться на такие объекты. Объекты класса Child обладают полем y и полем x, полученным по наследству от класса Parent. Стало быть, на такие объекты могут указывать ссылки типа Child или Parent. Второй случай уже иллюстрировался следующим примером: Parent p = new Child();
Обратите внимание, что с помощью такой ссылки p можно обращаться лишь к полю x созданного объекта. Аналогично, объекты класса Child2 обладают полем z и полем x, полученным по наследству от класса Parent. Значит, на такие объекты могут указывать ссылки типа Child2 или Parent.
Ссылки типа Parent могут указывать на объект любого из трех рассматриваемых типов, а ссылки типа Child иChild2 - только на объекты точно такого же типа. Расширение означает переход от более конкретного типа к менее конкретному, т.е. переход от детей к родителям. В нашем примере преобразование от любого наследника ( Child, Child2 ) к родителю ( Parent ) есть расширение, переход к более общему типу. Этот переход производится самой JVM при необходимости и незаметен для разработчика, он всегда проходит успешно: всегда можно обращаться к объекту, порожденному от наследника, по типу его родителя: Parent p1=new Child(); Parent p2=new Child2(); В строках переменным типа Parent присваивается значение другого типа, а значит, происходит преобразование. Поскольку это расширение, оно производится автоматически и всегда успешно.
Следующие преобразования являются расширяющими:
Второй случай иллюстрируется следующим примером: Parent p=null;
Однако на практике важно, что такое значение можно прозрачно преобразовать к любому объектному типу.
Обратный переход, то есть движение по дереву наследования вниз, к наследникам, является сужением.
Преобразование к строке .Любой тип может быть приведен к строке, т.е. к экземпляру класса String. Такое преобразование является исключительным в силу того, что охватывает абсолютно все типы, в том числе и boolean. Преобразование различных типов :
19. Применение приведения типов. Запрещенные приведения. Ошибки приведения при компиляции и исполнении программы. Явные и неявные приведения.
Применение приведений
ситуации, где могут встретиться или потребоваться приведения могут быть сгруппированы следующим образом.
Вызов метода
Это приведение возникает в случае, когда вызывается метод с объявленными параметрами одних типов, а при вызове передаются аргументы других типов. Надо отметить, что, в отличие от ситуации присвоения, при вызове методов компилятор не производит преобразований примитивных значений от byte, short, char или int к byte, short или char. Это привело бы к усложнению работы с перегруженными методами. В заключение рассмотрим пример
short get(Parent p) {
return 5+'A';
// приведение при возвращении значения
}
void main() {
long a = 5L;
// приведение при присвоении значения
get(new Child());
// приведение при вызове метода
}
Явное приведение
При таком преобразовании слева от выражения, тип значения которого необходимо преобразовать, в круглых скобках указывается целевой тип. Если преобразование пройдет успешно, то результат будет точно указанного типа.
Оператор конкатенации строк
Если обоими его аргументами являются строки, то происходит обычная конкатенация. Если же тип String имеет лишь один из аргументов, то второй необходимо преобразовать в текст. Это единственная операция, при которой производится универсальное приведение любого значения к типу String.
Это одно из свойств, выделяющих класс String из общего ряда.
Числовое расширение
Наконец, последний вид преобразований применяется при числовых операциях, когда требуется привести аргумент(ы) к типу длиной в 32 или 64 бита для проведения вычислений. Таким образом, при числовом расширении осуществляется только расширение примитивных типов.
Различают унарное и бинарное числовое расширение.
Унарное числовое расширение
Это преобразование расширяет примитивные типы byte, short или char до типов int по правилам расширения примитивных типов.
20.Объектная модель Java. Статические элекенты.
Статические элементы
До этого момента под полями объекта мы всегда понимали значения, которые имеют смысл только в контексте некоторого экземпляра класса. Например:
class Human {
private String name;
}
Прежде, чем обратиться к полю name, необходимо получить ссылку на экземпляр класса Human, невозможно узнать имя вообще, оно всегда принадлежит какому-то конкретному человеку.
Но бывают данные и иного характера. Предположим, необходимо хранить количество всех людей (экземпляров класса Human, существующих в системе). Понятно, что общее число людей не является характеристикой какого-то одного человека, оно относится ко всему типу в целом. Отсюда появляется название "поле класса", в отличие от "поля объекта". Объявляются такие поля с помощью модификатора static:
class Human {
public static int totalCount;
}
Чтобы обратиться к такому полю, ссылка на объект не требуется, вполне достаточно имени класса:
Human.totalCount++;
// рождение еще одного человека
Для удобства разрешено обращаться к статическим полям и через ссылки:
Human h = new Human();
h.totalCount=100;
Однако такое обращение конвертируется компилятором. Он использует тип ссылки, в данном случае переменная h объявлена как Human, поэтому последняя строка будет неявно преобразована в:
Human.totalCount=100;
В этом можно убедиться на следующем примере:
Human h = null;
h.totalCount+=10;
Значение ссылки равно null, но это не имеет значения в силу описанной конвертации. Данный код успешно скомпилируется и корректно исполнится. Таким образом, в следующем примере
Human h1 = new Human(), h2 = new Human();
Human.totalCount=5;
h1.totalCount++;
System.out.println(h2.totalCount);
все обращения к переменной totalCount приводят к одному единственному полю, и результатом работы такой программы будет 6. Это поле будет существовать в единственном экземпляре независимо от того, сколько объектов было порождено от данного класса, и был ли вообще создан хоть один объект.
Аналогично объявляются статические методы.
class Human {
private static int totalCount;
public static int getTotalCount() {
return totalCount;
}
}
Для вызова статического метода ссылки на объект не требуется.
Human.getTotalCount();
19. Применение приведения типов. Запрещенные приведения. Ошибки приведения при компиляции и исполнении программы. Явные и неявные приведения.
Ситуации, где могут встретиться или потребоваться приведения могут быть сгруппированы следующим образом.
Присвоение значений
Приведение может потребоваться, если переменной одного типа присваивается значение другого типа. Возможны следующие комбинации. Если сочетание этих двух типов образует запрещенное приведение, возникнет ошибка. Для удобства разработчика компилятор проводит дополнительный анализ при присвоении значений переменным типа byte, short и char. Если таким переменным присваивается величина типа byte, short,char или int, причем ее значение может быть получено уже на момент компиляции, и оказывается, что это значение укладывается в диапазон типа переменной, то явного приведения не требуется.
Вызов метода
В отличие от ситуации присвоения, при вызове методов компилятор не производит преобразований примитивных значений от byte, short, char или int к byte, short или char. Это привело бы к усложнению работы с перегруженными методами.
Явное приведение
При таком преобразовании слева от выражения, тип значения которого необходимо преобразовать, в круглых скобках указывается целевой тип. Если преобразование пройдет успешно, то результат будет точно указанного типа.
Оператор конкатенации строк
Если обоими его аргументами являются строки, то происходит обычная конкатенация. Если же тип String имеет лишь один из аргументов, то второй необходимо преобразовать в текст. Это единственная операция, при которой производится универсальное приведение любого значения к типу String.
Это одно из свойств, выделяющих класс String из общего ряда.
Числовое расширение
Наконец, последний вид преобразований применяется при числовых операциях, когда требуется привести аргумент(ы) к типу длиной в 32 или 64 бита для проведения вычислений. Унарное числовое расширение это преобразование расширяет примитивные типы byte, short или char до типов int по правилам расширения примитивных типов. Унарное числовое расширение может выполняться при следующих операциях:1)унарные, 2)операции + и - ; 3)битовое отрицание ~ ; 4)операции битового сдвига <<, >>, >>>.Бинарное числовое расширение это преобразование расширяет все примитивные числовые типы, кроме double, до типов int, long, float, double по правилам расширения примитивных типов. Бинарное числовое расширение : 1)если любой из аргументов имеет тип float, то и второй приводится к float ; 2)если любой из аргументов имеет тип long, то и второй приводится к long ;3) оба аргумента приводятся к int. Бинарное числовое расширение может выполняться при: 1)арифметические операции +, -, *, /, % ; 2)операции сравнения <, <=, >, >=, ==, != ; 3)битовые операции &, |, ^ ; 4)в некоторых случаях для операции с условием ?:.
20.Объектная модель Java. Статические элекенты.
Статические элементы До этого момента под полями объекта мы всегда понимали значения, которые имеют смысл только в контексте некоторого экземпляра класса. Но бывают данные и иного характера.
Кроме полей и методов, статическими могут быть инициализаторы. Они также называются инициализаторами класса, в отличие от инициализаторов объекта, рассматривавшихся ранее. Их код выполняется один раз во время загрузки класса в память виртуальной машины. Их запись начинается с модификатора static:
class Human {
static {
System.out.println("Class loaded");
}
}
Если объявление статического поля совмещается с его инициализацией, то поле инициализируется также однократно при загрузке класса. На объявление и применение статических полей накладываются те же ограничения, что и для динамических, нельзя использовать поле в инициализаторах других полей или в инициализаторах класса до того, как это поле объявлено:
class Test {
static int a;
static {
a=5;
// b=7; // Нельзя использовать до
// объявления!
}
static int b=a;
}
Это правило распространяется только на обращения к полям по простому имени. Если использовать составное имя, то обращаться к полю можно будет раньше (выше в тексте программы), чем оно будет объявлено:
class Test {
static int b=Test.a;
static int a=3;
static {
System.out.println("a="+a+", b="+b);
}
}
Если класс будет загружен в систему, на консоли появится текст:
a=3, b=0
Видно, что поле b при инициализации получило значение по умолчанию поля a, т.е. 0. Затем полю a было присвоенозначение 3.
Статические поля также могут быть объявлены как final, это означает, что они должны быть проинициализированы строго один раз и затем уже больше не менять своего значения. Аналогично, статические методы могут быть объявлены как final, а это означает, что их нельзя перекрывать в классах-наследниках.
Для инициализации статических полей можно пользоваться статическими методами и нельзя обращаться к динамическим. Вводят специальные понятия статический и динамический контексты. К статическому контексту относят статические методы, статические инициализаторы, инициализаторы статических полей. Все остальные части кода имеют динамический контекст
21. Ключевые слова this и super, их применение. Переопределение методов.
Если выполнение кода происходит в динамическом контексте, то должен быть объект, ассоциированный с ним. Ключевое слово this может возвращать ссылку на данный объект:
class Test { public Object getThis() { return this }
внутри методов слово this возвращает ссылку на объект, у которого этот метод вызван. Оно необходимо, если нужно передать аргумент, равный ссылке на данный объект, в какой-нибудь метод. public Human (String s) {name = s; register(this); // саморегистрация }
Другое применение this рассматривалось в случае "затеняющих" объявлений: public void setName(String name) { this.name=name;}
Слово this можно использовать для обращения к полям, которые объявляются ниже:int b=this.a; int a=5;
Слово this применяется в конструкторах для явного вызова в первой строке другого конструктора этого же класса. Там же может применяться и слово super, только уже для обращения к конструктору родительского класса.
Другие применения слова super также связаны с обращением к родительскому классу объекта. Например, оно может потребоваться в случае переопределения (overriding) родительского метода.
Переопределением называют объявление метода, сигнатура которого совпадает с одним из методов родительского класса.
class Child extends Parent { // Переопределение метода
public static void main(String s[]) { Child c = new Child();
// пример вызова переопределенного метода
Вызов переопределенного метода использует механизм полиморфизма.
Иногда при переопределении бывает полезно воспользоваться результатом работы родительского метода. Предположим, он делал сложные вычисления, а переопределенный метод должен вернуть округленный результат этих вычислений. Понятно, что гораздо удобнее обратиться к родительскому методу, чем заново описывать весь алгоритм. Здесь применяется слово super. Из класса наследника с его помощью можно обращаться к переопределенным методам родителя:
class Child extends Parent {// переопределение метода
public int getValue() { // обращение к методу родителя
return super.getValue()+1;}
Обращаться с помощью ключевого слова super к переопределенному методу родителя, т.е. на два уровня наследования вверх, невозможно. Если родительский класс переопределил функциональность своего родителя, значит, она не будет доступна его наследникам.
Поскольку ключевые слова this и super требуют наличия ассоциированного объекта, т.е. динамического контекста, использование их в статическом контексте запрещено.
22. Абстрактные классы и методы. Интерфейсы, их описание и реализация
Иногда имеет смысл описать только заголовок метода, без его тела, и таким образом объявить, что данный метод будет существовать в этом классе. Реализацию этого метода, то есть его тело, можно описать позже. Абстрактный метод в родительском классе. У него нет внешнего вида, но известно, что он есть у каждого наследника. Поэтому заголовок метода описывается в родительском классе, тело метода у каждого наследника свое, а контейнер может спокойно пользоваться только базовым типом, не делая никаких приведений.
Поскольку абстрактный метод не имеет тела, после описания его заголовка ставится точка с запятой. А раз у него нет тела, то к нему нельзя обращаться, пока его наследники не опишут реализацию. Нельзя создавать экземпляры класса, у которого есть абстрактные методы. Такой класс сам объявляется абстрактным.
Класс может быть абстрактным и в том случае, если у него нет абстрактных методов, но должен быть абстрактным, если такие методы есть. Разработчик может указать ключевое слово abstract в списке модификаторов класса, если хочет запретить создание экземпляров этого класса. Классы-наследники должны реализовать все абстрактные методы своего абстрактного родителя, чтобы их можно было объявлять неабстрактными и порождать от них экземпляры.
Конечно, класс не может быть одновременно abstract и final. Объект может быть порожден только от не абстрактного класса, который является наследником от абстрактного, и должен был реализовать все абстрактные методы.
Интерфейсы
В Java класс может иметь только одного родителя. Интерфейсы в Java устроены так что,от них нельзя порождать объекты, но другие классы могут реализовывать их.
Объявление интерфейсов
Объявление интерфейсов очень похоже на упрощенное объявление классов.Оно начинается с заголовка. Сначала указываются модификаторы. Интерфейс может быть объявлен как public и тогда он будет доступен для общего использования, либо модификатор доступа может не указываться, в этом случае интерфейс доступен только для типов своего пакета.
Далее записывается ключевое слово interface и имя интерфейса.
После этого может следовать ключевое слово extends и список интерфейсов, от которых будет наследоваться объявляемый интерфейс. Затем в фигурных скобках записывается тело интерфейса.Тело интерфейса состоит из объявления элементов, то есть полей-констант и абстрактных методов. Все поля интерфейса должны быть public final static, так что эти модификаторы указывать необязательно и даже нежелательно, чтобы не загромождать код
Реализация интерфейса
Каждый класс может реализовывать любые доступные интерфейсы. При этом в классе должны быть реализованы все абстрактные методы, появившиеся при наследовании от интерфейсов или родительского класса, чтобы новый класс мог быть объявлен неабстрактным.
В классе оказывается два разных метода с одинаковой сигнатурой, что является неразрешимым конфликтом. Это единственное ограничение на набор интерфейсов, которые может реализовывать класс.
Итак, если имя интерфейса указано после implements в объявлении класса, то класс реализует этот интерфейс.Наследники данного класса также реализуют интерфейс, поскольку им достаются по наследству его элементы.
Поскольку объекты порождаются только от классов, а все они наследуются от Object, это означает, что значения типа интерфейс обладают всеми элементами класса Object.
Применение интерфейсов
Распространенное мнение, что интерфейс это полностью абстрактный класс, в целом верно, но оно не отражает всех преимуществ, которые дают интерфейсы объектной модели. Как уже отмечалось, множественное наследование порождает ряд конфликтов, но отказ от него, хоть и делает язык проще, но не устраняет ситуации, в которых требуются подобные подходы.
23. Статические и динамические поля и методы. Переопределение методов с различными модификаторами доступа. Принципы полиморфизма применительно к переопределению методов
При объявлении одноименных полей или методов с совпадающими сигнатурами происходит перекрытие элементов из родительского и наследующего класса. Рассмотрим, как функционируют классы и объекты в таких ситуациях.
Поля .Наследники могут объявлять поля с любыми именами, даже совпадающими с родительскими. Компилятор может опираться только на тип ссылки, с помощью которой происходит обращение к полю. Перейдем к статическим полям. На самом деле, для них проблем и конфликтов, связанных с полиморфизмом, не существует.
Нужно вспомнить, как компилятор обрабатывает обращения к статическим полям через ссылочные значения. Неважно, на какой объект указывает ссылка. Более того, она может быть даже равна null. Все определяется типом ссылки.
Статическое поле принадлежит классу, а не объекту. В результате появление классов-наследников .
Такие объявления называют скрывающими. При этом объекты будут содержать оба значения, а компилятор будет каждый раз определять, с каким из них надо работать.
Методы . переопределение методов должно производиться с осторожностью. Если слишком сильно изменить логику их работы, нарушить принятые, это может привести к сбоям в работе родительского класса, а значит, объекта наследника. Более того, существуют и некоторые обязательные ограничения.
Вспомним, что заголовок метода состоит из модификаторов, возвращаемого значения, сигнатуры и throws -выражения. Сигнатура (имя и набор аргументов) остается неизменной, если говорить о переопределении. Возвращаемое значение также не может меняться, иначе это приведет к появлению двух разных методов с одинаковыми сигнатурами.
Рассмотрим модификаторы доступа.
Именно компилятор выполняет проверку уровня доступа, и он будет ориентироваться на родительский класс. Но ссылка-то указывает на объект, порожденный от Child, и по правилам полиморфизма исполняться будет метод именно этого класса. А значит, доступ к переопределенному методу не может быть более ограниченным, чем к исходному. Итак, методы с доступом по умолчанию можно переопределять с таким же доступом, либо protected или public. Protected -методы переопределяются такими же, или public, а для public менять модификатор доступа и вовсе нельзя.
Что касается private -методов, то они определены только внутри класса, снаружи не видны, а потому наследники могут без ограничений объявлять методы с такими же сигнатурами и произвольными возвращаемыми значениями, модификаторами доступа и т.д.
Если абстрактный метод переопределяется неабстрактным, то говорят, что он его реализовал (implements). Как ни странно, абстрактный метод может переопределить другой абстрактный, или даже неабстрактный, метод. Перейдем к статическим методам. То есть статические методы, подобно статическим полям, принадлежат классу и появление наследников на них не сказывается.
Статические методы не могут перекрывать обычные, и наоборот.
Полиморфизм и объекты
В заключение рассмотрим несколько особенностей, вытекающих из свойств полиморфизма.
Ссылочный тип обладает следующими элементами: 1)непосредственно объявленными в его теле;2)объявленными в его родительском классе и реализуемых интерфейсах, кроме:private -элементов; 3)"скрытых" элементов переопределенных (динамических) методов.
Полиморфизм и объекты
В заключение рассмотрим несколько особенностей, вытекающих из свойств полиморфизма.
Ссылочный тип обладает следующими элементами:
переопределенных (динамических) методов.
24. Массивы как типы данных. Объявление и инициализация массивов. Массив как тип Java. Многомерные массивы
Массивы как тип данных в Java
Массивы (arrays) используются для хранения целого набора значений. Элементы не имеют имен, доступ к ним осуществляется по номеру индекса. Если массив имеет длину n, отличную от нуля, то корректными значениями индекса являются числа от 0 до n-1. Все значения имеют одинаковый тип и говорится, что массив основан на этом базовом типе. Массивы могут быть основаны как на примитивных ,так и на ссылочных. В Java массив символов char[] и класс String являются различными типами. Их значения могут легко конвертироваться друг в друга с помощью специальных методов, но все же они не относятся к идентичным типам. Массивы в Java являются объектамм, их тип напрямую наследуется от класса Object, поэтому все элементы данного класса доступны у объектов-массивов.
Базовый тип также может быть массивом. Таким образом конструируется массив массивов, или многомерный массив.
Работа с любым массивом включает обычные операции, уже описанные для других типов, - объявление, инициализация и т.д. Начнем последовательно изучать их в приложении к массивам.
Объявление массивов
Создание переменной типа массив еще не создает экземпляры этого массива. Такие переменные имеют объектный тип и хранят ссылки на объекты, однако изначально имеют значение null . Чтобы создать экземпляр массива, нужно воспользоваться ключевым словом new, после чего указывается тип массива и в квадратных скобках длина массива.
После его создания можно обращаться к элементам, используя ссылку на массив, далее в квадратных скобках указывается индекс элемента. Индекс меняется от нуля, пробегая всю длину массива, до максимально допустимого значения, на единицу меньшего длины массива.
Если бы индекс превысил максимально возможное для такого массива значение, то появилась бы ошибка времени исполнения. Проверка, не выходит ли индекс за допустимые пределы, происходит только во время исполнения программы. Для всех массивов существует специальное поле length, позволяющее узнать ее значение.
Значение индекса массива всегда имеет тип int. При обращении к элементу можно также использовать byte, short или char, поскольку эти типы автоматически расширяются до int. Попытка задействовать long приведет к ошибке компиляции.
Типы массива,могут быть :1)интерфейсы. В таком случае элементы массива могут иметь значение null или ссылаться на объекты любого класса, реализующего этот интерфейс; 2)абстрактные классы. В этом случае элементы массива могут иметь значение null или ссылаться на объекты любого неабстрактного класса-наследника.
Поскольку массив является объектным типом данных, его значения могут быть приведены к типу Object или, что то же самое, присвоены переменной типа Object.
Инициализация массивов
Рассмотрим создание массива на основе ссылочного типа. Предположим, это будет класс Point. При создании экземпляра массива с применением ключевого слова new не создается ни один объект класса Point, создается лишь один объект массива. Каждый элемент массива будет иметь пустое значение null.
Далее нужно инициализировать элементы массива по отдельности, например, в цикле. Кроме того, существует и другой способ создания массивов инициализаторы. В этом случае ключевое слово new не используется, а ставятся фигурные скобки, и в них через запятую перечисляются значения всех элементов массива.
Многомерные массивы
переменная i ссылается на двумерный массив, который можно представить себе в виде таблицы 3х5. В Java нет двумерных, и вообще многомерных массивов, а есть массивы, базовыми типами которых являются также массивы. Например, тип int[] означает "массив чисел", а int[][] означает "массив массивов чисел".
25. Преобразование типов для массивов. Переменные типа массив и их значения. Клонирование
Преобразование типов для массивов
Хотя массивы являются объектными типами, их также будет полезно разделить по базовому типу на две группы основанные на примитивном или ссылочном типе.
Переходы между массивами и примитивными типами являются запрещенными. Преобразования между массивами и другими объектными типами возможны только для класса Object и интерфейсов Cloneable и Serializable.Массив всегда можно привести к этим трем типам, обратный же переход является сужением и должен производиться явным образом по усмотрению разработчика.
Для ссылочных же типов такого строгого правила нет. Например, если создать экземпляр массива, основанного на типе Child, то ссылку на него можно привести к типу массива, основанного на типе Parent.
Универсальное правило: массив, основанный на типе A, можно привести к массиву, основанному на типе B, если сам тип A приводится к типу B.
Применяя это правило рекурсивно, можно преобразовывать многомерные массивы.
Если же копирование элементов действительно требуется, то нужно сначала создать новый массив, а затем воспользоваться стандартной функцией System.arraycopy(), которая эффективно выполняет копирование элементов одного массива в другой.
Клонирование
Механизм клонирования, как следует из названия, позволяет порождать новые объекты на основе существующего, которые обладали бы точно таким же состоянием, что и исходный.
Реализация такого метода clone() осложняется целым рядом потенциальных проблем. Возможны два варианта:
Первый вариант: разработчик может в своем классе переопределить этот метод и реализовать его по своему усмотрению, решая перечисленные проблемы так, как того требует логика разрабатываемой системы. Упомянутые условия, которые должны быть истинными для клонированного объекта, не являются обязательными и программист может им не следовать, если это требуется для его класса.
Второй вариант: предполагает использование реализации метода clone() в самом классе Object. То, что он объявлен как native, говорит о том, что его реализация предоставляется виртуальной машиной. Естественно, перечисленные трудности легко могут быть преодолены самой JVM, ведь она хранит в памяти все свойства объектов.
При выполнении метода clone() сначала проверяется, можно ли клонировать исходный объект. Если разработчик хочет сделать объекты своего класса доступными для клонирования через Object.clone(), то он должен реализовать в своем классе интерфейс Cloneable. Если интерфейс Cloneable реализован, то порождается новый объект от того же класса, от которого был создан исходный объект. При этом копирование выполняется на уровне виртуальной машины, никакие конструкторы не вызываются. Затем значения всех полей, объявленных, унаследованных либо объявленных в родительских классах, копируются. Полученный объект возвращается в качестве клона.
Напомним, что все массивы реализуют интерфейс Cloneable и, таким образом, доступны для клонирования.
Примитивное поле было скопировано и далее существует независимо в исходном и клонированном объектах. Изменение одного не сказывается на другом.
А вот ссылочное поле было скопировано по ссылке, оба объекта ссылаются на один и тот же экземпляр класса Point. Поэтому изменения, происходящие с исходным объектом, сказываются на клонированном.
26. Управление ходом выполнения программы. Нормальное и прерванное выполнение операторов. Блоки, локальные переменные. Метки.
Управление ходом программы
Управление потоком вычислений является фундаментальной основой всего языка программирования.
Синтаксис выражений весьма схож с синтаксисом языка С, что облегчает его понимание для программистов, знакомых с этим языком, и вместе с тем имеется ряд отличий, которые будут рассмотрены позднее и на которые следует обратить внимание.
Порядок выполнения программы определяется операторами. Операторы могут содержать другие операторы или выражения.
Нормальное и прерванное выполнение операторов
Последовательность выполнения операторов может быть непрерывной, а может и прерываться (при возникновении определенных условий). Выполнение оператора может быть прервано, если в потоке вычислений будут обнаружены операторы
break continue return
Тогда управление будет передано в другое место (в соответствии с правилами обработки этих операторов, которые мы рассмотрим позже).
Нормальное выполнение оператора может быть прервано также при возникновении исключительных ситуаций, которые тоже будут рассмотрены позднее. Явное возбуждение исключительной ситуации с помощью оператора throw также прерывает нормальное выполнение оператора и передает управление выполнением программы (далее просто управление) в другое место.
Прерывание нормального исполнения всегда вызывается определенной причиной. Приведем список таких причин:
Если в операторе содержится выражение, то в случае его аварийного завершения выполнение оператора тоже будет завершено преждевременно (т.е. нормальный ход выполнения оператора будет нарушен).
В том случае, если в операторе имеется вложенный оператор и его завершение происходит ненормально, то так же ненормально завершается оператор, содержащий вложенный (в некоторых случаях это не так, что будет оговариваться особо).
Пустой оператор
Точка с запятой ( ; ) является пустым оператором. Данная конструкция вполне применима там, где не предполагается выполнение никаких действий. Преждевременное завершение пустого оператора невозможно.
Метки
Любой оператор, или блок, может иметь метку. Метку можно указывать в качестве параметра для операторов break и continue. Область видимости метки ограничивается оператором, или блоком, к которому она относится.
В случае, если имеется несколько вложенных блоков и операторов, допускается обращение из внутренних блоков к меткам, относящимся к внешним.
В этом же примере можно увидеть, что метки используют пространство имен, отличное от пространства имен переменных, методов и классов.
Традиционно использование меток не рекомендуется, особенно в объектно-ориентированных языках, поскольку серьезно усложняет понимание порядка выполнения кода, а значит, и его тестирование и отладку. Для Java этот запрет можно считать не столь строгим, поскольку самый опасный оператор goto отсутствует. В некоторых ситуациях (как в рассмотренном примере с вложенными циклами) использование меток вполне оправданно, но, конечно, их применение следует ограничивать лишь самыми необходимыми случаями.
27. Условные операторы if, -else, switch. Операторы управления циклами. Именованные блоки
Оператор if (оператор условного перехода)
if (логическое выражение) выражение или блок 1
else выражение или блок 2
Логическое выражение может быть любой языковой конструкцией, которая возвращает булевский результат.
В Java возможно использование только логических выражений.
Если логическое выражение принимает значение "истина", то выполняется выражение или блок 1, в противном случае -выражение или блок 2. Вторая часть оператора ( else ) не является обязательной и может быть опущена. Т.е. конструкция if(x == 5) System.out.println("Five") вполне допустима.
Операторы if-else могут каскадироваться. Следует помнить, что оператор else относится к ближайшему к нему оператору if.
Заключительная конструкция else относится к самому последнему условию if и будет выполнена только в том случае, если ни одно из вышеперечисленных условий не будет истинным. Если хотя бы одно из условий выполнено, то все последующие выполняться не будут.
Управление циклами
В языке Java имеется три основных конструкции управления циклами: цикл while ; цикл do ; цикл for.
Цикл while
while(логическое выражение)
повторяющееся выражение, или блок;
В данной языковой конструкции повторяющееся выражение, или блок будет исполняться до тех пор, пока логическое выражение будет иметь истинное значение. Этот многократно исполняемый блок называют телом цикла
Операторы continue и break могут изменять нормальное исполнение тела цикла. Так, если в теле цикла встретился оператор continue, то операторы, следующие за ним, будут пропущены и выполнение цикла начнется сначала. Если continue используется с меткой и метка принадлежит к данному while, то выполнение его будет аналогичным. Если метка не относится к данному while, его выполнение будет прекращено и управление будет передано на оператор, или блок, к которому относится метка.
Если встретился оператор break, то выполнение цикла будет прекращено. Если выполнение блока было прекращено по какой-то другой причине (возникла исключительная ситуация), то выполнение всего цикла будет прекращено по той же причине.
Типичный вариант использования выражения while():
int i = 0;
while( i++ < 5) {
System.out.println("Counter is " + i);}
Следует помнить, что цикл while() будет выполнен только в том случае, если на момент начала его выполнения логическое выражение будет истинным. Таким образом, при выполнении программы может иметь место ситуация, когда цикл while() не будет выполнен ни разу.
boolean b = false;
while(b) { System.out.println("Executed");}
Цикл do
do
повторяющееся выражение или блок;
while(логическое выражение)
Цикл do будет выполняться до тех пор, пока логическое выражение будет истинным. В отличие от цикла while, этот цикл будет выполнен, как минимум, один раз.
Типичная конструкция цикла do:
int counter = 0;
do { counter ++;
System.out.println("Counter is "
+ counter);
} while(counter < 5);
В остальном выполнение цикла do аналогично выполнению цикла while, включая использование операторов break и continue.
28. Исключения, причины их возникновения и участие в управлении ходом выполнения программы. Обработка исключительных ситуаций, оператор throw
Ошибки при работе программы. Исключения (Exceptions)
В Java решение возникновения ошибок - обработка исключительных ситуаций.
try{
someAction();
anotherAction();
} catch(Exception e) {
// обработка исключительной ситуации}
Причины возникновения ошибок
Существует три причины возникновения исключительных ситуаций.
Причиной таких ошибок могут быть сбои внутри самой виртуальной машины (ведь она также является программой), или вызов метода stop() у потока выполнения Thread.
Таким образом, все ошибки в Java делятся на синхронные и асинхронные. С первыми сравнительно проще работать, так как принципиально возможно найти точное место в коде, которое является причиной возникновения исключительной ситуации. Асинхронные ошибки гораздо сложнее в обнаружении и исправлении. Это могут быть ошибки создателей JVM, несовместимость с операционной системой, аппаратный сбой и многое другое.
При возникновении исключительной ситуации управление передается от кода, вызвавшего исключительную ситуацию, на ближайший блок catch (или вверх по стеку) и создается объект, унаследованный от класса Throwable, или его потомков, который содержит информацию об исключительной ситуации и используется при ее обработке. Собственно, в блоке catch указывается именно класс обрабатываемой ситуации.
Иерархия, по которой передается информация об исключительной ситуации, зависит от того, где эта исключительная ситуация возникла. Если это
Допускается создание собственных классов исключительных ситуаций. Осуществляется это с помощью механизма наследования, то есть класс пользовательской исключительной ситуации должен быть унаследован от класса Throwable, или его потомков.
Обработка исключительных ситуаций
Конструкция try-catch
try { ...
} catch(SomeExceptionClass e) { ...
} catch(AnotherExceptionClass e) { ...}
Сначала выполняется код, заключенный в фигурные скобки оператора try. Если во время его выполнения не происходит никаких нештатных ситуаций, то далее управление передается за закрывающую фигурную скобку последнего оператора catch, ассоциированного с данным оператором try.
Если в пределах try возникает исключительная ситуация, то далее выполнение кода производится по одному из перечисленных ниже сценариев.
29. Проверяемые и непроверяемые исключения. Иерархия классов исключений, пользовательские исключения. Переопределение методов и исключения.
Проверяемые и непроверяемые исключения
Все исключительные ситуации можно разделить на две категории: проверяемые (checked) и непроверяемые (unchecked).
Все исключения, порождаемые от Throwable, можно разбить на три группы. Они определяются тремя базовыми типами: наследниками Throwable - классами Error и Exception, а также наследником Exception - RuntimeException.
Ошибки, порожденные от Exception (и не являющиеся наследниками RuntimeException ), являются проверяемыми. Т.е. во время компиляции проверяется, предусмотрена ли обработка возможных исключительных ситуаций. Как правило, это ошибки, связанные с окружением программы (сетевым, файловым вводом-выводом и др.), которые могут возникнуть вне зависимости от того, корректно написан код или нет. Например, открытие сетевого соединения или файла может привести к возникновению ошибки и компилятор требует от программиста предусмотреть некие действия для обработки возможных проблем. Таким образом повышается надежность программы, ее устойчивость при возможных сбоях.
Исключения, порожденные от RuntimeException, являются непроверяемыми и компилятор не требует обязательной их обработки.
30.Пакет AWT. Класс Component и его свойства. Работа с графикой и алгоритмы отрисовки. Определение ползовательских компонент.
Содержит все классы для того, чтобы создать пользовательские интерфейсы и для рисования графики и изображений.
Эта лекция начинает рассмотрение базовых библиотек Java, которые являются неотъемлемой частью языка и входят в его спецификацию, а именно описывается пакет java.awt, предоставляющий технологию AWT для создания графического (оконного) интерфейса пользователя GUI. Ни одна современная программа, предназначенная для пользователя, не обходится без удобного, понятного, в идеале красивого пользовательского интерфейса. С самой первой версии в Java существует специальная технология для создания GUI. Она называется AWT, Abstract Window Toolkit. Именно о ней пойдет речь в этой лекции. Пакет java.awt претерпел, пожалуй, больше всего изменений с развитием версий Java. Мы рассмотрим дерево компонентов, доступных программисту, специальную модель сообщений, позволяющую гибко обрабатывать пользовательские действия, и другие особенности AWT работа с цветами, шрифтами, отрисовка графических примитивов, менеджеры компоновки и т.д. Хотя технология AWT включает в себя гораздо больше, чем можно изложить в рамках одной лекции, здесь собраны все необходимые сведения для создания полноценного оконного интерфейса.
Поскольку Java-приложения предназначены для работы на разнообразных платформах, реализация графического пользовательского интерфейса (GUI) должна быть либо одинаковой для любой платформы, либо, напротив, программа должна иметь вид, типичный для данной операционной системы. В силу ряда причин, для основной библиотеки по созданию GUI был выбран второй подход. Во-первых, это лишний раз показывало гибкость Java действительно, пользователи разных платформ могли работать с один и тем же Java-приложением, не меняя своих привычек. Во-вторых, такая реализация обеспечивала большую производительность, поскольку была основана на возможностях операционной системы. В частности, это означало и более компактный, простой, а значит, и более надежный код.
Библиотеку назвали AWT Abstract Window Toolkit. Слово abstract в названии указывает, что все стандартные компоненты не являются самостоятельными, а работают в связке с соответствующими элементами операционной системы.
[ClassInterfaceAttribute(ClassInterfaceType.AutoDispatch)]
[ComVisibleAttribute(true)]
public class Component : MarshalByRefObject, IComponent,
IDisposable
Свойства:
CanRaiseEvents - Возвращает значение, показывающее, может ли компонент вызывать событие.
Container - Возвращает контейнер IContainer, содержащий компонент Component.
DesignMode - Возвращает значение, указывающее, находится ли данный компонентComponent в режиме конструктора в настоящее время.
Events - Возвращает список обработчиков событий, которые прикреплены к этому объекту Component.
Site - Получает или задает экземпляр ISiteдля компонента Component.
Вывод графики осуществляется с помощью объектов типа java.awt.Graphics. Для них определён ряд методов, описанных в следующей далее таблице.
Подразумевается, что w- ширина области или фигуры, h- высота; x,y- координаты левого верхнего угла области. Для фигуры x,y- координаты левого верхнего угла прямоугольника, в который вписана фигура.
31.Контейнеры пользовательских компонент. Прорисовка контейнеров, методы paint/repaint/update
Container
Контейнер описывается классом Container, который является наследником Component, а значит, обладает всеми свойствами графического компонента. Однако основная его задача группировать другие компоненты. Для этого в нем объявлен целый ряд методов. Для добавления служит метод add, для удаления remove и removeAll (последний удаляет все компоненты).
Добавляемые компоненты хранятся в упорядоченном списке, поэтому для удаления можно указать либо ссылку на компонент, который и будет удален, либо его порядковый номер в контейнере. Также определены методы для получения компонент, присутствующих в контейнере, все они довольно очевидны, поэтому перечислим их с краткими пояснениями:1)getComponent(int n) возвращает компонент с указанным порядковым номером;2)getComponents() возвращает все компоненты в виде массива;3)getComponentCount() возвращает количество компонент;4)getComponentAt(int x, int y) или ( Point p ) возвращает компонент, который включает в себя указанную точку;5)findComponentAt(int x, int y) или ( Point p ) возвращает видимый компонент, включающий в себя указанную точку.
Мы уже знаем, что положение компонента ( location ) задается координатами левого верхнего угла. Важно, что эти значения отсчитываются от левого верхнего угла контейнера, который таким образом является центром системы координат для каждого находящегося в нем компонента. Если важно расположение компонента на экране безотносительно его контейнера, можно воспользоваться методом getLocationOnScreen.Благодаря наследованию контейнер также имеет свойство size. Этот размер задается независимо от размера и положения вложенных компонент. Таким образом, компоненты могут располагаться частично или полностью за пределами своего контейнера.
Раз контейнер наследуется от Component, он сам является компонентом, а значит, может быть добавлен в другой, вышестоящий контейнер. В то же время компонент может находиться лишь в одном контейнере. Это означает, что все элементы сложного пользовательского интерфейса объединяются в иерархическое дерево. Такая организация не только облегчает операции над ними, но и задает основные свойства всей работы AWT. Одним из них является принцип отрисовки компонентов.
Paint Для этой задачи предназначен метод paint. Этот метод вызывается каждый раз, когда необходимо отобразить компонент на экране. У него есть один аргумент, тип которого абстрактный класс Graphics. В этом классе определено множество методов для отрисовки простейших графических элементов линий, прямоугольников и многоугольников, окружностей и овалов, текста, картинок и т.д.Наследники класса Component переопределяют метод paint и, пользуясь методами Graphics, задают алгоритм прорисовки своего внешнего вида Методы repaint и update
Для программной инициализации перерисовки компонента служит метод repaint. Метод repaint можно вызывать без аргументов. В этом случае компонент будет перерисован максимально быстро. Можно указать аргумент типа long количество миллисекунд. Система инициализирует перерисовку спустя указанное время. Можно указать четыре числа типа int ( x, y, width, height ), задавая прямоугольную область компонента, которая нуждается в перерисовке.
Прорисовка контейнера
Для его корректного отображения необходимо выполнить два действия. Во-первых, нарисовать сам контейнер,во-вторых, инициировать отрисовку всех компонентов, вложенных в него.
Первый шаг ничем не отличается от прорисовки обычного компонента. Как правило, контейнер не содержит никаких особых элементов отображения, ведь основную его площадь занимают вложенные компоненты. Поэтому перейдем ко второму шагу. Если контейнер не пустой, значит, в нем есть одна или несколько компонент. Они будут отрисованы последовательно в том порядке, в каком были добавлены. Однако недостаточно просто в цикле вызвать метод paint для каждого компонента.
32.Наследники класса Component и их использование. Наследники контейнеров.
Наследники класса Component
Класс Canvas.Класс Canvas является простейшим наследником Component. Он не добавляет никакой новой функциональности, но именно его нужно использовать в качестве суперкласса для создания пользовательского компонента с некоторым нестандартным внешним видом.
Класс Label.Как понятно из названия, этот компонент отображает надпись. Соответственно, и его основной конструктор принимает один аргумент типа String текст надписи. С помощью стандартных свойств класса Component шрифт, цвет, фоновый цвет можно менять вид надписи. Текст можно сменить и после создания Label с помощью метода setText.Обратите внимание, что при этом компонент сам обновляет свой вид на экране. Такой особенностью обладают все стандартные компоненты AWT.
Класс Button.Этот компонент позволяет добавить в интерфейс стандартные кнопки. Основной конструктор принимает в качестве аргументаString надпись на кнопке. Как обрабатывать нажатие на кнопку и другие пользовательские события, рассматривается ниже.
Классы Checkbox и CheckboxGroupКомпонент Checkbox имеет два способа применения.Когда он используется сам по себе, он представляет checkbox элемент, который может быть выделен или нет (например, нужна доставка для оформляемой покупки или нет). В этом случае в конструктор передается лишь текст подпись к checkbox.
Классы Choice и List.Компонент Choice служит для выбора пользователем одного из нескольких возможных вариантов (выпадающий список). Рассмотрим пример:
Обратите внимание, что для компонента Choice всегда есть выбранный элемент. Компонент List, подобно Choice, предоставляет пользователю возможность выбирать варианты из списка предложенных. Отличие заключается в том, что List отображает сразу несколько вариантов
Рисунок иллюстрирует еще одно свойство List возможность выбрать сразу несколько из предложенных вариантов. Для этого надо либо в конструкторе вторым параметром передать булевское значение true ( false соответствует выбору только одного элемента), либо воспользоваться методом setMultipleMode.
Классы TextComponent, TextField, TextArea
Класс TextComponent является наследником Component и базовым классом для компонент, работающих с текстом,TextField и TextArea.
TextField позволяет вводить и редактировать одну строку текста. Различные методы позволяют управлять содержимым этого поля ввода:
В коде вторая строка устанавливает значение текста в поле ввода (метод getText позволяет получить текущее значение).
TextField обладает еще одним свойством. Все хорошо знакомы с полем ввода для пароля вводимые символы не отображаются, вместо них появляется один и тот же символ. Для TextField его можно установить с помощью методаsetEchoChar (например, setEchoChar('*') ). TextArea позволяет вводить и просматривать многострочный текст.
Класс Scrollbar. Класс Scrollbar позволяет работать с полосами прокрутки, которые используются для перемещения внутренней области от начальной до конечной позиции. Полоса может быть расположена горизонтально или вертикально. Стрелки на каждом из ее концов служат для перемещения "на один шаг" в соответствующем направлении. "Взявшись" курсором мыши за бегунок, можно переместить его в любую позицию. С помощью кликов мыши по полосе прокрутки, но вне положения бегунка, можно делать перемещение "на страницу" вверх или вниз.
Наследники Container.
Класс Panel. Класс Panel является суперклассом для новых контейнеров с особой работой с вложенными компонентами. Впрочем, поскольку Panel класс не абстрактный, его можно использовать для иерархической организации сложного пользовательского интерфейса, группируя компоненты в такие простейшие контейнеры.
33.Обработка пользовательских событий в AWT. Слушатели и их программная реализация. Обработка событий с помощью внутренних классов.
Модель обработки событий построена на основе стандартного шаблона проектирования ООП Observer/Observable. В качестве наблюдаемого объекта выступает тот или иной компонент AWT. Для него можно задать один или несколько классов-наблюдателей. В AWT они называются слушателями (listener) и описываются специальными интерфейсами, название которых оканчивается на слово Listener. Когда с наблюдаемым объектом что-то происходит, создается объект "событие" (event), который "посылается" всем слушателям. Так слушатель узнает, например, о действии пользователя и может на него отреагировать. Каждое событие является подклассом класса java.util.EventObject. События пакета AWT, которые и рассматриваются в данной лекции, являются подклассами java.awt.AWTEvent. Для удобства классы различных событий и интерфейсы слушателей помещены в отдельный пакет java.awt.event.
События AWT
Рассмотрим обзорно все события AWT и соответствующих им слушателей, определенных в Java начиная с версии 1.1.
MouseMotionListener и MouseEvent. Это событие отвечает за перемещение курсора мыши. Соответствующий слушатель имеет два метода mouseMoved для обычного перемещения и mouseDragged для перемещения с нажатой кнопкой мыши. Обратите внимание, что этот слушатель работает не с событием MouseMotionEvent (такого класса нет), а с MouseEvent, как иMouseListener.
MouseListener и MouseEvent. Этот слушатель имеет методы mouseEntered и mouseExited. Первый вызывается, когда курсор мыши появляется над компонентом, а второй когда выходит из его границ.Для обработки нажатий кнопки мыши служат три метода: mousePressed, mouseReleased и mouseClicked.
ComponentListener и ComponentEven. Это событие отражает изменение основных параметров компонента положение, размер, свойство visible.
ContainerListener и ContainerEvent. Это событие позволяет отслеживать изменение списка содержащихся в этом контейнере компонент.
С развитием Java в AWT появляются и другие события, например, позволяющие поддерживать колесико мыши.
Обработка событий с помощью внутренних классов
Еще в лекции, посвященной объявлению классов, было указано, что в теле класса можно объявлять внутренние классы. До сих пор такая возможность не была востребована в наших примерах, однако обработка событий AWT как раз удобный случай рассмотреть такие классы на примере анонимных классов.
Предположим, в приложение добавляется кнопка, которой следует добавить слушателя. Зачастую бывает удобно описать логику действий в отдельном методе того же класса. Если вводить слушателя, как делалось раньше в отдельном классе, то это сразу порождает ряд неудобств: появляется новый, малосодержательный класс, которому к тому же необходимо передать ссылку на исходный класс и так далее.
Рассмотрим подробно, что происходит в этом примере. Сначала создается кнопка, у которой затем вызывается методaddActionListener. Обратим внимание на аргумент этого метода. Может сложится впечатление, что производится попытка создать экземпляр интерфейса ( new ActionListener() ), однако это невозможно. Дело меняет фигурная скобка, которая указывает, что порождается экземпляр нового класса, объявление которого последует за этой скобкой. Класс наследуется отObject и реализует интерфейс ActionListener. Ему необходимо реализовать метод actionPerformed, что и делается. Обратите внимание на еще одну важную деталь в этом методе вызывается processButton. Это метод, который мы планировали разместить во внешнем классе. Таким образом, внутренний класс может напрямую обращаться к методам внешнего класса.
34.Многопоточная архитектура, процедура квантования и принципы передачи управления между потоками. Базовые классы для работы с потоками.
Многопоточная архитектура
Многопоточные программы работают медленнее из-за времени необходимого для переключения между задачами.Первый тип приложений, который выигрывает от поддержки многопоточности, предназначен для задач, где действительно требуется выполнять несколько действий одновременно. Другой пример активные игры, или подобные приложения. Следующее преимущество проистекает из того, что компьютер состоит не только из одного или нескольких процессоров. Вычислительное устройство лишь один из ресурсов, необходимых для выполнения задач. Всегда есть оперативная память, дисковая подсистема, сетевые подключения, периферия и т.д. Предположим, пользователю требуется распечатать большой документ и скачать большой файл из сети.Значит, если выполнять задачи одновременно, то замедление от организации квантования времени будет незначительным, процессор легко справится с обслуживанием обеих задач. Если же задачи в основном загружают процессор, то их одновременное исполнение займет в лучшем случае столько же времени, что и последовательное, а то и больше.Третье преимущество появляется из-за возможности более гибко управлять выполнением задач. Предположим, пользователь системы, не поддерживающей многопоточность, решил скачать большой файл из сети, или произвести сложное вычисление, что занимает, скажем, два часа. Запустив задачу на выполнение, он может внезапно обнаружить, что ему нужен не этот, а какой-нибудь другой файл (или вычисление с другими начальными параметрами). Однако если приложение занимается только работой с сетью (вычислениями) и не реагирует на действия пользователя (не обрабатываются данные с устройств ввода, таких как клавиатура или мышь), то он не сможет быстро исправить ошибку. Получается, что процессор выполняет большее количество вычислений, но при этом приносит гораздо меньше пользы.Процедура квантования времени поддерживает приоритеты (priority) задач. В Java приоритет представляется целым числом. Чем больше число, тем выше приоритет. Есть общее правило поток с более высоким приоритетом будет получать большее количество квантов времени на исполнение и таким образом сможет быстрее выполнять свои действия и реагировать на поступающие данные.Раньше, когда рассматривались однопоточные приложения, завершение вычислений однозначно приводило к завершению выполнения программы. Теперь же приложение должно работать до тех пор, пока есть хоть один действующий поток исполнения.
Базовые классы для работы с потоками
Класс Thread. Поток выполнения в Java представляется экземпляром класса Thread. Для того, чтобы написать свой поток исполнения, необходимо наследоваться от этого класса и переопределить метод run(). Метод run() содержит действия, которые должны выполняться в новом потоке исполнения. Чтобы запустить его, необходимо создать экземпляр класса-наследника и вызвать унаследованный метод start(), который сообщает виртуальной машине, что требуется запустить новый поток исполнения и начать выполнять в нем метод run().Когда метод run() завершен (в частности, встретилось выражение return ), поток выполнения останавливается. Однако ничто не препятствует записи бесконечного цикла в этом методе. В результате поток не прервет своего исполнения и будет остановлен только при завершении работы всего приложения.
Интерфейс Runnable. Описанный подход имеет один недостаток. Поскольку в Java множественное наследование отсутствует, требование наследоваться от Thread может привести к конфликту. Более простой способ создать свой поток исполнения. Достаточно реализовать интерфейс Runnable, в котором объявлен только один метод уже знакомый void run(). Если раньше объект, представляющий сам поток выполнения, и объект с методом run(), реализующим необходимую функциональность, были объединены в одном экземпляре класса MyThread, то теперь они разделены. Какой из двух подходов удобней, решается в каждом конкретном случае.Подчеркнем, что Runnable не является полной заменой классу Thread, поскольку создание и запуск самого потока исполнения возможно только через метод Thread.start().
35.Приоритеты потоков и работа с ними в Java. Потоки-демоны
Работа с приоритетами
Рассмотрим, как в Java можно назначать потокам приоритеты. Для этого в классе Thread существуют методы getPriority() иsetPriority(), а также объявлены три константы:MIN_PRIORITY;MAX_PRIORITY; NORM_PRIORITY. Из названия понятно, что их значения описывают минимальное, максимальное и нормальное (по умолчанию) значения приоритета.
Формула вычисления приоритетов позволяет равномерно распределить все допустимые значения для всех запускаемых потоков. На самом деле, константа минимального приоритета имеет значение 1, максимального 10, нормального 5. Так что в простых программах можно явно пользоваться этими величинами и указывать в качестве, например, пониженного приоритета значение3.
Потоки, как и раньше, стартуют последовательно. Но затем мы видим, что чем выше приоритет, тем быстрее отрабатывает поток. Тем не менее, весьма показательно, что поток с минимальным приоритетом ( Thread 0 ) все же получил возможность выполнить одно действие раньше, чем отработал поток с более высоким приоритетом ( Thread 1 ). Это говорит о том, что приоритеты не делают систему однопоточной, выполняющей единовременно лишь один поток с наивысшим приоритетом. Напротив, приоритеты позволяют одновременно работать над несколькими задачами с учетом их важности.
Демон-потоки
Демон -потоки позволяют описывать фоновые процессы, которые нужны только для обслуживания основных потоков выполнения и не могут существовать без них. Для работы с этим свойством существуют методы setDaemon() и isDaemon().
За процессом наблюдает демон -поток DaemonDemo. Этот поток регулярно получает список всех существующих потоков ThreadTest и распечатывает их имена для удобства наблюдения.
Несмотря на то, что демон -поток никогда не выходит из метода run(), виртуальная машина прекращает работу, как только все не- демон -потоки завершаются.
В примере использовалось несколько дополнительных классов и методов, которые еще не были рассмотрены:
Все потоки находятся в группах, представляемых экземплярами класса ThreadGroup. Группа указывается при создании потока. Если группа не была указана, то поток помещается в ту же группу, где находится поток, породивший его.
Методы activeCount() и enumerate() возвращают количество и полный список, соответственно, всех потоков в группе.
Этот статический метод класса Thread приостанавливает выполнение текущего потока на указанное количество миллисекунд. Обратите внимание, что метод требует обработки исключения InterruptedException. Он связан с возможностью активизировать метод, который приостановил свою работу. Например, если поток занят выполнением методаsleep(), то есть бездействует на протяжении указанного периода времени, его можно вывести из этого состояния, вызвав метод interrupt() из другого потока выполнения. В результате метод sleep() прервется исключениемInterruptedException.
Кроме метода sleep(), существует еще один статический метод yield() без параметров. Когда поток вызывает его, он временно приостанавливает свою работу и позволяет отработать другим потокам. Один из методов обязательно должен применяться внутри бесконечных циклов ожидания, иначе есть риск, что такой ничего не делающий поток затормозит работу остальных потоков.
36. Синхронизация потоков, блокировки. Примеры синхронизации. Deadlock, примеры дедлоков и способов их избежания.
Синхронизация
Блокировки
В основном хранилище для каждого объекта поддерживается блокировка ( lock ), над которой можно произвести два действия установить ( lock ) и снять ( unlock ). Только один поток в один момент времени может установить блокировку на некоторый объект. Если до того, как этот поток выполнит операцию unlock, другой поток попытается установить блокировку, его выполнение будет приостановлено до тех пор, пока первый поток не отпустит ее. Операции lock и unlock накладывают жесткое ограничение на работу с переменными в рабочей памяти потока. После успешно выполненного lock рабочая память очищается и все переменные необходимо заново считывать из основного хранилища. Аналогично, перед операцией unlock необходимо все переменные сохранить в основном хранилище.
Важно подчеркнуть, что единственное действие, которое становится невозможным, установка этой же блокировки другим потоком, до тех пор, пока первый поток не выполнит unlock. В Java-программе для того, чтобы воспользоваться механизмом блокировок, существует ключевое слово synchronized. Оно может быть применено в двух вариантах для объявления synchronized -блока и как модификатор метода.В обоих случаях действие его примерно одинаковое.
Synchronized-блок записывается следующим образом: synchronized (ref) {..}
Прежде, чем начать выполнять действия, описанные в этом блоке, поток обязан установить блокировку на объект, на который ссылается переменная ref (поэтому она не может быть null ). Если другой поток уже установил блокировку на этот объект, то выполнение первого потока приостанавливается до тех пор, пока не удастся выполнить операцию lock.После этого блок выполняется. При завершении исполнения (как успешном, так и в случае ошибок) производится операция unlock, чтобы освободить объект для других потоков.
Synchronized -методы работают аналогичным образом. Прежде, чем начать выполнять их, поток пытается заблокировать объект, у которого вызывается метод. После выполнения блокировка снимается. В предыдущем примере аналогичной упорядоченности можно было добиться, если использовать не synchronized -блок, а объявить метод process()синхронизированным. Также допустимы методы static synchronized. При их вызове блокировка устанавливается на объект класса Class, отвечающего за тип, у которого вызывается этот метод.
При работе с блокировками всегда надо помнить о возможности появления deadlock взаимных блокировок, которые приводят к зависанию программы. Если один поток заблокировал один ресурс и пытается заблокировать второй, а другой поток заблокировал второй и пытается заблокировать первый, то такие потоки уже никогда не выйдут из состояния ожидания.
В Java нет никаких средств распознавания или предотвращения ситуаций deadlock. Также нет способа перед вызовом синхронизированного метода узнать, заблокирован ли уже объект другим потоком. Программист сам должен строить работу программы таким образом, чтобы неразрешимые блокировки не возникали. Например, в рассмотренном примере достаточно было организовать блокировки объектов в одном порядке (всегда сначала первый, затем второй) и программа всегда выполнялась бы успешно.
Опасность возникновения взаимных блокировок заставляет с особенным вниманием относиться к работе с потоками. Например, важно помнить, что если у объекта потока был вызван метод sleep(..), то такой поток будет бездействовать определенное время, но при этом все заблокированные им объекты будут оставаться недоступными для блокировок со стороны других потоков, а это потенциальный deadlock. Такие ситуации крайне сложно выявить путем тестирования и отладки, поэтому вопросам синхронизации надо уделять много времени на этапе проектирования.
37.Основные составляющие пакета java.lang, их предназначение и методы.Класс Object и его методы equals, hashCode, toString, их применение, реализации по умолчанию и правила переопределения
состав пакета java.lang
В состав пакета java.lang входят классы, составляющие основу для всех других, и поэтому он является наиболее важным из всех, входящих в Java API. Поскольку без него не может обойтись ни один класс, каждый модуль компиляции содержит неявное импортирование этого пакета ( import java.lang.*; ).
Интерфейсы:Cloneable должен быть реализован объектами, которые планируется клонировать с помощью средств JVM;Comparable позволяет упорядочивать (сортировать, сравнивать) объекты каждого класса, реализующего этот интерфейс.
Object
Класс Object является базовым для всех остальных классов. Он определяет методы, которые поддерживаются любым классом вJava.
Метод public final native Class getClass() возвращает объект типа Class, соответствующий классу объекта. Метод public boolean equals(Object obj) определяет, являются ли объекты одинаковыми. Если оператор == проверяет равенство по ссылке, то метод equals() равенство по значению. Поскольку класс Object не содержит полей, реализация в нем этого метода такова, что значение true будет возвращено только в случае равенства по ссылке, то есть: public boolean equals(Object obj) {return (this == obj);}
Метод public int hashCode() возвращает хеш-код ( hash code ) для объекта. Хеш-код это целое число, которое сопоставляется с данным объектом. Оно позволяет организовать хранение набора объектов с возможностью быстрой выборки (стандартная реализация такого механизма присутствует в Java и будет описана в следующей лекции).
Для этого метода также принят ряд соглашений, которым стоит следовать при переопределении:
В классе Object этот метод реализован на уровне JVM. Сама виртуальная машина генерирует хеш-код, основываясь на расположении объекта в памяти. Это позволяет для различных объектов (неравенство по ссылке) получать различные хеш-коды.
В силу первого соглашения при переопределении метода equals() необходимо переопределить также метод hashCode(). При этом нужно стремиться, во-первых, к тому, чтобы метод возвращал значение как можно быстрее, иначе основная цель быстрая выборка не будет достигнута. Во-вторых, желательно для различных объектов, то есть когда метод equals(Object)возвращает false, генерировать различные хеш-коды. В этом случае хеш-таблицы будут работать особенно эффективно. Однако, понятно, что это не всегда возможно. Диапазон значений int 232, а количество различных строк, или двумерных точек, с координатами типа int заведомо больше.
38.Класс Class. Механизм reflection и его применение.
В запущенной программе Java каждому классу соответствует объект типа Class. Этот объект содержит информацию, необходимую для описания класса поля, методы и т.д.
Класс Class не имеет открытого конструктора объекты этого класса создаются автоматически Java-машиной по мере загрузки описания классов из class -файлов. Получить экземпляр Class для конкретного класса можно с помощью методаforName():
public static Class forName(String name, boolean initialize, ClassLoader loader) возвращает объект Class, соответствующий классу, или интерфейсу, с названием, указанным в name необходимо указывать полное название класса или интерфейса, используя переданный загрузчик классов. Если в качестве загрузчика классов loader передано значение null, будет взят ClassLoader, который применялся для загрузки вызывающего класса. При этом класс будет инициализирован, только если значение initialize равно true и класс не был инициализирован ранее.
Зачастую проще и удобнее воспользоваться методом forName(), передав только название класса: public static Class forName(String className), при этом будет использоваться загрузчик вызывающего класса и класс будет инициализирован (если до этого не был).
public Object newInstance() создает и возвращает объект класса, который представляется данным экземпляром Class. Создание будет происходить с использованием конструктора без параметров. Если такового в классе нет, будет брошеноисключение InstantiationException. Это же исключение будет брошено, если объект Class соответствует абстрактному классу, интерфейсу, или какая-то другая причина помешала созданию нового объекта.
Каждому методу, полю, конструктору класса также соответствуют объекты, список которых можно получить вызовом соответствующих методов объекта Class: getMethods(), getFields(), getConstructors(), getDeclaredMethods() и т.д. В результате будут получены объекты, которые отвечают за поля, методы, конструкторы объекта. Их можно использовать для формирования динамических вызовов Java этот механизм называется reflection . Необходимые классы содержатся в пакетеjava.lang.reflection.
39.Классы-обёртки. Особенности класса String. Конкатенация строк, класс StringBuffer
классы-обертки. Для каждого примитивного типа Java существует свой класс-обертка . Такой класс является неизменяемым (если необходим объект, хранящий другое значение, его нужно создать заново), к тому же имеет атрибут final от него нельзя наследовать класс. Все классы-обертки (кроме Void ) реализуют интерфейсSerializable, поэтому объекты любого (кроме Void ) класса-обертки могут быть сериализованы. Все классы-оберткисодержат статическое поле TYPE, ссылающееся на объект Class, соответствующий примитивному оборачиваемому типу. Также классы-обертки содержат статические методы для обеспечения удобного манипулирования соответствующими примитивными типами, например, преобразование к строковому виду.
При этом классы-обертки числовых типов Byte, Short, Integer, Long, Float, Double наследуются от одного класса Number. В нем объявлены методы, возвращающие числовое значение во всех числовых форматах Java ( byte, short, int,long, float и double ). Все классы-обертки реализуют интерфейс Comparable. Все классы-обертки числовых типов имеют метод equals(Object), сравнивающий примитивные значения объектов.
String Этот класс используется в Java для представления строк. Он обладает свойством неизменяемости. После того как создан экземпляр этого класса, его содержимое уже не может быть модифицировано.
Существует много способов создать объект String. Наиболее простой, если содержимое строки известно на этапе компиляции, написать текст в кавычках:String abc = "abc";
Можно использовать и различные варианты конструктора. Наиболее простой из них конструктор, получающий на входе строковый литерал.
String s = new String("immutable");
На первый взгляд, эти варианты создания строк отличаются только синтаксисом. На самом же деле различие есть, хотя в большинстве случаев оно несущественно.
В Java для строк определен оператор +. При использовании этого оператора производится конкатенация строк. В классеString также определен метод:
public String concat(String s);Он возвращает новый объект-строку, дополненный справа строкой s. Как уже отмечалось, строка состоит из двухбайтных Unicode-символов. Однако во многих случаях требуется работать со строкой как с набором байт (ввод/вывод, работа с базой данных и т.д.). Преобразование строки в последовательность байтов производится следующими методами:1)byte[] getBytes() возвращает последовательность байтов в кодировке, принятой по умолчанию (как правило, зависит от настроек операционной системы); 2)byte[] getBytes(String encoding) возвращает последовательность байтов в указанной кодировке encoding. Для выполнения обратной операции (преобразования байтов в строку) необходимо сконструировать новый объект-строку с помощью следующих методов:1)String(byte[] bytes) создает строку из последовательности байтов в кодировке, принятой по умолчанию;2)String(byte[] bytes, String enc) создает строку из последовательности байтов в указанной кодировке.
StringBuffer Этот класс используется для создания и модификации строковых выражений, которые после можно превратить в String. Он реализован на основе массива char[], что позволяет, в отличие от String, модифицировать его значение после создания объекта. Рассмотрим наиболее часто используемые конструкторы класса StringBuffer:
40.Классы Classloader, System, Runtime, Process, их методы, примеры их применения.
Системные классы ClassLoader System
Следующие методы класса System позволяют работать с некоторыми параметрами системы:
Runtime
Process абстрактный класс, определяющий, какие методы должны присутствовать в реализациях для конкретных платформ. Методы класса Process:
Класс Date. Класс Date класс для работы с датой и временем, сейчас считается нежелательным к использованию, т.к. всю функциональность имеет класс Calendar.
Существует несколько конструкторов класса Date, однако рекомендовано к использованию два:
Date() - создает экземпляр, соответствующий текущему моменту)
Date(long date) date - количество миллисекунд, прошедших с 1 января 1970 г., 00:00:00 по Гринвичу.
Классы Calendar и GregorianCalendar
Calendar является абстрактным классом, который имеет все нужные функции для работы с датой\временем.
Для различных платформ реализуются конкретные подклассы календаря.
GregorianCalendar реализация грегорианского клендаря, экземпляр создаётся стат. методом getInstance(), который возвращает экземпляр класса GregorianCalendar. Подклассы класса Calendar должны интерпретировать объект Date по-разному. В будущем предполагается реализовать также лунный календарь, используемый в некоторых странах.
Значения по умолчанию - YEAR = 1970, MONTH = JANUARY, DATE = 1
Методы:
set(int field,int value) - метод производит установку какого-либо поля даты, Пересчёт даты не производится.
Пересчет даты будет осуществлен только после вызова методов get(),getTime() или getTimeInMillis()
add(int field,int delta) - Добавляет некоторое смещение к существующей величине поля. В принципе, то же самое можно сделать с помощью set(f, get(f) + delta).
roll(int field,int delta) - Добавляет некоторое смещение к существующей величине поля и не производит изменения старших полей.
Класс TimeZone предназначен для совместного использования с классами Calendar и DateFormat. Класс абстрактный, поэтому от него порождать объекты нельзя. Вместо этого определен статический метод getDefault(), который возвращает экземпляр наследника TimeZone с настройками, взятыми из операционной системы, под управлением которой работает JVM. Для того, чтобы указать произвольные параметры, можно воспользоваться статическим методом getTimeZone(String ID), в качестве параметра которому передается наименование конкретного временного пояса, для которого необходимо получить объект TimeZone
Класс SimpleTimeZone, как потомок TimeZone, реализует его абстрактные методы и предназначен для применения в настройках, использующих Григорианский календарь
Локализация
Класс Locale предназначен для отображения определенного региона.
Имеет два конструктора:
Locale(String language, String country)
Locale(String language, String country, String variant)
Список поддерживаемых стран и языков можно получить и с помощью вызова статических методовLocale.getISOLanguages() Locale.getISOCountries(), соответственно
Variant - информация о платформе. Дополнительные параметры разделяются подчёркиванием.
Collection - корень всей иерархии классов-коллекций. Он определяет базовую функциональность любой коллекции - набор методов, которые позволяют добавлять, удалять, выбирать элементы коллекции. Классы, которые реализуют интерфейс Collection, могут содержать дубликаты и пустые ( null ) значения.
AbstractCollection, как абстрактный класс, служит основой для создания конкретных классов коллекций и содержит реализацию некоторых методов, определенных в интерфейсе Collection.
Set реализ. его классы недопускают наличия дубликатов. В коллекции этого типа разрешено наличие только одной ссылки типа null. Интерфейс Set расширяет интерфейс Collection, таким образом, любой класс, имплементирующий Set, реализует все методы, определенные в Collection. Любой объект, добавляемый в Set, должен реализовать метод equals, чтобы его можно было сравнить с другими.
AbstractSet, являясь абстрактным классом, представляет собой основу для реализации различных вариантов интерфейса Set.
List - реализ. его классы содержат упорядоченную последовательность объектов. List расширяет интерфейс Collection, и любой класс, имплементирующий List, реализует все методы, определенные в Collection, и в то же время вводятся новые методы, которые позволяют добавлять и удалять элементы из списка. List также обеспечивает ListIterator, который позволяет перемещаться как вперед, так и назад по элементам списка.
Iterator исп. для перебора элементов коллекции. Все классы, которые реализуют интерфейс Collection, должны реализовать метод iterator, который возвращает объект, реализующий интерфейс Iterator.
Конкретные классы коллекций
java.util.ArrayList - этот класс расширяет AbstractList и весьма похож на класс Vector. Он также динамически расширяется, как Vector, однако его методы не являются синхронизированными, вследствие чего операции с ним выполняются быстрее.
java.util.LinkedList - представляет собой реализацию интерфейса List. Он реализует все методы интерфейса List, помимо этого добавляются еще новые методы, которые позволяют добавлять, удалять и получать элементы в конце и начале списка. LinkedList является двухсвязным списком
java.util.Hashtable - расширяет абстрактный класс Dictionary. В JDK 1.2 класс Hashtable также реализует интерфейсMap. Hashtable предназначен для хранения объектов в виде пар ключ/значение.
java.util.HashMap - этот класс расширяет AbstractMap и весьма похож на класс Hashtable. HashMap предназначен для хранения пар объектов ключ/значение.
java.util.TreeMap - расширяет класс AbstractMap и реализует интерфейс SortedMap. TreeMap содержит ключи в порядке возрастания. Используется либо натуральное сравнение ключей, либо должен быть реализован интерфейс Comparable.
Класс Collections содержит несколько вспомогательных методов для работы с классами, обеспечивающими различные интерфейсы коллекций. Например, для сортировки элементов списков, для поиска элементов в упорядоченных коллекциях и т.д. Но, пожалуй, наиболее важным свойством этого класса является возможность получения синхронизированных вариантов классов-коллекций. Например, для получения синхронизированного варианта Map можно использовать следующий подход:
HashMap hm = new HashMap();
Map syncMap = Collections.synchronizedMap(hm);
Интерфейс Comparator
В коллекциях многие методы сортировки или сравнения требуют передачи в качестве одного из параметров объекта, который реализует интерфейс Comparator. Этот интерфейс определяет единственный метод compare(Object obj1,Object obj2), который на основании определенного пользователем алгоритма сравнивает объекты, переданные в качестве параметров. Метод compare должен вернуть:
-1 если obj1 < obj2
0 если obj1 = obj2
1 если obj1 > obj2
Алгоритм хэширования используется для того, чтобы увеличить скорость поиска. Каждый объект в Java унаследован от Object. hash определено как целое число, которое уникально идентифицирует экземпляр класса Object и, соответственно, все экземпляры классов, унаследованных от Object. Это число возвращает метод hashCode(). Именно оно используется при сохранении ключа в Hashtable следующим образом: разделив длину массива, предназначенного для хранения ключей, на код, получаем некое целое число, которое служит индексом для хранения ключа в массиве array.length % hashCode().
Далее, если необходимо добавить новую пару ключ/значение, вычисляется новый индекс, и если этот индекс совпадает с уже имеющимся, то создается список ключей, на который указывает элемент массива ключей. Таким образом, при обратном извлечении ключа необходимо вычислить индекс массива по тому же алгоритму и получить его. Если ключ в массиве единственный, то используется значение элемента массива, если хранится несколько ключей, то необходимо обойти список и выбрать нужный.
Если массив окажется слишком мал, то связанные списки будут слишком длинными и скорость поиска станет существенно снижаться, так как просмотр элементов списка будет такой же, как в обычном массиве. Чтобы этого избежать, задается некий коэффициент заполнения. При заполнении элементов массива, в котором хранятся ключи (или списки ключей) на эту величину, происходит увеличение массива и производится повторное реиндексирование. Таким образом, если массив окажется слишком мал, то он будет быстро заполняться и будет производиться операция повторного индексирования, которая отнимает достаточно много ресурсов. С другой стороны, если массив сделать большим, то при необходимости просмотреть последовательно все элементы коллекции, использующей алгоритм хэширования, придется обрабатывать большое количество пустых элементов массива ключей.
Использование алгоритма хэширования позволяет гарантировать, что скорость доступа к элементам коллекции такого типа будет увеличиваться не линейно, а логарифмически. Таким образом, при частом поиске каких-либо значений по ключу имеет смысл задействовать коллекции, применяющие алгоритм хэширования.
java.util.HashMap - этот класс расширяет AbstractMap и весьма похож на класс Hashtable. HashMap предназначен для хранения пар объектов ключ/значение. Как для ключей, так и для элементов допускаются значения типа null. Порядок хранения элементов в этой коллекции не совпадает с порядком их добавления. Порядок элементов в коллекции также может меняться во времени. HashMap обеспечивает постоянное время доступа для операций get и put.
Методы HashMap не являются синхронизированными. Для того, чтобы обеспечить нормальную работу в многопоточном варианте, следует использовать либо внешнюю синхронизацию потоков, либо синхронизированный вариант коллекции.
java.util.TreeMap - расширяет класс AbstractMap и реализует интерфейс SortedMap. TreeMap содержит ключи в порядке возрастания. Используется либо натуральное сравнение ключей, либо должен быть реализован интерфейс Comparable. Реализация алгоритма поиска обеспечивает логарифмическую зависимость времени выполнения основных операций (containsKey, get, put и remove ). Запрещено применение null значений для ключей. При использовании дубликатов ключей ссылка на объект, сохраненный с таким же ключом, будет утеряна.
Класс Properties предназначен для хранения набора свойств (параметров). Методы
String getProperty(String key)String getProperty(String key,String defaultValue)
позволяют получить свойство из набора.
setProperty(String key, String value) устанавливает это св-во
Метод load(InputStream inStream) позволяет загрузить набор свойств из входного потока (потоки данных подробно рассматриваются в лекции 15). Как правило, это текстовый файл, в котором хранятся параметры. Параметры - это строки, которые представляют собой пары ключ/значение. Предполагается, что по умолчанию используется кодировка ISO 8859-1. Каждая строка должна оканчиваться символами \r,\n или \r\n. Строки из файла будут считываться до тех пор, пока не будет достигнут его конец.
save(OutputStream inStream,String header) сохраняет набор свойств в выходной поток в виде, пригодном для вторичной загрузки с помощью метода load. Символы, считающиеся служебными, кодируются так, чтобы их можно было считать при вторичной загрузке.. Если указан header, то он будет помещен в начало потока в виде комментария (т.е. с символом # в начале), далее будет следовать комментарий, в котором будет указано время и дата сохранения свойств в потоке.
поток данных ( stream . Соответственно, поток и делятся на входящие читающие данные и выходящие передающие (записывающие) данные. Введение концепции stream позволяет отделить основную логику программы, обменивающейся информацией с любыми устройствами одинаковым образом, от низкоуровневых операций с такими устройствами ввода/вывода.
Классы InputStream и OutputStream
InputStream это базовый класс для потоков ввода, т.е. чтения. Соответственно, он описывает базовые методы для работы с байтовыми потоками данных. Эти методы необходимы всем классам, которые наследуются от InputStream.
read() абстрактный метод, предназначен для считывания ровно одного байта из потока, однако возвращает при этом значение типа int. В том случае, если считывание произошло успешно, возвращаемое значение лежит в диапазоне от 0 до 255 и представляет собой полученный . Если достигнут конец потока, то есть в нем больше нет информации для чтения, то возвращаемое значение равно -1.
Если же считать из потока данные не удается из-за каких-то ошибок, или сбоев, будет брошено исключениеjava.io.IOException. Этот класс наследуется от Exception, т.е. его всегда необходимо обрабатывать явно. программа. А это означает, что нужно быть готовым к ним, чтобы пользователь не потерял нужные данные.
На практике обычно приходится считывать не один, а сразу несколько байт то есть массив байт. Для этого используется методread(), где в качестве параметров передается массив byte[].Если же мы изначально хотим заполнить не весь массив, а только его часть, то для этих целей используется метод read(), которому, кроме массива byte[], передаются еще два int значения. Первое это позиция в массиве, с которой следует начать заполнение, второе количество байт, которое нужно считать.
Чтобы узнать, сколько байт в потоке готово к считыванию, применяется метод available(). Этот метод возвращает значение типа int, которое показывает, сколько байт в потоке готово к считыванию. Когда работа с входным потоком данных окончена, его следует закрыть. Для этого вызывается метод close
Точно так же, как InputStream это базовый класс для потоков ввода, класс OutputStream это базовый класс для потоков вывода.
В классе OutputStream аналогичным образом определяются три метода write() один принимающий в качестве параметраint, второй byte[] и третий byte[], плюс два int -числа. Все эти методы ничего не возвращают ( void ).
Метод write(int) является абстрактным и должен быть реализован в классах-наследниках. Для записи в поток сразу некоторого количества байт методу write() передается массив байт. Или, если мы хотим записать только часть массива, то передаем массив byte[] и два int -числа отступ и количество байт для записи. Понятно, что если указать неверные параметры например, отрицательный отступ, отрицательное количество байт для записи, либо если сумма отступ плюс длина будет больше длины массива, во всех этих случаях кидается исключение IndexOutOfBoundsException. Чтобы убедиться, что данные записаны в поток, а не хранятся в буфере, вызывается метод flush(), определенный в OutputStream. В этом классе его реализация пустая, но если какой-либо из наследников использует буферизацию данных, то этот метод должен быть в нем переопределен. Когда работа с потоком закончена, его следует закрыть. Для этого вызывается метод close(). Этот метод сначала освобождает буфер (вызовом метода flush ), после чего поток закрывается и освобождаются все связанные с ним системные ресурсы. Закрытый поток не может выполнять операции вывода и не может быть открыт заново. В классе OutputStream реализация метода close() не производит никаких действий.
Классы-реализации потоков данных
Классы ByteArrayInputStream и ByteArrayOutputStream
КлассByteArrayInputStream представляет поток, считывающий данные из массива байт. Этот класс имеет конструктор, которому в качестве параметра передается массив byte[]. Соответственно, при вызове методов read() возвращаемые данные будут браться именно из этого массива
Аналогично, для записи байт в массив применяется класс ByteArrayOutputStream. Этот класс использует внутри себя объектbyte[], куда записывает данные, передаваемые при вызове методов write(). Чтобы получить записанные в массив данные, вызывается метод toByteArray(). Пример:
ByteArrayOutputStream out = new ByteArrayOutputStream(); out.write(10);out.write(11); byte[] bytes = out.toByteArray();
В этом примере в результате массив bytes будет состоять из двух элементов: 10 и 11.
Использовать классы ByteArrayInputStream и ByteArrayOutputStream может быть очень удобно, когда нужно проверить, что именно записывается в выходной поток. Например, при отладке и тестировании сложных процессов записи и чтения из потоков.
Классы FilterInputStream и FilterOutputStream и их наследники
Задачи, возникающие при вводе/выводе весьма разнообразны - это может быть считывание байтов из файлов, объектов из файлов, объектов из массивов, буферизованное считывание строк из массивов и т.д. В такой ситуации решение с использованием простого наследования приводит к возникновению слишком большого числа подклассов. Более эффективно применение надстроек (в ООП этот шаблон называется адаптер) Надстройки наложение дополнительных объектов для получения новых свойств и функций. Таким образом, необходимо создать несколько дополнительных объектов адаптеров к классам ввода/вывода. В java.io их еще называют фильтрами. При этом надстройка-фильтр включает в себя интерфейс объекта, на который надстраивается, поэтому может быть, в свою очередь, дополнительно надстроена.
В java.io интерфейс для таких надстроек ввода/вывода предоставляют классы FilterInputStream (для входных потоков ) иFilterOutputStream (для выходных потоков ). Эти классы унаследованы от основных базовых классов ввода/вывода InputStream и OutputStream, соответственно. Конструктор FilterInputStream принимает в качестве параметра объектInputStream и имеет модификатор доступа protected.
Классы FilterI/OStream являются базовыми для надстроек и определяют общий интерфейс для надстраиваемых объектов. Потоки-надстройки не являются источниками данных. Они лишь модифицируют (расширяют) работу надстраиваемого потока.
BufferedInputStream и BufferedOutputStream
На практике при считывании с внешних устройств ввод данных почти всегда необходимо буферизировать. Для буферизации данных служат классы BufferedInputStream и BufferedOutputStream.
BufferedInputStream содержит массив байт, который служит буфером для считываемых данных. Также класс BufferedInputStream добавляет поддержку методов mark() (запоминает точку во входном потоке) и reset()(все байты, полученные после последнего вызова mark(), будут считываться повторно прежде, чем новые байты начнут поступать из надстроенного входного потока).
BufferedOutputStream предоставляет возможность производить многократную запись небольших блоков данных без обращения к устройству вывода при записи каждого из них. Инициировать передачу содержимого буфера на устройство вывода можно и явным образом, вызвав метод flush(). Так же буфер освобождается перед закрытием
Классы BufferedI/OStream добавляют только внутреннюю логику обработки запросов, но не добавляют никаких новых методов. Следующие два фильтра предоставляют некоторые дополнительные возможности для работы с потоками.
Для объектов процесс преобразования в последовательность байт и обратно организован несколько сложнее объекты имеют различную структуру, хранят ссылки на другие объекты и т.д. Поэтому такая процедура получила специальное название -сериализация (serialization), обратное действие, то есть воссоздание объекта из последовательности байт десериализация.
классыObjectInputStream и ObjectOutputStream.
Эти классы используют стандартный механизм сериализации, который предлагает JVM. Для того, чтобы объект мог быть сериализован, класс, от которого он порожден, должен реализовывать интерфейс java.io.Serializable. В этом интерфейсе не определен ни один метод. Он нужен лишь для указания, что объекты класса могут участвовать в сериализации.
Чтобы начать сериализацию объекта, нужен выходной поток OutputStream, в который и будет записываться сгенерированная последовательность байт. Этот поток передается в конструктор ObjectOutputStream. Затем вызовом метода writeObject()объект сериализуется и записывается в выходной поток.
Однако, сериализация (десериализация) объектов довольно сложная процедура, поэтому возникающие сложности не всегда очевидны. Рассмотрим основные исключения, которые может генерировать метод readObject() класса ObjectInputStream.
Поскольку стандартный механизм сериализации записывает в поток байт лишь состояние объекта, для успешной десериализации необходимо наличие описание класса
сериализация объекта заключается в сохранении и восстановлении состояния объекта.
До этого мы рассматривали объекты, которые имеют поля лишь примитивных типов. Если же сериализуемый объект ссылается на другие объекты, их также необходимо сохранить (записать в поток байт), а при десериализации восстановить. Эти объекты, в свою очередь, также могут ссылаться на следующие объекты. При этом важно, что если несколько ссылок указывают на один и тот же объект, то этот объект должен быть сериализован лишь однажды, а при восстановлении все ссылки должны вновь указывать на него одного. Например, сериализуемый объект A ссылается на объекты B и C, каждый из которых, в свою очередь, ссылается на один и тот же объект D. После десериализации не должно возникать ситуации, когда B ссылается наD1, а C на D2, где D1 и D2 равные, но все же различные объекты.
Для организации такого процесса стандартный механизм сериализации строит граф, включающий в себя все участвующие объекты и ссылки между ними. Если очередная ссылка указывает на некоторый объект, сначала проверяется нет ли такого объекта в графе. Если есть объект второй раз не сериализуется. Если нет новый объект добавляется в граф.
При построении графа может встретиться объект, порожденный от класса, не реализующего интерфейс Serializable.
Некоторым сложно организованным классам требуется особый подход для сериализации. Для расширения стандартного механизма можно объявить в классе два метода с точно такой сигнатурой:
private void writeObject(
java.io.ObjectOutputStream out)
throws IOException;
private void readObject(
java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;
Если в классе объявлены такие методы, то при сериализации объекта для записи его состояния будет вызван writeObject, который должен сгенерировать последовательность байт и записать ее в поток out, полученный в качестве аргумента. При этом можно вызвать стандартный механизм записи объекта путем вызова метода
out.defaultWriteObject();
Этот метод запишет все не- transient и не- static поля в поток данных.
В свою очередь, при десериализации метод readObject должен считать данные из потока in (также полученного в качестве аргумента) и восстановить значения полей класса.
Известно, что Java использует кодировку Unicode, в которой символы представляются двухбайтовым кодом. Байтовые потокизачастую работают с текстом упрощенно они просто отбрасывают старший байт каждого символа. В реальных же приложениях могут использовать различные кодировки (даже для русского языка их существует несколько). Поэтому в версии Java 1.1 появился дополнительный набор классов, основывающийся на типах Reader и Writer.
Эта иерархия очень схожа с аналогичной для байтовых потоков InputStream и OutputStream. Главное отличие между ними Reader и Writer работают с потоком символов ( char ). Только чтение массива символов в Reader описывается методомread(char[]), а запись в Writer write(char[]).
Объект класса File является абстрактным представлением файла и пути к нему. Он устанавливает только соответствие с ним, при этом для создания объекта неважно, существует ли такой файл на диске. После создания можно выполнить проверку, вызвав метод exists, который возвращает значение true, если файл существует. Создание или удаление объекта класса Fileникоим образом не отображается на реальных файлах. Для работы с содержимым файла можно получить экземплярыFileI/OStream.
Объект File может указывать на каталог (узнать это можно путем вызова метода isDirectory ). Метод list возвращает список имен (массив String ) содержащихся в нем файлов (если объект File не указывает на каталог будет возвращенnull ).
Также класс File предоставляет возможность получения некоторой информации о файле.
Методы canRead и canWrite возвращается boolean значение, можно ли будет приложению производить чтение и изменение содержимого из файла, соответственно.
getName возвращает строку имя файла (или каталога).
getParent, getParentName возвращают каталог, где файл находится в виде объекта и строки названия File, соответственно.
getPath возвращает путь к файлу (при этом в строку преобразуется абстрактный путь, на который указывает объект File).
isAbsolutely возвращает boolean значение, является ли абсолютным путь, которым указан файл. Определение, является ли путь абсолютным, зависит от системы, где запущена Java-машина. Так, для Windows абсолютный путь начинается с указания диска, либо символом '\'. Для Unix абсолютный путь начинается символом '/'.
isDirectory, isFile возвращает boolean значение, указывает ли объект на каталог либо файл, соответственно.
isHidden возвращает boolean значение, указывает ли объект на скрытый файл.
lastModified дата последнего изменения.
length длина файла в байтах.
OSI модель взаимодействия открытых систем (Open Systems Interconnected).
Процесс "обертывания" передаваемых данных служебной информацией называется инкапсуляцией ( encapsulation ).
физический уровень ( Physical layer ) самый первый. Этот уровень описывает среду передачи данных. Стандартизируются физические устройства, отвечающие за передачу электрических сигналов (разъемы, кабели и т.д.) и правила формирования этих сигналов. Рассмотрим по порядку все составляющие этого уровня.
Большая часть сетей строится на кабельной структуре (хотя существуют сети, основанные на передаче информации с помощью, например, радиоволн).
Физический уровень пересылает просто набор сигналов битов. При этом не учитывается, что несколько компьютеров, подключенных к одной среде передачи данных (например, к одному кабелю), могут начать одновременно передавать информацию в виде электрических импульсов, что, очевидно, приведет к смешению сигналов. Поэтому одной из задач Data layer(канальный уровень) является проверка доступности среды передачи. Также этот уровень отвечает за доставку фреймов между источником и адресатом в пределах сети с одной топологией. Для обеспечения такой функциональности Data layer разделяют на два подуровня:
логическая передача данных ( Logical Link Control, LLC );
управление доступом к среде ( Media Access Control, MAC ).
LLC отвечает за переход со второго уровня на более высокий третий сетевой уровень.
MAC отвечает за передачу данных на более низкий уровень Physical layer.
На сетевом уровне (Network layer) существует несколько протоколов, которые позволяют передавать данные между сетями. Наиболее распространенным из них на сегодняшний день является IP.
IP-адрес представляется 32-битным бинарным числом, которое часто записывают в виде 4 десятичных чисел, от 0 до 255 каждое.
Логически он состоит из двух частей адреса машины (host) и адреса сети (network). Сетевая часть IP-адреса показывает, к какой сети принадлежит адресат, а хост-часть (host) идентифицирует сетевое устройство в этой сети. Компьютеры с одинаковой сетевой частью находятся в одной локальной сети, а потому могут легко обмениваться данными. Если же у них различные network-ID, то, даже находясь в одном физическом сегменте, они обычно не могут "увидеть" друг друга.
TCP/IP представляет собой комбинацию двух уровней, TCP и IP. IP протокол третьего уровня обеспечивает наилучшую, но не гарантированную доставку данных через сеть. TCP протокол четвертого уровня позволяет эту гарантию обеспечить. Поэтому совместно они могут предоставить большее количество сервисов.
Утилиты для работы с сетью
IPCONFIG (IFCONFIG)
ARP
Ping
Traceroute
Route
Netstat
Классы, работающие с сетевыми протоколами, располагаются в пакете java.net, и простейшим из них является класс URL. С его помощью можно сконструировать uniform resource locator (URL), который имеет следующий формат:
protocol://host:port/resource
Здесь protocol название протокола, используемого для связи; host IP-адрес, или DNS-имя сервера, к которому производится обращение; port номер порта сервера (если порт не указан, то используется значение по умолчанию для указанного протокола); resource имя запрашиваемого ресурса, причем, оно может быть составным, например:
ftp://myserver.ru/pub/docs/Java/JavaCourse.txt
Затем можно воспользоваться методом openStream(), который возвращает InputStream, что позволяет считать содержимое ресурса.
Класс URLConnection является абстрактным. Виртуальная машина предоставляет реализации этого класса для каждого протокола, например, в том же пакете java.net определен класс HttpURLConnection. Понятно, что классы URL иURLConnection предоставляют возможность работы через сеть на прикладном уровне с помощью высокоуровневых протоколов.
Пакет java.net также предоставляет доступ к протоколам более низкого уровня TCP и UDP. Для этого сначала надо ознакомиться с классом InetAddress, который является Internet-адресом, или IP.
Для работы с TCP-протоколом используются классы Socket и ServerSocket. Первым создается ServerSocket сокет на стороне сервера. Его простейший конструктор имеет только один параметр номер порта, на котором будут приниматься входящие запросы. После создания вызывается метод accept(), который приостанавливает выполнение программы и ожидает, пока какой-нибудь клиент не инициирует соединение. В этом случае работа сервера возобновляется, а метод возвращает экземпляр класса Socket для взаимодействия с клиентом
Теперь рассмотрим UDP. Для работы с этим протоколом и на стороне клиента, и на стороне сервера используется классDatagramSocket. У него есть следующие конструкторы:
DatagramSocket()
DatagramSocket(int port)
DatagramSocket(int port, InetAddress laddr)
При вызове первого конструктора сокет открывается на произвольном доступном порту, что уместно для клиента. Конструктор с одним параметром, задающим порт, как правило, применяется на серверах, чтобы клиенты знали, на каком порту им нужно пытаться устанавливать соединение. Наконец, последний конструктор необходим для машин, у которых присутствует несколько сетевых интерфейсов.
После открытия сокетов начинается обмен датаграммами. Они представляются экземплярами класса DatagramPacket. При отсылке сообщения применяется следующий конструктор:
DatagramPacket(byte[] buf, int length,
InetAddress address, int port)
Массив содержит данные для отправки (созданный пакет будет иметь длину, равную length ), а адрес и порт указывают получателя пакета. После этого вызывается метод send() класса DatagramSocket.
50. Модель OSGi и ее реализация в Java
SGi (Open Services Gateway Initiative) спецификация динамической плагинной (модульной) шины для создания Java-приложений, разрабатываемая консорциумом OSGi Alliance. Суть заключается в возможности переинсталлировать динамически компоненты и составные части приложения без необходимости останавливать и перезапускать его.
Круг применений данной спецификации довольно широк: изначально разрабатывалась для создания встроенных систем (в частности, для автомобилей BMW, также в разработке спецификации активно участвует Siemens), но сейчас на базе OSGi строят многофункциональные десктоп-приложения (например, Eclipse SDK) и Enterprise-системы.
OSGi Alliance, ранее известная, как Open Services Gateway initiative (инициатива Доступа к Открытым Службам) организация открытых стандартов (open Standards Development Organization SDO).
В течение последних нескольких лет она разрабатывала основанную на Java служебную платформу (OSGi The Dynamic Module System for Java), которая могла управляться удаленно. Основная часть этой разработки framework, который определяет модель жизненного цикла приложения и служебного реестра.
На основе этого framework'а было создано огромное количество OSGi-служб:
|
|
|