Створення об’єктів

Для першої версії класу Body створення і ініціалізація обєктів, що становлять небесні тіла, відбувається наступним чином:

Body sun = new Body() sunidNum = BodynextID++ sunnameFor = “Sol”

sunorbits = null / / Сонце є центром Сонячної

/ / Системи

Body earth = new Body() earthidNum = BodynextID++ earthnameFor = “Earth” earthorbits = sun

Спочатку ми оголосили два посилання (sun і earth) на обєкти типу Body Як згадувалося вище, обєкти при цьому НЕ створюються – лише оголошуються змінні, які посилаються на обєкти Первинне значення посилань одно null, а відповідні їм обєкти повинні явним чином створюватися в програмі

Обєкт sun створюється за допомогою оператора new Конструкція new вважається найпоширенішим способом побудови обєктів (пізніше ми розглянемо й інші можливості) При створенні обєкта оператором new слід вказати тип конструируемого обєкта та необхідні параметри Runtime-система виділяє область памяті, достатню для зберігання полів обєкта, і ініціалізує її відповідно до розглянутими нижче правилами Після завершення ініціалізації runtime-система повертає посилання на створений обєкт

Якщо системі не вдається виділити память, достатню для створення обєкта, вона може запустити збирач сміття, який звільняє невикористовувану память Якщо ж і після цього памяті все одно не вистачає, оператор new збуджує виняток OutOfMemoryError

Створивши новий обєкт Body, ми инициализируем його змінні Кожен обєкт класу Body повинен мати унікальний ідентифікатор, який він отримує із статичного поля nextID Програма нарощує значення nextID, щоб ідентифікатор наступного обєкту Body також був унікальним

У нашому прикладі будується модель Сонячної системи У її центрі знаходиться Сонце, тому полю orbits обєкта sun присвоюється значення null – у Сонця немає обєкта, навколо якого воно б оберталося При створенні та ініціалізації обєкту earth (Земля) ми присвоїли полю orbits значення sun Для Місяця, що обертається навколо Землі, поле orbits отримало б значення earth Якби ми будували модель Галактики, то Сонце б також оберталося навколо чорної діри, що знаходиться десь в середині Чумацького Шляху

Вправа 24

Напишіть для класу Vehicle метод main, який створює кілька обєктів-автомашин і виводить значення їх полів

Вправа 25

Напишіть для класу LinkedList метод main, який створює кілька обєктів типу

Vehicle і заносить їх у список

25 Конструктори

Кожен новостворений обєкт має деяким вихідним станом Значення полів можуть инициализироваться при їх оголошенні – інколи цього буває достатньо

/ Ініціалізація даних докладно розглядається в розділі Ініціалізація, проте в сутності за цим терміном ховається звичайне привласнення початкового значення Якщо в програмі полю присвоюють ніякого значення, воно одержить значення нуль,

\ U0000, false або null, залежно від типу / Однак досить часто для визначення

вихідного стану простий ініціалізації даних виявляється недостатньо наприклад, можуть знадобитися будь вихідні дані, або ж виконувані операції не можуть бути представлені у вигляді простого присвоювання

Для тих випадків, коли простий ініціалізації недостатньо, використовуються

конструктори Імя конструктора збігається з імям класу, який він ініціалізує Конструктори, подібно методам, можуть отримувати один або декілька параметрів, проте вони не є методами і не можуть повертати ніякого значення Параметри конструктора (якщо вони є) зазначаються в дужках за імям типу при створенні

обєкта оператором new Конструктор викликається після того, як змінним в екземплярі новостворюваного обєкта будуть присвоєні початкові значення по

замовчуванням і буде виконана безпосередня ініціалізація

У вдосконаленої версії класу Body початковий стан обєкта частково встановлюється за допомогою ініціалізації, а частково – в конструкторі:

class Body {

public long idNum

public String name = “”

public Body orbits = null

private static long nextID = 0

Body() {

idNum = nextID++

}

}

Конструктор класу Body викликається без аргументів, однак він виконує важливу функцію, а саме встановлює у знову створюваному обєкті правильне значення поля idNum Найпростіша помилка, можлива при роботі зі старою версією класу, – скажімо, ви забули привласнити значення полю idNum або нарощували nextID після його використання – призводила до того, що в програмі виникали різні обєкти класу Body з однаковими значеннями поля idNum В результаті виникали проблеми в тій частині коду, яка була заснована на положенні контракту, що стверджує: Всі значення idNum повинні бути різними.

