JavaRush /Java Blog /Random-IT /Le 50 migliori domande e risposte per l'intervista su Jav...
Roman Beekeeper
Livello 35

Le 50 migliori domande e risposte per l'intervista su Java Core. Parte 1

Pubblicato nel gruppo Random-IT
Ciao a tutti, signore e signori ingegneri del software! Parliamo delle domande dell'intervista. Informazioni su cosa devi prepararti e cosa devi sapere. Questo è un ottimo motivo per ripetere o studiare questi punti da zero. Le 50 migliori domande e risposte per l'intervista su Java Core.  Parte 1 - 1Ho una raccolta abbastanza ampia di domande frequenti su OOP, sintassi Java, eccezioni in Java, raccolte e multithreading, che per comodità dividerò in più parti. Importante:parleremo solo delle versioni Java fino alla 8. Tutte le innovazioni dalla 9, 10, 11, 12, 13 non verranno prese in considerazione qui. Eventuali idee/commenti su come migliorare le risposte sono benvenuti . Buona lettura, andiamo!

Intervista Java: domande OOP

1. Quali funzionalità ha Java?

Risposta:
  1. Concetti OOP:

    1. orientamento agli oggetti;
    2. eredità;
    3. incapsulamento;
    4. polimorfismo;
    5. astrazione.
  2. Multipiattaforma: un programma Java può essere eseguito su qualsiasi piattaforma senza alcuna modifica. L'unica cosa di cui hai bisogno è una JVM (Java Virtual Machine) installata.

  3. Prestazioni elevate: JIT (compilatore Just In Time) consente prestazioni elevate. JIT converte il bytecode in codice macchina e quindi la JVM inizia l'esecuzione.

  4. Multithreading: un thread di esecuzione noto come Thread. La JVM crea un thread chiamato main thread. Un programmatore può creare più thread ereditando dalla classe Thread o implementando un'interfaccia Runnable.

2. Cos'è l'eredità?

Ereditarietà significa che una classe può ereditare (" estendere ") un'altra classe. In questo modo puoi riutilizzare il codice della classe da cui erediti. La classe esistente è conosciuta come superclass, mentre quella in fase di creazione è conosciuta come subclass. Dicono anche parente child.
public class Animal {
   private int age;
}

public class Dog extends Animal {

}
dov'è Animale - . parent_Dogchild

3. Cos'è l'incapsulamento?

Questa domanda emerge spesso durante le interviste agli sviluppatori Java. L'incapsulamento nasconde l'implementazione utilizzando modificatori di accesso, utilizzando getter e setter. Questo viene fatto al fine di chiudere l'accesso per uso esterno nei luoghi in cui gli sviluppatori lo ritengono necessario. Un esempio accessibile dalla vita è un'auto. Non abbiamo accesso diretto al funzionamento del motore. Per noi il compito è inserire la chiave nel quadro e accendere il motore. E quali processi avranno luogo dietro le quinte non sono affari nostri. Inoltre, la nostra interferenza in questa attività può portare a una situazione imprevedibile, a causa della quale possiamo rompere l'auto e farci del male. Nella programmazione accade esattamente la stessa cosa. Ben descritto su Wikipedia . C'è anche un articolo sull'incapsulamento su JavaRush .

4. Cos'è il polimorfismo?

Il polimorfismo è la capacità di un programma di utilizzare in modo identico oggetti con la stessa interfaccia senza informazioni sul tipo specifico di quell'oggetto. Come si suol dire, un'interfaccia: molte implementazioni. Con il polimorfismo puoi combinare e utilizzare diversi tipi di oggetti in base al loro comportamento comune. Ad esempio, abbiamo una classe Animale, che ha due discendenti: Cane e Gatto. La classe generica Animal ha un comportamento comune a tutti: emette un suono. Nel caso in cui dobbiamo mettere insieme tutti i discendenti della classe Animale ed eseguire il metodo "fai un suono", utilizziamo le possibilità del polimorfismo. Ecco come apparirà:
List<Animal> animals = Arrays.asList(new Cat(), new Dog(), new Cat());
animals.forEach(animal -> animal.makeSound());
Quindi il polimorfismo ci aiuta. Inoltre, questo vale anche per i metodi polimorfici (sovraccaricati). Esercitazioni sull'uso del polimorfismo

