- •Краткая историческая справка.
- •Преимущества языка Java.
- •Недостатки java:
- •Лекция 2. Этапы разработки java-приложений. Этапы разработки java-приложений.
- •Установка jdk.
- •Инсталляция исходных кодов библиотек
- •Инсталляция пакета документации.
- •Тестирование правильности установки и создание простейшей программы
- •Визуальные среды программирования.
- •Лекция 3. Переменные и типы данных. Переменные и типы данных.
- •Подробности о примитивных типах.
- •Лекция 4. Операторы и функции. Операторы и функции.
- •Операторы ветвлений и циклов.
- •Оператор цикла while.
- •Оператор цикла for.
- •Лекция 5. Объектно ориентированное программирование Объектно ориентированное программирование.
- •Определение объекта.
- •Инкапсуляция.
- •Наследование.
- •Полиморфизм (перегрузка).
- •Пример ооп – программы.
- •Отличие перегрузки функций от переопределения.
- •Отличие классов от интерфейсов.
- •Лекция 6. Массивы и строки. Массивы и строки.
- •Многомерные массивы.
- •Приведение типов и динамические массивы.
- •Строки в java.
- •Лекция 7. Организация ввода-вывода данных Организация ввода-вывода данных.
- •Функции стандартной библиотеки ввода/вывода.
- •Новая библиотека ввода/вывода.
- •Классы потокового ввода/вывода из пакета java.Io.
- •Лекция 8. Обработка исключений. Обработка исключений.
- •Классификация исключений.
- •Перехват исключений блоками try/catch.
- •Самостоятельное выбрасывание исключений.
- •Разработка собственных классов исключений.
- •Лекция 9. Потоки. Потоки.
- •1. Cпециальный класс Thread.
- •2. Реализация интерфейса Runnable.
- •Выбор между использованием класса Thread и интерфейса Runnable.
- •Синхронизация потоков с помощью оператора synchronized.
- •Синхронизация потоков с помощью семафоров.
- •Лекция 10. Подключаемые библиотеки java. Подключаемые библиотеки java.
- •Библиотека awt
- •Внутреннее устройство системы обработки событий awt.
- •Библиотека Swing.
Полиморфизм (перегрузка).
Понятия полиморфизма и перегрузки можно использовать как синонимы. Смысл полиморфизма заключается в том, что в классе можно создавать методы с одинаковыми именами, отличающиеся только количеством и типом входных параметров. В этом случае на этапе компиляции компилятор сам определяет, когда какую функцию вызывать.
Наример, в стандартном классе String есть несколько методов с именем indexOf():
indexOf(int),
indexOf(int, int),
indexOf(String),
indexOf(String, int),
Все эти методы называются indexOf(), но действуют по-разному в зависимости от того, что передано им на вход.
Еще один пример. Допустим, вы написали некий класс А, который выполняет сложение двух объектов с помощью метода Summa(), но в программе вы можете передавать ему как числовые, так и строковые параметры. Если передаются числа, то этот метод должен вернуть арифметическую сумму, а если передаются строки, то выполнить конкатенацию их и вернуть склеенную таким образом новую строку.
Чтобы решить данную задачу, вы определяете в классе два различных метода с одинаковым именем, но различными входными параметрами:
Summa(int, int)
Summa(String, String)
В этом случае вы можете для сложения двух сущностей использовать вызов метода класса с одним о тем же именем, но в зависимости от того, что вы ему передадите на вход, он вернет разный результат. То есть, в терминах ООП говорят, что вы перегружаете метод Summa(). Например:
Summa(1, 2) // вернет число 3
Summa (“1”, “2”) // вернет строку «12»
Перегружать можно не только методы, но и конструкторы класса (о них чуть позже).
Если создать в классе несколько конструкторов с одинаковыми именами, но различными входными параметрами, можно инициализировать один и тот же класс полями с различными значениями или содержимым. Предположим, что вы пишете некий аудиовидеоредактор. Аудио и видео требуют различных кодеков для обработки. Допустим также, что вы включили в класс обработки файла несколько конструкторов, которые подключают различные типы кодеков, в зависимости от того, какой тип файла им передан. В этом случае, при открытии звукового файла, ваша программа будет автоматически подгружать звуковые кодеки, а в случае открытия видеофайла, будут подключаться видеокодеки.
Все эти принципы ООП делают возможным написание очень гибких программ, поведение которых легко адаптируется под меняющиеся условия их функционирования. При этом код не дублируется и программа остается хорошо структурированной и легко переносимой на другие платформы.
Конкретные детали объявления классов, полей и методов разберем на примере написания простейшей объектно ориентированной программы. Чтобы не было совсем скучно, используем бытовые аналогии и сущности
Пример ооп – программы.
Допустим, вы хотите создать некую компьютерную игру, описывающую повседневную жизнь некоторого человека у которого есть домашние животные – хомячек и кошка.
В этом случае вам понадобится несколько классов, отображающих эти сущности и взаимоотношения между ними:
Класс дом (Home), в котором они все живут.
Классы хомячек (Hamster), кошка (Cat), и хозяин (Master).
Вспомогательный класс Pets, обобщающий всех домашних животных.
У нас получается следующая иерархия классов:
Отобразим эту структуру в программный код java.
package homeanimals;
/**
*
* @author name */
public class Homeanimals {
public static void main(String[] args) {
Home.getS(); // проверяем доступную жилую площадь
Master m = new Master(); // поселяем в дом хозяина
m.buyPets(new Pets("Cat")); // покупаем кошку
m.buyPets(new Pets("Hamster", "Chip")); // покупаем хомячка по имени "Сhip"
m.getPets(); // пересчитываем питомцев
m.getFoods(); // кормим питомцев
m.getFoods("Hamster"); // кошка не наелась, скармливаем ей хомячка
}
}
class Home {
private static int S = 100; // общая жилая площадь
protected static Pets[] Pt = new Pets[10]; // массив с питомцами
protected static int foods; // запас еды в доме
public static int getS() { return S; } // метод, возвращающий значение S
} // class Home
class Pets extends Home {
private Object p; // объект питомца
public int id; // порядковый номер питомца в массиве
public String angry = "Yes"; // переменная сытый-голодный
public Pets() { } // конструктор 1
public Pets(String name) { // конструктор 2
if (name.equals("Cat")) { this.p = new Cat(); }
}
public Pets(String name, String nm) { // конструктор 3
if (name.equals("Cat")) { this.p = new Cat(nm); }
if (name.equals("Hamster")) { this.p = new Hamster(nm); } }
public Object getPet() { return p; } // функция, возвращающая объект питомца
} // class Pets
class Master extends Home {
public String name = "Vasia"; // переменная, содержащая имя хозяина
private double cash; // количество денег в кошельке хозяина
public Master() { // конструктор класса
setFoods(100); // задаем начальные значения переменных «еда» и «деньги»
cash = 100;
}
public void buyPets(Pets p) { // функция покупки питомцев
for(int i = 0; i < Pt.length; i++){
if(Pt[i] != null) { continue; }
p.id = i; Pt[i] = p; cash--;
break;
}
}
public void setFoods(int f) { foods = f; } // функция установки переменной еды
public void getFoods() { // функция кормления питомцев 1
for(Pets pet: Pt) {
if(pet == null) continue;
pet.angry = "No";
foods--;
}
}
public void getFoods(String pt_name) { // функция кормления питомцев 2
for(Pets pet: Pt) {
if(pet == null) continue;
if("Cat".equals(pet.getPet().getClass().getSimpleName())) {
pet.angry = "No";
}
if(pt_name.equals(pet.getPet().getClass().getSimpleName())) {
Pt[pet.id] = null; }
}
}
public int getPets() { // функция, вычисляющая число питомцев в доме
int numPets = 0;
for(Pets pet: Pt) {
if(pet != null) { numPets++; }
}
return numPets;
}
} // class Master
class Cat extends Pets { // класс «Кошка»
public final String name; // кличка
public String teil = "Long"; // отличительный параметр – длинна хвоста
public Cat(String nm) { // конструктор 1
super(nm);
this.name = nm; // обращение к полю через this
}
public Cat() { // конструктор 2
super();
name = "Cat"; }
} // class Cat
class Hamster extends Pets { // класс «Хомячек»
public final String name; // кличка
public String teil = "Short"; // отличительный параметр – длинна хвоста
public Hamster(String nm) { // конструктор
name = nm;
}
} // class Hamster
Данный тестовый пример в компактной форме отображает все базовые понятия, входящие в парадигму реализации ООП в java. Теперь давайте разберем все эти понятия подробнее.
Для начала остановимся на режимах доступа к полям и методам в классе. Рассмотрим класс Home:
class Home {
private static int S = 100; // общая жилая площадь
protected static Pets[] Pt = new Pets[10]; // массив с питомцами
protected static int foods; // запас еды в доме
public static int getS() { return S; } // метод, возвращающий значение S
} // class Home
Режимы доступа к полям и методам класса определяются модификаторами доступа. Всего их 4:
public
private
protected
(отсутствие модификатора)
Так, например, запись protected static Pets[] Pt = new Pets[10]; означает, что объявлен статический массив типа Pets, с режимом доступа «protected». Модификатор доступа всегда идет первым. Модификатор «public» означает, что переменная доступна везде, где только можно и её область видимости ничем не ограничена. Модификатор «private» означает, что к данной переменной могут получать доступ только методы того класса, в котором она объявлена. Модификатор «protected» означает, что область видимости ограничена подклассами данного класса и пакетом, в котором они находятся.
Отсутствие модификатора эквивалентно заданию модификатора доступа по умолчанию.
В этом случае поле или метод будут общедоступны но только в пределах текущего пакета.
Выбор правильных режимов доступа к полям и методам очень важный этап потому, что в соответствии с идеологией ООП, классы редко создаются для того, чтобы использоваться только в одной программе. Очень часто классы переносятся из одной программы в другую и даже включаются в состав общедоступных библиотек. Это приводит к тому, что другие программисты (или вы сами) в своих программах начинают использовать ваши объекты, вызывая из них те методы, которые помечены как общедоступные. В этом случае вы как разработчик библиотеки уже не можете свободно распоряжаться своими полями и методами, которые у вас помечены как public. Не можете к примеру, удалить или переименовать их, поскольку это вызовет нработоспособность огромного количества кода, который сторонние разработчики уже успеют наваять к этому времени.
Напротив, поля и методы, которые помечены как private, вы в любой момент можете удалить, если они вам больше не нужны, так как вы точно знаете, что кроме внутренностей вашего модуля, они нигде больше не используются.
Обратите внимание, что все переменные класса Home объявлены статическими, о чем говорит ключевое слово «static» в их названии.
Переменные и функции, объявленные статическими принадлежат к самому классу, а не к экземпляру класса. То есть, к ним можно обратиться даже в том случае, если ни одного экземпляра класса еще не создано. Название «статические» на самом деле выбрано не особо удачно, так как оно может дезориентировать. Кажется, что эти переменные не изменяются. Но это вовсе не так! Они могут перезаписываться и изменяться так же как и обычные. Меняется только способ обращения к ним. Если к обычным переменным можно обращаться только через экземпляр, и только, если экземпляр класса уже создан и под него выделена память, то к статическим переменным можно обращаться в любое время по имени самого класса. Создание экземпляра класса не является здесь необходимым, так как под все статические переменные и функции память выделяется сразу и на все время работы программы.
Object a = new Object();
a.some_field = 1; // пример обращения к обычному (нестатическому) полю
Object.b = 1; // пример обращения к статическому полю.
В нашем случае все поля и методы класса Home объявлены статическими из тех соображений, что сущность дом существует в единственном экземпляре независимо от жильцов. Список жильцов обязательно должен существовать в единственном экземпляре и всегда быть доступным. Случайное создание нескольких экземпляров этого списка крайне нежелательно. Объявление массива питомцев как статической переменной предотвращает случайные дублирования этого списка в различных экземплярах класса Home, даже если бы эти экземпляры создавались динамически.
Однако в реальных программах так делают не слишком часто. Статические поля в основном предназначаются для хранения каких – либо констант. Например, в классе Math так хранится число pi. Можно в любой момент получить к этой константе доступ через обращение
Math.PI
На всякий случай поясним общую логику работы программы. В классе Home объявлены ряд статических переменных. S представляет общую жилую площадь, которая определяет максимальное количество проживающих. Получить это значение можно через статический метод getS(). В доме есть некоторый запас еды для питомцев, который определяется переменной foods. Нам совсем не нужно, чтобы кто-то со стороны разбазаривал нашу еду, так же как и подселял к нам посторонних питомцев, поэтому эти поля объявлены protected. Они доступны как методам самого класса, так и методам потомков данного класса и больше никому. Потомком данного класса является класс Хозяин, в котором есть методы для управления количеством питомцев в доме и запасом еды.
Таким образом, мы приходим к важному понятию наследования. На самом деле наследование в основном необходимо для того, чтобы не дублировать один и тот же код в различных классах – потомках и при этом иметь доступ ко всем методам и полям родительского класса. Кроме того, наследование позволяет расширять функционал родительского класса путем переопределения методов. Чтобы переопределить родительский метод в классе – потомке, необходимо создать функцию с таким же именем и списком вызываемых параметров. Тогда при попытке вызвать из класса-потомка эту функцию, будет вызвана именно функция класса-потомка. Говорят, что данный метод «перекрыт». Если в классе-потомке функция с таким именем не переопределена, при попытке вызвать функцию из экземпляра класса-потомка, на самом деле вызывается функция родительского класса.
Отношение наследования между классами в java обозначается ключевым словом extends в заголовке класса:
class Master extends Home {
…
Кстати, о количестве доступной площади в доме можно было бы спросить и у самого хозяина:
Master m = new Master(); // создаем экземпляр класса хозяина оператором new
…
m.getS(); // будет вызван метод родительского класса
Кроме класса хозяина, в доме проживают и питомцы, которые должны пользоваться некоторыми удобствами жилища, поэтому класс питомцы (Pets) также является классом-потомком класса Home. Однако в данной программе это наследование чисто формальное, поскольку никакими полями или методами класса Home класс Pets не пользуется. Скорее наоборот, клас Home содержит массив, содержащий экземпляры класса Pets. Однако питомцы могут быть самыми разными, поэтому уже от класса Pets мы наследуем два различных класса Cat и Hamster. У каждого питомца есть свои отличительные признаки, например, длинна хвоста и собственное имя, поэтому эти признаки целесообразно вынести в отдельные классы. Есть у них и общее – например, чувство голода, поэтому поле определяющее сыт питомец или голоден помещено в родительский класс Pets.
В этом же классе содержится номер питомца в массиве Pt из класса Home. Это поле id. Оно добавлено для того, чтобы каждый питомец сам знал свое место в доме, то есть – в массиве Pt и в переборе массива для того, чтобы найти питомца необходимости не было.
Поскольку класс Pets понятия не имеет, какой питомец будет в него записан, переменная, которая содержит конкретно класс питомца объявлена объектом общего вида:
…
private Object p;
…
Это порождает некоторые проблемы, о которых мы поговорим чуть позже.
Очень часто при создании любого класса необходимо инициализировать какие – то переменные класса. При создании класса Master нужно установить начальные параметры запаса еды и денег. При инициализации экземпляра класса Pets, нужно создать подходящий класс питомца, разместить его в массиве Pt и заполнить поля p и id. Этими действиями в классе занимается специальный метод, который называется конструктор. Этот метод вызывается только один раз при создании экземпляра класса оператором new и ему через оператор new передаются какие-либо параметры. Имя конструктора обязательно должно совпадать с именем класса и он обязательно должен иметь модификатор доступа public. Или доступ по умолчанию.
Таких конструкторов в классе Pets целых три. Дело в том, что при покупке питомца ему не всегда дают имя сразу, поэтому мы воспользовались полиморфизмом, чтобы отразить этот факт в нашей программе. Три конструктора класса Pet могут в качестве входных параметров не принимать ничего,
public Pets()
принимать один параметр (имя зверушки)
public Pets(String name)
или принимать и название класса питомца и его имя
public Pets(String name, String nm)
при этом внутри конструкторов производится выбор, какие конкретные объекты питомцев создавать и записывать в переменную p. При создании экземпляра класса-потомка компилятор должен быть уверен, что класс – предок существует, иначе из класса-потомка невозможно будет обратиться к полям и методам родителя. Для этих целей предусмотрен явный вызов конструктора родителя в классе – потомке перед тем, как будут инициализироваться поля класса-потомка. Такое обращение с помощью зарезервированной функции super() продемонстрировано в конструкторах класса Cat. Надо понимать, что неявный вызов конструктора класса – родителя производится в любом случае. Обычно этот конструктор вызывают явно чтобы передать ему какие –либо параметры, в данном случае имя животного. Там же продемонстрировано обращение к собственному полю класса Cat через зарезервированное ключевое слово this. Оно также употребляется неявно даже если вы его не укажете с переменной, но явное прописывание this позволяет программисту быть уверенным, что вызывается именно локальная переменная класса – потомка, а не поле класса – родителя.
Обратите также внимание, что в этих конкретных классах зверьков, есть переменные, помеченные модификатором final:
public final String name;
Модификатор final объявляет о том, что данное поле после инициализации больше никогда не изменяется. Нам обязательно необходимо объявить поля name, которые содержат клички животных как final, поскольку после того, как животное назвали, его кличку обычно не меняют. При попытке непреднамеренного изменения такого поля, появится ошибка.
Все основные функции по управлению домашним зверинцем сосредоточены в классе Master. При покупке животного, в функции buyPets() –
public void buyPets(Pets p) {
for(int i = 0; i < Pt.length; i++){
if(Pt[i] != null) { continue; }
p.id = i; Pt[i] = p; cash--;
break;
}
мы находим свободное место в массиве питомцев, запоминаем в питомце его номер, записываем питомца в массив и уменьшаем количество денег в кошельке на величину покупки.
Самый интересный момент наступает во время кормления. Хомяк может питаться чем угодно, а кошке за неимением подходящего корма можно скормить хомяка.
Это функция getFoods(). Есть простой вариант этой функции, а есть более сложный. Здесь так же использован полиморфизм и перебор массива питомцев в цикле for-each для поиска кошки и подходящего хомяка на съедение. Особенность процесса поиска в том, что по сути мы сравниваем два объекта. Так как в java все является объектом, то даже простое сравнение двух строк превращается в некоторую проблему. Поэтому мы вынуждены использовать специальную функцию equals() как для поиска хомяка так и для поиска кошки. Она вычисляет полную эквивалентность двух объектов, а конкретно нас интересуют имена классов объектов, которые содержатся в массиве.
…
if("Cat".equals(pet.getPet().getClass().getSimpleName())) {
…
Некоторое время назад, если помните, мы обсуждали, что объект Pets ничего не знает, внутреннем содержимом объекта р, который в него записан. Поэтому нам необходимо точно убедиться, что мы нашли именно кошку, и не пытались скормить хомяка другому хомяку. Тоесть, прежде, чем менять состояние поля pet.angry.
Синтаксис такого сравнения немного странный, но вполне понятный, если вспомнить, что строка «Cat» - это по сути тоже объект и у него есть метод equals(), который может сравнить для нас имя класса с этой строкой.
Если хомяк съеден, то в массиве питомцев его быть уже не должно, поэтому обнуляем соответствующую ссылку:
…
if(pt_name.equals(pet.getPet().getClass().getSimpleName())) {
Pt[pet.id] = null; }
…
Запускается все это как обычно из функции main() главного модуля, которая является входной точкой любого приложения java.