Покладаючи відповідальність за генерацію значень idNum на сам клас Body, ми тим самим запобігаємо помилки подібного роду Конструктор Body стає єдиним місцем в програмі, де idNum присвоюється значення Наступним кроком є ​​оголошення поля nextID з ключовим словом private, щоб доступ до нього здійснювався тільки з класу Тим самим ми усуваємо ще одне можливе джерело помилок для програмістів, працюють з класом Body

Крім того, таке рішення дозволить нам вільно змінювати спосіб привласнення значень полю idNums в обєктах Body Наприклад, майбутня реалізація нашого класу може переглядати базу даних відомих астрономічних обєктів і привласнювати нове значення idNum лише в тому випадку, якщо раніше даним небесному тілу ні присвоєний іншій ідентифікатор Така зміна ніяк не позначиться на існуючому коді програми, оскільки він ніяк не бере участь у присвоєнні значення idNum

При ініціалізації полях name і orbits присвоюються деякі розумні початкові значення Отже, після наведеного нижче виклику конструктора всі поля нового обєкта Body будуть ініціалізовані Після цього ви можете змінити стан обєкта, присвоївши його полях потрібні значення:

Body sun = new Body () / / Значення idNum одно 0 sunname = Sol;

Body earth = new Body () / / Значення idNum одно 1 earthname = Earth;

earthorbits = sun

Конструктор Body викликається при створенні нового обєкта оператором new, але після того, як полях name і orbits будуть присвоєні початкові значення Ініціалізація поля orbits значенням null означає, що sunorbits в нашій програмі не присвоюється ніякого значення

Якщо створення обєкта для небесного тіла з двома параметрами – назвою та центром поводження – відбуватиметься досить часто, то можна передбачити окремий конструктор, в який обидва цих значення передаються в якості параметрів:

Body(String bodyName, Body orbitsAround) {

this()

name = bodyName

orbits = orbitsAround

}

Як бачите, з одного конструктора можна викликати інший конструктор цього ж класу – для цього першим виконуваним оператором повинен бути виклик this () Це називається явним викликом конструктора. Якщо для виклику конструктора необхідні параметри, вони можуть передаватися У нашому випадку для присвоєння значення полю idNum використовується конструктор, що викликається без аргументів Тепер створення обєктів відбувається значно простіше:

Body sun = new Body(“Sol”, null) Body earth = new Body(“Earth”, sun)

При бажанні можна задати окремий конструктор з одним аргументом для тих випадків, коли для створюваного обєкта Body не існує центру обертання Виклик такого конструктора рівносильний застосуванню конструктора з двома аргументами, при якому другий з них дорівнює null

Для деяких класів необхідно, щоб творець обєкта надавав деякі дані Наприклад, додаток може вимагати, щоб для всіх обєктів Body було зазначено їх назву Щоб переконатися, що всім створюваних обєктів Body передається назва, необхідно включити відповідний параметр у всі конструктори класу Body

Наведемо кілька загальноприйнятих міркувань на користь створення спеціалізованих конструкторів:

Деякі класи не володіють розумним початковим станом, якщо не передати їм параметри

При конструюванні обєктів деяких видів передача вихідного стану виявляється найбільш зручним і розумним виходом (прикладом може служити конструктор Body з двома аргументами)

Конструювання обєктів потенційно повязане з великими накладними витратами, так що бажано при створенні обєкта відразу встановлювати правильне початковий стан Наприклад, якщо кожен обєкт класу містить таблицю, то конструктор, який одержує вихідний розмір таблиці як параметр, дозволить з самого початку створити обєкт з таблицею потрібного розміру

Конструктор, атрибут доступу якого відрізняється від public, обмежує можливості створення обєктів даного класу Наприклад, ви можете заборонити програмістам, що працюють з вашим пакетом, розширювати той чи інший клас, якщо зробите все конструктори доступними лише з пакета Крім того, можна помітити ключовим словом protected ті конструктори, які призначені для використання виключно в підкласах

Конструктори, які не одержують при виклику ніяких аргументів, зустрічаються настільки часто, що для них навіть зявився спеціальний термін: безаргументний (no-arg) конструктори

Якщо ви не надасте для класу ніяких конструкторів, мова створює безаргументний конструктор за замовчуванням, який не робить нічого Цей конструктор створюється автоматично лише в тих випадках, коли немає ніяких інших конструкторів, – існують класи, для яких безаргументний конструктор буде працювати невірно (наприклад, клас Attr, з яким ми познайомимося в наступному розділі)

