Класс Box определяет три переменные экземпляра: width (ширина), height (высота) и depth (глубина). В настоящий момент класс Box не содержит никаких методов.
Как мы уже говорили, класс определяет новый тип данных. В данном случае новый тип данных называется Box. Это имя будет использоваться для объявления объектов типа Box. Не следует забывать, что объявление class создает только шаблон, но не конкретный объект. Таким образом, приведенный выше код не приводит к появлению каких-нибудь объектов типа Box.
Чтобы действительно создать объект класса Box, нужно воспользоваться оператором new
После выполнения этого оператора объект myBox станет экземпляром класса Box. Таким образом, он обретет "физическое" существование.
Также следует напомнить, что каждый объект содержит собственную копию переменной экземпляра, которая определена в классе. Каждый объект типа Boxбудет содержать собственные копии переменных width, height и depth(рис. 4.2).
Изменения в переменных экземпляра одного объекта не влияют на переменные экземпляра другого объекта. Таким образом, каждый объект класса Box будет содержать собственные копии переменных width, height и depth. Для доступа к этим переменным служит оператор-точка (.). Эта операция связывает имя объекта с именем переменной экземпляра. Например, чтобы присвоить переменной width экземпляра myBox значение 100, нужно выполнить следующий оператор:
myBox.width=100;
Этот оператор предписывает компилятору, что копии переменной width, хранящейся в объектe myBox, требуется присвоить значение 100. В общем, операция-точка служит для доступа как к переменным экземпляра, так и к методам в пределах объекта.
Ниже приведет пример программы, в которой используется класс Box
publicclassMain {publicstaticvoidmain(String[] args) {// Создаем объект типа BoxBox myBox =newBox();// Присваиваем значения переменным экземпляра myBoxmyBox.width=10;myBox.height=20;myBox.depth=15;// Рассчитываем объем коробкиdouble volume =myBox.width*myBox.height*myBox.depth;System.out.println("Объем равен: "+ volume); }}
Как пояснялось ранее, каждый объект содержит собственные копии переменных экземпляра. Это означает, что при наличии двух объектов класса Box каждый из них будет содержать собственные копии переменных width, height и depth. Следует, однако, иметь ввиду, что изменения в переменных экземпляра одного объекта не влияют на переменные экземпляра другого. Например, в следующей программе объявлены два объекта класса Box:
publicclassMain {publicstaticvoidmain(String[] args) {Box myBox1 =newBox();Box myBox2 =newBox();// Присваиваем значения для mybox1myBox1.width=10;myBox1.height=20;myBox1.depth=15;// Присваиваем значения для mybox2myBox2.width=3;myBox2.height=6;myBox2.depth=9;double volume;// объем первой коробки volume =myBox1.width*myBox1.height*myBox1.depth;// будет выведено 3000System.out.println("Объем равен: "+ volume);// объем второй коробки volume =myBox2.width*myBox2.height*myBox2.depth;// будет выведено 162System.out.println("Объем равен: "+ volume); }}
Программа выводит следующий результат:
Объем равен: 3000.0
Объем равен: 162.0
Как видите, данные из объекта myBox1 полностью изолированы от данных, содержащихся в объекте myBox2.
Методы класса
Как упоминалось ранее, классы состоят из двух компонентов: переменных экземпляра и методов. Общая форма метода выглядит следующим образом:
[возвращаемый тип] имя ([список параметров]) {
[тело метода]
}
где возвращаемый тип означает конкретный тип данных, возвращаемый методом. Он может быть любым допустимым типом данных, в том числе и типом созданного класса. Если метод не возвращает значение, то его возвращаемым типом должен быть void. В качестве имени методов может быть любой допустимый идентификатор, кроме тех, которые уже используются другими элементами кода в текущей области действия. А список параметров обозначает последовательность пар "тип-идентификатор", разделенных запятыми. По существу, параметры - это переменные, которые принимают значения аргументов, передаваемых методу во время его вызова. Если у метода отсутствуют параметры, то список параметров оказывается пустым. Методы, возвращаемый тип которых отличается от void, возвращают значение вызывающей части программы с помощью оператора return.
Вернемся к нашему примеру с классом Box. Было бы логично, если бы расчет объема коробки выполнялся в классе Box, поскольку объем коробки зависит от ее размеров. Для этого добавим в класс Box метод getVolume()
В первой строке вызывается метод volume() для объекта myBox1. Следовательно, метод volume() вызывается по отношению к объекту myBox1, для чего было указано имя объекта, а вслед за ним - операция-точка. Таким образом, в результате вызова метода myBox1.volume() выводится объем коробки, определяемого объектом myBox1, а в результате вызова метода myBox2.volume() - объем коробки, определяемого объектом myBox2.
При вызове метода myBox1.volume() исполняющая система Jаvа передает управление коду, определенному в теле метода volume(). По окончании выполнения всех операторов в теле метода управление возвращается вызывающей части программы и далее ее выполнение продолжается со строки кода, следующей за вызовом метода. В самом общем смысле метод - это способ реализации подпрограмм в Java.
В методе volume() следует обратить внимание на еще одну очень важную особенность: ссылка на переменные экземпляра width, height и depth делается непосредственно без указания перед ними имени объекта или операции-точки. Когда в методе используется переменная экземпляра, определенная в его же классе, это делается непосредственно, без указания явной ссылки на объект и применения операции-точки . Это становится понятным, если немного подумать. Метод всегда вызывается по отношению к какому-то объекту его класса. Как только этот вызов сделан, объект известен. Таким образом, в теле метода вторичное указание объекта совершенно излишне. Это означает, что переменные экземпляра width, height и depth неявно ссылаются на копии этих переменных, хранящиеся в объекте, который вызывает метод volume().
Подведем краткие итоги. Когда доступ к переменной экземпляра выполняется из кода, не входящего в класс, где определена переменная экземпляра, следует непременно указать объект с помощью операции-точки. Но когда такой доступ осуществляется из кода, входящего в класс, где определена переменная экземпляра, ссылка на переменную может делаться непосредственно. Эти же правила относятся и к методам.
Следует обратить внимание, что метод getVolume() возвращает значение 3000, и это значение рассчитанного объема сохраняется в переменной vol. При обращении с возвращаемыми значениями следует принимать во внимание два важных обстоятельства:
тип данных, возвращаемых методом, должен быть совместим с возвращаемым типом, указанным в методе. Так, если какой-нибудь метод должен возвращать логический тип boolean, то возвратить из него целочисленное значение нельзя;
переменная, принимающая возвращаемое методом значение (например, vol), также должна быть совместима с возвращаемым типом, указанным для метода.
2. Язык моделирования UML
UML – унифицированный язык моделирования (Unified Modeling Language) – это система обозначений, которую можно применять для объектно-ориентированного анализа и проектирования. Его можно использовать для визуализации, спецификации, конструирования и документирования программных систем.
Словарь UML включает три вида строительных блоков:
Диаграммы.
Сущности.
Связи.
Сущности – это абстракции, которые являются основными элементами модели, связи соединяют их между собой, а диаграммы группируют представляющие интерес наборы сущностей.
Диаграмма – это графическое представление набора элементов, чаще всего изображенного в виде связного графа вершин (сущностей) и путей (связей). Язык UML включает 13 видов диаграмм, среди которых на первом месте в списке — диаграмма классов.
Диаграмма классов описывает типы объектов системы и различного рода статические отношения, которые существуют между ними. На диаграммах классов отображаются также свойства классов, операции классов и ограничения, которые накладываются на связи между объектами.
Если кто-нибудь подойдет к вам в темном переулке и спросит: «Хотите посмотреть на диаграмму UML?», знайте – скорее всего, речь идет о диаграмме класса. Большинство диаграмм UML, которые я встречал, были диаграммами классов.
- Мартин Фаулер
Графически класс изображается в виде прямоугольника, разделенного на три блока горизонтальными линиями:
имя класса;
атрибуты (свойства) класса;
операции (методы) класса.
Для атрибутов и операций может быть указан один из трех типов видимости:
- private;
~ без модификатора (default);
# protected;
+ public.
Видимость для полей и методов указывается в виде левого символа в строке с именем соответствующего элемента.
Каждый класс должен обладать именем, отличающим его от других классов. Имя – это текстовая строка. Имя класса может состоять из любого числа букв, цифр и знаков препинания (за исключением двоеточия и точки) и может записываться в несколько строк.
Атрибут (свойство) – это именованное свойство класса, описывающее диапазон значений, которые может принимать экземпляр атрибута. Класс может иметь любое число атрибутов или не иметь ни одного. В последнем случае блок атрибутов оставляют пустым. Можно уточнить спецификацию атрибута, указав его тип, кратность (если атрибут представляет собой массив некоторых значений) и начальное значение по умолчанию.
Статические атрибуты класса обозначаются подчеркиванием.
Операция (метод) – это реализация метода класса. Класс может иметь любое число операций либо не иметь ни одной. Часто вызов операции объекта изменяет его атрибуты. Графически операции представлены в нижнем блоке описания класса. Допускается указание только имен операций. Имя операции, как и имя класса, должно представлять собой текст. Можно специфицировать операцию, устанавливая ее сигнатуру, включающую имя, тип и значение по умолчанию всех параметров, а применительно к функциям – тип возвращаемого значения.
Абстрактные методы класса обозначаются курсивным шрифтом. Статические методы класса обозначаются подчеркиванием.
Изображая класс, не обязательно показывать сразу все его атрибуты и операции. Для конкретного представления, как правило, существенна только часть атрибутов и операций класса. В силу этих причин допускается упрощенное представление класса, то есть для графического представления выбираются только некоторые из его атрибутов. Если помимо указанных существуют другие атрибуты и операции, вы даете это понять, завершая каждый список многоточием. Чтобы легче воспринимать длинные списки атрибутов и операций, желательно снабдить префиксом (именем стереотипа) каждую категорию в них. В данном случае стереотип – это слово, заключенное в угловые кавычки, которое указывает то, что за ним следует.
Рассмотрим пример класса - его графическое представление и код на языке Java.
Human.java
publicclassHuman {privateString name; // private это "-"Boolean gender =true; // default это "~"protectedlong chromosome; // protected это "#"publicint age; // public это "+"// Статические атрибуты подчеркиваютсяpublicstaticlong dna;// Константы можно отобазить как readOnlyfinalint SECRET =924;/* Как правило, конструкторы * изображаются как обычные методы */publicHuman() {}publicHuman (String name) {this.name= name;}/* Методы отображаются как * [-~#+]имя(тип_аргументов): возвращаемый тип * Например: public String foo (int a, double b) * будет +foo(int, double): String */publicvoidbreath() {}privatevoidsleep(int hours) {}protectedbooleansneeze() { returntrue; }intrun (int speed,String direction) { return0; }publicstaticintcalculateAge() { return0; }}
Задание на лабораторную работу
Для каждого класса необходимо предоставить его графическое представление в диаграмме классов UML. Графическое представление добавить в протокол лабораторной работы и принести на защиту лабораторной работы.
1. Класс TimeSpan
1.1 Создание класса
Создайте класс с названием TimeSpan. Объект класса TimeSpanхранит интервал времени в часах и минутах (к примеру, временной интервал между 8:00 и 10:30 это 2 часа 30 минут). Класс TimeSpanдолжен иметь следующие методы
Вы должны продумать:
как хранить значение временного интервала;
типы входных аргументов методов;
корректно реализовать требуемые методы;
в методах предусмотреть проверки на корректность входных данных.
1.2. Добавление метода subtract()
Добавьте к написанному ранее классу метод subtract()со следующей сигнатурой
Метод вычитает из текущего временного интервала входной временной интервал. Если входной интервал больше текущего, выйдите из метода и не модифицируйте текущий интервал (можете выбросить исключение IllegalArgumentException, если знаете что это и знаете как выбрить исключение).
1.3 Добавление метода scale()
Добавьте к написанному ранее классу метод subtract()со следующей сигнатурой
Метод увеличивает текущий интервал в factorчисло раз. Например, если текущий временной интервал равен 1 час 45 минут, а входной аргумент равен 2, то интервал увеличится до 3 часов 30 минут. Убедитесь, что factor- неотрицательное число, помните про пересчет минут в часы при увеличении интервала.
2. Класс BankAccount
2.1 Добавление поля transactionFee
В программе существует класс BankAccount, исходный код которого приведен ниже.
Каждый объект класса предназначен для описания одного счета клиента банка, включая его имя и баланс. Модифицируйте класс следующим образом:
добавьте поле transactionFeeтипа double, которое хранит сумму, которая вычитается из баланса каждый раз, когда клиент банка снимает деньги (метод withdraw()). Изначальное значение равно 0.00, но значение может быть изменено в дальнейшем. Производите вычитание суммы каждый раз, когда клиент осуществляет снятие денег;
сделайте так, чтобы в результате снятия денег и снятия transactionFee, баланс счета не мог опуститься ниже нуля. Если в результате снятия денег и transactionFee баланс может стать отрицательным, выйдите из метода и не производите модификацию баланса вообще;
в методe deposit() входной аргумент не должен быть ноль или меньше;
в методе withdraw() входной аргумент не должно быть ноль или меньше.
Модификация класса может потребовать создания новых методов и полей.
2.2 Добавление метода transfer()
Добавьте к модифицированному ранее классу BankAccoutметод transfer() со следующей сигнатурой
Метод transfer() осуществляет перевод денег из одного счета (текущего) на другой счет (объект receiver). Первый параметр - счет получателя денег, второй параметр - количество денег, которые переводятся со счета на счет.
Важно!
При снятии денег должна учитываться комиссия (поле transactionFee). Следовательно, из текущего объекта должна быть вычтено amount + transactionFee.
Метод должен модифицировать баланс двух объектов. У текущего объекта должна вычтена сумма amount + transactionFeeиз баланса, а у объекта receiverдолжен быть пополнен баланс счета на сумму amount.
При реализации метода убедитесь, что amount > 0.
Если у текущего объекта баланс меньше amount + transactionFee, то выйдите из метода без какого-либо перевода денег.
3. Класс Student
Создайте класс Student, который хранит информацию о студенте. У студента есть имя, год обучения (от 1 до 4), а также массив изучаемых дисциплин.
Класс Student должен иметь следующие методы
Вы должны продумать:
как хранить данные студенты (типы данных, структуры и так далее);
типы входных аргументов методов и тип возвращаемого значения;
корректно реализовать требуемые методы;
в методах предусмотреть проверки на корректность входных данных.
Название метода
Описание метода
TimeSpan(hours, minutes)
Конструктор. Входные аргументы – количество часов и минут
getHours()
Возвращает целое количество часов во временном интервале, без учета количества минут (например, если интервал 2 часа 15 минут, то метод вернет 2, так как 15 минут мы не учитываем)
getMinutes()
Возвращает количество минут во временном интервале, без учета количества часов (например, если интервал 2 часа 15 минут, то метод вернет 15, так как 2 часа мы отбрасываем)
add(hours, minutes)
Добавляет указанное количество часов и минут к промежутку. Новое значение промежутка должно пересчитываться корректно. Например, 2 часа 15 минут + 1 час 45 минут = 4 часа 0 минут. Помните, что необходимо проверить корректность входных аргументов (количество часов - не отрицательное число, количество минут от 0 до 59).
addTimeSpan(timespan)
Добавляет входной промежуток времени к указанному промежутку (используйте методы getHours() и getMinutes() для получения значений часов и минут).
getTotalHours()
Возвращает количество часов в промежутке в виде дробного числа. Например, если временной интервал 9 часов 45 минут, то метод должен вернуть 9.75.
getTotalMinutes()
Возвращает количество минут в текущем промежутке времени (помните, что в интервале есть еще показатель часов, который нужно умножить на 60)
Название метода
Описание метода
Student(name, year)
Конструктор. Входные аргументы – имя и год обучения
addCourse(courseName)
Добавляет дисциплину(в формате String) к массиву дисциплин студента
dropAll()
Удаляет все дисциплины студента
getCourseCount()
Возвращает количество дисциплин, которые студент изучает
getName()
Возвращает имя студента
getTuition()
Возвращает количество денег, которые студент заплатил за обучение (с условием, что каждый курс обошелся студенту в 1000 гривен)
getYear()
Возвращает год обучения студента (от 1 до 4)
publicvoidsubtract(TimeSpan span)
publicvoidscale(int factor)
// Каждый объект класса BankAccount представляет данные одного// счета пользователя, включая имя и баланс счетаpublicclassBankAccount {String name;double balance;publicvoiddeposit(double amount) { balance = balance + amount; }publicdoublegetBalance() {returnthis.balance; }publicbooleanwithdraw(double amount) { balance = balance - amount;returntrue; }}
научиться проектировать классы с учетом принципа инкапсуляции;
научиться применять механизм перегрузки методов для реализации статического полиморфизма классов.
Теоретические вопросы
Теоретические вопросы (ответы содержатся в лекционном материале):
Из каких частей состоит тело класса?
Опишите процесс создания объектов в Java. Какую роль в создании объектов играет оператор new?
Что такое ссылочная переменная? Как передаются данные в метод в Java?
Что такое конструктор? Зачем нужен конструктор в ООП? Чем синтаксис конструктора отличается от синтаксиса обычного метода?
Объясните, откуда берется ключевое слово this в методах класса? Как можно использовать ключевое слово this при написании кода методов?
Задание на лабораторную работу
Для каждого класса необходимо предоставить его графическое представление в диаграмме классов UML. Графическое представление добавить в протокол лабораторной работы и принести на защиту лабораторной работы.
Класс DynamicIntArray
Необходимо создать класс DynamicIntArray, который реализует функционал динамического массива целых чисел.
При реализации динамического массива учитывайте следующее:
при реализации класса вам необходимо указать область видимости для полей и методов. Подумайте о том, какие методы необходимо отнести к "интерфейсу", а какие к реализации;
размер динамического массива для внешнего пользователя не имеет значения и теоретически неограничен;
внешний пользователь не должен знать и догадываться, что динамический массив "внутри" реализован с помощью обычного статического массива;
вы должны предусмотреть различные проверки на некорректность индексов, которые мог ввести пользователь;
вы должны написать "реализацию" класса таким образом, чтобы можно моделировать динамическое поведение массива с помощью обычного статического;
продумайте, какие аргументы методы должны принимать и какие значения возвращать.
Перейдите во вкладку "Подсказка", чтобы получить подсказку, которая поможет вам в реализации класса или попробуйте реализовать класс без подсказки.
При создании динамического массива инициализируйте внутренний статический массив с каким-то дефолтным изначальным размером;
При вставке элементов проверяйте, не выходите ли вы за пределы внутреннего статического массива;
Если внутренний массив переполнен, создавайте новый массив большего размера: создавайте новый массив, копируйте значения из старого массива в новый, заменяйте новый массив на старый. Помните, что массивы в Java - объекты и что поле - просто указатель на массив и что указатель может указывать на другой массив;
При уменьшении размера массива реализуйте уменьшение внутреннего статического массива. Подумайте, как это лучше сделать, чтобы не уменьшать внутренний массив при каждом удалении элемента.
ВАЖНО! Динамический массив необходимо реализовать с помощью статического целочисленного массива!
Для внешнего пользователя класса поведение класса выглядит следующим образом:
пользователь может создать новый динамический массив с конструктором без параметров или с указанием начальных значений динамического массива;
пользователь может вставить элемент в определенный индекс динамического массива (остальные элементы динамического массива сдвигаются вправо) с помощью метода add(). Если метод add() вызывается без указания индекса, элемент добавляется в конец динамического массива;
пользователь может перезаписать значение i-го элемента динамического массива с помощью метода set();
пользователь может удалить элемент динамического массива по индексу с помощью метода removeAt(). Если метод removeAt() вызывается без указания индекса, то удаляется последний элемент в динамическом массиве.
пользователь может удалить первое вхождение значения в динамическом массиве с помощью метода remove().
пользователь может получить индекс первого вхождения элемента в динамическом массиве с помощью метода indexOf(). Подумайте о том, как сигнализировать пользователю о том, что искомый элемент в динамическом массиве не найден;
пользователь может проверить, есть ли такой элемент в массиве с помощью метода contains();
пользователь может получить размер динамического массива с помощью метода size();
пользователь может проверить, пустой ли массив с помощью метода isEmpty();
пользователь может очистить массив полностью с помощью метода clear().
1. Инкапсуляция
Инкапсуляция в Java реализована с помощью использования модификаторов доступа.
Язык Java предоставляет несколько уровней защиты, которые позволяет настраивать область видимости данных и методов. В Java имеется четыре категории видимости элементов класса:
private– члены класса доступны только членам данного класса. Всё что объявлено private, доступно только конструкторам и методам внутри класса и нигде больше. Они выполняют служебную или вспомогательную роль в пределах класса и их функциональность не предназначена для внешнего пользования. Закрытие (private) полей обеспечивает инкапсуляцию;
по умолчанию (package-private) – члены класса доступны классам, которые находятся в этом же пакете;
protected– члены класса доступны классам, находящимся в том же пакете, и подклассам – в других пакетах;
public– члены класса доступны для всех классов в этом и других пакетах.
Модификатор класса указывается перед остальной частью описания типа отдельного члена класса. Это означает, что именно с него должен начинаться оператор объявления класса.
Когда член класса обозначается модификатором доступа public, он становится доступным для любого другого кода в программе, включая и методы, определенные в других классах.
Когда член класса обозначается модификатором private, он может быть доступен только другим членам этого класса. Следовательно, методы из других классов не имеют доступа к закрытому члену класса.
При отсутствии модификатора доступа, члены класса доступны другим членам класса, который находится в этом же пакете.
Модификатор доступа protected связан с использованием механизма наследования и будет рассмотрен позже.
Модификатор доступа указывается перед остальной частью описания типа отдельного члена класса (то есть, именно с модификатора доступа начинается объявление члена класса).
Член класса (переменная, конструктор, методы), объявленный public, доступен из любого метода вне класса.
Всё что объявлено private, доступно только конструкторам и методам внутри класса и нигде больше. Они выполняют служебную или вспомогательную роль в пределах класса и их функциональность не предназначена для внешнего пользования. Закрытие (private) полей обеспечивает инкапсуляцию.
Сокрытие полей класса
В подавляющем большинстве случаев, поля класса объявляются как private (это не касается статических переменных и констант, там ситуация может быть другая). Должны быть веские основания объявить поле класса общедоступным. Манипулирование данными должно осуществляться только с помощью методов.
Для того чтобы дать возможность получить доступ к переменной или дать возможность изменить ее значение, объявляют специальные методы, которые называются "геттерами" и "сеттерами".
Геттер возвращает значение приватного поля, тогда как сеттер меняет значение приватного поля (новое значение передается в качестве аргумента метода).
Хотя сигнатура и имена геттеров и сеттеров могут быть любыми, приучите себя соблюдать строгий шаблон для объявления геттеров и сеттеров.
Геттер должен иметь префикс get, после которого идет название поля с большой буквы. Геттер, как правило, не имеет входных аргументов.
Сеттер должен иметь префикс set, после которого идет название поля с большой буквы. Сеттер принимает на вход новое значение поля. Возвращаемый тип, как правило, void.
Большинство IDE для Java имеют механизм для генерации геттеров и сеттеров. В IntelliJ IDEA нажмите комбинацию Alt+Insert находясь в окне редактирования java-файла. Откроется контекстное меню Generate, где вы можете выбрать генерацию геттера и сеттера, после чего указать поля, для которых необходимо сгенерировать методы.
5. Пример использования инкапсуляции
Представим, что нам необходимо создать класс «Корзина» (Cart), который хранит в себе набор объектов класса «Товар» (Item).
Какие методы «Корзина» должна предоставлять для внешнего использования? Это могут быть, например, методы «Добавить товар», «Убрать последний добавленный товар», «Подсчет суммы цен товаров в корзине», «Повышение цен в корзине на N процентов» и «Снижение цен в корзине на N процентов».
Как вы можете заметить, это публичные методы, а значит, их можно вызвать через оператор-точку имея ссылку ну объект.
Перечень этих публичных методов и составляет интерфейс класса – то есть, с помощью этих методов объект класса будет взаимодействовать с внешним миром.
Эти методы имеют вполне четко определенные входные аргументы и могут возвращать значения четко определенных типов, и никак иначе. По аналогии с этим, поворот колес автомобиля осуществляется четко определенным образом – поворотом руля, и бензин надо заливать в четко определенное отверстие крышки бензобака, а не как-то еще.
То – как будет реализовано хранение товаров в корзине – это внутренняя логика класса и она не должна быть доступна внешнему миру, она должна быть скрыта от внешнего вмешательства. Другие классы, которые будут использовать объекты класса Cartне должны знать и не должны иметь доступ к тому – как там «внутри» реализовано хранение товаров, подсчет цен и изменение цены на определенный процент и так далее, они могут только лишь использовать предоставленные им публичные методы. Давайте реализуем «Корзину» с помощью структуры «стек», которая, в свою очередь, реализована обычным массивом.
Cart.java
publicclassCart {privateItem[] stack; // массив для реализации стекаprivateint topIndex; // указатель на вершину стека// При создании корзины мы должны// указать максимальное количество элементов// в корзинеpublicCart(int capacity) { stack =newItem[capacity]; topIndex =-1; }// Добавление нового товара в корзинуpublicbooleanaddItem(Item item) {returnpush(item); }// Приватный метод, который реализует добавление в стекprivatebooleanpush (Item item) {// Добавляем товар в стекreturntrue; // или false если не стек переполнен }// Удаление последнего добавленного товара в корзинуpublicItemdeleteLastAddedItem() {returnpop(); }// Приватный метод, который реализует извлечение из стекаprivateItempop() {returnnewItem(); // Извлеченный из стека товар }}
Как мы видим, массив с товарами, указать на вершину стек объявлены как privateчлены класса. Это значит, что мы не можем получить к ним доступ извне – они доступны только внутри данного класса.
Программиста, который будет использовать класс Cart, не должна волновать ситуация с переполнением стека, с попыткой извлечь элемент из пустого стека, он не должен следить за указателем на вершину стека, он даже не должен знать что это стек.
Для него объект класса Cart это некоторый объект, который предоставляет «услугу» в виде корзины товаров и с этой корзиной можно работать с помощью определенных публичных методов.
В дальнейшем мы можем переделать класс Cart и поменять внутреннюю реализацию. Мы можем использовать структуру "очередь", мы можем использовать коллекции, мы можем иначе реализовать операции добавления и удаления элемента в стеке, но если мы сохраним интерфейс класса неизменным, то для внешнего мира эти изменения внутренней логики не будут важны и если мы поменяем внутреннюю логику одного небольшого участка программы, то вся остальная программа будет работать так же.
2. Перегрузка методов
1. Что такое перегрузка методов
В Java разрешается в одном и том же классе определять два или более метода с одинаковым именем, если только объявления их параметров отличаются. В этом случае методы называются перегружаемыми, а сам процесс – перегрузкой метода (method overloading).
Если у методов одинаковые имена, как Java узнает, какой именно из них вызывается? Ответ прост: перегружаемые методы должны отличаться по типу и/или количеству входных параметров. Даже разного порядка аргументов достаточно для того, чтобы методы считались разными (хотя это не рекомендуется).
Перегрузка по возвращаемым значениям
Логично спросить, почему при перегрузке используются только имена классов и списки аргументов? Почему не идентифицировать методы по их возвращаемым значениям?
Идентифицировать их нельзя, потому что Java в этом случае не может определить, какая версия метода должна выполняться.
При вызове перегружаемого метода для определения нужного варианта в Java используется тип и\или количество аргументов метода. Следовательно, перегружаемые методы должны отличаться по типу и\или количеству их параметров. Возвращаемые типы перегружаемых методов могут отличаться, но самого возвращаемого метода недостаточно, чтобы отличить два разных варианта метода. Когда в исполняющей среде Java встречается вызов перегружаемого метода, в ней просто выполняется тот вариант, параметры которого соответствуют аргументам, указанным в вызове.
Перегрузка методов позволяет поддерживать принцип «один интерфейс, несколько методов».
В языках программирования без перегрузки методов, каждому методу должно быть присвоено однозначное имя. Но зачастую требуется реализовать, по существу, один и тот же метод для разных типов данных.
В таком случае, в языках программирования без перегрузки реализуют несколько методов, которые немного отличаются названиями.
Перегрузка методов ценна тем, что позволяет обращаться к похожим методам по общему имени. Следовательно, имя представляет общее действие, которое должно выполняться. Выбор подходящего варианта метода для конкретной ситуации входит в обязанности компилятора.
Ничто не запрещает вам реализовать несколько перегруженных методов, каждый из которых будет работать совершенно по-разному. Но на практике крайне рекомендуется, чтобы перегруженные методы реализовывали одну и ту же общую операцию.
Перегрузка конструкторов
Наряду с перегрузкой обычных методов можно также выполнять перегрузку конструкторов. Перегружаемые конструкторы – это норма и часто используемый прием.
Соответствующий перегружаемый конструктор вызывается в зависимости от параметров, указываемых при выполнении оператора new.
Если вы пишете для класса несколько конструкторов, иногда бывает удобно вызвать один конструктор из другого, чтобы избежать дублирования кода. Такая операция проводится с использованием ключевого слова this.
Название метода
Описание
public Cart(int capacity)
Конструктор с 1 параметром – максимальным количеством товаров в корзине.
public boolean addItem(Item item)
Добавление товара в корзину. Возвращает успешность операции.
public Item deleteLastAddedItem()
Удаление последнего добавленного товара в корзину. Возвращает удаленный товар.
public double calculateItemPrices()
Подсчет суммы цен всех товаров в корзине.
public void raiseItemPrices(double percent)
Поднять цены товаров в корзине на определенный процент (значение процента передается как аргумент метода).
public void cutItemPrices(double percent)
Снизить цены товаров в корзине на определенный процент (значение процента передается как аргумент метода).
classMyClass {publicvoidfoo() {// ... код }publicvoidfoo(String s) {// ... код }}
// ДАННЫЙ КОД ВЫЗОВЕТ ОШИБКУ КОМПИЛЯЦИИ!classMyClass {publicintfoo() {return0; }publicdoublefoo() {return0; }}
classMyClass {publicMyClass() {// какой-то код }publicMyClass(int arg0) {// какой-то код }publicMyClass (int arg0,String arg1) {// какой-то код }}
научиться проектировать классы с учетом принципа инкапсуляции;
научиться применять механизм перегрузки методов для реализации статического полиморфизма классов.
Наследование и композиция
Повторное использование кода
Возможность повторного использования кода принадлежит к числу важнейших преимуществ языков объектно-ориентированного программирования.
композиция (composition) - объекты уже имеющихся классов просто создаются внутри нового класса. Программист просто использует функциональность уже готового кода;
наследование (inheritance) - новый класс создается как специализация уже существующего класса. Взяв существующий класс за основу, вы добавляете к нему свой код без изменения существующего класса.
Композиция - использование функционала одних объектов в составе других объектов. Рассмотрим пример класса FileManager, в котором определен метод для сохранения текстовых данных в файл. Класс Document использует функционал класса FileManager, чтобы сохранить текстовый документ на жесткий диск.
classFileManager {publicvoidsaveToFile(String text,String path) {// тело метода }}classDocument {// класс Document содержит ссылку на объект// класса FileManagerprivateFileManager manager;privateStringBuilder contents;privateString path;publicDocument(FileManager manager,String path) {this.manager= manager;this.contents=newStringBuilder();this.path= path; }publicvoidsaveDocument() {manager.saveToFile(contents.toString(), path); }}
Базовые понятия механизма наследования
Наследование - отношение между классами, в котором один класс повторяет структуру и поведение другого класса (или нескольких других классов).
Класс, на основе которого создается новый класс, называется суперклассом (базовым классом, родительским классом). Новый создаваемый класс называется подклассом (дочерним классом, производным классом, классом-наследником и так далее).
Создание подкласса в Java
Создание подкласса практически не отличается от создания обычного класса, кроме необходимости указать суперкласс, на основе которого создается подкласс. В Java для этого существует ключевое слово extends:
В Java, в отличие от C++, отсутствует множественное наследование, то есть подкласс может создаваться на основе только одного суперкласса.
// СуперклассclassPerson {String firstName;String lastName;}classUniversityMember{}// МНОЖЕСТВЕННОЕ НАСЛЕДОВАНИЕ ЗАПРЕЩЕНО!// ЭТОТ КОД ВЫЗОВЕТ ОШИБКУ КОМПИЛЯТОРАclassStudentextendsPerson,UniversityMember {String group;long id;}
В Java присутствует многоуровневое наследование: подкласс может быть суперклассом для другого класса. Благодаря этому можно создавать целые цепочки классов, связанные механизмом наследования
classVehicle {publicvoidmoveTo(Point destination) {// тело метода }}classTruckextendsVehicle {publicvoidcarryWeight(double weight) {// тело метода }}classDumpTruckextendsTruck {publicvoiddumpWeight() {// тело метода }}
Наследование членов суперкласса
Если член класса определен как private, то при наследовании доступ к нему со стороны подкласса закрыт. Важно понимать, что приватный член суперкласса в подклассе есть, только он закрыт для прямого доступа. К примеру, данный код не скомпилируется
classShape2D {privatedouble width;privatedouble height;}classRectangleextendsShape2D {// ОШИБКА НА ЭТАПЕ КОМПИЛЯЦИИpublicdoublegetArea() {return width * height; }}
Закрытыми могут быть как поля класса, так и его методы. Если необходимо открыть поля или методы для доступа к ним со стороны подкласса, при объявлении членов суперкласса используют слово protected либо создают геттеры и сеттеры для доступа к полям. К примеру, данный код скомпилируется и будет работать корректно
classShape2D {protecteddouble width;protecteddouble height;}classRectangleextendsShape2D {// Данный код корректенpublicdoublegetArea() {return width * height; }}
Данный пример демонстрирует доступ к полям с помощью геттеров
Так как в наследовании участвуют два класса, базовый и производный, не сразу понятно, какой же объект получится в результате. Внешне все выглядит так, словно новый класс имеет тот же интерфейс, что и базовый класс, плюс еще несколько дополнительных полей и методов.
Однако наследование не просто копирует интерфейс базового класса. Когда вы создаете объект производного класса, внутри него содержится подобъект базового класса. Этот подобъект выглядит точно так же, как выглядел бы созданный обычным порядком объект базового класса. Поэтому извне представляется, будто бы в объекте производного класса "упакован" объект базового класса.
Чтобы подобъект базового класса был правильно инициализирован, при вызове конструктора подкласса, сначала вызывается конструктор базового класса, у которого есть необходимые знания и привилегии для проведения инициализации базового класса.
При использовании конструкторов без параметров, у компилятора не возникает проблем с вызовом таких конструкторов, так как нет нужды передавать аргументы. В этом случае Java автоматически вставляет вызовы конструктора базового класса в конструктор производного класса.
classAnimal {publicAnimal() {System.out.println("Конструктор класса Animal"); }}classMammalextendsAnimal {publicMammal() {System.out.println("Конструктор класса Mammal"); }}classCatextendsMammal {publicCat() {System.out.println("Конструктор класса Cat"); }}publicclassMain {publicstaticvoidmain(String[] args) {Cat cat =newCat(); }}
Результат работы такого приложения будет следующим
Конструктор класса Animal
Конструктор класса Mammal
Конструктор класса Cat
Как видно из данного примера, цепочка вызовов конструкторов начинается с самого базового класса. Таким образом, подобъект базового класса инициализируется еще до того, как он станет доступным для конструктора производного класса. Даже если конструктор класса Cat не будет определен, Java сгенерирует конструктор по умолчанию, в котором также будет вызван конструктор базового класса.
Если в классе не определен конструктор без параметров, то вызов конструктора базового класса надо будет оформлять явно. К примеру, такой код вызовет ошибку на этапе компиляции
Для явного вызова конструктора суперкласса используется ключевое слово super. Более подробно мы рассмотрим его ниже, а сейчас приведем пример корректного вызова конструктора суперкласса
Как было сказано ранее, наследование в Java реализуется следующим образом - в объект производного класса добавляется скрытый объект базового класса, который и обеспечивает вызов методов суперкласса.
Ключевое слово super как раз и ссылается на этот скрытый объект суперкласса. Используя это ключевое слово, можно получить доступ к членам суперкласса (если позволяет их модификатор доступа)
Как видно из примера, ключевое слово super имеет что-то общее с ключевым словом this.
Переопределение методов
При использовании механизма наследования возникает проблема с использованием методов суперкласса. Часто метод суперкласса не отражает изменения и нововведения, внесенные в подклассе и вызов таких методов дает некорректную информацию об объекте. Рассмотрим следующий пример
был вызван метод getInfo() суперкласса, который выводит информацию только о двух параметрах коробки, тогда как класс Box3D содержит три параметра. Таким образом, метод getInfo() для класса Box3D становится как бы некорректным, неправильным, он выдает неполную информацию об объекте. Как решить эту проблему?
Первый вариант - создать в подклассе Box3D свой метод для вывода информации
Объект Box3D {ширина = 100.0, высота = 200.0, глубина = 300.0}
Такой вариант является интуитивно понятным, но все-таки не совсем корректным. Теперь у объекта Box3D существует аж целых два метода для получения информации об объекте, один из которых является некорректным, что вносит путаницу для нас и для тех программистов, которые будут использовать наш код.
Самым правильным вариантом будет как бы "переписать" метод getInfo(), предоставить версию метода getInfo() для класса Box3D. Таким образом и класс Box и класс Box3D будет иметь метод getInfo(), просто в классе Box3D он будет иначе реализован, с учетом появления третьего параметра. Кроме того, объект класса Box3D теперь будет содержать только один метод для получения информации об объекте и этот метод будет корректно работать.
Для реализации своеобразного "переписывания" метода в подклассе, в Java существует механизм переопределения метода (method overriding).
Воспользуемся механизмом переопределения метода, чтобы корректно решить проблему с классами Box и Box3D (в примере для наглядности опущены некоторые члены классов)
Обратите внимание что сигнатуры двух методов getInfo() полностью совпадают - это обязательное условие для срабатывания механизма переопределения метода. Метод в суперклассе называется переопределенным.
Также обратите внимание на строку 33, где записано @Override. Такая запись называется аннотацией.
Аннотация - это дополнительное пояснение для компилятора и для различных утилит, которые работают с кодом (анализаторы, генераторы документации и так далее). Указание аннотации не является обязательным, код будет работать и без аннотации, но указание аннотации является одним из важных правил грамотного написания кода.
Результат работы нашего примера будет следующим
Объект Box {ширина = 600.0, высота = 600.0}
Объект Box3D {ширина = 100.0, высота = 200.0, глубина = 300.0}
Обратите внимание, что и для класса Box и для класса Box3D мы вызываем метод с одним и тем же названием и с одной и той же сигнатурой
Но в зависимости от того, для какого класса мы вызываем этот метод, в первом случае отрабатывает метод в классе Box, а во втором случае - метод в классе Box3D.
Следует отличать механизм перегрузки метода от механизма переопределения метода.
Метод может быть как перегруженным, так и переопределенным.
Запрет наследования с помощью ключевого слова final
Иногда бывает необходимо запретить наследоваться от какого-то класса либо запретить переопределять метод. В этом случае, в объявлении класса или метода укажите ключевое слово final
В Java определен специальный класс Object, который является суперклассом для всех классов Java. Иными словами, все классы в языке Java являются подклассами, производными от класса Object.
В классе Object определены перечисленные ниже методы, которые доступны в любом объекте
С некоторыми методами класса Object мы встретимся позже, а сейчас нам интересен только метод toString().
Метод toString()
Метод toString() призван возвращать строковое представление объекта (список значений полей). Рассмотрим пример
Обратите внимание, что класс Box не содержит никаких членов, однако, так как Box наследуется от класса Object, ему доступны методы суперкласса и метод toString() в частности.
Результатом работы данного примера является следующая строка
com.company.Box@1540e19d
Метод toString() в классе Object выводит полное название класса и 16-ричное представление хеш-кода объекта.
Если вам необходимо вывести значения полей объекта в виде строки, используйте метод toString(), он для этого и был предназначен, его использование является общепринятым правилом.
Метод getInfo() в примере выше был использован только в демонстрационных целях!
Чтобы метод toString() выводил нужную информацию, его необходимо пепреопределить как в следующем примере
Также метод toString() имеет одну примечательную особенность - его можно явно не вызывать, а просто указывать ссылочную переменную, Java сама вызовет метод toString(). Например, следующий код
Как вы видите, результат работы строк 5 и 6 является идентичным.
Наследование и композиция в диаграмме классов UML
Наследование (обобщение)
Отношение обобщения (generalization) в UML изображается при помощи сплошной линии и жирной треугольной стрелки, ведущей от подкласса с суперклассу.
classVehicle {}classCarextendsVehicle {}
Что обозначает это отношение? В спецификации UML по этому поводу сказано
Обобщение - таксономическое отношение между более общим элементом и более конкретным. Каждый экземпляр конкретного элемента также является непрямым экземпляром обобщенного элемента. Таким образом, конкретизированный элемент косвенно обладает свойствами обобщенного элемента
Является ли отношение обобщения синонимом наследования (inheritance) в объектно-ориентированном проектировании? Ответ зависит от области применения отношения. Для диаграммы концептуальных классов из модели предметной области ответом будет "нет". В этом случае отношение обобщения подразумевает то, что суперкласс является множеством, а подкласс - подмножеством. С другой стороны, на диаграмме классов проектирования это отношение подразумевает объектно-ориентированное наследование свойств подкласса от суперкласса.
Композиция
Композиция (composition), так же известная как композитная агрегация (composite aggregation), является строго определенным типом связи "целое-часть" и полезна в некоторых моделях. Отношение композиции предполагает, что
экземпляр части (например, Wheel) в каждый момент времени принадлежит только одному целому предмету (например, Car);
часть всегда принадлежит целому (пальцы не существуют отдельно от руки);
целое ответственно за создание и удаление своих частей - либо через самостоятельное создание\удаление, либо через взаимодействие с другими объектами. Следствием этих ограничений является то, что при уничтожении композитного объекта его части должны быть либо уничтожены, либо присоединены в другому композитному объекту. Например, если реальная настольная игра "Монополия" уничтожается, то также уничтожаются все ее клетки (с концептуальной точки зрения). Аналогично, если программный объект Car уничтожается, то уничтожаются и программные объекты Wheel.
Для обозначения композиции в UML используется закрашенный ромб на линии ассоциации со стороны целого.
В данной лабораторной работе задания выполняются с помощью плагина EduTools!
При решении задачи сначала добавьте сигнатуру методов и возвращаемые значения для того, чтобы тесты могли скомпилироваться корректно!
Реализуйте каждую задачу с помощью механизма наследования, а потом - с помощью механизма композиции.
Для каждой реализации нарисуйте UML-диаграмму классов. Вы можете нарисовать одну диаграмму для двух заданий, реализованных с помощью наследования и одну диаграмму для заданий, реализованных с помощью механизма наследования.
1. Класс Point3D
Дан класс Point, который моделирует точку в двумерном пространстве. Класс включает в себя следующие конструкторы и публичные методы:
Создайте класс Point3D, который расширяет класс Point через наследование. Он должен вести себя как Point, за исключением того что это должна быть точка в трехмерном пространстве, которая хранит значение координаты Z.
Вы должны предоставить те же методы, что и суперкласс, а также реализовать дополнительное поведение
Класс Point3D() должен переопределить требуемые методы, чтобы они работали корректно с учетом третьей координаты. Также класс Point3D должен вести себя иначе в следующих ситуациях:
при вызове метода setLocation(int x, int y), координата z должна быть выставлена в 0;
при вызове метода toString(), строка должна выводить три координаты, а не две;
2. Класс DiscountBill
Дан класс GroceryBill, который моделирует чек и хранит список товаров, который покупает человек в супермаркете. Класс включает в себя следующие конструкторы и публичные методы:
Объект GroceryBill взаимодействует с объектами класса Item. Класс Item включает следующие публичные методы:
К примеру, товар стоит 1.35, а размер скидки 0.25 для постоянных покупателей. Это означает, что постоянный покупатель должен заплатить 1.10. Некоторые товары могут не иметь скидки (размер скидки 0.0). В классе GroceryBill не предусмотрена логика для учета скидки, то есть учитывается только полная стоимость товара.
Разработайте класс DiscountBill, который расширяет класс GroceryBill и добавляет логику для учета скидки для постоянных клиентов. Конструктор класс DiscountBill должен принимать на вход параметр, который указывает, является ли клиент постоянным.
Класс DiscountBill должен реализовывать собственную логику метода getTotal() для постоянных покупателей. Например, если полная сумма равна 80 гривен, а скидка для постоянного клиента составила 20 гривен, метод должен возвращать 60 гривен.
Также, вам необходимо отслеживать количество товаров со скидкой (у которых размер скидки больше 0.0), а также общую скидку, как в гривнах, так и в процентах от суммы в чеке (то есть, насколько в процентах постоянный покупатель заплатил меньше, чем если бы скидки не было).
Помимо переопределенных методов, класс DiscountBill должен иметь следующие конструкторы и публичные методы:
Если покупатель не является регулярным, класс DiscountBill должен вести себя как будто общая скидка равна 0 и все товары учтены по их полной стоимости.
метод distanceFromOrigin() должны учитывать координату z и возвращать расстояние по формуле .
Метод
Описание
Object clone()
Создает новый объект, аналогичный клонируемому объекту
boolean equals(Object объект)
Определяет равнозначность объектов
void finalize()
Вызывается перед тем, как неиспользуемый объект будет удален "сборщиком мусора"
Class<?> getClass()
Определяет класс объекта во время выполнения
int hashCode()
Возвращает хеш-код, связанный с вызывающим объектом
void notify()
Возобновляет работу потока, ожидающего уведомления от вызывающего объекта
void notifyAll()
Возобновляет работу всех потоков, ожидающих уведомления от вызывающего объекта
String toString()
Возвращает символьную строку, описывающую объект
void wait()
void wait(long мсек)
void wait(long мсек, int наносек)
Ожидает исполнения другого потока
Сигнатура
Описание
public Point3D()
Создает точку с координатами (0, 0, 0)
public Point3D(int x, int y, int z)
Создает точку с координатами (x, y, z)
public void setLocation(int x, int y, int z)
Устанавливает новые координаты
public int getZ()
Возвращает координату Z
(x1−x2)2+(y1−y2)2+(z1−z2)2
Сигнатура
Описание
public GroceryBill(Employee clerk)
Создает объект GroceryBill для данного clerk
public void add(Item i)
Добавляет товар в чек
public double getTotal()
Возвращает итоговую стоимость товаров
public void printReceipt()
Распечатывает список товаров
Сигнатура
Описание
public double getPrice()
Возвращает стоимость товара
public double getDiscount()
Возвращает скидку для этого товара
Сигнатура
Описание
public DiscountBill
(Employee clerk, boolean regularCustomer)
Создает объект DiscountBill для данного clerk
public int getDiscountCount()
Возвращает количество товаров со скидкой
public double getDiscountAmount()
Возвращает общую скидку в гривнах
public double getDiscountPercent()
Возвращает процент скидки для товаров
(на сколько процентов покупатель
заплатил меньше)
Лабораторная работа 5
Тема: Механизм полиморфизма.
Цель лабораторной работы:
изучить механизм восходящего и нисходящего преобразования;
рассмотреть принцип полиморфизма в Java;
изучить раннее и позднее связывание;
изучить принцип потери информации при восходящем преобразовании.
Теоретические вопросы
Теоретические вопросы:
Что такое восходящее и нисходящее преобразование ссылочных типов?
Как работает восходящее и нисходящее преобразование для ссылочных типов в Java?
Что такое связывание "метод-вызов"? Чем отличаются статическое и динамическое связывание?
Почему происходит потеря информации при восходящем преобразовании?
Зачем нужно нисходящее преобразование? Как безопасно осуществить нисходящее преобразование?
Лабораторная работа 6
Тема: Абстрактные классы и интерфейсы. Анонимные классы и лямбда-выражения
Цель лабораторной работы:
изучить назначение и синтаксис абстрактных классов;
рассмотреть механизм интерфейсов;
изучить использование лямбда-выражений.
Лабораторная работа 8
Тема: Работа с потоками (Stream API) в Java
Цель лабораторной работы:
изучить предназначение потоков в Java;
рассмотреть процесс создания потоков и источники потоков в Java;
изучить промежуточные и терминальные операции для работы с потоками.
Теоретические вопросы
Теоретические вопросы:
Что такое абстрактный метод, зачем нужны абстрактные методы?
Чем абстрактный класс отличается от обычного?
Что такое интерфейс и что он описывает?
Зачем нужны интерфейсы в ООП?
Что такое анонимные объекты? Зачем они нужны?
Что такое анонимные классы? Особенность анонимных классов.
Что такое лямбда-выражения? Особенности использования лямбда-выражений в Java.
Сигнатура
Описание
public Point()
Создает точку с координатами (0, 0)
public Point(int x, int y)
Создает точку с координатами (x, y)
public void setLocation(int x, int y)
Устанавливает новые координаты точки
public int getX()
Возвращает значение координаты X
public int getY()
Возвращает значение координаты Y
public String toString()
Возвращает строку в виде "(x,y)"
public int distanceFromOrigin()
Задание на лабораторную работу
Задания для выполнения:
1. Цепочка наследования. В данном задании необходимо выполнить следующие действия:
1. Создайте класс Person, который должен содержать конструктор, геттеры\сеттеры и следующие поля:
фамилия;
имя;
возраст.
Переопределите метод toString(), который должен возвращать строку следующего формата:
Человек <фамилия> <имя>, возраст: <возраст>
2. Создайте класс Student, который должен наследоваться от класса Person. Добавьте конструктор, геттеры\сеттеры и следующие поля:
группа;
номер студенческого билета.
Переопределите метод toString(), который должен возвращать строку следующего формата:
Студент группы <группа>, <фамилия> <имя>, возраст: <возраст>. Номер студенческого билета: <номер>
3. Создайте класс Lecturer, который должен наследоваться от класса Person. Добавьте конструктор, геттеры\сеттеры и следующие поля:
кафедра;
зарплата.
Переопределите метод toString(), который должен возвращать строку следующего формата:
Преподаватель кафедры <кафедра>, <фамилия> <имя>, возраст: <возраст>. Зарплата: <зарплата>
4. Используя восходящее преобразование, создайте в классе Main несколько объектов классов Student и Lecturer, после чего создайте массив, который бы мог включать объекты классов Person, Student, Lecturer. Заполните массив объектами этих классов.
5. Используя цикл, обратитесь к элементам массива и выведите в консоль, с помощью метода toString(), информацию от каждого объекта.
Данная программа позволяет рисовать простые фигуры. С помощью кнопок на верхней панели выбираете нужную фигуру, после чего в поле рисования зажимаете кнопку мыши и рисуете фигуру нужных размеров.
Задание состоит в следующем:
Откройте проект, прочитайте комментарии, разберитесь как работает приложение;
Используя механизм полиморфизма, модифицируйте программу, добавив возможность выбирать и рисовать эллипсы;
Задание на дополнительный балл. Добавьте в программу кнопку Clear, по нажатию на которую программа должна стирать все фигуры.
При сдаче лабораторной работы, вы должны быть готовыми показать, где и для каких целей используется полиморфизм и восходящее преобразование.
Задание (старое)
Нарисуйте диаграмму классов UML для 1 и 2 заданий
1. Приложение Draw
Модифицируйте приложение Draw из лабораторной работы №5, что приложение использовало механизм абстрактных классов.
Данная программа выводит список студентов и позволяет отсортировать их по имени, фамилии и по среднему баллу.
В программе уже реализована сортировка студентов по имени.
Задание состоит в следующем:
доделать программу, чтобы по нажатию на соответствующие кнопки происходила сортировка студентов по фамилии и по среднему баллу;
задание на дополнительные баллы - доработайте программу так, чтобы при повторном нажатии на ту же кнопку сортировки, осуществлялась обратная сортировка студентов. То есть, при первом нажатии на кнопку "Сортировка по имени" студенты сортировались в прямом порядке (по возрастанию), а при повторном нажатии на эту же кнопку, студенты сортировались в обратном порядке (по убыванию).
Список студентов находится в коллекции students, для сортировки необходимо вызвать у коллекции метод sort() и в этот метод передать объект компаратора.
Что такое компаратор. Базовая операция для почти любой сортировки – сравнение двух элементов. Если вы сортируете обычные числа или строки, сравнение происходит элементарно по известным всем правилам. Но как быть, если вы хотите отсортировать объекты, которые могут иметь десятки полей различных типов?
Для этого существует так называемый компаратор. Компаратор в Java – это объект класса, который реализует интерфейс Comparator. Интерфейс определяет всего один метод compare(), который принимает на вход два объекта типаObject. Если первый объект «меньше» – метод возвращает отрицательное число (обычно это просто -1, но может быть и любое другое отрицательное число), если первый объект «больше» – метод возвращает положительное число (обычно это 1, но может быть и любое другое положительное число), если объекты «равны» – метод возвращает 0.
Задача программиста - прописать нужную логику сравнения и вернуть -1/0/1 в том или ином случае.
Вы создаете класс, указываете, что этот класс реализует интерфейс Comparator и в методе compare() описываете логику сравнения двух объектов. В случае со сравнением двух студентов по имени, код компаратора выглядит следующим образом
Обратите внимание, что сначала мы должны убедиться, что оба объекта являются объектами класса Student, после чего делаем нисходящее преобразование.
В данном случае мы сравниваем два поля name у двух объектов класса Student. Так как это тип String, нам нет нужды сравнивать строки «вручную», мы просто можем воспользоваться методом compareTo(), который есть у любого объекта класса String.
3. Модификация SortingList
Модифицируйте приложение SortingList из второго задания, чтобы вместо отдельных классов использовался механизм анонимных классов.
4. Модификация SortingList
Модифицируйте приложение SortingList из второго задания, чтобы вместо отдельных классов использовался механизм лямбда-выражений.
Возвращает расстояние от начала координат (0, 0)
до точки по формуле
расстояния Евклида
(x1−x2)2+(y1−y2)2
Лабораторная работа 9
Тема: Исключения в Java. Обработка исключений.
Цель лабораторной работы:
Ознакомиться с механизмом исключений;
Научиться обрабатывать исключения в программе (блок try-catch, ключевое слово throws);
Разобраться с различными видами исключений;
Научиться добавлять собственные типы исключений в программу;
Изучить использование блока finally и try-with-resources.
Задание на лабораторную работу
Задание 1.
Вам дан файл Sums.java, в которой содержится программа.
Данная программа позволяет пользователю ввести набор целых чисел и вывести сумму этих чисел. Программа спрашивает пользователя, хочет ли он посчитать сумму чисел; если пользователь отвечает ‘y’, то программа принимает на вход из консоли множество целых чисел до тех пор, пока пользователь не введет число ‘0’. После этого программа выводит сумму этих чисел и повторяет вопрос снова. Если пользователь отвечает ‘n’, программа заканчивает работу.
Sums.java
publicclassSums {publicstaticvoidsum(BufferedReader in){// Программа получает последовательность целых чисел на вход// и возвращает сумму этих чиселint s, nextInt; s =0;System.out.println("Пожалуйста, введите последовательность чисел, для окончания ввода, введите 0"); nextInt =Integer.parseInt(in.readLine());// Читаем следующее значение. Ожидаем целое числоwhile (nextInt!=0) { s = s + nextInt; nextInt =Integer.parseInt(in.readLine()); }System.out.println("Сумма равна "+ s); }publicstaticvoidmain(String[] arg) {BufferedReader in =newBufferedReader(new InputStreamReader(System.in));// in будет получать данные из стандартного входного потокаSystem.out.println("Вы хотите посчитать сумму чисел? (y/n)");String c =in.readLine();// Проверяем правильность ввода// Если ввод некорректен - просим повторить сноваwhile (!c.equals("y") &&!c.equals("n")) {System.out.println("Пожалуйста, повторите ввод (y/n)"); c =in.readLine(); }while (c.equals("y")) {sum(in); // Функция для ввода и подсчета суммы чиселSystem.out.println("Вы хотите посчитать сумму еще раз? (y/n)"); c =in.readLine();while (!c.equals("y") &&!c.equals("n")) {System.out.println("Пожалуйста, повторите ввод (y/n)"); c =in.readLine(); } }System.out.println("Программа заканчивает работу."); }}
На данный момент программа не компилируется из-за проблем с checked-исключениями. Модифицируйте программу путем добавления обработчиков исключений в нужных местах. Подумайте, могут ли в программе возникнуть какие-то unchecked-исключения, если да – предусмотрите обработчик событий для этих исключений.
Внимание! Обработчики исключений должны быть реализованы так, чтобы программа адекватно реагировала на исключения и продолжила работу, как было задумано изначально!
Задание 2.
Дан класс ListOfNumbers (листинг класса представлен ниже). Создайте консольное приложение на основе этого класса. Добавьте в класс ListOfNumbers метод readList(). Этот метод должен считывать int значения из файла, выводить в консоль каждое значение, после чего добавлять значение в коллекцию List. Ваш код должен «отлавливать» все соответствующие исключения. Также, создайте текстовый файл со значениями. При сдаче лабораторной работы вы должны представить работоспособную консольную программу.
Также замените блок finally на try-with-resources.
ListOfNumbers.java
importjava.io.FileWriter;importjava.io.IOException;importjava.io.PrintWriter;importjava.util.ArrayList;importjava.util.List;publicclassListOfNumbers {privateList<Integer> list;privatestaticfinalint LIST_SIZE =10;publicListOfNumbers() { list =newArrayList<>(LIST_SIZE);for (int i =0; i <list.size(); i++)list.add(i); }publicvoidwriteList() {PrintWriter out =null;try {System.out.println("Entering try statement"); out =newPrintWriter(new FileWriter("OutFile.txt"));for (int i =0; i <list.size(); i++)out.println("Value at: "+ i +" = "+list.get(i)); } catch (IndexOutOfBoundsException e) {System.err.println("Caught IndexOutOfBoundsException: "+e.getMessage()); } catch (IOException e) {System.err.println("Caught IOException: "+e.getMessage()); } finally {if (out !=null) {System.out.println("Closing PrintWriter");out.close(); } else {System.out.println("PrintWriter not open"); } } }}
Задание 3.
Перепишите код второй лабораторной работы с использованием механизма исключений. При необходимости, добавьте свои классы исключений (подумайте, от каких стандартных классов исключений они должны наследоваться).
Задание 4.
Перепишите код первого задания шестой лабораторной работы с использованием механизма исключений. При необходимости, добавьте свои классы исключений (подумайте, от каких стандартных классов исключений они должны наследоваться).
2. Вам необходимо выполнить все задания из раздела Java/Functional-1 и Java/Functional-2 с использованием механизма потоков (Stream API).
3. Для решения каждой задачи необходимо вписать код метода в окно ввода. Задание и примеры находятся в верхней части страницы, после чего идет поле ввода, куда необходимо ввести код, который приведет к желаемой работе метода.
4. После ввода кода, необходимо нажать кнопку "Go", после чего ваш код пройдет рад тестов. Если код неправильный, то некоторые тесты будут провалены
5. Задание считается выполненным, если будут пройдены все тесты
6. Протокол лабораторной работы должен содержать код выполненных заданий. При сдаче лабораторной работы необходимо зайти под своим аккаунтом и показать, что все задания были решены верно.
2. Обработка литературного текста
Найдите 500 самых длинных слов в романе "Война и мир", используя параллельный поток данных (выведите 500 самый длинных слов в консоль в порядке убывания длины слова, слова одинаковой длины должны быть выведены в алфавитном порядке).
Для успешного выполнения работы: скачайте роман в текстовом формате, загуглите, как получить Stream из файла, после чего загуглите, как разбить строку на отдельные слова.
3. Поиск простых чисел
Найдите 500 простых чисел, состоящих из 50 цифр, используя параллельный поток данных типа BigInteger и метод BigInteger.isProbablePrime() для проверки числа на простоту.
Для генерации чисел НЕ используйте метод BigInteger.probablePrime().
Выведите 500 найденных простых чисел в консоль в порядке возрастания значения числа.
Для получения массива чисел используйте метод Stream.generate().
4. Дополнительное задание
Модифицируйте задачу 3 следующим образом: найдите способ измерить длительность работы метода, после чего выполните задание 3 с использованием обычного потока и с помощью параллельного потока и сравните время выполнения задачи в обоих случаях.