Общи характеристики на езика


Java е език, осигуряващ автоматично освобождаване на паметта (Garbage Collected)



страница4/5
Дата25.02.2017
Размер432.83 Kb.
#15745
ТипЛекция
1   2   3   4   5

Java е език, осигуряващ автоматично освобождаване на паметта (Garbage Collected)


Изпълнителната среда на езика (run-time environment) следи за блоковете от паметта, определяйки дали те се използват или не. Когато даден блок памет не се използва повече, системата автоматично го освобождава. Например, ако даден обект се създава в тялото на метод, но не се връща на викащата функция и не се предава като част от глобален обект, същият автоматично се премахва от паметта, когато завърши изпълнението на метода. Системата установява кой обект от паметта се използва като съхранява брояч на референции към обекта. Един обект не се използва, ако стойността на брояча на референции е нулева. При всяко адресиране на обекта (предаване чрез референция към функция), броячът на референции се увеличава, при всяко връщане от функция се намалява. Тези обекти, които имат нулева референция, не могат да се адресират повече (това е една от причините, поради които в езика няма указатели). В резултат от това, програмистът не е длъжен да прави програмно освобождаване на паметта чрез функция, както в C++. Така се икономисва времето за тест и откриване на грешки, предизвикани от обекти, които са в употреба, както и образуването на остатъци от заета памет (memory leaks), предизвикани от неизтрити обекти, които вече не се използват. Използването на автоматичното почистване на Java драстично намалява тези грешки, но не ги елиминира. - лошата програмна логика може да предизвика остатъци от неосвободени обекти, свързани с активни структури данни, които менажера на паметта не може да установи като неизползвани и да почисти автоматично. Много класове в C++ съдържат деструктори, създадени само за освобождаването на обекти от даден клас. Фактът, чe Java автоматично почиства паметта, означава, че не е задължително да се пишат деструктори на класовете. Това обаче не означава, че трябва да се изключва писането на деструктори за всички класове, които се създават. Например, един обект, който създава мрежова връзка, се нуждае от почистване при приключване на връзката, когато се унищожава. За тази цел се използва разгледания метод за финализиране на обекти "finalization method."

В/И потоци

От примера се вижда, че в класа ArgPrint na Java липсва стандартния изходен поток cout. Има причина за това: Java не имплементира стандартните потоци на С++. Java притежава еквиваленти на потоците в C++. Класът System на Java имплементира три потокови обекта, които са много подобни на стандартните потокови обекти в С++: in, out и err. Достъпът до тях се осъществява чрез класа System.

Класът ArgPrint показа как да се използва потока out за отпечатване на текст. Обектът out е от тип OutputStream и съдържа набор от методи като println – за отпечатване на текст в стандартния изходен поток. Аналогично, обектите in и err съдържат няколко метода за потоков вход и изход. В повечето случаи конвертирането на стандартен С++ В/И код се свежда до промяна на реферирането от cin към System.in и от cout в System.out. Тъй като Java не поддържа предефиниране на оператори, ще е нужно да се променят операциите << и >> с техните Java еквиваленти.

Указатели и референции

1. Java няма указатели, като обектите се предават индиректно, с използване на референции, вместо да се предават указатели към тях.

2. Работата с масивите се прави само с помощта на индекси.

В повечето случаи тези недостатъци не са проблем, освен ако в системата се използват указатели към функции или указатели към член-функции. Това са програмни системи, които включват обработващи функции с общ основен тип и множество от методи с една и съща спецификация, които динамично се пренасочват към различни тела за изпълнение.

Повечето разработчици се съгласяват с факта, че повечето от грешките в С/С++ програмите се причиняват от неправилно използване на указатели. Иначе казано, когато се използват указатели, съществува възможността да се прави невалиден достъп до паметта. Често С++ програмистите използват математически изрази (аритметика) с указателите, за да поддържат сложни типове данни. В резултат на това, могат да се допуснат трудни за откриване грешки, причинени от използването на тази аритметика с указатели.

Езикът Java не поддържа указатели. Предоставя се подобна функционалност чрез използване на референции. Java предава всички обекти и масиви чрез техните референции. Този подход елиминира грешки при достъпа чрез указатели. Референциите също така улесняват програмирането, защото правилното използване на указатели е много по-трудно, освен за най-опитните програмисти.

Може да се предположи, че липсата на указатели лишава програмиста от възможността да създава много структури от данни, като например динамично разширяващи се масиви. Всъщност, всяка операция с указатели се извършва също толкова лесно и много по-сигурно чрез референции. Ако това е така, се получава ефекта от сигурността, предоставена от системата за изпълнение в реално време на Java. Тя, например, извършва проверка за валиден размер на всички операции за индексиране на масиви.