Якщо безаргументний конструктор повинен існувати поряд з одним або декількома конструкторами, що використовують аргументи, можна явно визначити його Автоматично створюваний безаргументний конструктор класу, що не має суперкласу, еквівалентний наступному (як ми побачимо на прикладі розширеного класу в розділі 3):

class SimpleClass {

/ ** Еквівалент конструктора за замовчуванням * /

public SimpleClass() {

}

}

Конструктор за замовчуванням має атрибут public, якщо такий же атрибут має клас, і не має його в іншому випадку

Вправа 26

Включіть в клас Vehicle два конструктора Перший з них – безаргументний, а

інший повинен отримувати як аргумент імя власника машини Модифікуйте метод main так, щоб він видавав ті ж результати, що й раніше

Вправа 27

Які конструктори ви б вважали за потрібне додати в клас LinkedList

26 Методи

Методи класу зазвичай містять код, який аналізує стан обєкта і змінює його Деякі класи мають поля public, до яких програмісти можуть звертатися безпосередньо, але в більшості випадків такий підхід виявляється не дуже вдалим

(Див Проектування расширяемого класу) Багато класів мають функції,

які неможливо звести до читання або зміни деякої величини – для них необхідні обчислення

Виклик методу являє собою операцію, виконувану з обєктом допомогою посилання на нього з використанням оператора:

referencemethod(parameters)

Кожен метод викликається з певною кількістю параметрів Java не підтримує методів, у яких допускається змінне число параметрів Кожен параметр має строго певний тип – примітивний або контрольний Крім того, методи володіють типом значення, що повертається, який вказується перед їх імям Наприклад, наведемо метод класу Body, який створює рядок типу String з описом конкретного обєкта Body:

public String toString() {

String desc = idNum + “ (” + name + “)”

if (orbits = null)

desc += “ orbits ” + orbitstoString()

return desc

}

У цьому методі проводиться конкатенація обєктів String за допомогою операторів + і + = Спочатку утворюється рядок, що містить ідентифікатор і назва обєкта Якщо спірне небесне тіло обертається навколо іншого, то до неї приєднується рядок з описом центру обертання, для чого викликається метод toString відповідного обєкта Послідовність рекурсивних викликів продовжує будувати ланцюжок тіл, які обертаються навколо один одного, поки не буде знайдено тіло, яке не має центру обертання

Метод toString не зовсім звичайний Якщо в обєкта є метод з імям toString, який викликається без параметрів і повертає значення типу String, то він використовується для приведення обєкта до типу String, якщо він бере участь у конкатенації рядків, виконуваної оператором + У таких висловлюваннях:

Systemoutprintln(“Body ” + sun) Systemoutprintln(“Body ” + earth)

відбувається непрямий виклик методів toString для обєктів sun і earth, що приводить до наступних результатів:

Body 0 (Sol)

Body 1 (Earth) orbits 0 (Sol)

Існує кілька способів, якими вдається домогтися повернення методом кількох значень: можна повертати посилання на обєкт, в якому ці значення зберігаються у вигляді полів приймати в якості параметрів посилання на обєкти, в яких повинні зберігатися результати нарешті, можна повертати масив з результатами

Наприклад, припустимо, що вам потрібно написати метод для повернення переліку фінансових операцій, які певна особа може виконувати з банківським рахунком Таких операцій може бути кілька (зарахування і зняття коштів з рахунку і т д) отже, метод повинен повертати декілька значень Ось як виглядає обєкт Permissions, в якому зберігаються логічні значення, що визначають допустимість тієї чи іншої операції:

class Permissions {

public boolean canDeposit, canWithdraw, canClose

}

А ось як виглядає метод, що заповнює ці поля:

class Account {

public Permissions permissionsFor(Person who) { Permissions perm = new Permissions() permcanDeposit = canDeposit(who) permcanWithdraw = canWithdraw(who) permcanClose = canClose(who)

return perm

}

/ / .. Визначення методу canDeposit ()

}

Якщо метод не повертає ніякого значення, то на місці повертається типу ставиться ключове слово void Інакше кожен можливий шлях виконання його операторів повинен повертати значення, яке може бути присвоєно змінної оголошеного типу Наприклад, метод permissions For не може повертати значення типу String, оскільки неможливо привласнити обєкт типу String змінної типу Permissions Однак ви можете оголосити, що метод permissionsFor повертає значення String, не змінюючи при цьому оператор return, оскільки посилання на обєкт Permissions може бути присвоєна змінної типу Object

261 Значення параметрів

