Informazioni generali sui costruttori
Конструктор
è una struttura simile ad un metodo, il cui scopo è creare un'istanza di una classe. Caratteristiche del progettista:
- Il nome del costruttore deve corrispondere al nome della classe (per convenzione la prima lettera è maiuscola, solitamente un sostantivo);
- C'è un costruttore in ogni classe. Anche se non ne scrivi uno, il compilatore Java creerà un costruttore predefinito, che sarà vuoto e non farà altro che chiamare il costruttore della superclasse.
- Un costruttore è simile a un metodo, ma non è un metodo, non è nemmeno considerato un membro della classe. Pertanto non può essere ereditato o sovrascritto in una sottoclasse;
- I costruttori non vengono ereditati;
- Possono esserci più costruttori in una classe. In questo caso si dice che i costruttori sono sovraccarichi;
- Se una classe non definisce un costruttore, il compilatore aggiunge automaticamente al codice un costruttore senza parametri;
- Un costruttore non ha un tipo restituito; non può nemmeno essere un tipo
void
; se viene restituito un tipo void
, allora non è più un costruttore ma un metodo, nonostante la coincidenza con il nome della classe.
- L'operatore è consentito nel costruttore
return
, ma solo vuoto, senza alcun valore restituito;
- Il costruttore consente l'uso di modificatori di accesso; è possibile impostare uno dei modificatori:
public
, protected
o private
senza modificatore.
- Un costruttore non può avere i modificatori
abstract
, final
, o ;native
static
synchronized
- La parola chiave
this
si riferisce a un altro costruttore nella stessa classe. Se utilizzata, la chiamata deve essere la prima riga del costruttore;
- La parola chiave
super
chiama il costruttore della classe genitore. Se utilizzato, il riferimento ad esso deve essere la prima riga del costruttore;
- Se il costruttore non effettua una chiamata al
super
costruttore della classe antenata (con o senza argomenti), il compilatore aggiunge automaticamente il codice per chiamare il costruttore della classe antenata senza argomenti;
Costruttore predefinito
C'è un costruttore in ogni classe. Anche se non ne scrivi uno, il compilatore Java creerà un costruttore predefinito. Questo costruttore è vuoto e non fa altro che chiamare il costruttore della superclasse. Quelli. se scrivi:
public class Example {}
allora questo equivale a scrivere:
public class Example
{
Example()
{
super;
}
}
In questo caso, la classe antenata non è specificata esplicitamente e, per impostazione predefinita, tutte le classi Java ereditano la classe,
Object
quindi il costruttore della classe viene chiamato
Object
. Se una classe definisce un costruttore con parametri, ma non esiste un costruttore sovraccaricato senza parametri, chiamare il costruttore senza parametri è un errore. Tuttavia, in Java a partire dalla versione 1.5, è possibile utilizzare costruttori con argomenti di lunghezza variabile. E se esiste un costruttore che ha un argomento di lunghezza variabile, chiamare il costruttore predefinito non sarà un errore. Non lo farà perché l'argomento a lunghezza variabile può essere vuoto. Ad esempio, l'esempio seguente non verrà compilato, ma se si rimuove il commento dal costruttore con un argomento di lunghezza variabile, verrà compilato ed eseguito correttamente e il risultato sarà una riga di codice in esecuzione
DefaultDemo dd = new DefaultDemo()
; il costruttore verrà chiamato
DefaultDemo(int ... v)
. Naturalmente in questo caso è necessario utilizzare JSDK 1.5. File
DefaultDemo.java
class DefaultDemo
{
DefaultDemo(String s)
{
System.out.print("DefaultDemo(String)");
}
public static void main(String args[])
{
DefaultDemo dd = new DefaultDemo();
}
}
Il risultato dell'output del programma con il costruttore senza commenti:
DefaultDemo(int ...)
Tuttavia, nel caso comune in cui la classe non definisce alcun costruttore, sarà necessario chiamare il costruttore predefinito (senza parametri), poiché la sostituzione del costruttore predefinito avviene automaticamente.
Creazione di oggetti e costruttori
Quando si crea un oggetto, le seguenti azioni vengono eseguite in sequenza:
- La classe oggetto viene cercata tra le classi già utilizzate nel programma. Se non è presente, viene cercato in tutti i cataloghi e le librerie disponibili nel programma. Una volta rilevata una classe in una directory o in una libreria, i campi statici della classe vengono creati e inizializzati. Quelli. Per ogni classe, i campi statici vengono inizializzati una sola volta.
- La memoria viene allocata per l'oggetto.
- È in corso l'inizializzazione dei campi della classe.
- Il costruttore della classe viene eseguito.
- Viene formato un collegamento all'oggetto creato e inizializzato. Questo riferimento è il valore dell'espressione che crea l'oggetto. Un oggetto può anche essere creato chiamando un metodo
newInstance()
di classe java.lang.Class
. In questo caso viene utilizzato un costruttore senza elenco di parametri.
Costruttori di sovraccarico
I costruttori della stessa classe possono avere lo stesso nome e firma diversa. Questa proprietà è chiamata combinazione o sovraccarico. Se una classe ha più costruttori, è presente l'overload del costruttore.
Costruttori parametrizzati
La firma di un costruttore è il numero e i tipi di parametri, nonché la sequenza dei loro tipi nell'elenco dei parametri del costruttore. Il tipo di reso non viene preso in considerazione. Il costruttore non restituisce alcun parametro. Questa affermazione spiega, in un certo senso, come Java distingue tra costruttori o metodi sovraccaricati. Java distingue i metodi sovraccaricati non in base al tipo restituito, ma in base al numero, ai tipi e alla sequenza dei tipi di parametri di input. Un costruttore non può nemmeno restituire un type
void
, altrimenti si trasformerà in un metodo regolare, anche se è simile al nome della classe. L'esempio seguente lo dimostra. File
VoidDemo.java
class VoidDemo
{
VoidDemo()
{
System.out.println("Constructor");
}
void VoidDemo()
{
System.out.println("Method");
}
public static void main(String s[])
{
VoidDemo m = new VoidDemo();
}
}
Di conseguenza, il programma restituirà:
Constructor
Ciò dimostra ancora una volta che un costruttore è un metodo senza parametri di ritorno. Tuttavia, al costruttore può essere assegnato uno dei tre modificatori
public
,
private
o
protected
. E l'esempio ora sarà simile a questo: File
VoidDemo2.java
class VoidDemo2
{
public VoidDemo2()
{
System.out.println("Constructor");
}
private void VoidDemo2()
{
System.out.println("Method");
}
public static void main(String s[])
{
VoidDemo2 m = new VoidDemo2();
}
}
È consentito scrivere un operatore in un costruttore
return
, ma solo vuoto, senza alcun valore di ritorno. File
ReturnDemo.java
class ReturnDemo
{
public ReturnDemo()
{
System.out.println("Constructor");
return;
}
public static void main(String s[])
{
ReturnDemo r = new ReturnDemo();
}
}
Costruttori parametrizzati con argomenti di lunghezza variabile
Java SDK 1.5 ha introdotto uno strumento tanto atteso: argomenti a lunghezza variabile per costruttori e metodi. In precedenza, un numero variabile di documenti veniva elaborato in due modi scomodi. Il primo è stato progettato per garantire che il numero massimo di argomenti sia limitato a un numero limitato e noto in anticipo. In questo caso era possibile creare versioni sovraccaricate del metodo, una per ogni versione dell'elenco di argomenti passati al metodo. Il secondo metodo è progettato per qualcosa di sconosciuto in anticipo e un gran numero di argomenti. In questo caso, gli argomenti sono stati inseriti in un array e questo array è stato passato al metodo. Gli argomenti a lunghezza variabile sono spesso coinvolti in successive manipolazioni con inizializzazioni variabili. È conveniente sostituire l'assenza di alcuni degli argomenti previsti del costruttore o del metodo con valori predefiniti. L'argomento a lunghezza variabile è un array e viene trattato come un array. Ad esempio, il costruttore di una classe
Checking
con un numero variabile di argomenti sarebbe simile a questo:
class Checking
{
public Checking(int ... n)
{
}
}
La combinazione di caratteri ... dice al compilatore che verrà utilizzato un numero variabile di argomenti e che questi argomenti verranno memorizzati in un array il cui valore di riferimento è contenuto nella variabile n. Il costruttore può essere chiamato con un numero diverso di argomenti, incluso nessun argomento. Gli argomenti vengono automaticamente inseriti in un array e passati attraverso n. Se non sono presenti argomenti, la lunghezza dell'array è 0. L'elenco dei parametri, insieme agli argomenti a lunghezza variabile, può includere anche parametri obbligatori. In questo caso un parametro contenente un numero variabile di argomenti deve necessariamente essere l'ultimo della lista dei parametri. Per esempio:
class Checking
{
public Checking(String s, int ... n)
{
}
}
Una limitazione molto evidente riguarda il numero di parametri a lunghezza variabile. Nell'elenco dei parametri deve essere presente un solo parametro a lunghezza variabile. Dati due parametri di lunghezza variabile, è impossibile per il compilatore determinare dove finisce un parametro e inizia l'altro. Per esempio:
class Checking
{
public Checking(String s, int ... n, double ... d)
{
}
}
File
Checking.java
Ad esempio, esistono apparecchiature in grado di riconoscere le targhe delle auto e di ricordare i numeri dei quadrati della zona dove ha transitato ciascuna delle auto durante la giornata. È necessario selezionare dalla massa totale delle auto registrate quelle che durante la giornata hanno visitato due determinate piazze, diciamo 22 e 15, secondo la mappa della zona. È del tutto naturale che un'auto possa visitare molte piazze durante il giorno, o magari solo una. Ovviamente il numero di piazze visitabili è limitato dalla velocità fisica dell'auto. Creiamo un piccolo programma dove il costruttore della classe prenderà come argomenti il numero dell'auto come parametro obbligatorio ed il numero dei quadrati visitati dell'area, il cui numero può essere variabile. Il costruttore controllerà se un'auto è apparsa in due quadrati; in tal caso, ne visualizzerà il numero sullo schermo.
Passaggio dei parametri al costruttore
Nei linguaggi di programmazione esistono principalmente due tipi di parametri:
- tipi base (primitivi);
- riferimenti agli oggetti.
Il termine chiamata per valore significa che il costruttore riceve il valore passatogli dal modulo chiamante. Al contrario, chiamata per riferimento significa che il costruttore riceve l'indirizzo della variabile dal chiamante. Java utilizza solo la chiamata per valore. Per valore del parametro e per valore del collegamento del parametro. Java non utilizza la chiamata per riferimento per gli oggetti (sebbene molti programmatori e gli autori di alcuni libri lo affermino). Quando si passano oggetti a Java, i parametri non vengono passati
per riferimento , ma
per il valore del riferimento all'oggetto ! In entrambi i casi, il costruttore riceve copie dei valori di tutti i parametri. Il costruttore non può fare con i suoi parametri di input:
- il costruttore non può modificare i valori dei parametri di input dei tipi principali (primitivi);
- il costruttore non può modificare i riferimenti ai parametri di input;
- il costruttore non può riassegnare i riferimenti ai parametri di input a nuovi oggetti.
Il costruttore può fare con i suoi parametri di input:
- modificare lo stato dell'oggetto passato come parametro di input.
L'esempio seguente dimostra che in Java i parametri di input a un costruttore vengono passati in base al valore di riferimento dell'oggetto. Questo esempio riflette anche il fatto che il costruttore non può modificare i riferimenti dei parametri di input, ma in realtà modifica i riferimenti delle copie dei parametri di input. File
Empoyee.java
class Employee
{
Employee(String x, String y)
{
String temp = x;
x = y;
y = temp;
}
public static void main(String args[])
{
String name1 = new String("Alice");
String name2 = new String("Mary");
Employee a = new Employee(name1, name2);
System.out.println("name1="+name1);
System.out.println("name2="+name2);
}
}
L'output del programma è:
name1=Alice
name2=Mary
Se Java utilizzasse la chiamata per riferimento per passare gli oggetti come parametri, il costruttore scambierebbe
name1
e in questo esempio
name2
. Il costruttore in realtà non scambierà i riferimenti agli oggetti memorizzati nelle variabili
name1
e
name2
. Ciò suggerisce che i parametri del costruttore siano inizializzati con copie di questi riferimenti. Quindi il costruttore scambia le copie. Quando il costruttore completa il suo lavoro, le variabili xey vengono distrutte e le variabili originali
name1
continuano
name2
a fare riferimento agli oggetti precedenti.
Modifica dei parametri passati al costruttore.
Il costruttore non può modificare i parametri passati dei tipi base. Tuttavia, il costruttore può modificare lo stato dell'oggetto passato come parametro. Consideriamo ad esempio il seguente programma: File
Salary1.java
class Salary1
{
Salary1(int x)
{
x = x * 3;
System.out.println("x="+x);
}
public static void main(String args[])
{
int value = 1000;
Salary1 s1 = new Salary1(value);
System.out.println("value="+value);
}
}
L'output del programma è:
x=3000
value=1000
Ovviamente, questo metodo non modificherà il parametro di tipo principale. Pertanto, dopo aver chiamato il costruttore, il valore della variabile
value
rimane uguale a
1000
. Succedono essenzialmente tre cose:
- La variabile
x
viene inizializzata con una copia del valore del parametro value
(ovvero un numero 1000
).
- Il valore della variabile
x
è triplicato: ora è uguale a 3000
. Tuttavia, il valore della variabile value
rimane uguale a 1000
.
- Il costruttore termina e la variabile
x
non viene più utilizzata.
Nell'esempio seguente, lo stipendio del dipendente viene triplicato con successo perché il valore di un riferimento a un oggetto viene passato come parametro al metodo. File
Salary2.java
class Salary2
{
int value = 1000;
Salary2()
{
}
Salary2(Salary2 x)
{
x.value = x.value * 3;
}
public static void main(String args[])
{
Salary2 s1 = new Salary2();
Salary2 s2 = new Salary2(s1);
System.out.println("s1.value=" +s1.value);
System.out.println("s2.value="+s2.value);
}
}
L'output del programma è:
s1.value=3000
s2.value=1000
Il valore del riferimento all'oggetto viene utilizzato come parametro. Durante l'esecuzione della linea
Salary2 s2 = new Salary2(s1)
; al costruttore
Salary2(Salary x)
verrà passato il valore di un riferimento alla variabile oggetto
s1
, e il costruttore effettivamente triplicherà lo stipendio di
s1.value
, poiché anche la copia
(Salary x)
creata all'interno del costruttore punta alla variabile oggetto
s1
.
Costruttori parametrizzati da primitive.
Se i parametri di un costruttore sovraccaricato utilizzano una primitiva che può essere ristretta (ad esempio
int <- double
), è possibile chiamare un metodo con un valore ristretto, nonostante non esista alcun metodo sovraccaricato con tale parametro. Ad esempio: File
Primitive.java
class Primitive
{
Primitive(double d)
{
d = d + 10;
System.out.println("d="+d);
}
public static void main(String args[])
{
int i = 20;
Primitive s1 = new Primitive(i);
}
}
L'output del programma è:
d=30.0
Nonostante il fatto che la classe
Primitive
non abbia un costruttore con un parametro di tipo
int
, funzionerà un costruttore con un parametro di input
double
. Prima di chiamare il costruttore, la variabile
i
verrà espansa da type
int
a type
double
. L'opzione opposta, quando la variabile
i
fosse di tipo
double
e il costruttore avesse solo un parametro
int
, in questa situazione porterebbe a un errore di compilazione.
Chiamata costruttore e operatorenew
Il costruttore viene sempre chiamato dall'operatore
new
. Quando un costruttore viene chiamato con l'operatore
new
, il costruttore genera sempre un riferimento a un nuovo oggetto. È impossibile forzare il costruttore a formare un riferimento a un oggetto già esistente invece di un riferimento a un nuovo oggetto, se non sostituendo l'oggetto da deserializzare. E con l'operatore new, invece di un riferimento a un nuovo oggetto, è impossibile formare un riferimento a un oggetto già esistente. Ad esempio: File
Salary3.java
class Salary3
{
int value = 1000;
Salary3()
{
}
Salary3(Salary3 x)
{
x.value = x.value * 3;
}
public static void main(String args[])
{
Salary3 s1 = new Salary3();
System.out.println("First object creation: "+s1.value);
Salary3 s2 = new Salary3(s1);
System.out.println("Second object creation: "+s2.value);
System.out.println("What's happend with first object?:"+s1.value);
Salary3 s3 = new Salary3(s1);
System.out.println("Third object creation: "+s3.value);
System.out.println("What's happend with first object?:"+s1.value);
}
}
L'output del programma è:
First object creation: 1000
Second object creation: 1000
What's happend with first object?: 3000
Third object creation: 1000
What's happend with first object?: 9000
Innanzitutto, utilizzando la linea
Salary3 s1 = new Salary3()
; viene creato un nuovo oggetto. Successivamente, se si utilizza la linea
Salary3 s2 = new Salary3(s1)
; o stringhe
Salary3 s3 = new Salary3(s1)
; sarebbe possibile creare un collegamento a un oggetto già esistente, quindi
s1.value s2.value
memorizzerebbero
s3.value
lo stesso valore
1000
. In realtà in linea
Salary3 s2 = new Salary3(s1)
; verrà creato un nuovo oggetto per la variabile
s2
e lo stato dell'oggetto per la variabile cambierà
s1
passando il suo valore di riferimento all'oggetto nel parametro del costruttore. Ciò può essere verificato dai risultati di output. E durante l'esecuzione della linea
Salary3 s3 = new Salary3(s1)
; verrà creato un NUOVO oggetto per la variabile
s3
e lo stato dell'oggetto per la variabile cambierà nuovamente
s1
.
Costruttori e blocchi di inizializzazione, sequenza di azioni quando si chiama un costruttore
La sezione
Creazione di un oggetto e costruttori elenca le azioni generali che vengono eseguite durante la creazione di un oggetto. Tra questi ci sono i processi di inizializzazione dei campi della classe e di elaborazione del costruttore della classe, che a loro volta hanno anche un ordine interno:
- Tutti i campi dati vengono inizializzati sui valori predefiniti (0, false o null).
- Tutti gli inizializzatori di campo e i blocchi di inizializzazione vengono eseguiti nell'ordine in cui sono elencati nella dichiarazione della classe.
- Se sulla prima riga di un costruttore viene chiamato un altro costruttore, viene eseguito il costruttore chiamato.
- Viene eseguito il corpo del costruttore.
Il costruttore è legato all'inizializzazione perché in Java ci sono tre modi per inizializzare un campo in una classe:
- assegnare un valore nella dichiarazione;
- assegnare valori nel blocco di inizializzazione;
- impostarne il valore nel costruttore.
Naturalmente è necessario organizzare il codice di inizializzazione in modo che sia di facile comprensione. A titolo di esempio viene fornita la seguente classe:
class Initialization
{
int i;
short z = 10;
static int x;
static float y;
static
{
x = 2000;
y = 3.141;
}
Initialization()
{
System.out.println("i="+i);
System.out.println("z="+z);
z = 20;
System.out.println("z="+z);
}
}
Nell'esempio precedente, le variabili vengono inizializzate nel seguente ordine: le variabili statiche vengono inizializzate per prime
x
con
y
valori predefiniti. Successivamente, viene eseguito il blocco di inizializzazione statico. Quindi la variabile viene inizializzata
i
al valore predefinito e la variabile viene inizializzata
z
. Successivamente, il designer si mette al lavoro. La chiamata ai costruttori di classi non dovrebbe dipendere dall'ordine in cui i campi vengono dichiarati. Ciò potrebbe causare errori.
Costruttori ed eredità
I costruttori non vengono ereditati. Per esempio:
public class Example
{
Example()
{
}
public void sayHi()
{
system.out.println("Hi");
}
}
public class SubClass extends Example
{
}
La classe
SubClass
eredita automaticamente il metodo
sayHi()
definito nella classe genitore. Allo stesso tempo, il costruttore
Example()
della classe genitore non viene ereditato dal suo discendente
SubClass
.
Parola chiave this
nei costruttori
I costruttori vengono utilizzati
this
per fare riferimento a un altro costruttore nella stessa classe, ma con un elenco di parametri diverso. Se il costruttore utilizza la parola chiave
this
, allora deve essere sulla prima riga; ignorare questa regola risulterà in un errore del compilatore. Ad esempio: File
ThisDemo.java
public class ThisDemo
{
String name;
ThisDemo(String s)
{
name = s;
System.out.println(name);
}
ThisDemo()
{
this("John");
}
public static void main(String args[])
{
ThisDemo td1 = new ThisDemo("Mary");
ThisDemo td2 = new ThisDemo();
}
}
L'output del programma è:
Mary
John
In questo esempio ci sono due costruttori. Il primo riceve un argomento di tipo stringa. Il secondo non riceve argomenti, chiama semplicemente il primo costruttore utilizzando il nome predefinito "John". Pertanto, è possibile utilizzare i costruttori per inizializzare i valori dei campi in modo esplicito e predefinito, cosa spesso necessaria nei programmi.
Parola chiave super
nei costruttori
I costruttori vengono utilizzati
super
per chiamare un costruttore di superclasse. Se il costruttore utilizza
super
, questa chiamata deve essere sulla prima riga, altrimenti il compilatore genererà un errore. Di seguito è riportato un esempio: File
SuperClassDemo.java
public class SuperClassDemo
{
SuperClassDemo()
{
}
}
class Child extends SuperClassDemo
{
Child()
{
super();
}
}
In questo semplice esempio, il costruttore
Child()
contiene una chiamata
super()
che crea un'istanza della classe
SuperClassDemo
, oltre alla classe
Child
. Poiché
super
deve essere la prima istruzione eseguita in un costruttore di sottoclassi, questo ordine è sempre lo stesso e non dipende dal fatto che
super()
. Se non viene utilizzato, verrà eseguito per primo il costruttore predefinito (senza parametri) di ciascuna superclasse, a partire dalla classe base. Il seguente programma mostra quando vengono eseguiti i costruttori. File
Call.java
class A
{
A()
{
System.out.println("Inside A constructor.");
}
}
class B extends A
{
B()
{
System.out.println("Inside B constructor.");
}
}
class C extends B
{
C()
{
System.out.println("Inside C constructor.");
}
}
class Call
{
public static void main(String args[])
{
C c = new C();
}
}
Output da questo programma:
Inside A constructor.
Inside B constructor.
Inside C constructor.
I costruttori vengono chiamati in ordine di subordinazione della classe. Questo ha un senso. Poiché la superclasse non conosce alcuna sottoclasse, qualsiasi inizializzazione che deve eseguire è separata. Se possibile, dovrebbe precedere qualsiasi inizializzazione eseguita dalla sottoclasse. Ecco perché dovrebbe essere fatto prima.
Costruttori personalizzabili
Il meccanismo di identificazione del tipo in fase di esecuzione è uno dei potenti principi fondamentali del linguaggio Java che implementa il polimorfismo. Tuttavia, in alcuni casi tale meccanismo non protegge lo sviluppatore dal cast di tipi incompatibili. Il caso più comune è la manipolazione di un gruppo di oggetti, le cui diverse tipologie non sono note in anticipo e vengono determinate in fase di esecuzione. Poiché gli errori associati all'incompatibilità di tipo possono comparire solo in fase di runtime, ciò li rende difficili da trovare ed eliminare. L'introduzione dei tipi personalizzati in Java 2 5.0 sposta alcuni di questi errori dal runtime al tempo di compilazione e fornisce parte dell'indipendenza dai tipi mancante. Non è necessario eseguire il cast esplicito del tipo quando si passa da un tipo
Object
a un tipo concreto. Va tenuto presente che gli strumenti di personalizzazione dei tipi funzionano solo con gli oggetti e non si applicano ai tipi di dati primitivi che si trovano al di fuori dell'albero di ereditarietà delle classi. Con i tipi personalizzati, tutti i cast vengono eseguiti automaticamente e dietro le quinte. Ciò consente di proteggersi dalle mancate corrispondenze di tipo e di riutilizzare il codice molto più spesso. I tipi personalizzati possono essere utilizzati nei costruttori. I costruttori possono essere personalizzati anche se la loro classe non è di tipo personalizzato. Per esempio:
class GenConstructor
{
private double val;
<T extends Number> GenConstructor(T arg)
{
val = arg.doubleValue();
}
void printValue()
{
System.out.println("val: "+val);
}
}
class GenConstructorDemo
{
public static void main(String args[])
{
GenConstructor gc1 = new GenConstructor(100);
GenConstructor gc2 = new GenConstructor(123.5F);
gc1.printValue();
gc2.printValue();
}
}
Poiché il costruttore
GenConstructor
specifica un parametro di tipo personalizzato che deve essere una classe derivata da class
Number
, può essere chiamato da any
GO TO FULL VERSION