JavaRush /Java Blog /Random-IT /Analisi di domande e risposte da interviste per sviluppat...

Analisi di domande e risposte da interviste per sviluppatori Java. Parte 7

Pubblicato nel gruppo Random-IT
Ciao a tutti! La programmazione è piena di insidie. E non esiste praticamente alcun argomento in cui non inciamperai e non otterrai colpi. Ciò è particolarmente vero per i principianti. L’unico modo per ridurlo è studiare. Ciò vale in particolare per l'analisi dettagliata degli argomenti più basilari. Oggi continuo ad analizzare oltre 250 domande dalle interviste agli sviluppatori Java, che coprono bene gli argomenti di base. Vorrei sottolineare che l'elenco contiene anche alcune domande non standard che ti consentono di guardare argomenti comuni da una prospettiva diversa.Analisi di domande e risposte da interviste per sviluppatori Java.  Parte 7 - 1

62. Cos'è uno string pool e perché è necessario?

Nella memoria in Java (Heap, di cui parleremo più avanti) c'è un'area - String pool o string pool. È progettato per memorizzare valori di stringa. In altre parole, quando crei una determinata stringa, ad esempio tramite virgolette doppie:
String str = "Hello world";
viene effettuato un controllo per verificare se lo string pool ha il valore indicato. In tal caso, alla variabile str viene assegnato un riferimento a quel valore nel pool. In caso contrario, verrà creato un nuovo valore nel pool e un riferimento ad esso verrà assegnato alla variabile str . Diamo un'occhiata ad un esempio:
String firstStr = "Hello world";
String secondStr = "Hello world";
System.out.println(firstStr == secondStr);
true verrà visualizzato sullo schermo . Ricordiamo che == confronta i riferimenti, ovvero questi due riferimenti si riferiscono allo stesso valore dal pool di stringhe. Questo viene fatto per non produrre in memoria molti oggetti identici del tipo String , perché come ricordiamo, String è una classe immutabile e se abbiamo molti riferimenti allo stesso valore, non c'è niente di sbagliato in questo. Non è più possibile una situazione in cui la modifica di un valore in un punto porta a modifiche per diversi altri collegamenti contemporaneamente. Tuttavia, se creiamo una stringa utilizzando new :
String str = new String("Hello world");
verrà creato in memoria un oggetto separato che memorizzerà questo valore di stringa (e non importa se abbiamo già tale valore nello string pool). A conferma:
String firstStr = new String("Hello world");
String secondStr = "Hello world";
String thirdStr = new String("Hello world");
System.out.println(firstStr == secondStr);
System.out.println(firstStr == thirdStr);
Otterremo due valori falsi , il che significa che qui abbiamo tre valori diversi a cui si fa riferimento. In realtà, questo è il motivo per cui è consigliabile creare stringhe utilizzando semplicemente le virgolette doppie. Tuttavia, è possibile aggiungere (o ottenere un riferimento a) valori in un pool di stringhe durante la creazione di un oggetto utilizzando new . Per fare ciò, utilizziamo il metodo della classe string - intern() . Questo metodo crea forzatamente un valore nel pool di stringhe o ottiene un collegamento ad esso se è già archiviato lì. Ecco un esempio:
String firstStr = new String("Hello world").intern();
String secondStr = "Hello world";
String thirdStr = new String("Hello world").intern();
System.out.println(firstStr == secondStr);
System.out.println(firstStr == thirdStr);
System.out.println(secondStr == thirdStr);
Di conseguenza, riceveremo nella console tre valori veri , il che significa che tutte e tre le variabili si riferiscono alla stessa stringa.Analisi di domande e risposte da interviste per sviluppatori Java.  Parte 7 - 2

63. Quali modelli GOF vengono utilizzati nello string pool?

Il modello GOF è chiaramente visibile nello string pool - flyweight , altrimenti chiamato colono. Se vedi un altro modello qui, condividilo nei commenti. Bene, parliamo del modello leggero. Lightweight è un modello di progettazione strutturale in cui un oggetto che si presenta come un'istanza unica in diversi punti del programma, in realtà, non lo è. Il peso leggero consente di risparmiare memoria condividendo lo stato condiviso degli oggetti tra loro, invece di archiviare gli stessi dati in ciascun oggetto. Per comprendere l'essenza, diamo un'occhiata all'esempio più semplice. Diciamo che abbiamo un'interfaccia dipendente:
public interface Employee {
   void work();
}
E ci sono alcune implementazioni, ad esempio, avvocato:
public class Lawyer implements Employee {

   public Lawyer() {
       System.out.println("Юрист взят в штат.");
   }

   @Override
   public void work() {
       System.out.println("Решение юридических вопросов...");
   }
}
E il ragioniere:
public class Accountant implements Employee{

   public Accountant() {
       System.out.println("Бухгалтер взят в штат.");
   }