Всі параметри в Java передаються за значенням. Іншими словами, значення змінних-параметрів методу є копіями значень, зазначених при його виклику Якщо передати методу змінну деякого типу, то параметр представлятиме

собою копію цієї змінної її зміна всередині методу ніяк не вплине на значення змінної в коді за його межами Наприклад:

class PassByValue {

public static void main(String[] args) {

double one = 10

Systemoutprintln(“before: one = ” + one)

halveIt(one)

Systemoutprintln(“after: one = ” + one)

}

public static void halveIt(double arg) {

arg /= 20 //

Systemoutprintln(“halved: arg = ” + arg)

}

}

Наведені нижче результати показують, що поділ на два змінної arg в методі

halveIt не змінює значення змінної one в методі main:

before: one = 1 halved: arg = 05 after:  one = 1

Однак, якщо параметр методу являє собою посилання на обєкт, то за значенням передається посилання, а не сам обєкт Отже, ви можете змінювати у методі той обєкт, на який вона посилається, – значення посилання залишається колишнім Можна змінювати будь-які поля обєкта або викликати методи, що впливають на його стан, – відбудеться зміна обєкта у всіх фрагментах програми, де є посилання на нього Наведений нижче приклад наочно показує, чим даний випадок відрізняється від попереднього:

class PassRef {

public static void main(String[] args) {

Body sirius = new Body(“Sirius”, null)

Systemoutprintln(“before: ” + sirius) commonName(sirius) Systemoutprintln(“after: ” + sirius)

}

public static void commonName(Body bodyRef) {

bodyRefname = “Dog Star”

bodyRef = null

}

}

Результат буде наступним:

before: 0 (Sirius)

after: 0 (Dog Star)

Зверніть увагу на те, що назва обєкта змінилося, тоді як посилання bodyRef все одно вказує на обєкт Body (хоча метод commonName привласнював параметру bodyRef значення null)

Наведена вище діаграма показує стан посилань безпосередньо після виклику commonName в main Обидві посилання – sirius (в main) і bodyRef (в commonName) – вказують на один і той же обєкт Коли commonName змінює значення поля bodyRefname, то назва змінюється в обєкті, спільно використовуваному обома посиланнями Однак при присвоєнні null посиланням bodyRef змінюється тільки її значення, тоді як значення посилання на обєкт sirius залишається тим же самим згадаємо, що параметр bodyRef є переданої за значенням копією sirius Усередині методу commonName змінюється лише значення змінної-параметра bodyRef, подібно до того як у методі halveIt змінювалося лише значення параметра-змінної arg Якби зміна bodyRef відносилося і до значення sirius в main, то в рядку, що починається з after:, стояло б null. Проте змінні bodyRef в commonName і sirius в main вказують на один і той же обєкт, тому зміни, що вносяться до commonName, відображаються і в тому обєкті, на який посилається sirius

262 Застосування методів для обмеження доступу

Користуватися класом Body з декількома конструкторами стало значно зручніше, ніж його старим варіантом, що складався з одних даних крім того, ми забезпечили правильне автоматичне присвоєння значень idNum І все ж програміст може все зіпсувати, змінюючи значення поля idNum після конструювання обєкта, – адже дане поле оголошено як public і відкрито для будь-яких дій Необхідно, щоб поле idNum містило дані, доступні тільки для читання Подібні дані в обєктах зустрічаються досить часто, але в мові Java не існує ключового слова, яке зробило б поле за межами класу доступним тільки для читання

Щоб зробити поле доступним тільки для читання, ми повинні приховати його Для цього поле idNum оголошується з ключовим словом private, а в клас додається новий метод, за допомогою якого код за межами класу може отримати значення цього поля:

class Body {

private long idNum / / Поле стало private

public String name = “” public Body orbits = null private static long nextID = 0

Body() {

}

idNum = nextID++

public long id() {

return idNum

}

// ..

}

Починаючи з цього моменту програміст, якому знадобилося дізнатися ідентифікатор небесного тіла, повинен викликати метод id, який повертає необхідну значення У програміста не залишається жодної можливості змінити ідентифікатор – по суті, за межами класу його можна розглядати як величину, доступну тільки для читання Дане поле може бути змінено тільки внутрішніми методами класу Body

Методи, що регулюють доступ до внутрішніх даних класу, іноді називаються методами доступу (accessor methods) З їх допомогою також можна (і, напевно, потрібно) захистити поля name і orbits

