В данной лабораторной работе мы рассмотрим процесс описания классов и создания объектов.
Тема: Создание классов и объектов
Цель лабораторной работы:
изучить синтаксис и процесс создания классов;
научиться создавать объекты созданных классов.
Теоретические вопросы
Теоретические вопросы (ответы содержатся в лекционном материале):
Из каких частей состоит тело класса?
Опишите процесс создания объектов в Java. Какую роль в создании объектов играет оператор new?
Что такое ссылочная переменная? Как передаются данные в метод в Java?
Что такое конструктор? Зачем нужен конструктор в ООП? Чем синтаксис конструктора отличается от синтаксиса обычного метода?
Объясните, откуда берется ключевое слово this в методах класса? Как можно использовать ключевое слово this при написании кода методов?
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; }}
1. Синтаксис создания классов
Создадим класс Box, который описывает контейнер, допустим, на каком-то складе.
Класс 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), также должна быть совместима с возвращаемым типом, указанным для метода.
Задание на лабораторную работу
Для каждого класса необходимо предоставить его графическое представление в диаграмме классов UML. Графическое представление добавить в протокол лабораторной работы и принести на защиту лабораторной работы.
1. Класс TimeSpan
1.1 Создание класса
Создайте класс с названием TimeSpan. Объект класса TimeSpanхранит интервал времени в часах и минутах (к примеру, временной интервал между 8:00 и 10:30 это 2 часа 30 минут). Класс TimeSpanдолжен иметь следующие методы
Вы должны продумать:
как хранить значение временного интервала;
типы входных аргументов методов;
корректно реализовать требуемые методы;
в методах предусмотреть проверки на корректность входных данных.
1.2. Добавление метода subtract()
Добавьте к написанному ранее классу метод subtract()со следующей сигнатурой
publicvoidsubtract(TimeSpan span)
Метод вычитает из текущего временного интервала входной временной интервал. Если входной интервал больше текущего, выйдите из метода и не модифицируйте текущий интервал (можете выбросить исключение IllegalArgumentException, если знаете что это и знаете как выбрить исключение).
1.3 Добавление метода scale()
Добавьте к написанному ранее классу метод subtract()со следующей сигнатурой
publicvoidscale(int factor)
Метод увеличивает текущий интервал в factorчисло раз. Например, если текущий временной интервал равен 1 час 45 минут, а входной аргумент равен 2, то интервал увеличится до 3 часов 30 минут. Убедитесь, что factor- неотрицательное число, помните про пересчет минут в часы при увеличении интервала.
2. Класс BankAccount
2.1 Добавление поля transactionFee
В программе существует класс BankAccount, исходный код которого приведен ниже.
// Каждый объект класса BankAccount представляет данные одного// счета пользователя, включая имя и баланс счетаpublicclassBankAccount {String name;double balance;publicvoiddeposit(double amount) { balance = balance + amount; }publicdoublegetBalance() {returnthis.balance; }publicbooleanwithdraw(double amount) { balance = balance - amount;returntrue; }}
Каждый объект класса предназначен для описания одного счета клиента банка, включая его имя и баланс. Модифицируйте класс следующим образом:
добавьте поле 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 гривен)