Безспорно указателите са най-трудната част за превеждане на код от С/С++ на Java, защото в повечето С++ програми са кодирани доста действия с указатели. Първото нещо, което трябва да се направи е да се подменят всички масиви от символи в Java низове. В резултат от реализирането на това много от кода, зависим от указатели изчезва.

Следващата степен при конвертирането на кода е създаването и унищожаването на обекти. В С++ типичен начин за създаване на обекти е създаването на указатели към тях и използването на оператора new за да бъде създаден нов обект и присвоен на указателя. След като е приключена работата с обекта, се извиква функцията delete, за да се почисти паметта. Тази процедура не е чак толкова различна в Java; разликата е, че липсва последната стъпка.

Пример за създаване на референция към низ:

String s = new String(“тестов низ”);


В Java обекти, които не се ползват, се освобождават автоматично от системата на Java garbage collection, която обикновено е системна нишка с нисък приоритет. Тъй като Java системата се грижи за освобождаването на неизползваните обекти, не е нужно програмистът да освобождава паметта, която заема. За да се разбере по-добре разликата между С++ и Java при работата с указатели е разгледан пример. Пример 1.15 съдържа С++ клас с член-променлива указател.

пример 1.15. C++ класът CMemTest.

class CMemTest {


  MemBlock* pMem;

  CMemTest() {


    pMem = new MemBlock();
  }

  ~CMemTest() {


    if (pMem != 0) {
      delete pMem;
      pMem = 0;
    }
  }
};

Конструкторът на CMemTest инициализира член-променливата pMem чрез създаване на нов обект. Деструктора на класа освобождава заетата памет и задава на указателя стойност 0. Това е твърде прост пример, но помага да бъдат научени нещата на малки стъпки. Пример 1.16 съдържа Java версията на същия клас.



Пример 1.16. Java класа CMemTest.

class CMemTest {


  MemBlock pMem;

  CMemTest() {


    pMem = new MemBlock();
  }
}

Java кодът е много по-прост, дори в този пример, когато не се ползват много указатели на С++. В Java версията член-променливата pMem не е нужно да се инициализира с 0. Това показва предимство на Java – всички член-променливи се създават със стойности 0 или null, ако са създадени без да им е присвоена стойност. Garbage collector-ът на Java се грижи за освобождаването на обекта pMem, когато той не се използва.

Забележка: Използването на метода finalize , който се извиква от менажера на паметта (garbage collector) при унищожаването на обекта, не гарантира неговото извикване, поради спецификата на garbage collector.

Указателите в Java се използват масово, но чрез референции. Това може да се стори доста голямо ограничение, но когато се свикне с работата с референции, се вижда, че нищо не се губи. Като се използва тази постановка, следващата логична стъпка при прехвърлянето на код от С++ към Java е да се подменят указателите в С++ с референции в С++ кода, преди дори да се започне работата по прехвърлянето. Трябва да се направи С++ кода изцяло базиран на референции и тогава да се започне работата на Java. С подобна стъпка може да се спестят доста следващи проблеми.

Когато се създават обекти от класове на Java с оператора new, резултатът е референция (или handle в паметта). Например:

String s = new String("Hello");



За разлика от C++, където референциите трябва да се инициализират при обявяването им, т.е. те не могат да останат не присвоени, в Java това не е задължително. Референциите в C++ не могат да се променят след тяхното създаване. Такова изискване в Java няма, което доближава референциите на Java до указателите. Възможността за дефиниране на не присвоена референция също доближава референциите по-скоро към указателите. Друга особеност на C и C++ указателите е възможността да адресират памет, независимост какво сочи. За неопитните програмисти това може да е източник на грешки. В Java адресирането на незащитен обект е недопустимо. Указателите са ефективен начин за адресиране на масиви от прости типове данни. Масивите в Java допускат да се прави такава адресация при това по начин, защитен от грешки. В краен случай създаването на указатели е допустимо в методи, наречени native методи. Предаването на указатели към методи не е проблем, защото няма глобални функции, а само класове и те могат да предават референции към обектите. В този смисъл, референциите в Java се разглеждат като “ограничени указатели”. Ограничението засяга основно липсата на математика с указателите, която няма еквивалент в Java.

Наследяване

Java използва подразбираща се класова йерархия, при която всички обекти в основата си са наследници на класа “корен” Object. В C++, всяко дърво на наследяването може да стартира от произволен базов клас (подобно на гора с различни корени). В Java се образува обща точка (корен) на йерархията. Това може да се разглежда като ограничение, но то дава по-големи възможности, тъй като винаги е известно че всеки онект има интерфейс-този на класа Object. Езикът C ++ е единствен език за ООП, който не налага общ корен на йерархията.