Навіть якщо інтереси програми, але не вимагають полів, доступних тільки для читання, оголошення полів класу з ключовим словом private та створення методів для присвоєння / отримання їх значень дозволяє вам визначити необхідні дії над обєктом в майбутньому Якщо програміст може безпосередньо звертатися до полів класу, ви неминуче втрачаєте контроль над тим, які значення будуть приймати ці

поля і що відбувається в програмі при їх зміні З цих причин у подальших прикладах цієї книги поля public зустрічаються дуже рідко

Вправа 28

Оголосіть поля класу Vehicle з ключовим словом private і опишіть відповідні методи доступу Для яких полів слід передбачити методи, що змінюють їх значення, а для яких – ні

Вправа 29

Оголосіть поля класу LinkedList з ключовим словом private і включіть в клас відповідні методи доступу Для яких полів слід передбачити методи, що змінюють їх значення, а для яких – ні

Вправа 210

Включіть в клас Vehicle метод changeSpeed ​​для завдання поточної швидкості машини відповідно з переданим значенням і метод stop для обнулення швидкості

Вправа 211

Включіть в клас LinkedList метод для підрахунку кількості елементів у списку

27 Посилання this

Ми вже бачили (на стр), як на початку роботи конструктора відбувається явний виклик іншого конструктора класу Оголошення посилання на обєкт this може також застосовуватися всередині нестатичних методів вона вказує на поточний обєкт, для

якого був викликаний даний метод this найчастіше за все використовується для передачі посилання на

поточний обєкт як параметр для інших методів Припустимо, у методі необхідно поповнити список обєктів, які очікують обслуговування Такий виклик може виглядати наступним чином:

Serviceadd(this)

this неявно додається в початок кожного посилання на поле або метод, якщо тільки програміст не вказав посилання на інший обєкт Наприклад, присвоєння значення полю str в наступному класі:

class Name {

public String str Name() {

str = “”

}

}

рівносильне наступному:

thisstr = “”

Зазвичай this використовується тільки у випадку необхідності, тобто коли імя поля, до якого ви звертаєтеся, ховається оголошенням змінної або параметра Наприклад:

class Moose {

String hairdresser

Moose(String hairdresser) {

thishairdresser = hairdresser

}

}

Поле hairdresser всередині конструктора ховається присутністю однойменного параметра Щоб звернутися до поля hairdresser, а не до параметру, ми ставимо перед імям посилання this, вказуючи тим самим, що поле належить до поточного обєкту Навмисне приховування ідентифікаторів, здійснюване подібним чином, може бути віднесено до хорошого стилю програмування лише при ідіоматичною використанні в конструкторах і методах доступу

Крім посилання this, може також застосовуватися посилання super, за допомогою якої здійснюється доступ до прихованих полях і виклик перевизначених методів суперкласу Ключове слово super докладно розглядається в розділі Перевизначення методів і приховування полів.

28 Перевантаження методів

У мові Java кожен метод має певної сигнатурою, Яка представляє собою сукупність імені з кількістю і типом параметрів Два методи можуть мати однакові імена, якщо їх сигнатури відрізняються за кількістю або типам параметрів Це називається перевантаженням (overloading), Оскільки просте імя методу перевантажується кількома значеннями Коли програміст викликає метод, компілятор за кількістю

і типу параметрів шукає той з існуючих методів, сигнатура якого підходить краще за всіх інших Наведемо як приклад різні методи orbits Around

нашого класу Body:

public Body orbitsAround() {

return orbits

}

public void orbitsAround(Body around) {

orbits = around

}

При подібному стилі програмування перевантаження служить для того, щоб відрізняти вибірку значення (параметри не передаються) від його завдання (вказується аргумент, що представляє собою нове значення) Кількість параметрів у двох методах відрізняється, тому вибрати потрібний метод буде нескладно Якщо orbitsAround викликається без параметрів, то використовується метод, який повертає поточне значення Якщо orbitsAround викликається з одним аргументом типу Body, то використовується метод, що задає значення Якщо ж виклик не підходить ні під одну з цих сигнатур, то він є невірним, а програма не компілюватиметься

Питання про те, як мова вибирає серед декількох перевантажених методів той, що потрібен для даного виклику, докладно розглядається в розділі Доступ до членів на стор

Вправа 212

Включіть в клас Vehicle два нових методи: один як параметр отримує кількість градусів, на яке повертає машина, а інший – одну з констант VehicleTURN_LEFT або VehicleTURN_RIGHT

Джерело: Арнольд К, Гослінг Д – Мова програмування Java (1997)

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*