1 Материалы по курсу «Язык программирования Java

advertisement
1 Материалы по курсу «Язык программирования Java»
Нижеприведенные материалы по темам нескольких (5) лекций разработаны для использования молодыми преподавателями при подготовке лекционных занятий. Все они
снабжены соответствующими презентациями, используемыми для показа студентам во
время лекции. Приводятся лишь основные (лучше других проработанные) варианты лабораторных работ.
1.1 Основы синтаксиса Java
1.1.1 Простейшая программа
Программа по выводу в консоль строки “Output of this program”:
class SimpleProgramm
{
public static void main(String args[])
{
System.out.println(“Output of this program”);
}
}
Объяснить без применения терминов ООП структуру такой программы.
В программе присутствуют блоки программы, выделяемые фигурными скобками.
Самый верхний блок выделяется ключевым словом class. Класс является базовой единицей любого ООП языка, в частности Java, и можно утверждать, что рабочий код (который
будет выполняться системой и включающий в себя объявление переменных, вызов других
процедур и функций) всегда находится внутри такого блока. В терминах процедурных
языков его можно считать блоком описывающей саму программу.
Как и в языках C/C++, выполнение кода начинается с вызова процедуры main(), которую можно назвать точкой входа в программу. Параметры, которые ей передаются, это
массив строковых значений, который содержит введённые через командную строку значения после имени самой программы (если вызов происходил из командной строки) или
соответствующие значения в настройках запуска конкретной IDE.
1.1.2 Идентификаторы, константы (литералы), ключевые слова
Идентификаторами можно объяснить как имена некоторых частей программы: класса, переменной, метода (методом в терминах ООП называется вызываемая процедура или
функция), например, MyClass,get_value,flag2.
Литералами - значения определенного типа, которые описываются прямо в теле программы, например строковые литералы ‘S’, “some string” или числовые 34, 6.73. Могут
использоваться везде, где допустимы значения данного типа, то есть в инициализации переменных конкретного типа, вызове методов с параметрами нужного типа и т.д.
В языке Java присутствуют слова с зарезервированным значением, интерпретируемые компилятором неким специальным образом. Они называются ключевыми словами, и
их не разрешается использовать в качестве идентификаторов. Пример таких слов: public,
void, class, if, for, int и т.д. (более полный перечень слов можно посмотреть в спецификации языка).
1.1.3 Переменные
Упомянуть что, переменные в языке Java, как и в других языках имеют свой тип и
значение. Работа с переменными состоит из нескольких процедур: декларирования (объявления типа хранимых данных), инициализации (присвоении значения) и чтения данных.
При этом декларирование происходит всего один раз, а присвоение и чтение значений
столько, сколько понадобится. Подчеркнуть особенность языка Java в том, что объявление переменной может происходить в любой части блока кода, а не привязывается к его
началу.
Объявление переменных:
int dayNum;
double price;
boolean isBig;
Присваивание значений:
price = 10.34;
dayNum = 4;
idBig = false;
Объявление и присваивание:
char plus = ‘+’;
long someBigValue = 555444333222;
1.1.4 Примитивные типы данных
Описать следующие примитивные типы данных, встроенные в язык Java:
целые - описываются ключевыми словами byte, short, int и long, дробные – ключевые
слова float и double, символьный (двухбайтовая кодировка Unicode) - ключевые слово char
и логический - ключевое слово boolean.
Подчеркнуть: разработчиками языка продекларировано, что данные типы будут занимать одно и тоже количество бит для области значений типа независимо от платформы
и операционной системы (в отличие от языков, в которых нет жесткой привязки области
значений в битах к типу данных).
Количество бит области значений для каждого типа:
Byte – 8 бит, float – 32, char – 16,
Short – 16, double – 64,boolean – 1,
Int – 32,
Long – 64
При этом количество бит не отражает количество памяти выделяемой для хранения
переменной, (система хранит всё в виде базовых блоков памяти по 16,32 или 64 в зависимости от архитектуры), а скорее возможности данного типа для представления данных
(разрядности).
Подчеркнуть, что в языке отсутствуют специальные типы для беззнаковых значений
(аналогично типам C/C++ со словом unsigned).
Символы, такие как возврат каретки, табуляция и т.д. могут описываться специальными литералами с обратным слешем (\), например, ‘\t’ – табуляция, ‘\n’ – новая строка и
т.д. Имеется возможность указания символа посредством его номера (в кодовой таблице)
следующим образом \uxxxx – где xxxx 16-ричный код символа в Unicode, записанный
цифрами и буквами латинского алфавита от a до f, т. е. тип char можно представить как
беззнаковое 16-разрядное число (в отличие от знакового short).
Логический тип описывает только два возможных значения, это true и false (истина и
ложь).
Подчеркнуть: в языке Java используется строгая типизация, означающая запрет и
ограничение использования значений одного типа в программном коде, предназначенном
для другого, без использования явного или неявного преобразования между типами.
Существует два вида преобразования типов, это расширяющие и сужающие.
Расширяющими являются преобразования между схожими типами, когда один из
них может хранить только подмножество значений второго, от меньшего по диапазону
значений (разрядности) к большему. Такое преобразование не требует специальных конструкций и осуществляется неявным образом.
Пример расширяющего преобразования.
short s=29; int i = s;
Сужающим, соответственно, называется обратное преобразование в сторону типа с
меньшим диапазоном значений (разрядностью). Но в отличие от расширяющего, здесь
необходимо использовать явное приведение типов с помощью специальной конструкции,
называемого на программистском жаргоне “кастинг” от английского “cast”, которое за-
ключается в написании в круглых скобках нужного (более узкого) типа перед приводимой
переменной.
Пример сужающего преобразования:
int i=14; short s = (short) i;
Подчеркнуть, что тип char является, а тип boolean не является совместимыми с целыми типами (в отличие от языков C/C++ где 0 можно использовать как false, а любое
ненулевое значение – как true).
1.1.5 Операции
В языке Java присутствуют следующие операции:
арифметические: +,-,*,/,%
инкремента и декремента: ++,-логические: ==,!=,&&,||
побитовые: &,|,^,~,<<,>>(заполнение знаковым битом),>>>(заполнение нулём)
сокращенные: +=,-=,*=,/=,%=
Арифметические представлены сложением(+), вычитанием(-), умножением(*), делением(/) и нахождением целого остатка(%).
Операция инкремента(++) означает прибавление к целой переменной единицы, и соответственно декремента(--) – вычитание. При этом может использоваться как после переменной (“постфиксная” форма), так и до (“префиксная” форма). При использовании
“постфиксной” формы, сначала берётся значение, а потом производится операция, “префиксной” формы – сначала производится операция, а потом берется значение.
Использование инкремента и декремента:
int i=0;
i++; // i=1
i--; // i=0
System.out.println(i++);//печатает 0
//i=1
System.out.println(--i);//печатает 0
//i=0
Логические операции представлены проверкой равенства (==), неравенства (!=), логического И(&&) и логического ИЛИ(||).
Побитовые – битовое И(&), ИЛИ(|), исключающее ИЛИ(^), НЕ(~), битовый сдвиг
влево(<<), битовый сдвиг вправо(>>), беззнаковый сдвиг вправо(>>>).
Замечание:
При использовании операции (>>) места освободившиеся при сдвиге заполняются
битом отвечающим за знак (1 – отрицательное число, 0 – положительное), тем самым знак
числа при сдвиге не меняется, а при использовании операции(>>>) никаких специальных
операций для контроля знака не происходит, а на освободившиеся места записывается 0.
Операции сдвига можно рассматривать c разных точек зрения, как арифметические
(умножение и деление числа независимо от его знака на 2 в степени n, где n - число сдвигов на один бит, соответственно << и >>) и как логические (сдвиги разрядов двоичного
представления влево и вправо с заполнением нулями освободившегося места, << и >>>
соответственно).
Сокращенные операции представляют собой укороченную запись прибавления(+=),
вычитания(-=), умножения(*=), деления(/=) и нахождения остатка(%=) с записью результата вычисления в эту же переменную.
Сокращенные операции:
int a=10;
a+=5; //а=15
a*=2; //a=30
a%=7; //a=2
1.1.6 Управляющие операторы
Показать операторы, управляющие потоком выполнения программы: условные
(if/else), операторы многовариантного ветвления (switch/case), операторы цикла, включая
неопределённых циклов(while,do/while) и определённых (for), операторы прерывания цикла (break,continue).
Конструкции if/else, switch/case, while, do/while, for не отличаются от аналогичных в
C/C++. Единственное дополнение к использованию for в том, что переменную счетчика
можно не только инициализировать, но и объявлять прямо в теле оператора.
Объявление и инициализация счетчика в for:
for (int i=0;i<10;i++)
{
System.out.println("i = "+i);
}
Подчеркнуть: в языке Java отсутствует оператор безусловного перехода goto (но является зарезервированным ключевым словом). Вместо него есть два оператора выхода из
цикла, это break и continue. Для завершения текущего цикла служит break, а для завершения текущей итерации цикла – continue.
for(int i=0;i<10;i++)
{
if (i==3) continue;
System.out.println(i);//печатает числа 1,2,4,5,6,7
if (i==7) break;
}
При использовании вложенных циклов бывает ситуация, когда необходимо выйти
или продолжить итерации не текущего цикла, а некоторого вышестоящего. В таких случа-
ях можно применять такую возможность языка как метки. Метки обозначаются своим
именем и двоеточием перед тем циклом, к которому будет переходить поток выполнения,
тогда прерывание такого цикла будет выглядеть как “break метка;” а продолжение со следующей итерации этого цикла как “continue метка;”
Пример использования continue с меткой:
for(int i=0;i<3;i++)
label1:
for(int j=0;j<5;j++)
for(int k=0;k<6;k++)
{
System.out.println(i+" "+j+" "+k);
if (i==1) continue label1;
}
Пример использования break с меткой:
label2: for(int i=1;i<=10;i++)
for(int j=1;j<=5;j++)
{
System.out.println(i+" "+j);
if (i%j == 3 && i>j) break label2;
}
1.1.7 Область видимости переменных в блоке (цикле)
Блок – область кода ограниченная фигурными скобками {}. Блоки могут использоваться как в конструкциях управляющих операторов, так и независимо сами по себе. Объявления переменных действуют внутри своего блока, т.е. за его пределами переменная перестает существовать, и мы можем снова определять с тем же именем другую переменную. При этом невозможно объявить одинаково названные переменные в двух вложенных
блоках, так как область видимости одной переменной будет пересекаться с другой.
Использование переменных в блоках:
//объявление внутри блока for
for(int i=0;i<4;i++)
{
System.out.println("i = "+i);
}
//можем объявлять
int i=100;
//а так уже не можем (т.к. уже объявлена)
//for(int i=10;i<20;i++)
//можно так
for(i=10;i<20;i++)
{
System.out.println("i = "+i);
}
Использование блока независимо:
int i=0;
{
int j=10;
i=5;
System.out.println(i+" "+j);
}
i=j;//не верно, j не существует в пределах текущего блока
1.1.8 Комментарии
Показать три вида комментариев.
Символы после // и до конца текущей строки:
// некоторый комментарий
Все символы, заключенные между /* и */ :
/*
другой
большой
комментарий
*/
Все символы, заключенные между /** и */ :
/** специальный комментарий
* для документирования кода(javadoc)
*/
Пояснить, что специальные комментарии могут содержать простые HTML теги и
специальные ключевые слова вида @some_key, где some_key специальное зарезервированное слово. Такие комментарии используются для генерации html документации из исходного кода утилитой javadoc.
1.1.9 Общие замечания
Заметить, что язык Java является чувствительным к регистру, т.е. любые конструкции, которые будут использованы в программе, должны быть в точно таком же написании, в котором они описаны до этого в программе или предусмотрены синтаксисом Java.
Например, все ключевые слова(типы переменных, названия операторов и т.д.) должны писаться с маленькой буквы, объявление int j; и int J; это объявление разных переменных,
Main() и main() - разных процедур, и первая не будет вызываться при старте программы,
как вторая.
1.2 Массивы и объекты
1.2.1 Массивы
Так можно дать определение массивов: структура данных с набором величин одинакового типа, при этом доступ к элементу осуществляется с помощью целого индекса
Для работы с массивом необходимо выполнить его объявление, инициализацию и
заполнение. Разные конструкции языка позволяют делать эти операции как отдельно, так
и вместе.
Отдельно:
int[] myArray;
myArray = new int[20]; //выделяет память под 20 ячеек
for (int i=0;i<myArray.length;i++) myArray[i]=1000;
Способ 3 в 1:
byte[] someBytes = {0,2,4,8,16,32};
1.2.2 Многомерные массивы
Язык определяет многомерные массивы как набор одномерных массивов, то есть
массивы массивов.
Объявление + инициализация:
//автоматически
int[][] twoDimArr = new int[10][5];
//в ручную
int[][] twoDimArr = new int[10][];
for(int i = 0; i < 10; i++) twoDimArr[i] = new int[5];
В силу такого строения многомерных массивов, возможно объявление не прямоугольных массивов, в отличие от других языков.
Объявление + инициализация + заполнение треугольного массива:
int[][] arr3 = { {0},
{0, 1},
{0, 2, 4}
};
1.2.3 Создание и использование объектов
При создании любого объекта в языке используется ключевое слово new.
obj = new SomeClass();
Объяснить, что выделение памяти происходит не на этапе компиляции в байт код, а
на этапе выполнения программы, то есть динамически. Объект в памяти содержит некоторый набор данных (полей) и функций-процедур (методов). К ним можно получать доступ
с помощью точки после имени переменной (.).
Доступ к полям и вызов методов:
a = obj.someField;
obj.someMethod();
На объект может ссылаться переменная соответствующего типа данных (класса).
Объявление переменной:
SomeClass obj;
Все типы, описывающие данные в языке (классы) являются ссылочными (кроме
примитивных типов, которые не являются классами: int, double и т.д.). Отсюда следует,
что переменные описывают ссылку на объект в памяти, являющейся аналогом указателя
из C/C++, и сравнивание переменных не есть сравнивание объектов.
Правильное сравнивание:
obj1.equals(obj2)
Переменная, которая никуда не ссылается, содержит зарезервированное значение,
описываемое ключевым словом null.
В языке отсутствуют операции освобождения памяти, занимаемой объектами(free() и
delete() в C/C++). Для освобождения неиспользуемой памяти используется “сборщик мусора”. Это фоновый процесс виртуальной java машины, который следит за переменными
ссылающиеся на участки памяти (объекты) и при потере ссылки на такой участок, автоматически его удаляет.
1.2.4 Типы данных (классы) для работы с числами и символами
Классы оболочки примитивных типов:
Byte, Integer, Double, Character ,…
Большие числа:
BigInteger, BigDecimal
Объявление переменной на основе типа данных (класса), порождение объекта соответствующего класса и инициализация переменной ссылкой на созданный объект:
Integer objInt = new Integer(12);
Вызов одного из методов:
int i = objInt.intValue();
1.2.5 Классы для работы со строками
Ввести понятие строк и соответствующих классов.
Класс String, особый класс. Создание объектов происходит с помощью неявного вызова new.
Создание строки:
String s = “some string”;
Пояснить, что строка не есть массив данных с типом char, а есть полноценный объект класса.
Конкатенация строк с помощью оператора ‘+’:
s=“some”+”string”;
Класс StringBuffer – класс, предназначенный для работы с изменяемыми строками, в
отличие от String.
Объявление:
StringBuffer sb = new StringBuffer(“some buffer”);
sb.some_change(param1,param2,…);
Класс StringBuffer содержит методы для вставки, добавления, поиска подстрок и
других операций с текстом.
1.3 Создание классов. Инкапсуляция.
1.3.1 Классы, объекты
Пояснить различие между классом и объектом класса. Класс – форма для получения
объекта. Объект – сущность (экземпляр), создаваемая в памяти на основе класса (имеет
тип).
1.3.2 Поля и методы объекта
Объект объединяет данные и методы их обработки. Поля(переменные) – свойства(данные) конкретного объекта(могут быть тоже объектами). Методы(функции) – действия выполняемые над/с данными конкретного объекта.
1.3.3 Правила именования
Объяснить общепринятые правила именования (рекомендуемые, но не навязываемые). С большой буквы начинается название класса. С маленькой – пакета, поля, метода.
Большими буквами описываются константы.
1.3.4 Модификаторы доступа
Для полей и методов следующие:
public – видно всем
private – только себе
без модификатора – классам текущего пакета
Для полей:
final – после присваивания нельзя менять (константа)
1.3.5 Конструкторы
Объяснить понятие конструктора. Специальный метод, вызываемый при создании
объекта. Описание класса может не содержать ни одного, при этом он все равно существует (неявный конструктор). Пояснить, что на следующих занятиях узнаем, откуда он
берется. Конструкторов может быть несколько, с разными параметрами. Вызов соответствующего соответственно будет определяться по ним.
1.3.6 Ключевое слово this
Ввести два вида использования слова this.
Как ссылка на себя для доступа к полю:
this.some_field = 10;
Как вызов другого (своего) конструктора:
this(some_value);
1.3.7 ООП, инкапсуляция
Отделение (сокрытие) внутреннего устройства (данных и вспомогательных операций) от поведения (доступных операций).
Положительные стороны:
гибкость – можем менять внутреннее строение, не меняя поведение
безопасность – запрещаем и/или ограничиваем доступ к данным объекта
простота – меньше доступных операций -> легче использовать и понимать
1.3.8 Реализация инкапсуляции
Объяснить, что поля с внутренней информацией должны объявляться с модификатором private или protected, к которым получать доступ через специальные методы. Ввести
понятие геттеров и сеттеров (getFieldName/setFieldName, для boolean – isFieldName).
Упомянуть о модификаторе final для методов, как повышающем скорость и его
уместность для геттеров.
1.3.9 Поля и методы класса
Объяснить отличие от полей и методов объекта. Для таких полей и методов не нужно создавать экземпляры (объекты) для использования. Объявляются добавлением модификатора static. Привести примеры статических классов. Класс из библиотеки Java
java.lang.Math является полностью статическим.
1.4 Основы UML и ООП в Java
1.4.1 UML
Unified Modeling Language – унифицированный язык моделирования.
Пояснить, что с помощью него будем рисовать диаграммы классов, куда входит рисование самих классов (имя, поля, методы), отношения между классами (наследование,
ассоциация, в частности, агрегация и зависимость) и дополнительная информация (пояснения и т.д.).
1.4.2 Наследование в UML
Английский эквивалент: inheritance – “is-a”. Отношение между конкретным и общим, то есть присутствует расширение функциональности. Привести UML нотацию (линия со стрелкой).
Синтаксис наследования в Java:
class MyClass extends SuperClass { .. }
1.4.3 Агрегация в UML
Английский эквивалент: aggregation – “has-a”. Отношение между объектами, когда
один содержит (состоит из) объекты(ов) другого класса. Привести UML нотацию (ромбик
с линией).
1.4.4 Ассоциация в UML
Похожая на агрегацию, но более слабая связь, например, поле одного класса в другом, но аналогичное поле есть и в третьем классе. Привести UML нотацию однонаправленной и двунаправленной связи.
1.4.5 Зависимость в UML
Английский эквивалент: dependence - “uses-a”. Отношение, когда один класс использует другой (ие), т.е. методы одного манипулируют объектами другого класса. Привести
UML нотацию(пунктирная линия со стрелкой на конце).
1.4.6 Наследование в Java
Подчеркнуть, что множественное наследование отсутствует.
Объяснить механизм переопределения (overriding) методов, как переписывание метода родителя с той же сигнатурой (названием, параметрами) у потомка. Дать определение сигнатуры метода. При переопределении сигнатуру и возвращаемое значение менять
нельзя.
Подчеркнуть, что нужно отличать от перегрузки (overloading) методов, при которой
существуют одинаковые названия методов с разными параметрами и возвращаемыми значениями.
Дать определение полиморфизма. Свойства полиморфизма: объект подкласса можно
использовать вместо объекта суперкласса (т.е. объект будет ограничен поведением суперкласса). Или переменная типа суперкласса может ссылаться на объект любого подкласса
(обратное не верно):
SuperClass s = new ChildClass();
Дать определение динамического связывания. При динамическом связывании вызовы методов идут по фактическому типу объекта, независимо в переменной какого типа(предка) он хранится.
s.some_method(); //вызовет some_method() у ChildClass (если он
переопределён)
1.4.7 Ключевые слова, применяемые при наследовании
Показать разные варианты использования ключевого слова super.
Как доступ к полям родителя:
super.some_field;
// super.super.some_field2 не возможно(заменяется приведением к
типу класса-предка)
((SomeAncestor)this).some_field3;
Как вызов конструктора родителя:
public ChildClass(param1,param2)
{
super(param1,param1);
…
}
Как вызов переопределенного метода родителя:
public int some_method(int a)
{
return 1+super(a);
}
Привести модификаторы, используемые при наследовании.
Модификатор final – запрет переопределения
Для класса:
final class MyClass{ .. }
Для метода:
public final void some_method(){ .. }
Модификатор protected – видимость в подклассах (для полей и методов):
protected some_field;
protected int our_method(){ .. }
1.4.8 Object – глобальный суперкласс
Пояснить, что является общим родителем всех классов в Java и написан напрямую в
байт-коде. Т.е. при определении класса, не наследующего ни от какого другого, неявно
присутствует наследование от класса Object (extends Object). Так же вызывается его конструктор без параметров (конструктор по умолчанию), если в нашем классе нет конструкторов.
Используется в утилитах для работы с объектами любого класса(например в
java.util.ArrayList до версии jdk 1.4 включительно).
Существуют следующие важные переопределяемые методы.
метод equals():
if (a.equals(b)) { .. } // равенство двух объектов
метод toString():
System.out.println(a.toString());
//строковое
представление
объекта
Объяснить необходимость переопределения, для достижения своей логики работы.
1.4.9 Приведение типов для классов
Объяснить расширяющие и сужающие приведения типов применительно к классам.
В тип суперкласса (расширяющее):
SuperClass s = new ChildClass();
В тип подкласса (сужающее):
ChildClass c = (ChildClass) s;
При сужающем преобразовании для не родственных классов возникает ошибка:
NotChildClass n = (NotChildClass) s; //Ошибка
Можно проверять тип с помощью конструкции instanceof:
if (s instanceof ChildClass) { c = (ChildClass) s; … }
Но рекомендуется по возможности избегать.
Сужающие преобразования часто используется при работе с утилитными классами
(оперирующими Object до версии jdk 1.4):
ArrayList al = new ArrayList();
al.add(new SomeClass());
SomeClass c2 = (SomeClass) al.get(0);
1.4.10 Абстракция
Дать понятие абстракции как представление поведения без конкретной реализации,
“что делает” вместо “как делает”.
Объяснить, когда возникает необходимость в оперировании абстрактными вещами.
Отсутствие конкретных деталей при проектировании систем и желание выделить общие
части с неизвестной (пока) логикой работы вместе с известными в одну базовую часть.
1.4.11 Абстрактный класс
Ввести понятие абстрактного класса.
Часть объявленных методов(не обязательно всех) не имеет рабочего кода:
abstract class OurClass
{
public abstract void doSmth();
public int getField { .. }
}
Не может иметь экземпляров класса(объектов):
OurClass a = new OurClass(); //Ошибка
Переменная может иметь его тип, ссылаясь при этом на объект подкласса(полиморфизм):
public class ChildClass extends OurClass { .. }
OurClass b = new ChildClass();
1.4.12 Интерфейс
Ввести понятие интерфейса как полностью абстрактной сущности, но с возможностью объявления констант. Можно представить как полностью абстрактный класс, но
классом при этом не являющимся, который может наследовать другие интерфейсы.
Объявление интерфейса:
//без public – видимость в пакете
public
interface
SomeInterface
extends
OtherInterface1,OtherInterface2
{
int VALUE=10; //автоматически public final static
void someMethod(); //автоматически public
}
Используется собственный синтаксис для наследующих его классов (implements),
поэтому в переводе можно сказать, что мы “реализуем” интерфейс, а не “наследуем”.
В отличие от классов, может реализовывать сразу несколько других интерфейсов
(аналог множественного наследования):
public SomeClass2 implements SomeInterface1,SomeInterface2 { … }
Как и для абстрактных классов присутствуют следующие свойства. Нельзя создать
объект на основе интерфейса. Можно создавать переменные (с типом интерфейса) ссылающиеся на объекты класса, реализовывавшего данный интерфейс. Можно использовать
конструкцию instanceof для проверки принадлежности к данному интерфейсу. Можно создавать иерархию наследования интерфейсов.
Показать UML нотацию (круг, с названием пониже).
1.4.13 Применение интерфейсов в разных контекстах
Показать разные применения интерфейсов для разных целей:
Стандартное применение – как полностью абстрактного класса.
Хранение констант - реализуя интерфейс, можем обращаться к константам, как к полям напрямую (интерфейс SwingConstants,…).
Способность к какому то действию - реализующий класс способен к какому то действию (интерфейс имеет один или несколько методов) например, интерфейсы Runnable,
Comparable и т.д.
Для того, чтобы пометить класс, как обладающий некоторыми внутренними свойствами или логикой работы (тело интерфейса пустое). Например, интерфейсы Cloneable,
Serializable.
Проверка присутствия у объекта нужных нам свойств:
if (myObj instanceof Cloneable) { … }
И, наконец, множественная реализация интерфейсов, как замена множественного
наследования классов.
1.5 Обработка ошибок
1.5.1 Необходимость работы с ошибками
Объяснить необходимость. Можно привести следующие причины: стабильность,
безопасность, удобство, дружественное поведение. Написание кода готового к корректной
обработке предвидимых и непредвидимых ситуаций позволяет достигнуть большей стабильности и безопасности приложений. Следующими преимуществами являются удобство работы пользователя и дружественное поведение к нему при работе с такими приложениями.
1.5.2 Причины возникновения ошибок
Например, следующие:
Некорректный ввод данных (опечатки, намеренные попытки ввода некорректных
значений), в Java – IllegalArgumentException. Сбои оборудования (выключено-недоступно)
– NoRouteToHostException. Ограничения среды (исчерпание ресурсов: диск, память) OutOfMemoryError,StackOverflowError. Выполнение программного кода (выход за пределы буферов, обращение к несуществующему элементу, некорректный вызов подпро-
грамм) - IndexOutOfBoundsException, NullPointerException, BufferOverflowException,
NoSuchElementException
1.5.3 Способы обработки ошибок
Показать, как идет стандартная обработка ошибок, с помощью проверки возвращаемых значений и ее недостатки.
Обработка возвращаемых значений:
int errNum = firstMethod();
if (errNum == -1)
{
обработка 1-ой ошибки
}else
{
errNum = secondMethod();
if(errNum == -2)
{
обработка 2-ой ошибки
}
}
При таком способе код ошибки возвращается вызывающему, и ему предоставляется
её обработка. Недостатки при таком подходе: не всегда бывает возможным, возвращаемый код может совпадать с возвращаемым значением, связанным с работой программы.
Поэтому программа заполняется сложной смесью кода обработчиков(if else) c кодом, который выполняет основную функциональность, на всех уровнях вызова подпрограмм.
Объяснить альтернативный подход, применяемый в Java - встраивание в язык механизма проверки исключительных ситуаций (exception handling).
Механизм исключительных ситуаций:
try
{
firstMethod();
secondMethod();
}
catch(Exception1 e1)
{ обработка 1-ой ошибки }
catch(Exception2 e2)
{ обработка 2-ой ошибки }
Exceptions (исключительные ситуации) возникают когда невозможно дальнейшее
выполнение. Объяснить преимущества такого подхода: При использовании этого механизма, при отсутствии нужной информации для обработки, можно передать обработку на
уровень выше, где точно знают, как быть с данной ситуацией. Удобное разделение кода
отвечающего за функциональность, от кода обработчиков ошибок.
1.5.4 Механизм исключительных ситуаций (исключений)
Состоит из трех стадий: порождение объекта с информацией об исключении, остановка выполнения и поиск обработчика исключений.
1.5.5 Иерархия и классификация исключительных ситуаций и ошибок
Показать, что все классы, описывающие исключительные ситуации и ошибки являются производными от класса Throwable.
Привести три ветви ситуаций: Error, RuntimeException и остальные исключительные
ситуации (потомки Exception).
Класс Error отвечает за внутренние ошибки, ситуации исчерпания ресурсов. Такие
ситуации не могут быть обработаны, и не возможно породить самому (порождает система).
Класс RuntimeException отвечает за ошибки программирования(неверное приведение типов, выход за пределы массива, попытка обращения к null объекту и т.д.). Подчеркнуть, что происходят всегда по вине программиста (“ваша вина”).
Остальные Exceptions это непредвиденное стечение обстоятельств (обрыв файла, соединение по неправильному URL и т.д.)
Объяснить отличие контролируемых ситуаций от неконтролируемых. К неконтролируемым относятся производные от Error и RuntimeException, к контролируемым – остальные производные от Exception.
Контролируемые(checked): проверяются и навязываются еще на этапе компиляции,
необходимо писать обработчики.
Неконтролируемые(unchecked): перехватываются автоматически без нашего участия
и поэтому облегчается отладка.
Подчеркнуть, что в случае RuntimeException и его производных мы можем (но не
должны) написать обработчик.
1.5.6 Объявление исключительных ситуаций
Пояснить что, объявление необходимо, для того чтобы сообщить, какие исключительные ситуации мы можем породить, тем, кто будет использовать наш метод для их
корректной обработки. Подчеркнуть, что объявлять надо все контролируемые исключительные ситуации и можно неконтролируемые(RuntimeException и его потомки)
Объявление о возможности порождения исключения происходит с помощью ключевого слова throws:
class OurClass
{
…
public int someMethod() throws SomeException1,SomeException2
{
Код
который
_может_
породить
SomeException1
или
SomeException2
}
…
}
1.5.7 Перехват исключительных ситуаций
Для перехвата исключений используется блок try/catch:
try
{
Код, который может породить
исключительную ситуацию.
}
catch (ТипИсключения1 e)
{
Код обработчика для данного типа.
}
catch (ТипИсключения2 e)
{
Код обработчика для данного типа.
}
Объяснить, что необходимо перехватывать только те ситуации, которые сами можем
обработать (о которых мы имеем достаточно информации в нашем контексте).
1.5.8 Раздел finally
Блок try/catch/finally:
try
{
Код, который может породить
исключительную ситуацию.
}
catch (ТипИсключения1 e)
{
Код обработчика для данного типа.
}
finally
{
Код, который выполнится в любом случае.
}
Объяснить, зачем нужен блок finally, когда у нас есть сборщик мусора. Применяется,
когда необходимо вернуть что-то другое, а не память в первоначальное состояние, например, открытый файл или сетевое соединение, рисунок на экране и т.д.
Призвать к осторожному использованию, подчеркнув следующие моменты: блок finally выполняется до return, стараться не использовать в нём код, который тоже может вызвать исключительную ситуацию.
1.6 Ввод/вывод, файлы, сериализация
1.6.1 Потоки данных
Ввести понятие потоков данных. Разновидности потоков данных: потоки ввода – потребляют (читают) данные, потоки вывода – отдают (записывают) данные.
Потоки подсоединяются к физическим устройствам и данным.
На примере пакета java.io ввести понятие о байтовых (бинарных) потоках (Stream) и
символьных потоках (Reader и Writer) данных.
1.6.2 Бинарные (байтовые) потоки
Дать понятие о базовых классах байтовых потоков ввода и вывода.
Класс InputStream – базовый абстрактный класс ввода.
Основной метод InputStream:
public
abstract
int
read()
throws
IOException
//чтение
байта
(числа от 0 до 255)
Класс OutputStream – базовый абстрактный класс вывода.
Основной метод OutputStream:
public
abstract
void
write(int
b)
throws
IOExceptionwrite()
//запись байта (числа от 0 до 255)
Пояснить, что все производные от этих классов работают с бинарными данными
(байтами).
1.6.3 Текстовые (символьные) потоки
Базовые классы символьных потоков:
Класс Reader – класс ввода.
Основной метод Reader:
public int read() throws IOException// чтение символа Unicode(2
байта)
Класс Writer – класс вывода.
Основной метод Writer:
public
void
write(int
c)
throws
IOException
//запись
символа
Unicode(2 байта)
Все производные от этих классов работают с символьными(текстовыми) данными.
1.6.4 Разновидности потоков данных
Показать различные виды потоков.
Файловые
потоки(File*Stream,
File*er),
потоки
данных
по
типам
дан-
ных(Data*Stream), потоки каналов или pipes (Piped*Stream,Piped*er), потоки с сжатием
(java.util.zip.Zip*Stream), буфферизированные потоки (Buffered*Stream, Buffered*er), объектные потоки, необходимые для сериализации объектов из/в память (Object*Stream) и
т.д..
1.6.5 Сериализация объектов (object serialization)
Дать понятие сериализации объекта, например следующим образом: превращение
объекта в набор байт и запись в поток и десериализации: чтение и восстановление объекта
из последовательности байт. Пояснить, что при сериализации все внутренние свойства
объекта консервируются набор байт и при десериализации из того же набора восстанавливаются в исходное состояние.
Подчеркнуть, что классы сериализуемых объектов должны реализовывать интерфейс-метку Serializable, в противном случае возникнет исключительная ситуация
NotSerializableException.
1.6.6 Смешивание (объединение) потоков
Объяснить, что для получения свойств, сразу нескольких потоков используется механизм их смешивания.
Реализация механизма смешивания с помощью конструирования объекта потока на
основе объекта другого потока:
DataInputStream stream = new DataInputStream(
new BufferedInputStream(
new FileInputStream(“some_file.dat”)));
1.6.7 Работа с файловой системой
Показать, что присутствует класс File, который отвечает за сущности в файловой системе (файлы, директории). Методы этого класса отвечают за создание/удаление файла/директории, получение пути в файловой системе, проверка чтения/записи, проверка
типа(файл, директория), получение списка файлов в директории, получение родительского каталога и других операций.
Подчеркнуть, что создание объекта класса File не влечет за собой создание файла
или директории в файловой системе.
Download