   @Override
   public void work() {
       System.out.println("Ведение бухгалтерского отчёта....");
   }
}
Le modalità sono molto condizionate: basta vedere che vengano eseguite. La stessa situazione vale per il costruttore. Grazie all'output della console, vedremo quando verranno creati nuovi oggetti. Abbiamo anche un dipartimento dei dipendenti, il cui compito è rilasciare il dipendente richiesto e, se non è presente, assumerlo ed emettere in risposta alla richiesta:
public class StaffDepartment {
   private Map<String, Employee> currentEmployees = new HashMap<>();

   public Employee receiveEmployee(String type) throws Exception {
       Employee result;
       if (currentEmployees.containsKey(type)) {
           result = currentEmployees.get(type);
       } else {
           switch (type) {
               case "Бухгалтер":
                   result = new Accountant();
                   currentEmployees.put(type, result);
                   break;
               case "Юрист":
                   result = new Lawyer();
                   currentEmployees.put(type, result);
                   break;
               default:
                   throw new Exception("Данный сотрудник в штате не предусмотрен!");
           }
       }
       return result;
   }
}
Cioè, la logica è semplice: se esiste una determinata unità, restituirla; in caso contrario, crearla, metterla in un archivio (qualcosa come una cache) e restituirla. Ora vediamo come funziona il tutto:
public static void main(String[] args) throws Exception {
   StaffDepartment staffDepartment = new StaffDepartment();
   Employee empl1  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl2  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
   Employee empl3  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl4  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
   Employee empl5  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl6  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
   Employee empl7  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl8  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
   Employee empl9  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl10  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
}
E nella console, di conseguenza, ci sarà un output:
L'avvocato è stato assunto. Risoluzione di problemi legali... È stato assunto un contabile. Mantenere un rapporto contabile.... Risolvere problemi legali... Mantenere un rapporto contabile.... Risolvere problemi legali... Mantenere un rapporto contabile.... Risolvere problemi legali... Mantenere un rapporto contabile.... Risoluzione di problemi legali... Mantenimento dei rapporti contabili...
Come puoi vedere, sono stati creati solo due oggetti, che sono stati riutilizzati più volte. L'esempio è molto semplice, ma dimostra chiaramente come l'utilizzo di questo modello possa far risparmiare risorse. Ebbene, come avete notato, la logica di questo modello è molto simile alla logica del pool assicurativo. Puoi leggere ulteriori informazioni sui tipi di pattern GOF in questo articolo .Analisi di domande e risposte da interviste per sviluppatori Java.  Parte 7 - 3

64. Come dividere una stringa in parti? Si prega di fornire un esempio del codice corrispondente

Ovviamente, questa domanda riguarda il metodo di divisione . La classe String ha due varianti di questo metodo:
String split(String regex);
E
String split(String regex);
regex è un separatore di riga, un'espressione regolare che divide una stringa in un array di stringhe, ad esempio:
String str = "Hello, world it's Amigo!";
String[] arr = str.split("\\s");
for (String s : arr) {
  System.out.println(s);
}
Sulla console verrà visualizzato quanto segue:
Ciao mondo, sono Amigo!
Cioè, il nostro valore di stringa è stato diviso in un array di stringhe e il separatore era uno spazio (per la separazione, potremmo usare un'espressione regolare non spaziale "\\s" e solo un'espressione di stringa " " ). Il secondo metodo sovraccaricato ha un argomento aggiuntivo: limit . limite : il valore massimo consentito dell'array risultante. Cioè, quando la stringa è già stata divisa nel numero massimo consentito di sottostringhe, non ci sarà alcuna ulteriore suddivisione e l'ultimo elemento avrà un "resto" della stringa eventualmente sottodivisa. Esempio:
String str = "Hello, world it's Amigo!";
String[] arr = str.split(" ", 2);
for (String s : arr) {
  System.out.println(s);
}
Uscita console:
Ciao mondo, sono Amigo!
Come possiamo vedere, se non fosse per il vincolo limit = 2 , l'ultimo elemento dell'array potrebbe essere suddiviso in tre sottostringhe.Analisi di domande e risposte da interviste per sviluppatori Java.  Parte 7 - 4

65. Perché un array di caratteri è migliore di una stringa per memorizzare una password?

