Класове, обекти, конструктори и ключови думи
Обектно-ориентираното програмиране в Java използва класове и обекти за описание и представяне на данни. Класът описва тип данни, а обектът е конкретна стойност, създадена по това описание. Конструкторът участва в създаването на обекта и задава началното му състояние. Ключовите думи new, this и static определят как се създават обекти, как се достъпва текущият обект и как се декларират членове, които принадлежат на самия клас.
Клас
Класът е механизъм за създаване на потребителски тип данни в Java. Чрез клас се описва какви данни и какво поведение ще имат обектите от този тип.
Класът не е самият обект. Той е описание на структурата, по която могат да се създават конкретни обекти. Ако Student е клас, тогава конкретен студент с име и факултетен номер е обект от този клас.
Клас се декларира чрез ключовата дума class, следвана от име на класа и тяло, оградено с фигурни скоби.
class Student {
}
Декларацията на клас съдържа:
- ключова дума
class; - име на класа;
- тяло на класа;
- членове на класа, когато са необходими.
Имената на класовете в Java започват с главна буква и се записват в стил PascalCase. Примери за такива имена са Student, BankAccount, Car и ProductOrder.
Членове на клас
Тялото на класа може да съдържа полета, конструктори и методи.
Полетата съхраняват данните на обекта. Методите описват операции, които могат да използват тези данни. Конструкторите задават началното състояние на нов обект.
class Student {
String name;
int facultyNumber;
Student(String name, int facultyNumber) {
this.name = name;
this.facultyNumber = facultyNumber;
}
String getInformation() {
return name + " - " + facultyNumber;
}
}
В примера класът Student съдържа:
- поле
name, което съхранява име; - поле
facultyNumber, което съхранява факултетен номер; - конструктор
Student(String name, int facultyNumber); - метод
getInformation(), който връща текстово представяне на данните.
Полета
Полето е променлива, декларирана в тялото на клас. То описва част от състоянието на обекта.
class Product {
String name;
double price;
}
Всеки обект от класа Product има собствена стойност за name и собствена стойност за price. Това означава, че два различни обекта могат да бъдат създадени по един и същ клас, но да имат различно състояние.
Поле, локална променлива и параметър
Полето се декларира в тялото на клас и описва състояние на обект. Всеки обект има собствено копие на нестатичните полета.
Локалната променлива се декларира в тяло на метод, конструктор или блок. Тя съществува само докато се изпълнява съответният блок.
Параметърът се декларира в скобите на метод или конструктор. Той получава стойност при извикване.
class Product {
String name; // поле
Product(String name) { // параметър
String trimmedName = name.trim(); // локална променлива
this.name = trimmedName;
}
}
В примера name в класа е поле, name в конструктора е параметър, а trimmedName е локална променлива.
Методи
Методът е именуван блок от инструкции. Чрез методите се описва поведение на обектите.
class Product {
String name;
double price;
String getDescription() {
return name + " costs " + price;
}
}
Методът getDescription() използва стойностите на полетата name и price и връща текст. Методът се извиква върху конкретен обект от класа Product.
Методи за достъп до полета
Достъпът до стойности може да се реализира чрез методи за четене и методи за промяна. Метод за четене връща стойността на поле. Метод за промяна задава нова стойност на поле.
class Student {
String name;
String getName() {
return name;
}
void setName(String name) {
this.name = name;
}
}
Методът getName() връща текущата стойност на полето name. Методът setName(String name) задава нова стойност на същото поле.
Клас като тип данни
След като бъде деклариран клас, името му може да се използва като тип.
Student student;
Product product;
Тези декларации създават променливи, които могат да съхраняват референции към обекти от съответния тип. Самият обект още не е създаден. За създаване на обект се използва ключовата дума new.
Обект
Обектът е конкретна инстанция на клас. Класът описва структурата и поведението, а обектът е реално създаден екземпляр по това описание.
Ако Student е клас, тогава студентът с име Ivan Petrov и факултетен номер 12345 е конкретен обект от този клас.
Обектът има:
- състояние;
- поведение;
- идентичност.
Състоянието се определя от стойностите на полетата. Поведението се определя от методите. Идентичността означава, че всеки създаден обект е отделна единица в паметта, дори когато стойностите му съвпадат със стойностите на друг обект.
class Student {
String name;
int facultyNumber;
}
За обект от класа Student състоянието се състои от стойностите на name и facultyNumber.
Създаване на обект
Обект се създава чрез ключовата дума new. При създаването се извиква конструктор на класа.
Student student = new Student();
В този пример:
Studentвляво е типът на променливата;studentе името на референтната променлива;new Student()създава нов обект;- резултатът от
new Student()е референция към създадения обект; - референцията се записва в променливата
student.
Референция към обект
В Java променлива от класов тип не съдържа самия обект. Тя съдържа референция към обекта.
Student first = new Student();
Student second = first;
В примера се създава един обект. Променливите first и second съдържат референция към един и същ обект. Ако чрез едната променлива се промени поле на обекта, промяната ще бъде видима и чрез другата променлива.
first.name = "Ivan";
System.out.println(second.name);
Резултатът е Ivan, защото двете променливи сочат към един и същ обект.
Стойност null
Променлива от референтен тип може да не сочи към обект. Това състояние се представя със стойността null.
Student student = null;
В примера променливата student съществува, но не сочи към обект от тип Student.
null не е обект. Чрез null не могат да се достъпват полета и методи.
Student student = null;
// student.name = "Ivan"; // грешка по време на изпълнение
Преди използване на референция, която може да бъде null, трябва да бъде ясно дали тя сочи към реален обект.
Конструктор
Конструкторът е специален член на класа, който се изпълнява при създаване на нов обект. Основното му предназначение е да зададе началното състояние на обекта.
Конструкторът не е обикновен метод. Той няма тип на връщана стойност, не използва void и името му винаги съвпада с името на класа.
class Student {
String name;
int facultyNumber;
Student(String name, int facultyNumber) {
this.name = name;
this.facultyNumber = facultyNumber;
}
}
В примера Student(String name, int facultyNumber) е конструктор. Той приема две стойности и ги записва в полетата на новия обект.
Конструктор без параметри
Конструктор без параметри е конструктор, който не приема аргументи. Той може да бъде дефиниран явно в класа и да задава начални стойности на полетата.
class Student {
String name;
int facultyNumber;
Student() {
name = "Unknown";
facultyNumber = 0;
}
}
При извикване на този конструктор се създава обект със стойности, зададени в тялото на конструктора.
Student student = new Student();
Конструктор по подразбиране
Ако в даден клас не е дефиниран нито един конструктор, Java компилаторът създава автоматично конструктор по подразбиране. Той не приема параметри и не съдържа допълнителна логика.
class Student {
String name;
int facultyNumber;
}
Горният клас може да се използва така:
Student student = new Student();
Това е възможно, защото компилаторът добавя конструктор по подразбиране. Той има формата на конструктор без параметри, но не е изписан в програмния код.
Ако в класа бъде дефиниран поне един собствен конструктор, компилаторът не добавя автоматично конструктор по подразбиране.
class Student {
String name;
Student(String name) {
this.name = name;
}
}
Следната инструкция няма да се компилира:
Student student = new Student();
Причината е, че в класа има дефиниран конструктор с параметър, но няма дефиниран конструктор без параметри.
Конструктор с параметри за всички полета
Конструктор с параметри за всички полета приема стойности при създаване на обекта. Тези стойности се използват за инициализиране на всички полета, които описват началното състояние на обекта.
class Student {
String name;
int facultyNumber;
Student(String name, int facultyNumber) {
this.name = name;
this.facultyNumber = facultyNumber;
}
}
Създаване на обект:
Student student = new Student("Ivan Petrov", 12345);
Стойността "Ivan Petrov" се подава към параметъра name, а стойността 12345 се подава към параметъра facultyNumber. Така всяко поле получава стойност още при създаването на обекта.
Конструктор и задаване на начално състояние
Конструкторът задава началните стойности на полетата. Стойностите, които се подават към конструктора, трябва да съответстват на смисъла на обекта.
class Product {
String name;
double price;
Product(String name, double price) {
this.name = name;
this.price = price;
}
}
В примера стойностите на параметрите name и price се записват в полетата на новия обект. Така създаденият обект получава начално състояние още при извикване на конструктора.
Ключова дума new
Ключовата дума new се използва за създаване на нов обект в паметта. Тя свързва класа и конструктора: класът определя типа на обекта, а конструкторът определя как обектът ще бъде инициализиран.
При използване на new се извършват няколко действия:
- заделя се памет за новия обект;
- извиква се подходящ конструктор;
- полетата получават начални стойности;
- връща се референция към създадения обект.
Общ синтаксис:
ClassName variableName = new ClassName(arguments);
Пример:
Student student = new Student("Ivan Petrov", 12345);
В примера new Student("Ivan Petrov", 12345) създава нов обект от класа Student. Извиква се конструкторът с два параметъра. Получената референция се записва в променливата student.
Всеки израз с new създава нов обект.
Student first = new Student("Ivan Petrov", 12345);
Student second = new Student("Ivan Petrov", 12345);
В примера се създават два различни обекта. Те могат да имат еднакви стойности в полетата си, но са отделни обекти в паметта.
Ключова дума this
Ключовата дума this е референция към текущия обект. Текущ обект е обектът, върху който в момента се изпълнява нестатичен метод или конструктор.
this се използва, когато трябва ясно да се посочи член на текущия обект. Това е необходимо при съвпадение между имена на параметри и имена на полета.
class Student {
String name;
int facultyNumber;
Student(String name, int facultyNumber) {
this.name = name;
this.facultyNumber = facultyNumber;
}
}
В примера:
this.nameозначава полетоnameна текущия обект;nameозначава параметъра на конструктора;this.facultyNumberозначава полетоfacultyNumberна текущия обект;facultyNumberозначава параметъра на конструктора.
Без this инструкцията name = name; би присвоила параметъра на самия него и полето на обекта няма да бъде зададено по желания начин.
Извикване на метод чрез this
Ключовата дума this може да се използва и при извикване на нестатичен метод на текущия обект. Когато методът се извиква без изрично посочен обект, Java приема, че извикването е към текущия обект.
class Student {
String name;
Student(String name) {
this.name = name;
}
String getName() {
return this.name;
}
String getInformation() {
return this.getName();
}
}
В метода getInformation() изразът this.getName() извиква метода getName() върху същия обект. В този пример getName() и this.getName() имат еднакъв резултат. Използването на this прави явно, че методът принадлежи на текущия обект.
Извикване на друг конструктор чрез this()
Ключовата дума this може да се използва и като this(...) за извикване на друг конструктор от същия клас. Това извикване трябва да бъде първата инструкция в конструктора.
Такъв запис е подходящ, когато конструктор без параметри трябва да използва същата логика за начално състояние като конструктора с параметри за всички полета.
class Student {
String name;
int facultyNumber;
Student() {
this("Unknown", 0);
}
Student(String name, int facultyNumber) {
this.name = name;
this.facultyNumber = facultyNumber;
}
}
Конструкторът без параметри използва конструктора с параметри за всички полета. Така логиката за задаване на началните стойности остава в един конструктор.
this може да се използва само в нестатичен контекст. Тя не може да се използва в статични методи и статични блокове, защото там няма текущ обект.
class Example {
static void print() {
// this не може да се използва тук
}
}
Ключова дума static
Ключовата дума static се използва за деклариране на членове, които принадлежат на класа. Те не принадлежат на отделен обект.
Статични могат да бъдат:
- полета;
- методи;
- блокове за инициализация;
- вложени класове.
Разликата между нестатичен и статичен член е свързана със собствеността. Нестатичното поле принадлежи на конкретен обект. Статичното поле принадлежи на класа и има една обща стойност за този клас.
Основни употреби на static
| Елемент | Предназначение |
|---|---|
| статично поле | Съхранява стойност, обща за целия клас |
| статичен метод | Принадлежи на класа и може да бъде извикан без създаване на обект |
| статичен блок | Изпълнява се еднократно при зареждане на класа |
Статично поле
Статичното поле е общо за класа. Съществува една стойност, която се споделя от всички обекти.
class Student {
static String university = "Technical University of Varna";
String name;
Student(String name) {
this.name = name;
}
}
Полето university принадлежи на класа Student, а не на конкретен студент. Достъпът до него се извършва чрез името на класа.
System.out.println(Student.university);
Достъпът до статично поле чрез обект е синтактично възможен, но не показва правилно собствеността на полето.
Student student = new Student("Ivan");
System.out.println(student.university); // не е препоръчителен запис
Правилният запис използва името на класа, защото полето university принадлежи на класа Student.
Полето name е нестатично. Всеки обект от класа Student има собствена стойност за него.
Student first = new Student("Ivan");
Student second = new Student("Maria");
System.out.println(first.name);
System.out.println(second.name);
System.out.println(Student.university);
Константа чрез static final
Когато дадена стойност принадлежи на класа и не трябва да се променя, може да се използва комбинацията static final.
class MathConstants {
static final double PI = 3.14159;
}
static означава, че стойността принадлежи на класа, а не на отделен обект. final означава, че след задаване на стойността не може да бъде извършено ново присвояване.
Имената на константите обикновено се записват с главни букви и думи, разделени с долна черта.
| Употреба | Предназначение |
|---|---|
final променлива | Стойността не може да бъде променяна след инициализация |
static final поле | Стойността принадлежи на класа и се използва като константа |
Статичен метод
Статичният метод се извиква чрез името на класа и не изисква създаване на обект.
class Calculator {
static int square(int number) {
return number * number;
}
}
Извикване:
int result = Calculator.square(5);
Статичният метод може директно да достъпва само статични членове на същия клас. Той няма текущ обект и затова не може да използва this.
Статичният метод не може да използва и super, защото super изисква текущ обект от клас наследник. В статичен контекст няма конкретен обект, върху който да се изпълнява методът.
class Example {
String name;
static int counter;
static void printCounter() {
System.out.println(counter);
// System.out.println(this.name); // не се компилира
}
}
Ако статичен метод трябва да работи с нестатично поле, трябва да получи обект като параметър или да създаде обект.
Метод main
В Java методът main обикновено е статичен, защото трябва да бъде извикан от Java Virtual Machine без предварително създаване на обект от класа.
public class Application {
public static void main(String[] args) {
System.out.println("Program started.");
}
}
Статичен блок
Статичният блок е блок от инструкции, който се изпълнява еднократно при зареждане на класа.
class Configuration {
static String applicationName;
static {
applicationName = "Student System";
}
}
Статичните блокове се използват за начална подготовка на статични полета, когато инициализацията изисква повече от една инструкция.
Общ пример
Следният пример обединява клас, обекти, конструктор, new, this и static.
class Student {
static String university = "Technical University of Varna";
String name;
int facultyNumber;
Student(String name, int facultyNumber) {
this.name = name;
this.facultyNumber = facultyNumber;
}
String getInformation() {
return name + " - " + facultyNumber + " - " + university;
}
}
public class Application {
public static void main(String[] args) {
Student first = new Student("Ivan Petrov", 12345);
Student second = new Student("Maria Ivanova", 67890);
System.out.println(first.getInformation());
System.out.println(second.getInformation());
}
}
В примера Student е клас. Променливите first и second съдържат референции към два различни обекта. Изразите с new създават тези обекти и извикват параметризирания конструктор. В конструктора this.name и this.facultyNumber обозначават полетата на текущия обект. Полето university е статично и принадлежи на класа Student, затова стойността му е обща за всички обекти от този клас.