Наследяването в Java има същия ефект, като този в C++, но синтактично е различно. В Java се използва ключовата дума extends за да се определи наследяване от базов клас и super за определяне на методи, които се извикват от базовия клас, които имат предекларация (същата спецификация) в наследника. Това може да се прави само за достъп до методи, разположени с едно ниво по ниско в йерархията. В C++ се допуска произволно ниво на дълбочината при този процес. Конструкторът на базовия клас също може да се извика с ключова дума super. Както е известно вече, всички класове са наследници на Object. Няма експлицитен инициализиращ списък като при C++, но компилаторът ще изиска да се направят необходимите инициализации в базовия клас, при дефиране на тялото на конструктора на наследника. Инициализацията на членовете се гарантира в комбинация между автоматичната инициализация и изключване на неинициализирана памет в обекта.

Пример 1.17:

public class Derivative extends Base {

public Derivative(String msg) {

super(msg); // извиква конструктор на базовия клас Base

}

public baz(int i) { // предефиниране на същeствуващ метода на базовия клас



super.baz(i); // извиква метода в базовия Base

}

}



Наследяването в Java не променя нивото на достъп до членовете на базовия клас. Не може да се специфицира тип на наследяване public, private или protected, както се прави в С++. Предефинираните методи в наследника не могат да редуцират нивата на достъп в базовия клас. Например, ако метод е public в базовия клас и го предефинира наследника, предефинирания трябва да бъде също public. Това ще се провери от компилатора.

Множествено наследяване

Множественото наследяване е свойство на С++, което позволява наследяването на повече базови класове. Подобни конструкции са сложни за реализация от компилатора. Въпреки че то е доста мощно средство, множественото наследяване трудно се ползва правилно и може да доведе до грешки. Тъй като Java е основана на модел с общ корен в йерархията множественото наследяване не се вмества в данновия му модел.



Java заобикаля и не предоставя директна поддръжка за множествено наследяване.

Вместо него могат да се декларират интерфейси, които описват програмните връзки, за осъществяване на свързана функционалност. Интерфейсите на Java предлагат методи на обекти, но не предлагат тяхната имплементация. Класът може да имплементира един или повече интерфейси, като собствена уникална функционалност. Различни, несвързани класове могат да имплементират еднакъв интерфейс. Формалните параметри на методите могат да се декларират или като класове или като интерфейси. Ако са интерфейси, обектите на различни класове, които имплементират интерфейса могат да бъдат предавани като аргументи към метод.

Концепцията на интерфейсите е значително опростена за създаване на множествено наследяване, но има ограничения. Като минимум е необходимо да се напише код, който да повтаря исканата функционалност във всеки от класовете, имплементиращи един интерфейс.

Примерен код на С++, който използва множествено наследяване:

class InputDevice : public Clickable, public Draggable {
  // дефиниция на класа
};

С++ класът InputDevice е наследил класовете Clickable и Draggable. Това означава, че той наследява всичките методи и член-променливи на тези класове. Най-близката имплементация в Java е създаването на интерфейси Clickable и Draggable, които могат да имат дефиниции на методи, но няма същински код на методите, както и член-променливи. Наследникът на интерфейсите-класът InputDevice може да имплементира тези интерфейси като използва ключовата дума implements:

class InputDevice implements Clickable, Draggable {
  // дефиниция на класа

}

Както се вижда, синтактично може да се прехвърлите код на Java от С++ ползващ множествено наследяване. За да се направи прехвърлянето, трябва да подменят същинските тела на методите и да се имплементират в производни класове. Основната цел на обединяването на отделни логически организации в една разклонена ще се запази.



Синтаксис на наследяването

Примера за множествено наследяване показва още една важна разлика между С++ и Java. В С++ наследяването се означава с две точки след новия клас, последвани от родителският клас или класове:

class B : public A {
  // дефиниция на класа
};

За дефиниране на наследяване в Java се използват ключовите думи extends и implements:

class B extends A {
  // дефиниция на класа
}

Промяната на кода за определената част от програмата се свежда в търсене и промяна на ключовите думи една с друга. По трудната задача е преобразуването на множественото наследяване, което ще изисква повече промени.

В сложни обектно ориентирани системи, имплементацията на клас, който се нуждае от наследяване на функционалността на повече от един друг клас е често срещан проблем. Класът Manager например, може да изисква да бъде използван като глава на списък, (от служители, които управителят оглавява), но самият той трябва да е същевременно и служител (Employee ). Има различни начини да се реши този проблем. Един от тях е множественото наследяване – допускането един клас да бъде наследник на повече от един базов клас. В този пример, Manager може да бъде наследник на два класа Linked List и Employee. Начините за имплементация на класови йерархии се разглежда в следващите лекции.

