Una guida alla creazione di oggetti in Java

1. Panoramica

In poche parole, prima di poter lavorare con un oggetto sulla JVM, è necessario inizializzarlo.

Nelle sezioni seguenti, daremo uno sguardo ai vari modi in cui possiamo inizializzare tipi e oggetti primitivi.

2. Dichiarazione vs. inizializzazione

Cominciamo assicurandoci di essere sulla stessa pagina.

La dichiarazione è il processo di definizione della variabile insieme al suo tipo e nome.

Qui stiamo dichiarando la variabile id :

int id;

L'inizializzazione, d'altra parte, riguarda l'assegnazione di un valore; per esempio:

id = 1;

Per dimostrare, creeremo una classe utente con un nome e proprietà id :

public class User { private String name; private int id; // standard constructor, getters, setters, }

Successivamente, vedremo che l'inizializzazione funziona in modo diverso a seconda del tipo di campo che stiamo inizializzando.

3. Oggetti contro primitive

Java fornisce due tipi di rappresentazione dei dati: tipi primitivi e tipi di riferimento. In questa sezione, discuteremo le differenze tra i due per quanto riguarda l'inizializzazione.

Java ha otto tipi di dati incorporati, denominati tipi primitivi Java; le variabili di questo tipo mantengono i propri valori direttamente.

I tipi di riferimento contengono riferimenti a oggetti (istanze di classi). A differenza dei tipi primitivi che mantengono i propri valori nella memoria in cui è allocata la variabile, i riferimenti non contengono il valore dell'oggetto a cui si riferiscono.

Invece, un riferimento punta a un oggetto memorizzando l'indirizzo di memoria in cui si trova l'oggetto.

Nota che Java non ci consente di scoprire qual è l'indirizzo della memoria fisica. Piuttosto, possiamo usare solo il riferimento per fare riferimento all'oggetto.

Diamo un'occhiata a un esempio che dichiara e inizializza un tipo di riferimento fuori dalla nostra classe User :

@Test public void whenIntializedWithNew_thenInstanceIsNotNull() { User user = new User(); assertThat(user).isNotNull(); }

Come possiamo vedere qui, un riferimento può essere assegnato a un nuovo utilizzando la parola chiave new, che è responsabile della creazione del nuovo oggetto User .

Continuiamo con l'apprendimento di più sulla creazione di oggetti.

5. Creazione di oggetti

A differenza delle primitive, la creazione di oggetti è un po 'più complessa. Questo perché non stiamo solo aggiungendo il valore al campo; invece, attiviamo l'inizializzazione utilizzando la nuova parola chiave. Questo, in cambio, richiama un costruttore e inizializza l'oggetto in memoria.

Discutiamo i costruttori e la nuova parola chiave in maggiore dettaglio.

La nuova parola chiave è responsabile dell'allocazione della memoria per il nuovo oggetto tramite un costruttore.

Un costruttore viene tipicamente utilizzato per inizializzare le variabili di istanza che rappresentano le proprietà principali dell'oggetto creato .

Se non forniamo un costruttore in modo esplicito, il compilatore creerà un costruttore predefinito che non ha argomenti e alloca solo memoria per l'oggetto.

Una classe può avere molti costruttori purché i loro elenchi di parametri siano diversi ( sovraccarico ) . Ogni costruttore che non chiama un altro costruttore nella stessa classe ha una chiamata al suo costruttore genitore sia che sia stato scritto esplicitamente o inserito dal compilatore tramite super () .

Aggiungiamo un costruttore alla nostra classe User :

public User(String name, int id) { this.name = name; this.id = id; }

Ora possiamo usare il nostro costruttore per creare un oggetto Utente con valori iniziali per le sue proprietà:

User user = new User("Alice", 1);

6. Ambito variabile

Nelle sezioni seguenti, daremo uno sguardo ai diversi tipi di ambiti in cui può esistere una variabile in Java e al modo in cui ciò influisce sul processo di inizializzazione.

6.1. Istanza e variabili di classe

Le variabili di istanza e di classe non richiedono di inizializzarle. Non appena dichiariamo queste variabili, viene assegnato loro un valore predefinito come segue:

Ora, proviamo a definire alcune istanze e variabili relative alla classe e testiamo se hanno un valore predefinito o meno:

@Test public void whenValuesAreNotInitialized_thenUserNameAndIdReturnDefault() { User user = new User(); assertThat(user.getName()).isNull(); assertThat(user.getId() == 0); }

6.2. Variabili locali

Le variabili locali devono essere inizializzate prima dell'uso , poiché non hanno un valore predefinito e il compilatore non ci consente di utilizzare un valore non inizializzato.

Ad esempio, il codice seguente genera un errore del compilatore:

public void print(){ int i; System.out.println(i); }

7. La parola chiave finale

L' ultima parola chiave applicata a un campo significa che il valore del campo non può più essere modificato dopo l'inizializzazione. In questo modo, possiamo definire costanti in Java.

Let's add a constant to our User class:

private static final int YEAR = 2000;

Constants must be initialized either when they're declared or in a constructor.

8. Initializers in Java

In Java, an initializer is a block of code that has no associated name or data type and is placed outside of any method, constructor, or another block of code.

Java offers two types of initializers, static and instance initializers. Let's see how we can use each of them.

8.1. Instance Initializers

We can use these to initialize instance variables.

To demonstrate, let’s provide a value for a user id using an instance initializer in our User class:

{ id = 0; }

8.2. Static Initialization Block

A static initializer or static block – is a block of code which is used to initialize static fields. In other words, it’s a simple initializer marked with the keyword static:

private static String forum; static { forum = "Java"; }

9. Order of Initialization

When writing code that initializes different types of fields, of course, we have to keep an eye on the order of initialization.

In Java, the order for initialization statements is as follows:

  • static variables and static initializers in order
  • instance variables and instance initializers in order
  • constructors

10. Object Life Cycle

Now that we've learned how to declare and initialize objects, let's discover what happens to objects when they're not in use.

Unlike other languages where we have to worry about object destruction, Java takes care of obsolete objects through its garbage collector.

All objects in Java are stored in our program's heap memory. In fact, the heap represents a large pool of unused memory, allocated for our Java application.

On the other hand, the garbage collector is a Java program that takes care of automatic memory management by deleting objects that are no longer reachable.

For a Java object to become unreachable, it has to encounter one of the following situations:

  • The object no longer has any references pointing to it
  • All reference pointing to the object are out of scope

In conclusion, an object is first created from a class, usually using the keyword new. Then the object lives its life and provides us with access to its methods and fields.

Finally, when it's no longer needed, the garbage collector destroys it.

11. Other Methods for Creating Objects

In this section, we’ll take a brief look at methods other than new keyword to create objects and how to apply them, specifically reflection, cloning, and serialization.

Reflection is a mechanism we can use to inspect classes, fields, and methods at run-time. Here's an example of creating our User object using reflection:

@Test public void whenInitializedWithReflection_thenInstanceIsNotNull() throws Exception { User user = User.class.getConstructor(String.class, int.class) .newInstance("Alice", 2); assertThat(user).isNotNull(); }

In this case, we're using reflection to find and invoke a constructor of the User class.

The next method, cloning, is a way to create an exact copy of an object. For this, our User class must implement the Cloneable interface:

public class User implements Cloneable { //... }

Ora possiamo usare il metodo clone () per creare un nuovo oggetto clonedUser che ha gli stessi valori di proprietà dell'oggetto utente :

@Test public void whenCopiedWithClone_thenExactMatchIsCreated() throws CloneNotSupportedException { User user = new User("Alice", 3); User clonedUser = (User) user.clone(); assertThat(clonedUser).isEqualTo(user); }

Possiamo anche usare la classe sun.misc.Unsafe per allocare memoria per un oggetto senza chiamare un costruttore:

User u = (User) unsafeInstance.allocateInstance(User.class);

12. Conclusione

In questo tutorial, abbiamo coperto l'inizializzazione dei campi in Java. Abbiamo scoperto diversi tipi di dati in Java e come usarli. Abbiamo anche approfondito diversi modi per creare oggetti in Java.

L'implementazione completa di questo tutorial può essere trovata su Github.