Domande dell'intervista - Sintassi Java

5. Cos'è un costruttore in Java?

Valgono le seguenti caratteristiche:
  1. Quando viene creato un nuovo oggetto, il programma utilizza il costruttore appropriato per farlo.
  2. Un costruttore è come un metodo. La sua particolarità è che non esiste alcun elemento di ritorno (incluso void) e il suo nome è lo stesso del nome della classe.
  3. Se nessun costruttore viene scritto esplicitamente, verrà creato automaticamente un costruttore vuoto.
  4. Il costruttore può essere sovrascritto.
  5. Se è stato creato un costruttore con parametri, ma ne serve anche uno senza parametri, è necessario scriverlo separatamente, poiché non viene creato automaticamente.

6. Quali due classi non ereditano da Object?

Non fatevi ingannare dalle provocazioni, non esistono classi del genere: tutte le classi direttamente o tramite antenati sono ereditate dalla classe Object!

7. Cos'è la variabile locale?

Un'altra domanda popolare durante un'intervista allo sviluppatore Java. Una variabile locale è una variabile definita all'interno di un metodo ed esiste fino al momento in cui il metodo viene eseguito. Una volta terminata l'esecuzione, la variabile locale cesserà di esistere. Ecco un programma che utilizza la variabile locale helloMessage nel metodo main():
public static void main(String[] args) {
   String helloMessage;
   helloMessage = "Hello, World!";
   System.out.println(helloMessage);
}

8. Cos'è la variabile di istanza?

La variabile di istanza è una variabile definita all'interno di una classe ed esiste fino al momento in cui esiste l'oggetto. Un esempio è la classe Bee, che ha due variabili nectarCapacity e maxNectarCapacity:
public class Bee {

   /**
    * Current nectar capacity
    */
   private double nectarCapacity;

   /**
    * Maximal nectar that can take bee.
    */
   private double maxNectarCapacity = 20.0;

  ...
}

9. Cosa sono i modificatori di accesso?

I modificatori di accesso sono uno strumento che consente di personalizzare l'accesso a classi, metodi e variabili. Sono disponibili i seguenti modificatori, ordinati in ordine crescente di accesso:
  1. private- utilizzato per metodi, campi e costruttori. Il livello di accesso è solo la classe all'interno della quale è dichiarato.
  2. package-private(default)- può essere utilizzato per le lezioni. Accesso solo in un pacchetto specifico in cui è dichiarata una classe, un metodo, una variabile, un costruttore.
  3. protected— lo stesso accesso di package-private+ per quelle classi che ereditano da una classe con il modificatore protected.
  4. public- utilizzato anche per le lezioni. Accesso completo all'intera applicazione.
  5. Le 50 migliori domande e risposte per l'intervista su Java Core.  Parte 1 - 2

10. Che cosa sono i metodi prioritari?

L'override del metodo avviene quando il bambino vuole cambiare il comportamento della classe genitore. Se vuoi che ciò che è nel metodo genitore venga eseguito, puoi usare una costruzione come super.methodName() nel figlio, che farà il lavoro del metodo genitore e solo allora aggiungerà la logica. Requisiti da soddisfare:
  • la firma del metodo deve essere la stessa;
  • il valore restituito dovrebbe essere lo stesso.

11. Cos'è una firma del metodo?

Le 50 migliori domande e risposte per l'intervista su Java Core.  Parte 1 - 3Una firma del metodo è un insieme del nome del metodo e degli argomenti che il metodo accetta. Una firma del metodo è un identificatore univoco per un metodo durante l'overload dei metodi.

12. Cos'è l'overload del metodo?

L'overload dei metodi è una proprietà del polimorfismo in cui modificando la firma del metodo è possibile creare metodi diversi per le stesse azioni:
  • stesso nome del metodo;
  • argomenti diversi;
  • potrebbe esserci un tipo di reso diverso.