Определяне на типовете по време на изпълнение

Типовете могат да се определят по време на изпълнение подобно на езика С++. За получаване на информация за тип може да се използва следната кодова последователност:

X.getClass().getName();

където X е референция към паметта, която реферира обекта от класа, информация за който трябва да се получи. За да се преобразуват класовете от базов към наследник се използва подобно преобразуване на типове като в C++:

derived d = (derived)base;

Компилаторът автоматично извиква механизъм за преобразуване без да се използва допълнителен синтаксис. Проверката за съответствие анализира възможностите за преобразуването и генерира изключение (грешка в преобразуването-bad casts), както в С++.

Пример:

import java.util.*; // ArrayList, LinkedList


public class mainapp {

public static void test(List a) {

System.out.println("Testing " +

a.getClass().getName());

}

public static void main(String[] args) {



test(new ArrayList());

test(new LinkedList());

}

}


Модификатори за права на достъп и

контрол на достъпа до членовете
Вместо блоковете за контрол на достъпа, известни от C++ , спецификаторите за достъп-ключови думи (public,private и protected) се поставят пред всяка дефиниция на член на класа. Без да се специфицира нивото на достъп, елементите се подразбират като “приятелски”, което означава че те са достъпни в рамките на разработвания пакет, подобно на приятелската декларация в C++ ( friend) и недостъпни извън пакета. Класът и всеки метод на класа трябва да се определя като достъпен извън файла. (при много-нишковите се изисква използването на ключова дума private). Понякога достъпа с атрибут private се използва рядко, защото приятелски достъп е по приложим от това да се изключва достъпа от другите класове на същия пакет. В Java няма еквивалент на ключова дума protected означаваща “достъп само от наследниците” (комбинацията на двете- private protected се игнорира при компилация.

Модификаторите за достъп се поддържат и в С++ и в Java, но начините за деклариране са различни. В С++ модификаторите за достъп се декларират като етикет отнасящ се за група членове на класа:

class A {
public:
  int x, y;
private:
  float v;
};

В Java може да се направи същото, но трябва да се зададат за всяка декларация поотделно:

class A {
  public int x;

  public int y;


  private float v;
}

По този начин модификаторите за достъп в Java не са етикети, а истински модификатори. Конвертирането на модификаторите от С++ се състои в преминаване през декларациите и подменяне на всеки етикет пред член променлива или член функция и поставянето на съответния модификатор за всяка декларация поотделно.



Приятелски спецификатори и пакети

Java няма приятелски спецификатор. Езикът няма концепция за приятелски клас, както в С++. Приятелски клас в С++ е такъв, който може да прави достъп до всички членове на друг клас, независимо от нивото им на видимост. Java няма еквивалент на концепцията за приятели, но има специален модификатор, който позволява само на група класове да имат достъп до дадена член-променлива, което е в известна степен подобно на концепцията за приятели.

Този Java модификатор често се приема за модификатор по подразбиране. Може да се разглежда като рефериран приятелски модификатор за достъп. Той има ефекта да позволява достъп на всички класове в същия пакет като на текущия клас до съответния член на класа, деклариран с модификатора по подразбиране. За да се даде на определена променлива или метод подобен тип достъп се пропуска спецификатора за достъп, например:

class A {


  public int iPublic;
  private int iPrivete;
  int iPacket, jPacket;
}

В този примерен код iPublic и iPrivete получават специфичен достъп според модификаторите си, а iPacket и jPacket, получават такъв по подразбиране, или пакетен достъп. За да се конвертира С++ код към Java, трябва да се съберат приятелските класове в един и същ пакет и да се декларират членовете им с достъпа по подразбиране.



    1. Каталог: Course%20II -> Term%20Two -> OOP%20II
      OOP%20II -> Структурни диаграми по uml 0: Класова диаграма (Class diagram) Символи: (private) частен елемент: атрибут или метод
      Course%20II -> Приоритети на Европейския съюз в областта на компютърните и комуникационните технологии
      Term%20Two -> Конспект за изпита по дисциплината "Микропроцесорна техника"
      Term%20Two -> Б wm initdialog
      Course%20II -> Конспект по Основи на компютърните комуникации
      Term%20Two -> Задача: Езикът L е задaден чрез регулярен израз: r = ( a+b ba )( b + ab a ) ( b+ ab a) + a + b ba


      Сподели с приятели:
1   2   3   4   5




©obuch.info 2024
отнасят до администрацията

    Начална страница