* Класове и пакети
Публикувано на 03 октомври 2009 в раздел ПИК3 Java.
Досега често използвахме понятието "обект", а на места се споменаваше понятието "клас". Всъщност в обектно ориентираното програмиране тези две понятия са неразделно свързани. Ще си позволя да направя следните обобщени дефиниции:
(Деф) Клас (class) наричаме набор от ограничения за достъп и описания на възможни действия, които могат да се извършват върху дефинирани променливи
(Деф) Обект наричаме практически представител на даден клас, в който са "попълнени" конкретни данни в дефинираните променливи и така вече е възможно да бъдат извършвани конкретните действия
По този начин можем да приемем, че "класовете са шаблони за обекти". Също използвано определение е, че "класовете описват обектите". Най-често срещаното е, че "класовете са типове за обектите". Последното прави аналогия с връзката "типове данни" и "променливи". Накрая трябва да знаем също, че в Java обектите се създават чрез оператор new и стоят в частта на паметта heap. Техните референции (променливи съдържащи указател към обектите) пък се записват в паметта stack.
Нека вече разгледаме първия пример. Във файл kidsplay.java сме записали:
public class kidsplay{ public static void main(String[] args){ // Създаване на обект чрез конструктор по подразбиране Kid ivancho = new Kid(); // Създаване на обект чрез втори конструктор Kid maria = new Kid("Maria"); // Извикване на методи на обектите ivancho.introduce(); maria.introduce(); } } class Kid{ // Полета public String name; // Конструктор по подразбиране public Kid(){ java.util.Scanner s = new java.util.Scanner(System.in); System.out.print("Enter the kid name: "); this.name = s.next(); } // Втори конструктор public Kid(String name){ this.name = name; } // Метод на класа public void introduce(){ System.out.println("Hi, my name is "+this.name); } }
Полетата на класа също се наричат също характеристики на класа. Обекновено ние инициализираме (даваме стойност на) тези полета вътре в специалните методи, наречени "конструктори". Към полетата се обръщаме чрез this, което можем да приемем за указател на обекта към самия себе си.
Конструкторите са методи без никаква връщана стойност и с име съвпадащо с името на класа. Можем да имаме един или повече конструктори, като те се различават по своите входни параметри (т.е. както при предефинираните функции). Конструкторът е метод, който винаги се извиква еднократно при създаването на обект чрез оператор new. Обикновено функцията на този метод е да инициализира данните на полетата в класа.
Накрая методите на класовете се използват за извършване на конкретни операции с обектите. Вие вече сте се сблъсквали с много обекти и техните методи - метод println() на клас System.out, метод read() на клас BufferedReader, метод next() на клас Scanner и т.н.
За този пример ще отбележим нещо важно. В .java файла първият клас, съдържащ main метода, да бъде записан на първо място (тоест във файл "abc.java" първи ред трябва да бъде "public class abc...", а в този клас да има дефиниран "public static void main..."). Това е така, защото все още не сме се запознали с понятието пакет. Това обаче вече е необходимо:
(Деф) Пакет наричаме множество от логически свързани класове.
Конвенцията на Java е всички .java файлове от даден пакет да се съдържат в една и съща директория. Имената на папката трябва да съвпада с името на пакета, а имената на .java файловете в тази папка трябва да съвпадат с имената на класовете дефинирани в тях. Всеки .java файл, организиран в такава структура, трябва да започва с първи ред името на пакета.
Нека даден един пример. Създайте директория deca. В нея запишете файловете
1. kidsplay.java със съдържание:
package deca; public class kidsplay{ public static void main(String[] args){ // Създаване на обект чрез конструктор по подразбиране Kid ivancho = new Kid(); // Създаване на обект чрез втори конструктор Kid maria = new Kid("Maria"); // Извикване на методи на обектите ivancho.introduce(); maria.introduce(); } }
2. Kid.java със съдържание:
package deca; class Kid{ // Полета public String name; // Конструктор по подразбиране public Kid(){ java.util.Scanner s = new java.util.Scanner(System.in); System.out.print("Enter the kid name: "); this.name = s.next(); } // Втори конструктор public Kid(String name){ this.name = name; } // Метод на класа public void introduce(){ System.out.println("Hi, my name is "+this.name); } }
Когато компилираме kidsplay.java и изпълним kidsplay.class ще видим, че ще се компилира и Kid.class в същата директория. Двата файла и класовете в тях са от един и същи пакет и затова те "се виждат" един друг.
Ако искаме да използваме класове от други пакети то има два начина. Единият е чрез пълният път до класа в иерархията на Java. Вече сте го срещали, например в клас Kid това е:
java.util.Scanner s = new java.util.Scanner(System.in);
Ако желаем да не пишем винаги името на пакета java.util, то можем да си "включим" клас Scanner, чрез команда import. Ето как можем да модифицираме файл Kid.java:
package kids; import java.util.Scanner; class Kid{ // Полета public String name; // Конструктор по подразбиране public Kid(){ Scanner s = new Scanner(System.in); System.out.print("Enter the kid name: "); this.name = s.next(); } // Втори конструктор public Kid(String name){ this.name = name; } // Метод на класа public void introduce(){ System.out.println("Hi, my name is "+this.name); } }
Освен това можем да добавим всички класове от даден пакет чрез символ "*". Например "import java.util.*". Все пак препоръчваме да включвате само класовете, които ще използвате поради опасността от дублиране на имена с класове дефинирани във вашия пакет.
Накрая трябва да се запознаем и с още едно понятие, което използвахме често. Това са т.нар. "статични методи". Може би вече ви е направило впечатление, че някои методи започваха с "public static", а други само с "public".
(Деф) Статични са методите, които могат да се извикат без да е нужно да създадем инстанция на клас
Веднага можете да видите разликата между статичните методи (каквито правихме в статията методи в Java) и методът на класа introduce() написан по-горе. Накратко - вие не можете да извикате метод introduce() преди да сте създали обект, който го съдържа.
Нека дадем един пример:
1. Kid.java:
package deca; import java.util.Scanner; class Kid{ public String name; public Kid(){ Scanner s = new Scanner(System.in); System.out.print("Enter the kid name: "); this.name = s.next(); } public Kid(String name){ this.name = name; } public void introduce(){ System.out.println("Hi, my name is "+this.name); } public static void yell(){ System.out.println("Hooray!!!"); } }
2. kidsplay.java:
package deca; public class kidsplay{ public static void main(String[] args){ // Извикване на статичен метод Kid.yell(); // Извикване на статичен метод чрез инстанция на обект Kid ivancho = new Kid("Ivancho"); ivancho.yell(); // Извикване на НЕ-статичен метод ivancho.introduce(); } }
Извикването на не-статичен метод без инстанция няма да работи. Тоест при опит за извикване на "Kid.introduce()" програмата няма да се компилира. Toва се получава, защото нестатичните методи се опитват да извикват нестатични полета, които от своя страна не са инициализирани.
Кога да използваме статични методи и кога не зависи от програмата, която пишем. Ако методът е независим от полетата и променливите, които се дефинират при правене на инстанция на клас, то той може да бъде статичен. Ако за методът е нужно да работи с полетата от класа и те трябва да бъдат инициализирани чрез създаването на негова инстанция, то методът ни не може да бъде статичен. Естествено друг основен критерии е дали има въобще логика "да позволяваме" на метод да бъде статичен.
Накрая освен статични методи можем да създаваме и статични полета. Нека разгледаме два примера за клас "естествени числа" във файл numbersexample.java:
public class numbersexample{ public static void main(String[] args){ NaturalNumbers x = new NaturalNumbers(); x.showNextNum(); // 1 x.showNextNum(); // 2 NaturalNumbers y = new NaturalNumbers(); y.showNextNum(); // 1 y.showNextNum(); // 2 } } class NaturalNumbers{ public int value; public NaturalNumbers(){ this.value=1; } public void showNextNum(){ System.out.println(this.value); this.value++; } }
Тук нямаме нито статични методи, нито статични променливи в клас NaturalNumbers. Ето как бихме могли да постигнем подобна функционалност, но без да има нужда от създаване на инстанция, а променливата value ще е общовалидна за всички. За да услжним задачата обаче, ще я направим така, че при създаване на обект ще връшаме брояча "назад":
public class numbersexample{ public static void main(String[] args){ NaturalNumbers.showNextNum(); // 1 NaturalNumbers.showNextNum(); // 2 NaturalNumbers.showNextNum(); // 3 NaturalNumbers x = new NaturalNumbers(); x.showNextNum(); // 1 NaturalNumbers.showNextNum(); // 2 x.showNextNum(); // 3 NaturalNumbers y = new NaturalNumbers(); y.showNextNum(); // 1 x.showNextNum(); // 2 y.showNextNum(); // 3 } } class NaturalNumbers{ public static int value = 1; public NaturalNumbers(){ this.value=1; } public static void showNextNum(){ System.out.println(value); value++; } }
Статичните полета трябва да се инициализират. Също така трябва да видим нещо много важно. Когато полетата са статични, то различните инстанции на обекти "бъркат" един друг стойностите на полетата си. В първия пример извикването на метод showNextNum() от различни обекти получавахме различни стойности. Когато полето стане статично, то извикването на showNextNum() ще вземе, нека да наречем, "глобалната за целия клас" (т.е. статичната) стойност за целия клас. Впрочем няма значение дали извикващата функция е статична или не:
public class numbersexample{ public static void main(String[] args){ NaturalNumbers x = new NaturalNumbers(); x.showNextNum(); // 1 NaturalNumbers y = new NaturalNumbers(); y.showNextNum(); // 1 y.showNextNum(); // 2 y.showNextNum(); // 3 x.showNextNum(); // 4 } } class NaturalNumbers{ public static int value; public NaturalNumbers(){ this.value=1; } public void showNextNum(){ System.out.println(value); value++; } }
Поради тази причина бъдете много внимателни за това кога трябва да използвате статични полета и кога не.
"Обратният" вариант, т.е. статичен метод да достъпва не-статично поле не е възможен - компилаторът ще даде грешката: "non-static variable value cannot be referenced from a static context". Това идва поради логически обоснованата причина, че полето може да не е инициализирано при извикването на метода.
Добави коментар