Ad esempio, lo stesso add()of ArrayListpuò essere sovraccaricato come segue ed eseguirà l'addizione in modo diverso, a seconda degli argomenti in entrata:
  • add(Object o)- aggiunge semplicemente un oggetto;
  • add(int index, Object o)— aggiunge un oggetto a un indice specifico;
  • add(Collection<Object> c)— aggiunge un elenco di oggetti;
  • add(int index, Collection<Object> c)— aggiunge un elenco di oggetti, a partire da un determinato indice.

13. Cos'è l'interfaccia?

L'ereditarietà multipla non è implementata in Java, quindi per superare questo problema sono state aggiunte le interfacce come le conosciamo ;) Per molto tempo, le interfacce avevano solo metodi senza implementarli. Come parte di questa risposta, ne parleremo. Per esempio:

public interface Animal {
   void makeSound();
   void eat();
   void sleep();
}
Da ciò derivano alcune sfumature:
  • tutti i metodi nell'interfaccia sono pubblici e astratti;
  • tutte le variabili sono pubbliche statiche finali;
  • le classi non li ereditano (si estendono), ma li implementano (implementano). Inoltre, puoi implementare quante interfacce desideri.
  • le classi che implementano un'interfaccia devono fornire implementazioni di tutti i metodi di cui dispone l'interfaccia.
Come questo:
public class Cat implements Animal {
   public void makeSound() {
       // method implementation
   }

   public void eat() {
       // implementation
   }

   public void sleep() {
       // implementation
   }
}

14. Qual è il metodo predefinito nell'interfaccia?

Ora parliamo dei metodi predefiniti. Per cosa, per chi? Questi metodi sono stati aggiunti per rendere tutto “sia tuo che nostro”. Di cosa sto parlando? Sì, da un lato, era necessario aggiungere nuove funzionalità: lambda, Stream API, dall'altro era necessario lasciare ciò per cui Java è famoso: la compatibilità con le versioni precedenti. Per fare ciò, è stato necessario introdurre soluzioni già pronte nelle interfacce. È così che ci sono arrivati ​​i metodi predefiniti. Cioè, il metodo predefinito è un metodo implementato nell'interfaccia che ha la parola chiave default. Ad esempio, il noto metodo stream()nel file Collection. Dai un'occhiata, questa interfaccia non è così semplice come sembra ;). O anche un metodo altrettanto noto forEach()del Iterable. Inoltre non esisteva finché non furono aggiunti i metodi predefiniti. A proposito, puoi leggere questo argomento anche su JavaRush .

15. Come quindi ereditare due metodi predefiniti identici?

In base alla risposta precedente su quale sia il metodo predefinito, puoi porre un'altra domanda. Se puoi implementare metodi nelle interfacce, in teoria puoi implementare due interfacce con lo stesso metodo e come farlo? Esistono due diverse interfacce con lo stesso metodo:
interface A {
   default void foo() {
       System.out.println("Foo A");
   }
}

interface B {
   default void foo() {
       System.out.println("Foo B");
   }
}
Ed esiste una classe che implementa queste due interfacce. Per evitare incertezze e compilare il codice, dobbiamo sovrascrivere il metodo foo()nella classe Ce possiamo semplicemente chiamare un metodo foo()di una qualsiasi delle interfacce in essa contenute - Aoppure B. Ma come scegliere un metodo di interfaccia specifico Аo В? C'è una struttura come questa per questo A.super.foo():
public class C implements A, B {
   @Override
   public void foo() {
       A.super.foo();
   }
}
O:
public class C implements A, B {
   @Override
   public void foo() {
       B.super.foo();
   }
}
Pertanto, un metodo foo()di classe Cutilizzerà il metodo predefinito foo()dell'interfaccia Ao un metodo foo()dell'interfaccia B.

16. Cosa sono i metodi e le classi astratti?