Esistono diversi motivi per preferire un array a una stringa quando si memorizza una password: 1. Pool di stringhe e immutabilità delle stringhe. Quando si utilizza un array ( char[] ), possiamo cancellare esplicitamente i dati dopo aver finito con esso. Inoltre, possiamo riscrivere l'array quanto vogliamo e la password valida non sarà da nessuna parte nel sistema, anche prima della garbage collection (è sufficiente modificare un paio di celle in valori non validi). Allo stesso tempo, String è una classe immutabile. Cioè, se vogliamo cambiarne il valore, ne otterremo uno nuovo, mentre quello vecchio rimarrà nello string pool. Se vogliamo rimuovere il valore String di una password, questo può essere un compito molto difficile, poiché abbiamo bisogno che il garbage collector rimuova il valore dallo String pool e c'è un'alta probabilità che questo valore String rimanga lì per un a lungo. Cioè, in questa situazione, String è inferiore all'array di caratteri in termini di sicurezza dell'archiviazione dei dati. 2. Se il valore String viene accidentalmente restituito alla console (o ai log), verrà visualizzato il valore stesso:
String password = "password";
System.out.println("Пароль - " + password);
Uscita console:
Parola d'ordine
Allo stesso tempo, se restituisci accidentalmente un array alla console:
char[] arr = new char[]{'p','a','s','s','w','o','r','d'};
System.out.println("Пароль - " + arr);
Ci sarà un gobbledygook incomprensibile nella console:
Password: [C@7f31245a
In realtà non è gobbledygook, ma: [C è il nome della classe è un array di caratteri , @ è un separatore, seguito da 7f31245a è un hashcode esadecimale. 3. Il documento ufficiale, la Java Cryptography Architecture Guide, menziona esplicitamente la memorizzazione delle password in char[] invece che in String : “Sembrerebbe logico raccogliere e memorizzare una password in un oggetto di tipo java.lang.String . Tuttavia, c'è un avvertimento qui: gli oggetti String sono immutabili, cioè non ci sono metodi definiti per consentire la modifica (sovrascrittura) o l'annullamento del contenuto di un oggetto String dopo l'uso. Questa funzionalità rende gli oggetti String inadatti alla memorizzazione di informazioni riservate come le password degli utenti. Invece, dovresti sempre raccogliere e archiviare informazioni sensibili sulla sicurezza in un array di caratteri.Analisi di domande e risposte da interviste per sviluppatori Java.  Parte 7 - 5

Enum

66. Fornisci una breve descrizione di Enum in Java

Enum è un'enumerazione, un insieme di costanti stringa unite da un tipo comune. Dichiarato tramite la parola chiave -enum . Ecco un esempio con enum - ruoli validi in una determinata scuola:
public enum Role {
   STUDENT,
   TEACHER,
   DIRECTOR,
   SECURITY_GUARD
}
Le parole scritte in maiuscolo sono le stesse costanti di enumerazione che vengono dichiarate in modo semplificato, senza utilizzare l' operatore new . L'uso delle enumerazioni semplifica notevolmente la vita, poiché aiutano a evitare errori e confusione nella denominazione (poiché può esserci solo un determinato elenco di valori). Personalmente li trovo molto comodi se utilizzati nella progettazione logica di Switch .

67. Enum può implementare interfacce?

SÌ. Dopotutto, le enumerazioni devono rappresentare qualcosa di più che semplici raccolte passive (come i ruoli). In Java, possono rappresentare oggetti più complessi con alcune funzionalità, quindi potrebbe essere necessario aggiungere funzionalità aggiuntive. Ciò consentirà inoltre di utilizzare le funzionalità del polimorfismo sostituendo il valore enum nei punti in cui è richiesto il tipo di interfaccia implementata.

68. Enum può estendere una classe?

No, non può, perché un'enumerazione è una sottoclasse predefinita della classe generica Enum <T> , dove T rappresenta il tipo enum generico. Non è altro che una classe base comune per tutti i tipi enum del linguaggio Java. La conversione di enum in classe viene eseguita dal compilatore Java in fase di compilazione. Questa estensione non è esplicitamente indicata nel codice, ma è sempre presente in modo invisibile.

69. È possibile creare un Enum senza istanze di oggetti?

Per quanto mi riguarda la domanda è un po’ strana, oppure non l’ho capita fino in fondo. Ho due interpretazioni: 1. Può esserci un'enumerazione senza valori - sì, certo, sarebbe qualcosa come una classe vuota - priva di significato:
public enum Role {
}
E chiamando:
var s = Role.values();
System.out.println(s);
Riceveremo nella console:
[Lflyweight.Ruolo;@9f70c54
(array vuoto di valori Role ) 2. È possibile creare un'enumerazione senza l' operatore new - sì, ovviamente. Come ho detto sopra, non è necessario utilizzare l' operatore new per i valori enum (enumerazioni) , poiché si tratta di valori statici.

70. Possiamo sovrascrivere il metodo toString() per Enum?

Sì, ovviamente puoi sovrascrivere il metodo toString() per definire un modo specifico di visualizzare la tua enumerazione quando chiami il metodo toString (quando traduci l'enumerazione in una stringa regolare, ad esempio, per l'output sulla console o sui log).
public enum Role {
   STUDENT,
   TEACHER,
   DIRECTOR,
   SECURITY_GUARD;

   @Override
   public String toString() {
       return "Выбрана роль - " + super.toString();
   }
}
Per oggi è tutto, alla prossima parte!Analisi di domande e risposte da interviste per sviluppatori Java.  Parte 7 - 6
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION