неділю, 2 вересня 2012 р.

Java. Поняття та принципи програмування. Частина 1


Вступ
Виникненням комп'ютерної революції ми зобов'язані машині. Тому наші мови програмування намагаються бути ближче до цієї машини.
Але в той же час комп'ютери не стільки механізми, скільки засоби посилення думки («велосипеди для розуму», як любить говорити Стів Джоб), і ще один засіб самовираження. В результаті інструменти програмування все менше схиляються до машин і все більше тяжіють до наших умів, також як і до інших форм вираження людських устремлінь, якось: літературу, живопис, скульптура, анімація і кінематограф. Об'єктно-орієнтир-ванне програмування (ООП) - частина перетворення комп'ютера в засіб самовираження.
Ця глава познайомить вас із основами ООП, включаючи розгляд основних методів розробки програм. Вона, та книга взагалі, має на увазі наличие у вас досвіду програмування на процедурному мовою, не обов'язково С. Якщо вам здасться, що перед прочитанням цієї книги вам не вистачає знань в програмуванні та синтаксисі С.
Дана глава містить підготовчий і додатковий матеріали. Багато читачів вважають за краще спочатку уявити собі загальну картину, а вже потім розбиратися в тонкощах ООП. Тому багато ідей в даній главі служать того, щоб дати вам цілісне уявлення про ООП. Проте багато людей не сприймають загальної ідеї до тих пір, поки не побачать конкретно, як все працює; такі люди нерідко грузнуть в загальних словах, не маючи перед собою примірів. Якщо ви належите до останніх і палко бажаєте приступити до основам мови, можете відразу перейти до наступної глави - пропуск цієї не буде перешкодою для написання програм або вивчення мови. І все ж трохи пізніше вам варто повернутися до цієї чолі, щоб розширити свій кругозір і поняти, чому так важливі об'єкти і яке місце вони займають при проектуваннии програм.


Розвиток абстракції
Всі мови програмування побудовані на абстракції. Можливо, труднощі розв'язуваних завдань безпосередньо залежить від типу і якості абстракції. Під словом «тип» я маю на увазі: «Що конкретно ми абстрагуємося?» Мова асемблера є невелика абстракція від комп'ютера, на базі якого він працює.Багато так званих «командні» мови, створені слідом за ним (такі, * як Fortran, BASIC і С), являли собою абстракції наступного рівня. Ці мови володіли значною перевагою в порівнянні з асемблером, але їх основна абстракція, як і раніше змушує думати вас про структуру комп'ютера, а не про розв'язуваної задачі. Програміст повинен встановити зв'язок между моделлю машини (у «просторі рішення», яке представляє місце, де реалізується рішення, - наприклад, комп'ютер) і моделлю задачі, яку і потрібно вирішувати (в «просторі завдання», яке є місцем істотавання завдання - наприклад, прикладної областю). Для встановлення зв'язку требують зусилля, відірвані від власне мови програмування; в результаті з'являються програми, які важко писати і важко підтримувати. Мало того, це ще створило цілу галузь «методологій програмування».
Альтернативою моделюванню машини є моделювання розв'язуваної задачі. Ранні мови, подібні LISP і APL, вибирали особливий підхід до модиводжується навколишнього світу («Все завдання вирішуються списками» або «Алгоритми вирішують все» відповідно). PROLOG трактує всі проблеми як Ценирки рішень. Були створені мови для програмування, заснованого на системі обмежень, і спеціальні мови, в яких програмування здійсствлялось допомогою маніпуляцій з графічними конструкціями (область застосування останніх виявилася занадто вузькою). Кожен з цих підходів хороший в певній галузі вирішуваних завдань, але варто вийти з цієї сфери, як використовувати їх стає скрутно.
Об'єктний підхід робить крок вперед, надаючи програмісту засоби для представлення задачі в її просторі. Такий підхід має досить загальний характер і не накладає обмежень на тип розв'язуваної проблеми. Елементи простору завдання та їх представлення в просторі рішення нани опиняються «об'єктами». (Ймовірно, вам знадобляться і інші об'єкти, що не мають аналогів в просторі завдання.) Ідея полягає в тому, що програма може адаптуватися до специфіки задачі за допомогою створення нових типов об'єктів так, що під час читання коду, вирішального завдання, ви одновременно бачите слова, її описують. Це більш гнучка і потужна абстракція, що перевершує за своїми можливостями все, що існувало раніше. Таким чином, ООП дозволяє описати задачу в контексті самої задачі, а не в контексті комп'ютера, на якому буде виконане рішення. Втім, зв'язок з комп'ютером все ж збереглася. Кожен об'єкт схожий на маленький комп'ютер; у нього є стан та операції, які він дозволяє проводити. Такая аналогія непогано поєднується із зовнішнім світом, який є «реальність, дана нам у об'єктах», що мають характеристики і поведінку.
Алан Кей підбив підсумок і вивів п'ять основних рис мови Smalltalk - першого вдалого об'єктно-орієнтованої мови, одного з попередників Java. Ці характеристики представляють «чистий», академічний підхід до об'єктно-орієнтованого програмування:
• Всі є об'єктом. Уявляйте собі об'єкт як вдосконалятьванну змінну; він зберігає дані, але ви можете «поводитися з запросив» до об'єкта, вимагаючи у нього виконати операції над собою.Теоретично абсолютно будь-який компонент розв'язуваної задачі (собака, будівля, послуга тощо) може бути представлений у вигляді об'єкта.
• Програма - це група об'єктів, що вказують один одному, що делать, за допомогою повідомлень. Щоб звернутися із запитом до об'єкта, ви «посилаєте йому повідомлення». Більш наочно можна уявити повіщення як виклик методу, що належить визначеному об'єкту.
• Кожен об'єкт має власну «пам'ять», що складається з інших об'єктів. Іншими словами, ви створюєте новий об'єкт за допомогою вбудоввання в нього вже існуючих об'єктів. Таким чином, можна сконструіровать як завгодно складну програму, приховавши загальну складність за простотою окремих об'єктів.
• У кожного об'єкта є тип. В інших термінах, кожен об'єкт являєся екземпляром класу, де «клас» є аналогом слова «тип». Важдальшої відміну класів один від одного як раз і полягає у відповіді на питання: «Які повідомлення можна надсилати об'єкту?»
• Всі об'єкти певного типу можуть отримувати однакові сообщеня. Як ми незабаром переконаємося, це дуже важлива обставина. Так як об'єкт типу "коло" також є об'єктом типу «фігура», справедливе твердження, що «коло» свідомо здатний приймати повідомлення для «фігури». А це означає, що можна писати код для фігур і бути впевненийвим у тому, що він підійде для всього, що потрапляє під поняття фігури. Взаємозамінність представляє одне з найбільш потужних понять ООП.
Буч запропонував ще більш лаконічний опис об'єкта:
Об'єкт має статки, поведінкою і індивідуальністю.
Суть сказаного в тому, що об'єкт може мати у своєму розпорядженні внутренніе дані (які і є стан об'єкта), методи (які визначають поведінку), і кожен об'єкт можна унікальним чином відрізнити від будь-якого іншого об'єкта - кажучи більш конкретно, кожний об'єкт володіє унікальною адресою в пам'яті.

Об'єкт має інтерфейс
Ймовірно, Аристотель був першим, хто уважно вивчив поняття типу \ він говорив про «класі риб і класі птахів». Концепція, що всі об'єкти, будучи унікальними, в той же час є частиною класу об'єктів з подібними характеристики і поведінкою, була використана в першому об'єктно-орієнторовать мовою Simula-67, з введенням фундаментального ключового слова class, яке вводило новий тип в програму.
Мова Simula, як передбачає його ім'я, був створений для розвитку і моделівання ситуацій, подібних класичної задачі «банківський касир». У вас є групи касирів, клієнтів, рахунків, платежів і грошових одиниць - багато «об'єктів». Об'єкти, ідентичні у всьому, крім внутрішнього стану під час роботи програми, групуються в «класи об'єктів». Звідси і прийшло ключове слово class.Створення абстрактних типів даних є фунтальні поняття у всьому об'єктно-орієнтованому програмуванні. Абстрактні типи даних діють майже так само, як і вбудовані типи: ви можете створювати змінні типів (звані об'єктами або екземплярами в термінах ООП) і маніпулювати ними (що називається посилкою повідомлень або запитом; ви виробляєте запит, і об'єкт вирішує, що з ним робити ). Члени (елементи) кожного класу мають схожістю: у кожного рахунку є баланс, кожен касир приймає депозити, і т. п. У той же час всі члени відличать внутрішнім станом: у кожного рахунку баланс індивідуальний, каждий касир має людське ім'я. Тому всі касири, замовники, рахунки, переводи та інше можуть бути представлені унікальними сутностями всередині комп'ютерної програми. Це і є суть об'єкта, і кожен об'єкт принадилежить до певного класу, який визначає його характеристики та поведення.
Таким чином, хоча ми реально створюємо в об'єктних мовах нові типи даних, фактично всі ці мови використовують ключове слово «клас». Коли бачите слово «тип», думайте «клас», і навпаки.
Оскільки клас визначає набір об'єктів з ідентичними характеристиками (елементи даних) і поведінкою (функціональність), клас насправді є типом даних, тому що, наприклад, число з плаваючою запятій теж має ряд характеристик і особливості поведінки. Різниця полягає в тому, що програміст визначає клас для представлення деякого аспекта завдання, замість використання вже існуючого типу, що представляє одиницю зберігання даних в машині. Ви розширюєте мова програмування, додаючи нові типи даних, відповідні вашим потребам. Система програмування прихильна до нових класів і приділяє їм точно таку ж увагу, як і вбудованим типам.
Об'єктно-орієнтований підхід не обмежений побудовою моделей. Згідні ви чи ні, що будь-яка програма - модель розробляється вами системи, незалежно від вашої думки ООП-технології спрощують вирішення широкого кола завдань.
Після визначення нового класу ви можете створити будь-яку кількість об'єктів цього класу, а потім маніпулювати ними так, як ніби вони представляють собою елементи розв'язуваної задачі.Насправді однією з основних важкостей в ООП є встановлення однозначної відповідності між об'єктами простору завдання та об'єктами простору рішення.
Але як змусити об'єкт виконувати потрібні вам дії? Повинен существова механізм передачі запиту до об'єкту на виконання деякого действия - завершення транзакції, малювання на екрані і т. д. Кожен об'єкт вміє виконувати тільки певне коло запитів. Запити, які ви можете посилати об'єкту, визначаються його інтерфейсом, причому інтерфейс об'єкта визначається його типом.Найпростішим прикладом може стати електрична лампочка:
Ім'я типу
Інтерфейс
Light It = new LightO,
It on ().
Інтерфейс визначає, які запити ви маєте право робити до певного об'єкту. Однак десь повинен існувати і код, що виконує запити. Цей код, поряд з прихованими даними, становить реалізацію. З точки зреня процедурного програмування відбувається не так вже складно. Тип сотримає метод для кожного можливого запиту, і при отриманні виразного запиту викликається потрібний метод. Процес зазвичай поєднується в одне ціле: і «відправка повідомлення» (передача запиту) об'єкту, і його обробка об'єктом (виконання коду).
У даному прикладі існує тип (клас) з ім'ям Light (лампа), конкуруютьний об'єкт типу Light з ім'ям It, і клас підтримує різні запити до об'єкту Light: вимкнути лампочку, включити, зробити яскравіше або пригасити. Ви створюєте об'єкт Light, визначаючи «заслання» на нього (It) і викликаючи оператор new для створення нового екземпляра цього типу. Щоб послати повідомлення об'єкекту, слід вказати ім'я об'єкта і пов'язати його з потрібним запитом знаком точки. З точки зору користувача заздалегідь певного класу, цього цілком достаточно для того, щоб оперувати його об'єктами.
Діаграма, показана вище, слід формату UML (Unified Modeling Language). Кожен клас представлений прямокутником, всі описувані поля даних поміщені в середній його частині, а методи (функції об'єкта, якому ви посилаєте повідомлення) перераховуються в нижній частині прямокутника.
Часто на діаграмах UML показуються тільки ім'я класу і відкриті методи, а середня частина відсутня. Якщо ж вас цікавить тільки ім'я класу, то можете пропустити і нижню частину.

Об'єкт надає послуги
У той момент, коли ви намагаєтеся розробити або зрозуміти структуру програми, часто буває корисно представити об'єкти в якості «постачальників послуг». Ваша програма надає послуги користувачеві, і робить вона це за допомогою послуг, що надаються іншими об'єктами. Ваша мета - справити (а ще краще відшукати в бібліотеках класів) той набір об'єктів, який буде оптімальним для вирішення вашого завдання.
Для початку запитайте себе: «якби я міг по чарівництву виймати об'єкти з капелюха, які б з них змогли вирішити мою задачу прямо зараз?» Предпложим, що ви розробляєте бухгалтерську програму. Можна уявити собі набір об'єктів, що надають стандартні вікна для введення бухгалтерської інформації, ще один набір об'єктів, що виконують бухгалтерські расчети, об'єкт, що відає роздруківкою чеків і рахунків на всіляких принтірах. Можливо, деякі з таких об'єктів вже існують, а для інших об'єктів варто з'ясувати, як вони могли б виглядати. Які послуги могли б надавати ті об'єкти, і які об'єкти знадобилися б їм для виконання своєї роботи? Якщо ви будете продовжувати в тому ж дусі, то рано чи поздно скажете: «Цей об'єкт досить простий, так що можна сісти і записати його», або «Напевно такий об'єкт уже існує». Це розумний спосіб розкинути рішення задачі на окремі об'єкти.
Подання об'єкта в якості постачальника послуг володіє дополнительвим перевагою: воно допомагає поліпшити связуемост' (cohesiveness) об'єкта. Хороша связуемост' - найважливіша якість програмного продукту: вона означає, що різні аспекти програмного компонента (такого як об'єкт, хоча сказане також може відноситися до методу або до бібліотеки об'єктів) добре «стикуються» один з одним. Однією з типових помилок, що допускаються при проектуванні об'єкта, є перенасичення його великим количеством властивостей і можливостей. Наприклад, при розробці модуля, який відає роздруківкою чеків, ви можете захотіти, щоб він «знав» все про форматування та друку. Якщо подумати, швидше за все, ви прийдете до висновку, що для одного об'єкта цього занадто багато, і перейдете до трьох або більше об'єктів. Один об'єкт буде являти собою каталог всіх можливих форм чеків, і його можна буде запитати про те, як слід роздрукувати чек. Інший об'єкт або набір об'єктів стануть відповідати за узагальнений інтерфейс друку, «знаючий» все про різні типи принтерів (але нічого не «розуміє» в бухгалтерії - такий об'єкт краще купити, ніж розробляти самому). Нарешті, третий об'єкт просто буде користуватися послугами описаних об'єктів, для того щоб виконати завдання. Таким чином, кожен об'єкт являє собою пов'язаний набір пропонованих їм послуг. У добре спланованому об'єктно-орієнтованому проекті кожен об'єкт добре справляється з однією конкретною задачею, не намагаючись при цьому зробити більше потрібного. Як було показано, це не тільки дозволяє визначити, які об'єкти варто придбати (об'єкт з інтерфейсом друку), але також дає можливість отримати в результаті об'єкт, який потім можна використовувати десь ще (каталог чеків).
Подання об'єктів в якості постачальників послуг значно спрощуєтьсяет задачу. Воно корисно не тільки під час розробки, але і коли хто-небудь понамагається зрозуміти ваш код або повторно використовувати об'єкт - тоді він зможе адекватно оцінити об'єкт за рівнем наданого сервісу, і це значительно спростить інтеграцію останнього в інший проект.

Прихована реалізація
Програмістів корисно розбити на творців класів (ті, хто створює нові типи даних) і програмістів-клієнтів (споживачі класів, що використовують типи даних в своїх додатках). Мета других - зібрати якомога більше класів, щоб займатися швидкої розробкою програм. Мета творця класу - побудувати клас, що відкриває тільки те, що необхідно программісту-клієнтові, і ховає все інше. Чому?Програміст-клієнт не сможет отримати доступ до прихованих частин, а значить, творець класів залишає за собою можливість довільно їх змінювати, не побоюючись, що це комусь зашкодить.«Потаєні» частина зазвичай і сама «крихка» частину об'єкта, яку легко може зіпсувати необережний або недосвідчена програміст-клієнт, тому приховування реалізації скорочує кількість помилок в програмах.
У будь-яких відносинах важливо мати які-небудь межі, не переступати ніким з учасників. Створюючи бібліотеку, ви встановлюєте стосунки з програмістом-клієнтом. Він є таким же програмістом, як і ви, але буде використовувати вашу бібліотеку для створення додатка (а може бути, бібліотеки більш високого рівня). Якщо надати доступ до всіх членів класу кому завгодно, програміст-клієнт зможе зробити з класом все, що йому заманеться, і ви ніяк не зможете змусити його «грати за правилам». Навіть якщо вам згодом знадобиться обмежити доступ до визналенним членам вашого класу, без механізму контролю доступу це осуществити неможливо. Всі будова класу відкрито для всіх бажаючих.
Таким чином, першою причиною для обмеження доступу є необність вберегти «тендітні» деталі від програміста-клієнта - частини внутренній «кухні», що не є складовими інтерфейсу, за допомогою якого користувачі вирішують свої завдання. Насправді це корисно і користувачам - вони відразу побачать, що для них важливо, а що вони можуть ігнорувати.
Друга причина появи обмеження доступу - прагнення дозволити розробнику бібліотеки змінити внутрішні механізми класу, не турбуйсяЯсь про те, як це працює на програміста-клієнті.Наприклад, ви можете реалізувати певний клас «на швидку руку», щоб прискорити розробку програми, а потім переписати його, щоб підвищити швидкість роботи. Якщо ви правильно розділили і захистили інтерфейс і реалізацію, зробити це буде зовсім нескладно.
Java використовує три явних ключових слова, що характеризують рівень доступа: public, private і protected. Їх призначення та вживання дуже прости. Ці специфікатори доступу визначають, хто має право використовувати наступні за ними визначення. Слово public означає, що подальші визнаділення доступні всім. Навпаки, слово private означає, що наступні за ним пропозиції доступні тільки творцеві типу, всередині його методів. Термін private - «кріпосна стіна» між вами і програмістом-клієнтом. Якщо хтось понамагається використовувати private-члени, він буде зупинений помилкою компіляції. Специфікатор protected діє схоже з private, за одним виняткомем - похідні класи мають доступ до членів, поміченим protected, але не мають доступса до private-членам (спадкування ми незабаром розглянемо).
У Java також є доступ «за умовчанням», використовуваний при відсутності какого-небудь з перерахованих специфікатор. Він також іноді називається доступому в межах пакету (package access), оскільки класи можуть використовувати дружні члени інших класів зі свого пакета, але за його межами ті ж дружні члени набувають статусу private.

Повторне використання реалізації
Створений і протестований клас повинен (в ідеалі) являти собою корисний блок коду. Однак виявляється, що досягти цієї мети набагато працянеї, ніж багато хто вважає; для розробки повторно використовуваних об'єктів вимагається досвід і розуміння суті справи. Але як тільки у вас вийде хороша конструкція, вона буде просто напрошуватися на впровадження в інші програми.Багаторазове використання коду - одне із самих вражаючих преимуществ об'єктно-орієнтованих мов.
Найпростіше використовувати клас повторно, безпосередньо створюючи його об'єкт, але ви можете також помістити об'єкт цього класу всередину нового класу. Ми називаємо це впровадженням об'єкта. Новий клас може містити будь колічество об'єктів інших типів, в будь-якому поєднанні, яке необхідно для досягнення необхідної функціональності. Так як ми складаємо новий клас із вже існуючих класів, цей спосіб називається композицією (якщо композиція виконується динамічно, вона зазвичай іменується агрегуванняем). Композицію часто називають зв'язком типу «має» (has-a), як, наприклад, у реченні «в автомобіля є двигун».

Автомобіль Двигун
(На UML-діаграмах композиція позначається зафарбовані ромбом. Я трохи спрощений цей формат: залишу тільки просту лінію, без ромба, щоб позначити зв'язок.)
Композиція - дуже гнучкий інструмент. Об'єкти-члени вашого нового класу зазвичай оголошуються закритими (private), що робить їх недоступними для програмістів-клієнтів, що використовують клас. Це дозволяє вносити изменання в ці об'єкти-члени без модифікації вже існуючого клієнтського коду. Ви можете також змінювати ці члени під час виконання програми, щоб динамічно управляти поведінкою вашої програми. Спадкування, описане нижче, не має такої гнучкості, так як компілятор накладає определенние обмеження на класи, створені із застосуванням наслідування.
Спадкування відіграє важливу роль в об'єктно-орієнтованому программування, тому на ньому часто акцентується підвищену увагу, і новічок може подумати, що спадкування повинно застосовуватися всюди. А це загрожує створенням незграбних і надмірно складних рішень. Замість цього при створенні нових класів насамперед слід оцінити можливість композиції, так як вона простіше і гнучкіше. Якщо ви візьмете на озброєння рекомендуємомий підхід, ваші програмні конструкції стануть набагато ясніше. А в міру накопичення практичного досвіду зрозуміти, де слід застосовувати спадкування, не складе труднощів.

Спадкування
Сама по собі ідея об'єкта вкрай зручна. Об'єкт дозволяє поєднувати дані і функціональність на концептуальному рівні, тобто ви можете уявити потрібне поняття проблемної області перш, ніж почнете його конкретизуютьвати стосовно до діалекту машини. Ці концепції і утворюють фундаментальне одиниці мови програмування, описувані за допомогою ключовихго слова class.
(Стрілка на UML-діаграмі спрямована від похідного класу до базового класу. Як ви незабаром побачите, може бути і більше одного похідного класу.)
Але погодьтеся, було б образливо створювати якийсь клас, а потім просунуввати всю роботу заново для схожого класу. Набагато раціональніше взяти готовий клас, «клонувати» його, а потім внести додавання і поновлення в підлозіченний клон. Це саме те, що ви отримуєте в результаті спадкування, з одним винятком - якщо початковий клас (званий також базовим-класом, суперкласом або батьківським класом) змінюється, то все зрадіня відбиваються і на його «клоні» (званому похідним класом, успадкуєванним класом, підкласом або дочірнім класом).
Тип визначає не тільки властивості групи об'єктів; він також пов'язаний з друшими типами. Два типи можуть мати спільні риси і поведінку, але розрізнятися кількістю характеристик, а також здатністю обробити більше число повідомлень (або обробити їх по-іншому). Для вираження цієї спільності типов при спадкуванні використовується поняття базових і похідних типів. Базовий тип містить всі характеристики і дії, загальні для всіх типів, проізводних від нього. Ви створюєте базовий тип, щоб представити основу свого уявлення про якісь об'єктах у вашій системі. Від базового типу порождаються інші типи, що виражають інші реалізації цієї сутності.
Наприклад, машина з переробки сміття сортує відходи. Базовим типом буде «сміття», і кожна частка сміття має вагу, вартість і т. п., і может бути роздроблена, розплавлена ??або розкладена.Відштовхуючись від цього, наслід більш певні види сміття, що мають додаткові характеристики (пляшка має колір) або риси поведінки (алюмінієву банку можна зім'яти, сталева банку притягується магнітом). Вдобавок, некоторие риси поведінки можуть різнитися (вартість паперу залежить від її типу і стану). Спадкоємство дозволяє скласти ієрархію типів, що описуєкають вирішувану завдання в контексті її типів.
Другий приклад - класичний приклад з геометричними фігурами. Базовим типом тут є «фігура», і кожна фігура має розмір, колір, расположення і т. п. Кожну фігуру можна намалювати, стерти, перемістити, зафарбувати р т. д. Далі виробляються (успадковуються) конкретні різновиди фігур: коло, квадрат, трикутник і т. п., кожна з яких має свої додаткові характеристики і риси поведінки.Наприклад, для деяких фігур підтримується операція дзеркального відображення. Окремі риси поведінки можуть різнитися, як у випадку обчислення площі фігури. Иерархія типів втілює як схожі, так і різні властивості фігур.
Приведення рішення до понять, використаним у прикладі, надзвичайно зручно, тому що вам не потрібно безліч проміжних моделей, що зв'язують опис рішення з описом завдання. При роботі з об'єктами первинної моделлю стає ієрархія типів, так що ви переходите від описания системи реального світу прямо до опису системи в програмному коді. Насправді одна з труднощів в об'єктно-орієнтованому плануванні полягає в тому, що вже дуже просто ви проходите від початку завдання до кінця реності. Розум, натренований на складні рішення, часто заходить у глухий кут при використанні простих підходів.
Використовуючи спадкоємство від існуючого типу, ви створюєте новий тип. Цей новий тип не тільки містить всі члени існуючого типу (хоча члени, помічені як private, приховані і недоступні), але і, що ще важливіше, повторет інтерфейс базового класу. Значить, всі повідомлення, які ви могли посилать базового класу, ви також вправі посилати і похідному класу. А так як ми розрізняємо типи класів за сукупністю повідомлень, які можем їм посилати, це означає, що похідний клас є приватним случаєм базового класу. У попередньому прикладі «окружність є фігура».Еквівалентність типів, що досягається при спадкуванні, є одним з основополагающіх умов розуміння змісту об'єктно-орієнтованого программирования.
Так як і базовий, і похідний класи мають однаковий основний інтерфейс, повинна існувати і реалізація для цього інтерфейсу. Іншими словами, десь повинен бути код, що виконується при отриманні об'єктом визнаділеного повідомлення. Якщо ви просто успадкували клас і більше не предприймали ніяких дій, методи з інтерфейсу базового класу перейдуть до похідний клас без змін. Це означає, що об'єкти похідного класу не тільки однотипні, але і володіють однаковим поведінкою, а при цьому саме спадкування втрачає сенс.
Існує два способи зміни нового класу в порівнянні з базовим класом. Перший досить очевидний: в похідний клас включаються нові методи. Вони вже не є частиною інтерфейсу базового класу.Мабуть, базовий клас не робив все, що було потрібно в даній задачі, і ви доповнили його новими методами. Втім, такий простий і примітивний підхід до Наслнання іноді виявляється ідеальним вирішенням проблеми. Однак треба уважно розглянути, чи дійсно базовий клас потребує цих додаткових методах. Процес виявлення закономірностей і перегляду архітектури є повсякденною справою в об'єктно-орієнтованому программирования.
Хоча спадкування іноді наводить на думку, що інтерфейс буде доповнений новими методами (особливо в Java, де успадкування позначається ключовим словом extends, тобто «розширювати»), це зовсім не обов'язково. Другий, більш важливий спосіб модифікації класів полягає в зміні поведінки вже існуючих методів базового класу. Це називається перевизначенням (або заміщенням) методу.
Для заміщення методу потрібно просто створити нове визначення цього метода в похідному класі. Ви як би говорите: «Я використовую той же метод інтерфейс, але хочу, щоб він виконував інші дії для мого нового типу».
Ставлення «є» в порівнянні з «схоже»
При використанні успадкування встає очевидне запитання: чи варто при наслідуванні перевизначати тільки методи базового класу (і не додавати ноші методи, не існуючі в базовому класі)?Це означало б, що виробводний тип буде точно такого ж типу, як і базовий клас, так як вони мають однаковий інтерфейс. В результаті ви можете вільно замінювати об'єкекти базового класу об'єктами похідних класів. Можна говорити про повної заміні, і це часто називається принципом заміни. У певному сенсі це спосіб успадкування ідеальний. Подібний спосіб взаємозв'язку базового і похідного класів часто називають зв'язком «є тим-то», оскільки можна сказати «коло є фігура». Щоб визначити, наскільки доречним будет спадкування, досить перевірити, чи існує відношення «є» між класами і наскільки воно виправдано.
В інших випадках інтерфейс похідного класу доповнюється новими елементами, що призводить до його розширення. Новий тип все ще може приміняться замість базового, але тепер ця заміна не ідеальна, тому що вона не позволяет використовувати нові методи з базового типу. Подібна зв'язок описується виразом «схоже на» (це мій термін); новий тип містить інтерфейс старого типу, але також включає в себе і нові методи, і не можна сказати, що ці типи абсолютно однакові. Для прикладу візьмемо кондиціонер.
Припустимо, що ваш будинок забезпечений всім необхідним обладнанням для контролю процесу охолодження. Уявімо тепер, що кондиціонер зламався і ви замінили його обігрівачем, здатним як нагрівати, так і охолоджувати. Обігрівач «схожий на" кондиціонер, але він здатний і на більше. Так як система управління вашого будинку здатна контролювати тільки охолодження, вона обмежена в комунікаціях з охолоджуючої частиною нового об'єкта. Інтерфейс нового об'єкту був розширений, а існуюча система нічого не признає, окрім оригінального інтерфейсу.
Звичайно, при вигляді цієї ієрархії стає ясно, що базовий клас «охлаждающего система» недостатньо гнучкий; його слід перейменувати у «систему контролю температури» так, щоб він включав і нагрів, - і після цього спрацьовує принцип заміни. Тим не менше ця діаграма являє приклад того, що може статися в реальності.
Після знайомства з принципом заміни може виникнути враження, що цей підхід (повна заміна) - єдиний спосіб розробки. Взагалі кажучи, якщо ваші ієрархії типів так працюють, це дійсно добре.Але в некоторих ситуаціях абсолютно необхідно додавати нові методи до Інтерфілсу похідного класу. При уважному аналізі обидва випадки представляються досить очевидними.

Взаємозамінні об'єкти і поліморфізм
При використанні ієрархій типів часто доводиться звертатися з об'єктом певного типу як з базовим типом. Це дозволяє писати код, не залежачищий від конкретних типів. Так, в прикладі з фігурами методи маніпулюють просто фігурами, не звертаючи уваги на те, чи є вони колами, прямокутниками, трикутниками або деякими ще навіть не визначеновими фігурами. Всі фігури можуть бути намальовані, стерті та переміщені, а методи просто посилають повідомлення об'єкту «фігура»; їм байдуже, як об'єкт обійдеться з цим повідомленням.
Подібний код не залежить від додавання нових типів, а додавання нових типів є найбільш поширеним способом розширення об'єктно-орієнтованих програм для обробки нових ситуацій.Наприклад, ви можете створити новий підклас фігури (п'ятикутник), і це не призведе до витрансформаційних змін методів, що працюють тільки з узагальненими фігурами. Можливість простого розширення програми введенням нових похідних типів дуже важлива, тому що вона помітно покращує архітектуру програми, в той же час знижуючи вартість підтримки програмного забезпечення.
Однак при спробі звернення до об'єктів похідних типів як до базовим типам (окружності як фігурі, велосипеду як засобу пересування, баклани як птаху і т. п.) виникає одна проблема. Якщо метод збирається наказати узагальненої фігурі намалювати себе, чи засобу пересування слідувати за певним курсом, або птаху полетіти, компілятор не може точно знати, яка саме частина коду виконається. У цьому вся справа - коли посилается повідомлення, програміст і не хоче знати, який код виконується; метод промальовування з однаковим успіхом може застосовуватися і до окружності, і до прямокутника, і до трикутника, а об'єкт виконає вірний код, залежачищий від його характерного типу.
Якщо вам не потрібно знати, який саме фрагмент коду виконується, то, когда ви додаєте новий підтип, код його реалізації може змінитися, але без змін у тому методі, з якого він був викликаний.Якщо компілятор не обладає інформацією, який саме код слід виконати, що ж він робить? У наступному прикладі об'єкт BirdController (управління птицею) може роботать тільки з узагальненими об'єктами Bird (птах), не знаючи типу конкретного об'єкта. З точки зору BirdController це зручно, оскільки для нього не прийдеся писати спеціальний код перевірки типу використовуваного об'єкта Bird для обництва якогось особливого поводження. Як же все-таки відбувається, що при визове методу move () без вказівки точного типу Bird виповнюється вірне действія - об'єкт Goose (гусак) біжить, летить або пливе, а об'єкт Penguin (пінгвін) біжить або пливе?
Відповідь пояснюється головною особливістю об'єктно-орієнтованого программирования: компілятор не може викликати такі функції традиційним способом. При викликах функцій, створених не ООП-компілятором, викоється раннє зв'язування - багато хто не знає цього терміна просто тому, що не уявляють собі іншого варіанту. При ранньому зв'язуванні компілятор генерує виклик функції з вказаним ім'ям, а компонувальник прив'язує цей виклик до абсолютного адресою коду, який необхідно виконати. У ООП програма не в змозі визначити адресу коду до часу виконання, поцього при відправці повідомлення об'єкту повинен спрацьовувати інший механізм.
Для вирішення цього завдання мови об'єктно-орієнтованого програмуваннявання використовують концепцію пізнього зв'язування. Коли ви посилаєте повіщення об'єкта, що викликається код невідомий аж до часу виконання. Компілятор лише переконується в тому, що метод існує, перевіряє типи для його параметрів і значення, що повертається, але не має уявлення, який саме код буде виконуватися.
Для здійснення пізнього зв'язування Java замість абсолютного виклику використовує спеціальні фрагменти коду. Цей код обчислює адресу тіла метода на основі інформації, що зберігається в об'єкті (процес дуже докладно описан в главі 7). Таким чином, кожен об'єкт може вести себе по-різному, в залежно від вмісту цього коду. Коли ви посилаєте повідомлення, об'єкт фактично сам вирішує, що ж з ним робити.
У деяких мовах необхідно явно вказати, що для методу повинен іскористуватися гнучкий механізм пізнього зв'язування (в С++ для цього передивіться ключове слово virtual). У цих мовах методи за замовчуванням компонуются не динамічно. У Java пізніше зв'язування проводиться за умовчнію, і вам не потрібно пам'ятати про необхідність додавання будь-яких ключівих слів для забезпечення поліморфізму.
Згадаймо про прикладі з фігурами. Сімейство класів (заснованих на одинаковом інтерфейсі) було показано на діаграмі трохи раніше в цій главі. Для демонстрації поліморфізму ми напишемо фрагмент коду, який ігнорує характерні особливості типів і працює тільки з базовим класом. Цей код відділений від специфіки типів, тому його простіше писати і розуміти. І якщо новий тип (наприклад, шестикутник) буде доданий допомогою наследованя, то написаний вами код буде працювати для нового типу фігури так само хорошо, як раніше. Таким чином, програма стає розширюваною.
Припустимо, ви написали на Java наступний метод (незабаром ви дізнаєтеся, як це робити):
void doSomething (Shape shape) {shape.eraseO: II стерти II ...
shape.drawO, II намалювати}
Метод працює з узагальненої фігурою (Shape), тобто не залежить від конконкретного типу об'єкта, який малюється або стирається. Тепер ми використовуємо виклик методу doSomething () в іншій частині програми:

Circle circle = new CircleO. / / Окружність 
Triangle triangle = new TriangleO; II трикутник 
Line line = new LineO; / / лінія 
doSomething (circle). doSomething (triangle). doSomething (line);

Виклики методу doStuff () автоматично працюють правильно, поза залежності від фактичного типу об'єкта. Насправді це досить важливий факт. Розглянемо рядок:
doSomething (c);
Тут відбувається наступне: методу, котрий очікує об'єкт Shape, передається об'єкт «окружність» (Circle). Так як окружність (Circle) одночасно являєся фігурою (Shape), то метод doSomething () і поводиться з нею, як з фігурою. Іншими словами, будь-яке повідомлення, яке метод може послати Shape, також приймається і Circle. Ця дія абсолютно безпечно і настільки ж логічної.
Ми називаємо цей процес поводження з похідним типом як з базовим висхідним перетворенням типів. Слово перетворення означає, що об'єкт трактується як приналежний до іншого типу, а висхідне воно тому, що на діаграмах спадкування базові класи зазвичай розташовуються вгорі, а похідні класи розташовуються внизу «віялом». Значить, перетворення до базового типу - це рух по діаграмі вгору, і тому воно «простуючиний».
Об'єктно-орієнтована програма майже завжди містить висхідний перетворення, тому що саме так ви позбавляєтеся від необхідності знати точний тип об'єкта, з яким працюєте. Подивіться на тіло методу doSomething ():
shape erase ().
/ /.
shape drawO,
Зауважте, що тут не сказано «якщо ти об'єкт Circle, роби це, а якщо ти об'єкт Square, роби те-то і те-то». Такий код з окремими діями для каждого можливого типу Shape буде плутаним, і його доведеться міняти кожного разу при додаванні нового підтипу Shape. А так, ви просто говорите: «Ти фігура, і я знаю, що ти здатна намалювати і стерти себе, ну так і роби це, а про деталях позбутися сама».
У коді методу doSomething () цікаво те, що все само собою виходить правильно. При виклику draw () для об'єкта Circle виповнюється інший код, а не той, що відпрацьовує при виклику draw () для об'єктів Square або Line, а коли draw () застосовується для невідомої фігури Shape, правильна поведінка забезпечує використанням реального типу Shape. Це у вищій мірі цікаво, тому що, як було відмічено трохи раніше, коли компілятор генерує код doSomething (), він не знає точно, з якими типами він працює. Відповідно, можна було б очікувати виклику версій методів draw () і erase () з базового класу Shape, а не їх варіантів з конкретних класів Circle, Square або Line. І тим не менше все працює правильно завдяки поліморфізму. Компілятор та система виконання беруть на себе всі подробиці; все, що вам потрібно знати, - як це відбувається ... і, що ще важливіше, як створювати програми, використовуючи такий підхід. Коли ви посилаєте повідомлення об'єкту, об'єкт виберет правильний варіант поведінки навіть при висхідному перетворенні.

Однокорневих ієрархія
Незабаром після появи С++ почало активно обговорюватися питання - чи повинні всі класи обов'язково наслідувати від єдиного базового класу? У Java (як практично у всіх інших ООП-мовах, окрім С++) на це питання було дано позитивну відповідь. В основі всієї ієрархії типів лежить єдиний базовий клас Object. Виявилося, що однокореневі ієрархія має безліч перевагществ.
Всі об'єкти в однокорневих ієрархії мають якийсь загальний інтерфейс, так що по великому рахунку всі вони можуть розглядатися як один основоположнийщий тип. У С + + був обраний інший варіант - спільного предка в цій мові не існує. З точки зору сумісності зі старим кодом ця модель краще відповідає традиціям С, і можна подумати, що вона менш обмежена. Але як тільки виникне необхідність у повноцінному об'єктно-орієнтованийном програмуванні, вам доведеться створювати власну ієрархію классов, щоб отримати ті ж переваги, що вбудовані в інші ООП-мови. Та й у будь-якій новій бібліотеці класів вам може зустрітися небудь несомісткість інтерфейс. Включення цих нових інтерфейсів в архітектуру вашей програми зажадає зайвих зусиль (і можливо, множинного Наслнання). Чи варто додаткова «гнучкість» С + + подібних витрат? Якщо вам це потрібно (наприклад, при великих вкладеннях у розробку коду С), то в програші ви не залишитеся. Якщо ж розробка починається «з нуля», підхід Java виглядає більш продуктивним.
Всі об'єкти з однокореневі ієрархії гарантовано володіють некоторій загальної функціональністю. Ви знаєте, що з будь-яким об'єктом в системі можна провести певні основні операції. Всі об'єкти легко створюються в динамічній «купі», а передача аргументів сильно спрощується.
Однокорневих ієрархія дозволяє набагато простіше реалізувати прибирання сміттяра - одне з найважливіших удосконалень Java в порівнянні з С + +. Так як інформація про тип під час виконання гарантовано присутня в будь-якому з об'єктів, в системі ніколи не з'явиться об'єкт, тип якого не вдасться визначити. Це особливо важливо при виконанні системних операцій, таких як обробка виключень, і для забезпечення більшої гнучкості програмування.

Контейнери
Часто буває заздалегідь невідомо, скільки об'єктів буде потрібно для вирішення певної задачі і як довго вони будуть існувати. Також незрозуміло, як зберігати такі об'єкти. Скільки пам'яті слід виділити для зберігання цих об'єктів? Невідомо, так як ця інформація стане доступна тільки під час роботи програми.
Багато проблем в об'єктно-орієнтованому програмуванні решаються простим дією: ви створюєте ще один тип об'єкта. Новий тип об'єкта, вирішального цю конкретну задачу, містить посилання на інші об'єкти. Звичайно, цю роль можуть виконати і масиви, підтримувані в блешністве мов. Однак новий об'єкт, зазвичай званий контейнером (або ж колекцією, але в Java цей термін використовується в іншому значенні), буде по нехідно розширюватися, щоб вмістити все, що ви в нього покладете. Поцього вам не потрібно буде знати заздалегідь, на скільки об'єктів розрахована емкість контейнера. Просто створіть контейнер, а він уже подбає про докладноностях.
На щастя, хороший ООП-мову поставляється з набором готових контейнерахрів. У С++ це частина стандартної бібліотеки С++, іноді звана бібліотекой стандартних шаблонів (Standard Template Library, STL). Smalltalk подається з дуже широким набором контейнерів. Java також містить конконтейнера в своїй стандартній бібліотеці. Для деяких бібліотек вважається, що досить мати один єдиний контейнер для всіх потреб, але в інших (наприклад, в Java) передбачені різні контейнери на всі випадки життя: нескільки різних типів списків List (для зберігання послідовностей елементів), карти Map (відомі також як асоціативні масиви, дозволяють пов'язувати об'єкти з іншими об'єктами), а також безлічі Set (забезпечующие унікальність значень для кожного типу). Контейнерні бібліотеки также можуть містити черги, дерева, стеки і т. п.
З позицій проектування, все, що вам дійсно необхідно, - це контейнер, здатний вирішити ваше завдання. Якщо один вид контейнера відповідає всім потребам, немає підстави використовувати інші види. Існує дві причини, по яких вам доводиться вибирати з наявних контейнерів. По-перше, контейнери надають різні інтерфейси і можливості взаємодії. Поведінка і інтерфейс стека відрізняються від поведінки і інтерфейс черги, яка веде себе по-іншому, ніж безліч або список. Один з цих контейнерів здатний забезпечити більш ефективне вирішення вашої задачі в порівнянні з іншими. По-друге, різні контейнери по-різному виконують однакові операції. Кращий приклад - це ArrayList і LinkedList. Обидва являють собою прості послідовності, які могут мати ідентичні інтерфейси та риси поведінки. Але деякі операції значно відрізняються за часом виконання. Скажімо, час вибірки продовільній елемента в ArrayList завжди залишається незмінним незалежно від того, який саме елемент вибирається. Однак у LinkedList невигідно роботать з довільним доступом - чим далі за списком знаходиться елемент, тим більшу затримку викликає його пошук. З іншого боку, якщо буде потрібно вставити елемент в середину списку, LinkedList зробить це швидше ArrayList. Ці та інші операції мають різну ефективність, що залежить від внутрішньої структури контейнера. На стадії планування програми ви можете вибрати список LinkedList, а потім, в процесі оптимізації, переключитися на ArrayList. Завдяки абстрактному характеру інтерфейсу List такий перехід зажадає мінімальних змін в коді.

Параметризрвані типи
До виходу Java SE5 в контейнерах могли зберігатися тільки дані Object - єдиного універсального типу Java. Однокорневих ієрархія означає, що будь-який об'єкт може розглядатися як Object, тому контейнер з елементами Object підійде для зберігання будь-яких об'ектов.
При роботі з таким контейнером ви просто ставите в нього посилання на об'єкекти, а пізніше витягаєте їх. Але якщо контейнер здатний зберігати тільки Object, то при приміщенні в нього посилання на інший об'єкт відбувається його преобразование до Object, тобто втрата його «індивідуальності». При вибірці ви підлозісподіваєтеся посилання на Object, а не посилання на тип, який було поміщено в контейнер. Як же перетворити її до конкретного типу об'єкта, поміщеного в контейнер?
Завдання вирішується тим же перетворенням типів, але на цей раз тип змінятьється не по висхідній (від часткового до загального), а по низхідній (від загального до конкретного) лінії. Даний спосіб називається низхідним перетворенням. У разі висхідного перетворення відомо, що окружність є фігура, тому перетворення свідомо безпечно, але при зворотному перетворенні неможливо заздалегідь сказати, чи представляє екземпляр Object об'єкт Circle або Shape, тому низхідний перетворення безпечно тільки в тому випадку, якщо вам точно відомий тип об'єкта.
Втім, небезпека не така вже велика - при низхідному перетворенні до невірного типу станеться помилка часу виконання, звана исклюням (див. далі). Але при витяганні посилань на об'єкти з контейнера необхідно якимось чином запам'ятовувати фактичний тип їх об'єктів, щоб виконати вірне перетворення.
Спадний перетворення і перевірки типу під час виконання вимагають додаткового часу та зайвих зусиль від програміста. А може бути, можна якимось чином створити контейнер, знаючий тип збережених об'єктів, і таким чином усуває необхідність перетворення типів і потенціальні помилки? параметризрвані типи являють собою класи, которие компілятор може автоматично адаптувати для роботи з визначенийвими типами. Наприклад, компілятор може налаштувати параметризрвані контейнер на збереження і вилучення тільки фігур (Shape).
Одним з найважливіших змін Java SE5 є підтримання параметрівзова типів (generics). Параметризрвані типи легко впізнати за кутовими дужками, в які полягають імена типів-параметрів; наприклад, контейнер ArrayList, призначений для зберігання об'єктів Shape, створюється наступним чином:
ArrayList <Shape> shapes = new ArrayList <Shape> (),
Багато стандартні бібліотечні компоненти також були змінені для використання узагальнених типів. Як ви незабаром побачите, узагальнені типи зустрічаються в багатьох прикладах програм цієї книги.
Створення, використання об'єктів і час їхнього життя
Один з найважливіших аспектів роботи з об'єктами - організація їх створення і знищення. Для існування кожного об'єкта потрібні деякі ресурси, перш за все пам'ять. Коли об'єкт стає не потрібен, він повинен бути знищений, щоб займані ним ресурси стали доступні іншим. У простих ситуаціях завдання не здається складною: ви створюєте об'єкт, використовуєте його, поки вимагається, а потім знищуєте. Однак на практиці часто зустрічаються і більш складні ситуації.
Припустимо, наприклад, що ви розробляєте систему для управління движеням авіатранспорту. (Ця ж модель придатна і для управління рухом тари на складі, або для системи відеопрокату, або в розпліднику для бродячих тварин.) Спочатку все здається просто: створюється контейнер для літаків, потім будується новий літак, який поміщається в контейнер певної зони регулювання повітряного руху . Що стосується звільнення ресурсів, відповідний об'єкт просто знищується при виході літака із зони спостереження.
Але можливо, існує й інша система реєстрації літаків, і ці дані не вимагають такої пильної уваги, як головна функція управління. Може бути, це записи про плани польотів всіх малих літаків, Покидають аеропорт. Так з'являється другий контейнер для малих літаків; каждий раз, коли в системі створюється новий об'єкт літака, він також включаєся і в другій контейнер, якщо літак є малим. Далі якийсь фоновий процес працює з об'єктами в цьому контейнері в моменти мінімальної занятости.
Тепер завдання ускладнюється: як дізнатися, коли потрібно видаляти об'єкти? Навіть якщо ви закінчили роботу з об'єктом, можливо, з ним продовжує взаємодіяти інша система. Це ж питання виникає і в ряді інших ситуацій, і в програмних системах, де необхідно явно видаляти об'єкти після завершення роботи з ними (наприклад, в С + +), він стає достаточно складним.
Де зберігаються дані об'єкта і як визначається час його життя? У С + + на перше місце ставиться ефективність, тому програмісту надаютьсяється вибір. Для досягнення максимальної швидкості виконання місце зберіганняня і час життя можуть визначатися під час написання програми. У цьому випадку об'єкти поміщаються в стек (такі змінні називаються автоматитичними) або в область статичного сховища. Таким чином, основним фактором є швидкість створення і знищення об'єктів, і це може бути неоціненно в деяких ситуаціях. Однак при цьому доводиться жертвовати гнучкістю, так як кількість об'єктів, час їхнього життя і типи повинні бути точно відомі на стадії розробки програми. При вирішенні завдань більш широкого профілю - розробки систем автоматизованого проектування
(CAD), складського обліку або управління повітряним рухом - цей підхід може виявитися надто обмеженим.
Другий шлях - динамічне створення об'єктів в області пам'яті, називаємій «купою» (heap). У такому випадку кількість об'єктів, їх точні типи і час життя залишаються невідомими до моменту запуску програми. Все це визначається «на ходу» під час роботи програми. Якщо вам знадобиться новий об'єкт, ви просто створюєте його в «купі» тоді, коли буде потрібно. Так як управління купою здійснюється динамічно, під час виконання программи на виділення пам'яті з купи потрібно набагато більше часу, ніж при виділенні пам'яті в стеку. (Для виділення пам'яті в стеку достатньо всього одної машинної інструкції, зрушує покажчик стека вниз, а звільнення здійснюється переміщенням цього покажчика вгору. Час, необхідний на виділення пам'яті в купі, залежить від структури сховища.)
При використанні динамічного підходу мається на увазі, що об'єкти великі і складні, таким чином, додаткові витрати часу на виокремня і звільнення пам'яті не здійснять помітного впливу на процес їх створення. Потім, додаткова гнучкість дуже важлива для вирішення основних задач програмування.
У Java використовується виключно другий підхід. Кожного разу при создании об'єкта використовується ключове слово new для побудови динамічного екземпляра.
Втім, є й інший фактор, а саме час життя об'єкта. У мовах, що підтримують створення об'єктів в стеку, компілятор визначає, як довго використовується об'єкт, і може автоматично знищити його.Однак при стводанії об'єкта в купі компілятор не має уявлення про терміни життя об'єкекта. У мовах, подібних С++, знищення об'єкта має бути явно оформлено в програмі; якщо цього не зробити, виникає витік пам'яті (звичайна проблема в програмах С++). У Java існує механізм, званий збіркою сміття; він автоматично визначає, коли об'єкт перестає використовуватися, і знищує його. Збирач сміття дуже зручний, тому що він позбавляє программіста від зайвого клопоту. Що ще важливіше, збирач сміття дає набагато більшу впевненість в тому, що в вашу програму не закралася підступна проблема витоку пам'яті (яка «поставила на коліна» не один проект на мові С++).
У Java збирач сміття спроектований так, щоб він міг самостійно решать проблему звільнення пам'яті (це не стосується інших аспектів завершення життя об'єкта). Збирач сміття «знає», коли об'єкт перестає іскористуватися, і застосовує свої знання для автоматичного звільнення пам'яті. Завдяки цьому факту (разом з тим, що всі об'єкти успадковуються від єдиного базового класу Object і створюються тільки в купі) програмуванняня на Java набагато простіше, ніж програмування на С++. Розробнику приходиться приймати менше рішень і долати менше перешкод.

Обробка винятків: боротьба з помилками
З перших днів існування мов програмування обробка помилок була одним з найбільш каверзних питань. Розробити хороший механізм обництва помилок дуже важко, тому багато мов просто ігнорують цю проблему, залишаючи її розробникам програмних бібліотек. Останні предоставляють половинчасті рішення, які працюють у багатьох ситуаціях, але які часто можна попросту обійти (як правило, просто не звертаючи на них уваги). Головна проблема багатьох механізмів обробки виключень полягає в тому, що вони покладаються на сумлінне дотримання программістом правил, виконання яких не забезпечується мовою. Якщо програмист проявить неуважність - а це часто відбувається при поспіху в роботі - він може легко забути про ці механізмах.
Механізм обробки виключень вбудовує обробку помилок прямо в мову програмування або навіть в операційну систему. Исключеня являє собою об'єкт, що генерується на місці виникненні помилки, який потім може бути «перехоплений» підходящим обробником исключений, призначеним для помилок певного типу. Обробка исключений немов визначає паралельний шлях виконання програми, вступающий в силу, коли щось іде не за планом. І так як вона визначає окремий шлях виконання, код обробки помилок не змішується із звичайним кодом. Це спрощує написання програм, оскільки вам не доводиться постійно проверять можливі помилки. Вдобавок виняток не схоже на числовий код помилки, що повертається методом, або на прапор, що встановлюється в разі проблемной ситуації, - останні можуть бути проігноровані. Виключення ж не можна пропустити, воно обов'язково буде десь оброблено. Нарешті, исключення дають можливість відновити нормальну роботу програми після невірної операції. Замість того, щоб просто завершити програму, можна виправити ситуацію і продовжити її виконання; тим самим підвищується нановні програми.
Механізм обробки виключень Java виділяється серед інших, тому що він був вбудований в мову з самого початку, і розробник зобов'язаний його виковати. Якщо він не напише коду для подобаюгцей обробки виключень, комптора видасть помилку. Подібний послідовний підхід іноді помітно упгай обробку помилок.
Варто відзначити, що обробка виключень не є особливістю об'єкпроектно-орієнтованої мови, хоча в цих мовах виключення зазвичай представлю об'єктом. Такий механізм існував і до виникнення об'єктно-орієнтованого програмування.
Паралельне виконання
Однією з фундаментальних концепцій програмування є ідея одновременно виконання декількох операції. Багато завдання вимагають, щоб програма перервала свою поточну роботу, вирішила якусь іншу задачу, а потім повернулася в основний процес. Проблема вирішувалася різними способами.
На перших порах програмісти, що знають машинну архітектуру, писали процедури обробки переривань, тобто припинення основного процесу винують на апаратному рівні. Таке рішення працювало непогано, але воно було складним і немобільним, що значно ускладнювало перенесення подібних програм на нові типи комп'ютерів.
Іноді переривання дійсно необхідні для виконання операцій завдань, критичних за часом, але існує цілий клас задач, де просто потрібно розбити задачу на кілька роздільно виконуваних частин так, щоб програма швидше реагувала на зовнішні впливи. Ці роздільно винують частини програми називаються потоками, а весь принцип отримав назву багатозадачності, або паралельних обчислень. Часто зустрічаючийся приклад багатозадачності - користувацький інтерфейс. У програмі, разбитою на потоки, користувач може натиснути кнопку і отримати швидкий відвет, не чекаючи, поки програма завершить поточну операцію.
Зазвичай потоки всього лише визначають схему розподілу часу на однопроцессорном комп'ютері. Але якщо операційна система підтримує багатопроцесорну обробку, кожен потік може бути призначений на окрений процесор; так досягається справжній паралелізм. Одне із зручних властивостей вбудованої в мову багатозадачності полягає в тому, що програмісту не потрібно знати, один процесор в системі або декілька. Програма логічно поділяється на потоки, і якщо машина має більше одного процесора, вона ісвиконується швидше, без будь-яких спеціальних налаштувань.
Все це створює враження, що потоки використовувати дуже легко. Але тут криється каверза: спільно використовувані ресурси. Якщо кілька потоків питають одночасно отримати доступ до одного ресурсу, виникають проблеми. Наприклад, два процеси не можуть одночасно посилати інформацію на принтер. Для запобігання конфлікту спільні ресурси (такі як принтер) повинні блокуватися під час використання. Потік блокує ресурс, завершує свою операцію, а потім знімає блокування для того, щоб хтось ще зміг отримати доступ до ресурсу.
Підтримка паралельного виконання вбудована в мову Java, а з виходом Java SE5 до неї додалася значна підтримка на рівні бібліотек.

Java та Інтернет
Якщо Java являє собою черговий мова програмування, виникає питання: чому ж він так важливий і чому він підноситься як революційний крок у розробці програм? З точки зору традиційних завдань программировання відповідь очевидна не відразу. Хоча мова Java нагоді і при побудові автономних додатків, найважливішим його застосуванням було і залишається програмування для мережі World Wide Web.

Що таке Веб?
На перший погляд Веб виглядає досить загадково через велику кількість новомодних термінів на кшталт «серфінгу», «присутності» і «домашніх сторінок». Щоб зрозуміти, що ж це таке, корисно уявити собі картину в цілому - але спочатку необхідно розібратися у взаємодії клієнт / серверних систем, які являють собою одну з найскладніших задач комп'ютерних обчислень.

Обчислення «клієнт / сервер»
Основна ідея клієнт / серверних систем полягає в тому, що у вас існує централізоване сховище інформації - зазвичай у формі бази даних - і ця інформація доставляється за запитами будь-яких груп людей або компьютеров. В системі клієнт / сервер ключова роль відводиться централізованому сховища інформації, яке зазвичай дозволяє змінювати дані так, що ці зміни будуть швидко передані користувачам інформації. Всі разом:. сховище інформації, програми, що розподіляють інформацію, і комп'ютер, на якому зберігаються програми і дані - називається сервером. Програмне забезпечення на машині користувача, яке встановлює зв'язок з сервером, отримує інформацію, обробляє її і потім відображає відповідним чином, називається клієнтом.
Таким чином, основна концепція клієнт / серверних обчислень не так вже складна. Проблеми виникають через те, що отримати доступ до сервера питають відразу декілька клієнтів одночасно.Зазвичай для вирішення прівлекається система управління базою даних, і розробник намагається «оптимізаціїровать» структуру даних, розподіляючи їх по таблицях. Додатково система часто дає можливість клієнту додавати нову інформацію на сервер. А це означає, що нова інформація клієнта повинна бути захищена від потери під час збереження в базі даних, а також від можливості її перезапису даними іншого клієнта. (Це називається обробкою транзакцій.) При измененіі клієнтського програмного забезпечення необхідно не тільки скомпігулювати і протестувати його, але і встановити на клієнтських машинах, що може обійтися набагато дорожче, ніж можна уявити. Особливо складно організовать підтримку безлічі різних операційних систем і компьютерну архітектур. Нарешті, необхідно враховувати найважливіший фактор виробводительности: до сервера одночасно можуть надходити сотні запитів, і найменша затримка загрожує серйозними наслідками. Для зменшення затримки програмісти намагаються розподілити обчислення, часто навіть проводячи їх на клієнтській машині, а іноді і переводячи на додаткові сервірні машини, використовуючи так зване сполучна програмне забезпечення (middleware). (Програми-посередники також спрощують супровід програм.)
Проста ідея поширення інформації між людьми має стільки рівнів складності в своїй реалізації, що в цілому її рішення здається недостіжімим. І все-таки вона життєво необхідна: приблизно половина всіх завдань програмування заснована саме на ній. Вона задіяна у вирішенні разнообразних проблем: від обслуговування замовлень та операцій по кредитних карточкам до поширення різноманітних даних - наукових, урядовихних, котирувань акцій ... список можна продовжувати до безкінечності. У проШломо для кожної нової задачі доводилося створювати окреме рішення. Ці рішення непросто створювати, ще важче ними користуватися, і користувачеві доводилося вивчати новий інтерфейс з кожною новою програмою. Завдання клієнт / серверних обчислень потребує більш широкому підході.

Веб як гігантський сервер
Фактично веб являє собою одну величезну систему «клієнт / сервер». Втім, це ще не все: в єдиній мережі одночасно співіснують всі сервери і клієнти. Втім, цей факт вас не повинен цікавити, оскільки зазвичай ви з'єднуєтеся і взаємодієте тільки з одним сервером (навіть якщо його доводиться розшукувати по всьому світу).
На перших порах використовувався простий односпрямований обмін інформацией. Ви робили запит до сервера, він відсилав вам файл, який обрабатвала для вас ваша програма перегляду (тобто клієнт). Але незабаром простого отримання статичних сторінок з сервера стало недостатньо. Користувачі хотіли використовувати всі можливості системи «клієнт / сервер», відсилати інформацію від клієнта до сервера, щоб, наприклад, переглядати базу даних сервера, додавати нову інформацію на сервер або робити замовлення (що требовало особливих заходів безпеки). Ці зміни ми постійно спостерігаємо в процесі розвитку веб.
Засоби перегляду веб (браузери) стали великим кроком вперед: вони ввели поняття інформації, яка однаково відображається на будь-яких типах компьютерів. Втім, перші браузери були все ж примітивні і швидко перестали відповідати вимогам. Вони виявилися не особливо інтерактивні і гальмували роботу як серверів, так і Інтернету в цілому - при будь-якій дії, що вимагає програмування, доводилося посилати інформацію сервера і чекати, коли він її обробить. Іноді доводилося чекати кілька хвилин тільки для того, щоб дізнатися, що ви пропустили в запросі одну букву. Так як браузер являв собою тільки засіб перегляду, він не міг виконати навіть найпростіших програмних завдань. (З іншого боку, це гарантувало безпеку - користувач був обгороджений від запуску програм, що містять віруси або помилки.)
Для вирішення цих завдань робилися різні підходи. Для початку були покращені стандарти відображення графіки, щоб браузери могли отображать анімацію і відео. Решта завдань вимагали появи можливості запуску програм на машині клієнта, всередині браузера. Це було названо програмуванням на стороні клієнта.

Програмування на стороні клієнта
Спочатку система взаємодії «сервер-браузер» розроблялася для інтерактивного вмісту, але підтримка цієї інтерактивності була повностю покладена на сервер. Сервер генерував статичні сторінки для браджЗера клієнта, який їх просто обробляв і показував. Стандарт HTML підтримує найпростіші засоби введення даних: текстові поля, перемикачки, прапорці, списки і списки, разом з кнопками, які можуть виконати тільки дві дії: скидання даних форми та її відправку сервіру. Надіслана інформація обробляється інтерфейсом CGI (Common
Gateway Interface), підтримуваним всіма веб-серверами. Текст запиту вказувати CGI, як саме слід вчинити з даними. Найчастіше за запитом запускається програма з каталогу cgi-bin на сервері. (У рядку з адресою страниці в браузері, після відправки даних форми, іноді можна розгледіти в меШаніна символів підрядок cgi-bin.) Такі програми можна написати майже на всіх мовах. Зазвичай використовується Perl, тому що він орієнтований на обробку тексту, а також є інтерпретується мовою, відповідно, може бути використаний на будь-якому сервері, незалежно від типу процесора або операційної системи. Втім, мова Python (мій улюблений мову - зайдіть на www.Python.org ) поступово відвойовує у нього «територію» завдяки своїй могутності і простоті.
Багато потужні веб-сервери сьогодні функціонують цілком на основі CGI; в принципі, ця технологія дозволяє вирішувати майже будь-які завдання. Однако веб-сервери, побудовані на CGI-програмах, важко обслуговувати, і на них існують проблеми зі швидкістю відгуку. Час відгуку CGI-програми залежить від кількості посилається інформації, а також від завантаження сервера і мережі. (Через всього згаданого запуск CGI-програми може зайняти продоллості час). Перші проектувальники веб не передбачали, як швидко виснажаться ресурси системи при її використанні в різних додаткахях. Наприклад, виводити графіки в реальному часі в ній майже неможно, так як при будь-якій зміні ситуації необхідно побудувати новий GIF-файл і передати його клієнту. Без сумніву, у вас є власний гіркий досвід - наприклад, отриманий при простої посилці даних форми. Ви нажімает кнопку для відправки інформації; сервер запускає CGI-програму, которая виявляє помилку, формує HTML-сторінку, сообщающую вам про це, а потім відсилає цю сторінку в вашу сторону; вам доводиться набирати дані заново і повторювати спробу. Це не тільки повільно, це просто неелегантно.
Проблема вирішується програмуванням на стороні клієнта. Як правило, браузери працюють на потужних комп'ютерах, здатних вирішувати широкий діапазона завдань, а при стандартному підході на базі HTML комп'ютер просто ожідає, коли йому подадуть наступну сторінку. При клієнтському програмуваннянии браузеру доручається вся робота, яку вона здатна виконати, а для користувача це обертається більш швидкою роботою в мережі і поліпшеної інтерактивністю.
Втім, обговорення клієнтського програмування мало чим відрізняється від дискусій про програмування в цілому. Умови всі ті ж, але платформи різні: браузер нагадує сильно усічену операційну систему. У любом випадку доводиться програмувати, тому програмування на стороне клієнта породжує запаморочливе кількість проблем і рішень. На завершення цього розділу наводиться огляд деяких проблем і підходів, властивих програмування на стороні клієнта.

Модулі розширення
Одним з самих найважливіших напрямків в клієнтському програмуванні стала розробка модулів розширення (plug-ins). Цей підхід дозволяє програмісту додати до браузера нові функції, завантаживши невелику програму, яка вбудовується в браузер. Фактично з цього моменту браузер обзаводиться новою функціональністю. (Модуль розширення завантажується тільки один раз.) Модулі дозволили оснастити браузери поруч швидрих і потужних нововведень, але написання такого модуля - зовсім непросте завдання, і навряд чи кожен раз при створенні якогось нового сайту ви захочете створювати розширення. Цінність модулів розширення для клієнтського программирования полягає в тому, що вони дозволяють досвідченому програмісту доповнити браузер новими можливостями, не питаючи дозволу у його стводавця. Таким чином, модулі розширення надають «чорний хід» для інтеграції нових мов програмування на стороні клієнта (хоча і не всі мови реалізовані в таких модулях).

Мови сценаріїв
Розробка модулів розширення привела до появи безлічі мов для написання сценаріїв. Використовуючи мову сценарію, ви вбудовуєте клієнтську програму прямо в HTML-сторінку, а модуль, що обробляє дану мову, автоматично активізується при її перегляді. Мови сценарію зазвичай довільно прості для вивчення; по суті, сценарний код являє собою текст, що входить до складу HTML-сторінки, тому він завантажується дуже швидко, як частина одного запиту до сервера під час отримання сторінки. Розплачусятися за це доводиться тим, що будь в силах переглянути (і вкрасти) ваш код. Втім, навряд чи ви будете писати що-небудь варте наслідування і витончене на мовах сценаріїв, тому проблема копіювання коду не така вже страшна.
Мовою сценаріїв, який підтримується практично будь-яким браузером без встановлення додаткових модулів, є JavaScript (що має дуже мало спільного з Java; ім'я було використане в цілях «урвати» шматочок успіху Java на ринку). На жаль, вихідні реалізації JavaScript в різних браузерах досить сильно відрізнялися один від одного і навіть між різними версіями одного браузера. Стандартизація JavaScript у формі ECMAScript була полізна, але потрібен час, щоб її підтримка з'явилася у всіх брадж-Зерах (на додачу компанія Microsoft активно просувала власну мову VBScript, віддалено нагадував JavaScript). У загальному випадку розробнику приходиться обмежуватися мінімумом можливостей JavaScript, щоб код гарантувативанно працював у всіх браузерах. Що стосується обробки помилок і налагодження коду JavaScript, то заняття це в кращому випадку непроста. Лише нещодавно разробника вдалося створити дійсно складну систему, написану на JavaScript (компанія Google, служба GMail), і це вимагало найвищого ентузіазму і досвіду.
Це показує, що мови сценаріїв, які використовуються в браузерах, були призначені для вирішення кола певних завдань, в основному для созданя більш насиченого і інтерактивного графічного користувальницького інтерфейс (GUI). Однак мова сценаріїв може бути використаний для вирішення 80% завдань клієнтського програмування. Ваше завдання може якраз входити в ці 80%.Оскільки мови сценаріїв дозволяють легко і швидко створювати програмний код, вам варто спочатку розглянути саме такий мову, перед тим як переходити до більш складних технологічних рішень на зразок Java.

Java
Якщо мови сценаріїв беруть на себе 80% завдань клієнтського програмуванняня, кому ж тоді «по зубах» інші 20%? Для них найбільш популярвим рішенням сьогодні є Java. Це не тільки потужний мову программування, розроблений з урахуванням питань безпеки, платформиної сумісності та інтернаціоналізації, але також постійно здійсненийствуемий інструмент, доповнюваний новими можливостями і бібліотеками, які елегантно вписуються в рішення традиційно складних завдань програмування: багатозадачності, доступу до баз даних, мережевого программирования і розподілених обчислень. Клієнтське програмування на Java зводиться до розробки аплетів, а також до використання пакета Java Web Start.
Аплет - міні-програма, яка може виконуватися тільки всередині браджЗера. Аплети автоматично завантажуються у складі веб-сторінки (так само, як завантажується, наприклад, графіка). Коли аплет активізується, він виконує програму. Це одна з переваг аплету - він дозволяє автоматично поширювати програми для клієнтів з сервера саме тоді, коли користувачів знадобляться ці програми, і не раніше. Користувач отримує самую свіжу версію клієнтської програми, без усяких проблем і труднощів, пов'язаних з встановленням заново. У відповідності з ідеологією Java, програміст створює лише одну програму, яка автоматично працює на всіх компьютерів, де є браузери з вбудованим інтерпретатором Java. (Це верале практично для всіх комп'ютерів.) Так як Java є повноцінною мовою програмування, як можна більша частина роботи повинна виконанняться на стороні клієнта перед зверненням до сервера (або після нього). Наприклад, вам не знадобиться пересилати запит по Інтернету, щоб дізнатися, що в отриманих даних або якихось параметрах була помилка, а комп'ютер кліента зможе швидко накреслити небудь графік, не чекаючи, поки це зробить сервер і відішле назад файл із зображенням. Така схема не тільки забезпеЧіван миттєвий виграш в швидкості і чуйності, але також знижує затаження основного мережного транспорту і серверів, запобігаючи уповільнення роботи з Інтернетом в цілому.

Альтернативи
Чесно кажучи, аплети Java не виправдали початкових захоплень. При першій появі Java всі ставилися до апплетам з великим ентузіазмом, тому що вони робили можливим серйозне програмування на стороні клієнта, підвищення ефектившалі швидкість відгуку і знижували завантаження каналу для Інтернет-додатків. Апплетам передрікали велике майбутнє.
І дійсно, в веб можна зустріти ряд дуже цікавих аплетів. І все ж масовий перехід на аплети так і не відбувся. Ймовірно, головна проблема полягала в тому, що завантаження 10-мегабайтного пакета для установки середовища Java Runtime Environment (JRE) занадто лякала рядового користувача. Той факт, що компанія Microsoft не стала включати JRE в поставку Internet Explorer, остаточно вирішив долю аплетів. Як би там не було, аплети Java так і не набули широкого застосування.
• Втім, аплети і додатки Java Web Start в деяких ситуаціях приносять велику користь. Якщо конфігурація комп'ютерів кінцевих користелей знаходиться під контролем (наприклад, в організаціях), застосування цих технологій для поширення і поновлення клієнтських додатків вполне виправдано; воно економить чимало часу, праці і грошей (особливо при годинатих оновленнях).

. NET і С #
Деякий час основним суперником Java-аплетів вважалися компоненти ActiveX від компанії Microsoft, хоча вони і вимагали для своєї роботи налиності на машині клієнта Windows. Тепер Microsoft протиставила Java повноценних конкурентів: це платформа. NET і мова програмування С #. Платформа. NET являє собою приблизно те ж саме, що і віртуальна машина Java (JVM) і бібліотеки Java, а мову С # має явну схожість з язиком Java. Поза всякими сумнівами, це найкраще, що створила компанія Microsoft в області мов і середовищ програмування. Звичайно, розробники з Microsoft мали деяку перевагу; вони бачили, що в Java вдалося, а що ні, і могли відштовхуватися від цих фактів, але результат вийшов цілком достойвим. Вперше з моменту свого народження у Java з'явився реальний суперник. Розробникам з Sun довелося як слід поглянути на С #, з'ясувати, по каким причин програмісти можуть захотіти перейти на цю мову, і додаткомжити максимум зусиль для серйозного поліпшення Java в Java SE5.
Виданий момент основні сумніви викликає питання про те, чи дозволить Microsoft повністю переносити. NET на інші платформи. У Microsoft утверждают, що ніякої проблеми в цьому немає, і проект Mono ( www.go-mono.com ) предоставляє часткову реалізацію. NET для Linux. Втім, раз реалізація ця неповна, то, поки Microsoft не вирішить викинути з неї якусь частину, робити ставку на. NET як на міжплатформна технологію ще рано.

Інтернет та інтрамережа
Веб надає рішення найбільш загального характеру для клієнт / серверних задач, так що має сенс використовувати ту ж технологію для вирішення завдань в окремих випадках; особливо це стосується класичного кліент/серверно- го взаємодії всередині компанії. При традиційному підході «кліент/сер- вір» виникають проблеми з відмінностями в типах клієнтських комп'ютерів, до них додається трудність установки нових програм для клієнтів; обидві проблеми вирішуються браузерами і програмуванням на стороні клієнта. Коли технологія веб використовується для формування інформаційної мережі всередині компанії, така мережа називається інтрамережа. Інтрамережі надаютьсяють набагато більшу безпеку в порівнянні з Інтернетом, тому що ви можете фізично контролювати доступ до серверів вашої компанії. Що касаєти навчання, людині, що розуміє концепцію браузера, набагато легше розібратися в різних сторінках і апплетах, так що час освоєння нових систем скорочується.
Проблема безпеки підводить нас до одного з напрямів, який авАвтоматичний виникає в клієнтському програмуванні. Якщо ваша програма виконується в Інтернеті, то ви не знаєте, на якій платформі їй належить працювати. Доводиться проявляти особливу обережність, щоб уникнути поширеннядження некоректного коду. Тут потрібні міжплатформна і безпечні рішення, зразок Java або мови сценаріїв.
У інтрамережі діють інші обмеження. Досить часто всі машини мережі працюють на платформі Intel / Windows. У інтрамережі ви відповідаєте за якосство свого коду і можете усувати помилки у міру їх виявлення. Вдобавок, у вас вже може накопичитися колекція рішень, які перевірені на міцність в більш традиційних клієнт / серверних системах, в той час як нові програми доведеться вручну встановлювати на машину клієнта при каждом оновленні. Час, що витрачається на поновлення, є самим вагуким доказом на користь браузерних технологій, де поновлення здійснюються невидимо і автоматично (то ж дозволяє зробити Java Web Start). Якщо ви берете участь в обслуговуванні інтрамережі, розсудливіше всього використовувати той шлях, який дозволить залучити вже наявні напрацювання, не переписуючи програми на нових мовах.
Стикаючись з обсягом завдань клієнтського програмування, здатним поставити в глухий кут будь-якого проектувальника, найкраще оцінити їх з позицій співвідношення «витрати / прибутки».Розгляньте обмеження вашого завдання і спробуйте уявити найкоротший спосіб її вирішення. Так як клієнтське програмування все ж залишається програмуванням, завжди актуальні технології розробки, які обіцяють найбільш швидке рішення. Така активна позиція дасть вам можливість підготуватися до неминучих проблем розролення програм.

Програмування на стороні сервера
Наше обговорення обійшло стороною тему серверного програмування, которої, як вважають багато хто, є найсильнішою стороною Java. Що відбувається іздит, коли ви посилаєте запит серверу? Найчастіше запит зводиться до простому вимогу «відправте мені цей файл». Браузер потім обробляє файл підходящим чином: як HTML-сторінку, як зображення, як Java-аплет, як сценарій і т. п.
Більш складний запит до сервера зазвичай пов'язаний зі зверненням до бази данних. У самому распростран випадку робиться запит на складний пошук в базі даних, результати якого сервер потім перетворить в HTML-сторінку і посилає вам. (Звичайно, якщо клієнт здатний виробляти якісь действия за допомогою Java або мови сценаріїв, дані можуть бути оброблені і у нього, що буде швидше і знизить завантаження сервера.) А може бути, вам понадобится зареєструватися в базі даних при приєднання до якоїсь групи, або оформити замовлення, що потребують змін у базі даних. Подібні запроси повинні оброблятися якимсь кодом на сервері; загалом це і називається серверним програмуванням. Традиційно програмування на сервері здійснювалося на Perl, Python,   С++ або іншою мовою, що дозволяє создавати програми CGI, але з'являються і більш цікаві варіанти. До їх числа відносяться і засновані на Java веб-сервери, що дозволяють займатися сервервим програмуванням на Java за допомогою так званих сервлетов. Серв-лети і їхнього дітища, JSPs, складають дві основні причини для переходу компаній по розробці веб-вмісту на Java, в головному через те, що вони решают проблеми несумісності різних браузерів.
Незважаючи на всі розмови про Java як мовою Інтернет-програмування, Java в дійсності є повноцінною мовою програмування, здатною вирішувати практично всі завдання, які вирішуються на інших мовах. Премайна Java не обмежуються хорошою переносимістю: це і пригодність до вирішення завдань програмування, і стійкість до помилок, і більшая стандартна бібліотека, і численні розробки сторонніх фірм - як існуючі, так і постійно з'являються.

Резюме
Ви знаєте, як виглядає процедурна програма: визначення даних і викликуви функцій. Щоб з'ясувати призначення такої програми, необхідно докласти зусилля, переглядаючи функції і створюючи в розумі загальну картину. Саме через це створення таких програм вимагає використання промежуточних засобів - самі по собі вони більше орієнтовані на комп'ютер, а не на вирішувану завдання.