Java ha una parola riservata abstractche viene utilizzata per denotare classi e metodi astratti. Innanzitutto alcune definizioni. Un metodo astratto è un metodo creato senza un'implementazione con una parola chiave abstractin una classe astratta. Cioè, questo è un metodo come nell'interfaccia, solo con l'aggiunta di una parola chiave, ad esempio:
public abstract void foo();
Una classe astratta è una classe che contiene anche abstractla parola:
public abstract class A {

}
Una classe astratta ha diverse funzionalità:
  • non è possibile creare un oggetto sulla base di esso;
  • può avere metodi astratti;
  • potrebbe non avere metodi astratti.
Le classi astratte sono necessarie per generalizzare una sorta di astrazione (scusate la tautologia), che non esiste nella vita reale, ma contiene molti comportamenti e stati comuni (cioè metodi e variabili). Ci sono più che sufficienti esempi dalla vita. Tutto è intorno a noi. Potrebbe essere “animale”, “macchina”, “figura geometrica” e così via.

17. Qual è la differenza tra String, String Builder e String Buffer?

I valori Stringvengono archiviati in un pool di stringhe costanti. Una volta creata una riga, verrà visualizzata in questo pool. E non sarà possibile cancellarlo. Per esempio:
String name = "book";
...la variabile farà riferimento al pool di stringhe Pool di stringhe costante Le 50 migliori domande e risposte per l'intervista su Java Core.  Parte 1 - 4 Se imposti il ​​nome della variabile su un valore diverso, otterrai quanto segue:
name = "pen";
Pool di stringhe costanti Le 50 migliori domande e risposte per l'intervista su Java Core.  Parte 1 - 5Quindi questi due valori rimarranno lì. Buffer di stringhe:
  • i valori Stringvengono memorizzati nello stack. Se il valore viene modificato, il nuovo valore verrà sostituito con quello vecchio;
  • String Buffersincronizzato e quindi thread-safe;
  • A causa della sicurezza del filo, la velocità di funzionamento lascia molto a desiderare.
Esempio:
StringBuffer name = "book";
Le 50 migliori domande e risposte per l'intervista su Java Core.  Parte 1 - 6Non appena il valore di name cambia, cambia il valore sullo stack: Le 50 migliori domande e risposte per l'intervista su Java Core.  Parte 1 - 7StringBuilder Esattamente come StringBuffer, solo che non è thread-safe. Pertanto, la sua velocità è chiaramente superiore a quella del StringBuffer.

18. Qual è la differenza tra una classe astratta e un'interfaccia?

Classe astratta:
  • le classi astratte hanno un costruttore predefinito; viene chiamato ogni volta che viene creato un figlio di questa classe astratta;
  • contiene sia metodi astratti che non astratti. Nel complesso, potrebbe non contenere metodi astratti, ma essere comunque una classe astratta;
  • una classe che eredita da una classe astratta deve implementare solo metodi astratti;
  • una classe astratta può contenere una variabile di istanza (vedi domanda n. 5).
Interfaccia:
  • non ha costruttore e non può essere inizializzato;
  • dovrebbero essere aggiunti solo metodi astratti (senza contare i metodi predefiniti);
  • le classi che implementano un'interfaccia devono implementare tutti i metodi (senza contare i metodi predefiniti);
  • le interfacce possono contenere solo costanti.

19. Perché l'accesso a un elemento in un array richiede O(1)?

Questa domanda proviene letteralmente dall'ultima intervista. Come ho appreso in seguito, questa domanda viene posta per vedere come pensa una persona. È chiaro che questa conoscenza ha poco significato pratico: è sufficiente conoscere questo fatto. Innanzitutto dobbiamo chiarire che O(1) è una designazione della complessità temporale di un algoritmo quando l'operazione avviene in tempo costante. Cioè, questa designazione è l'esecuzione più veloce. Per rispondere a questa domanda, dobbiamo capire cosa sappiamo sugli array? Per creare un array intdobbiamo scrivere quanto segue:
int[] intArray = new int[100];
Da questa registrazione si possono trarre diverse conclusioni:
  1. Quando si crea un array, il suo tipo è noto: se il tipo è noto, allora è chiaro quale sarà la dimensione di ciascuna cella dell'array.
  2. È noto quale sarà la dimensione dell'array.
Da ciò ne consegue: per capire in quale cella scrivere è sufficiente calcolare in quale area di memoria scrivere. Per un'auto non potrebbe essere più semplice. La macchina ha un inizio di memoria allocata, un numero di elementi e una dimensione di cella singola. Da ciò è chiaro che lo spazio di registrazione sarà uguale alla posizione iniziale dell'array + la dimensione della cella moltiplicata per la sua dimensione.

Come si ottiene O (1) nell'accesso agli oggetti in un ArrayList?

Questa domanda segue immediatamente la precedente. È vero che quando lavoriamo con un array e ci sono delle primitive, sappiamo in anticipo quale sarà la dimensione di questo tipo quando verrà creato. Ma cosa succede se esiste uno schema come quello nell'immagine: Le 50 migliori domande e risposte per l'intervista su Java Core.  Parte 1 - 8e vogliamo creare una raccolta con elementi di tipo A e aggiungere diverse implementazioni - B, C, D:
List<A> list = new ArrayList();
list.add(new B());
list.add(new C());
list.add(new D());
list.add(new B());
In questa situazione, come puoi capire quale sarà la dimensione di ciascuna cella, perché ogni oggetto sarà diverso e potrebbe avere campi aggiuntivi diversi (o essere completamente diversi). Cosa fare? Qui la domanda è posta in modo tale da confondere e confondere. Sappiamo che infatti la collezione non memorizza oggetti, ma si collega solo a questi oggetti. E tutti i collegamenti hanno la stessa dimensione, ed è noto. Quindi contare lo spazio qui funziona allo stesso modo della domanda precedente.

21. Autoboxing e unboxing

Contesto storico: l'autoboxing e l'autounboxing sono una delle principali innovazioni di JDK 5. L'autoboxing è il processo di conversione automatica da un tipo primitivo alla classe wrapper appropriata. Auto-unboxing : fa esattamente l'opposto dell'auto-boxing: converte una classe wrapper in una primitiva. Ma se è presente un valore wrapper null, verrà lanciata un'eccezione durante l'estrazione NullPointerException.

Primitiva corrispondente: wrapper

Primitivo Classe involucro
booleano Booleano
int Numero intero
byte Byte
car Carattere
galleggiante Galleggiante
lungo Lungo
corto Corto
Doppio Doppio

L'imballaggio automatico avviene:

  • quando si assegna a una primitiva un riferimento alla classe wrapper:

    PRIMA di Java 5:

    //manual packaging or how it was BEFORE Java 5.
    public void boxingBeforeJava5() {
       Boolean booleanBox = new Boolean(true);
       Integer intBox = new Integer(3);
       // and so on to other types
    }
    
    после Java 5:
    //automatic packaging or how it became in Java 5.
    public void boxingJava5() {
       Boolean booleanBox = true;
       Integer intBox = 3;
       // and so on to other types
    }
  • quando si passa una primitiva come argomento a un metodo che prevede un wrapper:

    public void exampleOfAutoboxing() {
       long age = 3;
       setAge(age);
    }
    
    public void setAge(Long age) {
       this.age = age;
    }

Si verifica il disimballaggio automatico:

  • quando assegniamo una variabile primitiva alla classe wrapper:

    //before Java 5:
    int intValue = new Integer(4).intValue();
    double doubleValue = new Double(2.3).doubleValue();
    char c = new Character((char) 3).charValue();
    boolean b = Boolean.TRUE.booleanValue();
    
    //and after JDK 5:
    int intValue = new Integer(4);
    double doubleValue = new Double(2.3);
    char c = new Character((char) 3);
    boolean b = Boolean.TRUE;
  • Nei casi con operazioni aritmetiche. Si applicano solo ai tipi primitivi; per questo è necessario eseguire l'unboxing sui tipi primitivi.

    // Before Java 5
    Integer integerBox1 = new Integer(1);
    Integer integerBox2 = new Integer(2);
    
    // for comparison it was necessary to do this:
    integerBox1.intValue() > integerBox2.intValue()
    
    //в Java 5
    integerBox1 > integerBox2
  • quando passato a un wrapper in un metodo che accetta la primitiva corrispondente:

    public void exampleOfAutoboxing() {
       Long age = new Long(3);
       setAge(age);
    }
    
    public void setAge(long age) {
       this.age = age;
    }

22. Qual è la parola chiave finale e dove usarla?

La parola chiave finalpuò essere utilizzata per variabili, metodi e classi.
  1. Una variabile finale non può essere riassegnata a un altro oggetto.
  2. la classe finale è sterile)) non può avere eredi.
  3. il metodo finale non può essere sovrascritto su un antenato.
Abbiamo coperto la parte superiore, ora parliamone più in dettaglio.

variabili finali

;Java ci offre due modi per creare una variabile e assegnarle un valore:
  1. Puoi dichiarare una variabile e inizializzarla in seguito.
  2. Puoi dichiarare una variabile e assegnarla immediatamente.
Esempio utilizzando la variabile finale per questi casi:
public class FinalExample {

   //final static variable, which is immediately initialized:
   final static String FINAL_EXAMPLE_NAME = "I'm likely final one";

   //final is a variable that is not initialized, but will only work if
   //initialize this in the constructor:
   final long creationTime;

   public FinalExample() {
       this.creationTime = System.currentTimeMillis();
   }

   public static void main(String[] args) {
       FinalExample finalExample = new FinalExample();
       System.out.println(finalExample.creationTime);

       // final field FinalExample.FINAL_EXAMPLE_NAME cannot be assigned
//    FinalExample.FINAL_EXAMPLE_NAME = "Not you're not!";

       // final field Config.creationTime cannot be assigned
//    finalExample.creationTime = 1L;
   }
}

La variabile Finale può essere considerata una costante?

Poiché non saremo in grado di assegnare un nuovo valore a una variabile finale, sembra che si tratti di variabili costanti. Ma questo è solo a prima vista. Se il tipo di dati a cui fa riferimento la variabile è immutable, allora sì, è una costante. Ma se il tipo di dato mutableè mutabile, utilizzando metodi e variabili sarà possibile modificare il valore dell'oggetto a cui finalsi riferisce la variabile, che in questo caso non potrà essere definita costante. Quindi, l'esempio mostra che alcune delle variabili finali sono realmente costanti, ma altre non lo sono e possono essere modificate.
public class FinalExample {

   //immutable final variables:
   final static String FINAL_EXAMPLE_NAME = "I'm likely final one";
   final static Integer FINAL_EXAMPLE_COUNT  = 10;

   // mutable filter variables
   final List<String> addresses = new ArrayList();
   final StringBuilder finalStringBuilder = new StringBuilder("constant?");
}

Variabili finali locali

Quando finaluna variabile viene creata all'interno di un metodo, viene chiamata local finalvariabile:
public class FinalExample {

   public static void main(String[] args) {
       // This is how you can
       final int minAgeForDriveCar = 18;

       // or you can do it this way, in the foreach loop:
       for (final String arg : args) {
           System.out.println(arg);
       }
   }

}
Possiamo usare la parola chiave finalnel ciclo esteso forperché una volta completata l'iterazione del ciclo, forviene creata ogni volta una nuova variabile. Ma questo non si applica a un normale ciclo for, quindi il codice seguente genererà un errore in fase di compilazione.
// final local changed j cannot be assigned
for (final int i = 0; i < args.length; i ++) {
   System.out.println(args[i]);
}

Lezione finale

Non è possibile estendere una classe dichiarata come final. In poche parole, nessuna classe può ereditare da questa. Un ottimo esempio finaldi classe nel JDK è String. Il primo passo per creare una classe immutabile è contrassegnarla come final, in modo che non possa essere estesa:
public final class FinalExample {
}

// Compilation error here
class WantsToInheritFinalClass extends FinalExample {
}

Metodi finali

Quando un metodo è contrassegnato come finale, viene chiamato metodo finale (logico, giusto?). Il metodo Final non può essere sottoposto a override in una classe discendente. A proposito, i metodi nella classe Object - wait() e notify() - sono definitivi, quindi non abbiamo l'opportunità di sovrascriverli.
public class FinalExample {
   public final String generateAddress() {
       return "Some address";
   }
}

