Konstruktorid
Õppevideo antud teemal:
Sissejuhatus
Konstruktor on eriline meetod, mida kutsutakse automaatselt välja objekti loomise hetkel. Selle peamine eesmärk on omistada objekti väljadele algväärtused argumentidest sisse tulevate andmetega.
Ilma konstruktoriteta tuleks igale väljale käsitsi väärtusi omistada, mis eeldab palju manuaalset tööd ning kus vead on kerged tekkima. Konstruktorid võimaldavad seda teha selgemalt ja töökindlamalt ning tagavad objektide korralikku loomist.
Objektid ilma konstruktorita
Olgu meil klass Student, millel puudub konstruktor:
class Student {
String name;
int age;
String studentId;
}
public class Main {
public static void main(String[] args) {
Student student = new Student();
// Must manually set each field
student.name = "Alice";
student.age = 20;
student.studentId = "S12345";
}
}
Sellisel lähenemisel on järgnevad probleemid:
- Väärtuste omistamine võib ununeda, mis tähendab, et väli omistab endale mõne vaikeväärtuse.
- Kuidas kontrollida määratavate andmete õigsust?
- Iga uue objekti loomisel on vaja palju koodi kirjutada.
- Vägagi veaaldine võte ehk kerge on sedasi vigu tekitada.
Lisaks, kui on soov objekti olekut ligipääsu modifikaatoritega, ära peita näiteks:
class Student {
private String name;
private int age;
private String studentId;
}
siis ei ole võimalik nendele muutujatele väärtusi omistadagi ja tekib kompileerimisviga:
Student student = new Student();
student.name = "Alice"; // error: 'name' has private access in 'Student'
Konstruktor
Konstruktor on eriline meetod klassis,
- mis jagab klassiga sama nime
- millel pole tagastustüüpi (isegi mitte
void)
Konstruktorit kutsutakse automaatselt välja, kui objekti luuakse new märksõnaga ning seda kasutatakse peamiselt selleks, et objekti väljadele väärtuseid omistada ja eelnevat kontrolli sooritada (ehk teisisõnu kas antud andmetega on seda objekti võimalik luua).
Konstruktori süntaks on järgnev:
class ClassName {
// Constructor
public ClassName(parameters) {
// initialization code
}
}
Ise konstruktoreid luues tuleks need vaikimisi koostada public ligipääsu modifikaatoriga.
Võimalik on kasutada ka private, protected või ka ilma modifikaatorita, vastavalt vajadusele.
Need olukorrad tuuakse tulevikus eraldi välja (näiteks mõne mustri puhul).
Vaikimisi konstruktor
Java tekitab igale objektile automaatselt vaikimisi ilma parameetriteta konstruktori. Sellise konstruktori põhiline eesmärk on objekti loomine ja mitte midagi muud. Näiteks:
class Student {
String name;
int age;
String studentId;
// Java automatically provides:
// Student() {}
}
public class Main {
public static void main(String[] args) {
Student student = new Student(); // Calls the default constructor
System.out.println(student.name); // null
System.out.println(student.age); // 0
}
}
See konstruktor asendatakse ära hetkel, kui kirjutate ise konstruktori. Kui on siis soov tekitada ilma parameetriteta konstruktor, peate selle ise looma.
Konstruktorite loomine
Loome Student klassile konstruktori, mis omistab sisendina saadud andmed objekti väljadele:
class Student {
String name;
int age;
String studentId;
// Constructor
public Student(String studentName, int studentAge, String id) {
name = studentName;
age = studentAge;
studentId = id;
}
}
public class Main {
public static void main(String[] args) {
// Create student with initialized values
Student student = new Student("Alice", 20, "S12345");
System.out.println("Name: " + student.name); // Name: Alice
System.out.println("Age: " + student.age); // Age: 20
System.out.println("ID: " + student.studentId); // ID: S12345
}
}
Tulemusena on võimalik nüüd Student klassi põhjal ühel real objekte luua.
Samuti on tagatud ka see, et objekt saab loomise hetkel kõik vajalikud andmed kätte.
Konstruktori eelised
Konstruktorite kasutamisel on mitu eelist:
- Võimalik määrata väljade algväärtused kohe objekti loomise hetkel
- Saame dikteerida, mis andmeid on objekti loomiseks vaja
- Võimalik vältida ebakorrektses seisus objekti loomist (näiteks tudengi vanus ei tohi olla negatiivne)
- Kood on üldisemalt selgem ja loetavam
Samuti ei pea piirduma ainult ühe konstruktoriga klassis. Kui varasemalt tutvusime meetodite ülelaadimise (method overload) võttega, siis sama on võimalik ka konstruktorite puhul.
Konstruktorite ülelaadimine
Nagu tavalisi meetodeid, on ka konstruktoreid võimalik üle laadida ehk tekitada seis kus ühel klassil on mitu konstruktorit. See võimaldab defineerida erinevaid viise, kuidas mõnda objekti saab luua, näiteks:
class Book {
String title;
String author;
int pages;
double price;
// Constructor with all parameters
public Book(String title, String author, int pages, double price) {
this.title = title;
this.author = author;
this.pages = pages;
this.price = price;
}
// Constructor with only title and author
public Book(String title, String author) {
this.title = title;
this.author = author;
this.pages = 0;
this.price = 0.0;
}
// Constructor with only title
public Book(String title) {
this.title = title;
this.author = "Unknown";
this.pages = 0;
this.price = 0.0;
}
public void displayInfo() {
System.out.println("Title: " + title + ", Author: " + author +
", Pages: " + pages + ", Price: $" + price);
}
}
public class Main {
public static void main(String[] args) {
// All params
Book book1 = new Book("Java Programming", "John Doe", 450, 49.99);
// Title and author
Book book2 = new Book("Clean Code", "Robert Martin");
// Only title
Book book3 = new Book("Design Patterns");
book1.displayInfo(); // Title: Java Programming, Author: John Doe, Pages: 450, Price: $49.99
book2.displayInfo(); // Title: Clean Code, Author: Robert Martin, Pages: 0, Price: $0.0
book3.displayInfo(); // Title: Design Patterns, Author: Unknown, Pages: 0, Price: $0.0
}
}
Tavaliselt sedasi mitut konstruktorit ei kirjutada, vaid kasutatakse this() meetodit ehk näiliselt delegeeritakse tööd ühelt konstruktorilt teisele edasi.
Konstruktorite aheldamine this() meetodiga
Hea tava mitme konstruktori loomisel on kirjutada välja üks suur konstruktor, mis võtab sisendina kõik andmed ning selle järgnevalt väiksemad konstruktorid, mis kasutavad endast suuremat läbi this() meetodi alusena.
Näiteks:
class Student {
String name;
int age;
String major;
double gpa;
// Main constructor with all parameters
public Student(String name, int age, String major, double gpa) {
this.name = name;
this.age = age;
this.major = major;
this.gpa = gpa;
}
// Constructor with three parameters - calls the main constructor
public Student(String name, int age, String major) {
this(name, age, major, 0.0); // Calls the 4-parameter constructor
}
// Constructor with two parameters - calls the 3-parameter constructor
public Student(String name, int age) {
this(name, age, "Undeclared"); // Calls the 3-parameter constructor
}
// Constructor with one parameter - calls the 2-parameter constructor
public Student(String name) {
this(name, 18); // Calls the 2-parameter constructor
}
// Constructor with no parameters
// If this is needed then it has to be written manually
// Java does not provide a parameterless constructor if other ones exist
public Student() {
this("Unknown student");
}
public void displayInfo() {
System.out.println("Name: " + name + ", Age: " + age +
", Major: " + major + ", GPA: " + gpa);
}
}
public class Main {
public static void main(String[] args) {
Student s1 = new Student("Alice", 20, "Computer Science", 3.8);
Student s2 = new Student("Bob", 19, "Mathematics");
Student s3 = new Student("Charlie", 21);
Student s4 = new Student("Diana");
Student s5 = new Student();
s1.displayInfo(); // Name: Alice, Age: 20, Major: Computer Science, GPA: 3.8
s2.displayInfo(); // Name: Bob, Age: 19, Major: Mathematics, GPA: 0.0
s3.displayInfo(); // Name: Charlie, Age: 21, Major: Undeclared, GPA: 0.0
s4.displayInfo(); // Name: Diana, Age: 18, Major: Undeclared, GPA: 0.0
s5.displayInfo(); // Name: Unknown student, Age: 18, Major: Undeclared, GPA: 0.0
}
}
Antud juhul on tähtis, et this() peab olema esimene käsk, mida siis konstruktoris välja kutsutakse.
Vastasel juhul tekib kompileerimisviga:
public Student(String name) {
System.out.println("Creating student"); // This line would cause an error
this(name, 18); // Error: call to this() must be first statement
}
// Correct version:
public Student(String name) {
this(name, 18); // Must be first
System.out.println("Creating student"); // Other code comes after
}
Antud reegel kehtib Java 22st varasematele versioonidele, alates Java 22st on võimalik ka muud koodi enne this() ja super()-it kasutada (JEP 513).
Reeglina võiks jätkuvalt konstruktoris esikohal olla kas this() või super() juhul, kui pole eelnevalt vaja andmete valideerimist läbi viia.
Konstruktorite aheldamine vähendab koodi kordumist, võimaldab kergemat haldamist ning üldiselt loob puhtama ja ühtselt mõistetavama koodi.
Valideerimine konstruktori kaudu
Konstruktoreid kasutatakse ka selleks, et teha andmete valideerimine enne nende objektiks vormimist. Näiteks:
class BankAccount {
String accountNumber;
double balance;
public BankAccount(String accountNumber, double initialBalance) {
// Validate account number
if (accountNumber == null || accountNumber.isEmpty()) {
throw new IllegalArgumentException("Error: Account number cannot be empty");
}
this.accountNumber = accountNumber;
// Validate initial balance
if (initialBalance < 0) {
throw new IllegalArgumentException("Error: Initial balance cannot be negative");
}
this.balance = initialBalance;
}
}
public class Main {
public static void main(String[] args) {
BankAccount acc1 = new BankAccount("ACC001", 1000); // OK
BankAccount acc2 = new BankAccount("", 500); // Would throw an exception
BankAccount acc3 = new BankAccount("ACC003", -100); // Also will end in an error
}
}
Head tavad
- Kasuta konstruktoreid, et tagada alati korrektne objekti seisund – valideeri parameetreid ja määra mõistlikud vaikeväärtused
- Kasuta dubleerimise vältimiseks konstruktorite aheldamist – paiguta põhiline initsialiseerimisloogika ühte konstruktorisse ja kutsu seda teistest välja
- Paku paindlikkuse tagamiseks mitut konstruktorit – võimalda objekti loomist erineva hulga andmetega
- Hoia konstruktorid lihtsad – keerukam initsialiseerimisloogika võib sobida paremini eraldi meetoditesse (mida saad konstruktoris välja kutsuda)
- Initsialiseeri kõik väljad – ära jäta välju seadistamata, kui selleks pole mõjuvat põhjust