Pass-By-Value come meccanismo di passaggio dei parametri in Java

1. Introduzione

Le due modalità più diffuse di passare argomenti ai metodi sono "passaggio per valore" e "passaggio per riferimento". Diversi linguaggi di programmazione utilizzano questi concetti in modi diversi. Per quanto riguarda Java, tutto è rigorosamente Pass-by-Value .

In questo tutorial, illustreremo come Java passa argomenti per vari tipi.

2. Pass-by-Value vs Pass-by-Reference

Cominciamo con alcuni dei diversi meccanismi per passare i parametri alle funzioni:

  • valore
  • riferimento
  • risultato
  • valore-risultato
  • nome

I due meccanismi più comuni nei linguaggi di programmazione moderni sono "Pass-by-Value" e "Pass-by-Reference". Prima di procedere, discutiamo prima di questi:

2.1. Pass-by-Value

Quando un parametro è un valore di passaggio, il chiamante e il metodo chiamato operano su due variabili diverse che sono copie l'una dell'altra. Qualsiasi modifica a una variabile non modifica l'altra.

Significa che durante la chiamata di un metodo, i parametri passati al metodo chiamato saranno cloni dei parametri originali. Qualsiasi modifica eseguita nel metodo chiamato non avrà effetto sui parametri originali nel metodo chiamante.

2.2. Pass-by-Reference

Quando un parametro è pass-by-reference, il chiamante e il chiamato operano sullo stesso oggetto.

Significa che quando una variabile è pass-by-reference, l'identificatore univoco dell'oggetto viene inviato al metodo. Qualsiasi modifica ai membri dell'istanza del parametro comporterà la modifica al valore originale.

3. Passaggio di parametri in Java

I concetti fondamentali in qualsiasi linguaggio di programmazione sono "valori" e "riferimenti". In Java, le variabili primitive memorizzano i valori effettivi, mentre le non primitive memorizzano le variabili di riferimento che puntano agli indirizzi degli oggetti a cui si riferiscono. Sia i valori che i riferimenti vengono memorizzati nella memoria dello stack.

Gli argomenti in Java vengono sempre passati per valore. Durante il richiamo del metodo, una copia di ogni argomento, sia esso un valore o un riferimento, viene creata nella memoria dello stack che viene quindi passata al metodo.

In caso di primitive, il valore viene semplicemente copiato all'interno della memoria dello stack che viene poi passato al metodo chiamato; in caso di non primitive, un riferimento nella memoria dello stack punta ai dati effettivi che risiedono nell'heap. Quando passiamo un oggetto, il riferimento nella memoria dello stack viene copiato e il nuovo riferimento viene passato al metodo.

Vediamo ora questo in azione con l'aiuto di alcuni esempi di codice.

3.1. Passaggio di tipi primitivi

Il linguaggio di programmazione Java dispone di otto tipi di dati primitivi. Le variabili primitive vengono archiviate direttamente nella memoria dello stack. Ogni volta che una variabile del tipo di dati primitivo viene passata come argomento, i parametri effettivi vengono copiati in argomenti formali e questi argomenti formali accumulano il proprio spazio nella memoria dello stack.

La durata di questi parametri formali dura solo finché il metodo è in esecuzione e, una volta restituiti, questi argomenti formali vengono eliminati dalla pila e scartati.

Proviamo a capirlo con l'aiuto di un esempio di codice:

public class PrimitivesUnitTest { @Test public void whenModifyingPrimitives_thenOriginalValuesNotModified() { int x = 1; int y = 2; // Before Modification assertEquals(x, 1); assertEquals(y, 2); modify(x, y); // After Modification assertEquals(x, 1); assertEquals(y, 2); } public static void modify(int x1, int y1) { x1 = 5; y1 = 10; } } 

Proviamo a capire le asserzioni nel programma sopra analizzando come questi valori vengono memorizzati in memoria:

  1. Le variabili " x" e " y" nel metodo principale sono tipi primitivi e i loro valori sono archiviati direttamente nella memoria dello stack
  2. Quando chiamiamo il metodo modify () , viene creata una copia esatta per ciascuna di queste variabili e memorizzata in una posizione diversa nella memoria dello stack
  3. Qualsiasi modifica a queste copie interessa solo loro e lascia inalterate le variabili originali

3.2. Passaggio di riferimenti a oggetti

In Java, tutti gli oggetti vengono archiviati dinamicamente nello spazio Heap sotto il cofano. Questi oggetti sono riferiti da riferimenti chiamati variabili di riferimento.

Un oggetto Java, a differenza di Primitives, viene memorizzato in due fasi. Le variabili di riferimento vengono archiviate nella memoria dello stack e l'oggetto a cui si riferiscono viene archiviato in una memoria Heap.

Ogni volta che un oggetto viene passato come argomento, viene creata una copia esatta della variabile di riferimento che punta alla stessa posizione dell'oggetto nella memoria heap della variabile di riferimento originale.

Di conseguenza, ogni volta che apportiamo una modifica allo stesso oggetto nel metodo, tale modifica si riflette nell'oggetto originale. Tuttavia, se allochiamo un nuovo oggetto alla variabile di riferimento passata, non si rifletterà nell'oggetto originale.

Proviamo a comprenderlo con l'aiuto di un esempio di codice:

public class NonPrimitivesUnitTest { @Test public void whenModifyingObjects_thenOriginalObjectChanged() { Foo a = new Foo(1); Foo b = new Foo(1); // Before Modification assertEquals(a.num, 1); assertEquals(b.num, 1); modify(a, b); // After Modification assertEquals(a.num, 2); assertEquals(b.num, 1); } public static void modify(Foo a1, Foo b1) { a1.num++; b1 = new Foo(1); b1.num++; } } class Foo { public int num; public Foo(int num) { this.num = num; } }

Analizziamo le affermazioni nel programma sopra. Abbiamo passato gli oggetti a e b nel metodo modify () che ha lo stesso valore 1 . Inizialmente, questi riferimenti a oggetti puntano a due posizioni di oggetti distinte in uno spazio di heap:

Quando questi riferimenti a e b sono passati nel Modifica () metodo, crea copie speculari di questi riferimenti A1 e B1 che indicano gli stessi vecchi oggetti:

Nel metodo modify () , quando modifichiamo il riferimento a1 , cambia l'oggetto originale. Tuttavia, per un riferimento b1, abbiamo assegnato un nuovo oggetto. Quindi ora punta a un nuovo oggetto nella memoria heap.

Qualsiasi modifica apportata a b1 non rifletterà nulla nell'oggetto originale:

4. Conclusione

In questo articolo, abbiamo esaminato come viene gestito il passaggio dei parametri in caso di primitivi e non primitivi.

Abbiamo appreso che il passaggio dei parametri in Java è sempre Pass-by-Value. Tuttavia, il contesto cambia a seconda che si tratti di Primitive o Oggetti:

  1. Per i tipi primitivi, i parametri sono valori di passaggio
  2. Per i tipi di oggetto, il riferimento all'oggetto è il valore di passaggio

Gli snippet di codice utilizzati in questo articolo sono disponibili su GitHub.