class ChildOfFinalExample extends FinalExample {

   // compile error here
   @Override
   public String generateAddress() {
       return "My OWN Address";
   }
}

Come e dove utilizzare Final in Java

  • utilizzare la parola chiave final per definire alcune costanti a livello di classe;
  • crea variabili finali per gli oggetti quando non vuoi che vengano modificati. Ad esempio, proprietà specifiche dell'oggetto che possiamo utilizzare per scopi di registrazione;
  • se non vuoi che la lezione venga estesa, contrassegnala come finale;
  • se devi creare una classe <immutabile, devi renderla finale;
  • se si desidera che l'implementazione di un metodo non cambi nei suoi discendenti, designare il metodo come final. Questo è molto importante per garantire che l’implementazione non cambi.

23. Cos'è mutevole immutabile?

Mutevole

Mutevoli sono oggetti i cui stati e variabili possono essere modificati dopo la creazione. Ad esempio, classi come StringBuilder, StringBuffer. Esempio:
public class MutableExample {

   private String address;

   public MutableExample(String address) {
       this.address = address;
   }

   public String getAddress() {
       return address;
   }

   // this setter can change the name field
   public void setAddress(String address) {
       this.address = address;
   }

   public static void main(String[] args) {

       MutableExample obj = new MutableExample("first address");
       System.out.println(obj.getAddress());

       // update the name field, so this is a mutable object
       obj.setAddress("Updated address");
       System.out.println(obj.getAddress());
   }
}

Immutabile

Immutabili sono oggetti i cui stati e variabili non possono essere modificati dopo la creazione dell'oggetto. Perché non un'ottima chiave per una HashMap, giusto?) Ad esempio String, Integer, Double e così via. Esempio:
// make this class final so no one can change it
public final class ImmutableExample {

   private String address;

   ImmutableExample (String address) {
       this.address = address;
   }

   public String getAddress() {
       return address;
   }

   //remove the setter

   public static void main(String[] args) {

       ImmutableExample obj = new ImmutableExample("old address");
       System.out.println(obj.getAddress());

       // Therefore, do not change this field in any way, so this is an immutable object
       // obj.setName("new address");
       // System.out.println(obj.getName());

   }
}

24. Come scrivere una classe immutabile?

Dopo aver capito cosa sono gli oggetti mutabili e immutabili, la domanda successiva è naturale: come scriverlo? Per scrivere una classe immutabile immutabile, è necessario seguire semplici passaggi:
  • rendere la lezione definitiva.
  • rendi privati ​​tutti i campi e crea solo getter per essi. I setter, ovviamente, non sono necessari.
  • Rendi definitivi tutti i campi modificabili in modo che il valore possa essere impostato solo una volta.
  • inizializzare tutti i campi tramite il costruttore, eseguendo una copia profonda (ovvero copiando l'oggetto stesso, le sue variabili, le variabili delle variabili e così via)
  • clonare oggetti variabili mutabili nei getter per restituire solo copie di valori e non riferimenti a oggetti reali.
Esempio:
/**
* An example of creating an immutable object.
*/
public final class FinalClassExample {

   private final int age;

   private final String name;

   private final HashMap<String, String> addresses;

   public int getAge() {
       return age;
   }


   public String getName() {
       return name;
   }

   /**
    * Clone the object before returning it.
    */
   public HashMap<String, String> getAddresses() {
       return (HashMap<String, String>) addresses.clone();
   }

   /**
    * In the constructor, deep copy the mutable objects.
    */
   public FinalClassExample(int age, String name, HashMap<String, String> addresses) {
       System.out.println("Performing a deep copy in the constructor");
       this.age = age;
       this.name = name;
       HashMap<String, String> temporaryMap = new HashMap<>();
       String key;
       Iterator<String> iterator = addresses.keySet().iterator();
       while (iterator.hasNext()) {
           key = iterator.next();
           temporaryMap.put(key, addresses.get(key));
       }
       this.addresses = temporaryMap;
   }
}
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION