JavaRush /Blog Java /Random-PL /Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla ...

Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java. Część 15

Opublikowano w grupie Random-PL
Cześć cześć! Ile programista Java musi wiedzieć? Możesz długo spierać się w tej kwestii, ale prawda jest taka, że ​​​​na rozmowie kwalifikacyjnej będziesz w pełni napędzany teorią. Nawet w tych obszarach wiedzy, których nie będziesz miał okazji wykorzystać w swojej pracy. Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 15 - 1Cóż, jeśli jesteś początkujący, Twoja wiedza teoretyczna będzie traktowana bardzo poważnie. Ponieważ nie ma jeszcze doświadczenia i wielkich osiągnięć, pozostaje tylko sprawdzić siłę bazy wiedzy. Dziś będziemy nadal wzmacniać tę właśnie bazę, badając najpopularniejsze pytania podczas rozmów kwalifikacyjnych dla programistów Java. Lećmy!

Rdzeń Javy

9. Jaka jest różnica pomiędzy wiązaniem statycznym i dynamicznym w Javie?

Odpowiedziałem już na to pytanie w tym artykule w pytaniu 18 na temat polimorfizmu statycznego i dynamicznego, radzę go przeczytać.

10. Czy w interfejsie można używać zmiennych prywatnych lub chronionych?

Nie, nie możesz. Ponieważ kiedy deklarujesz interfejs, kompilator Java automatycznie dodaje słowa kluczowe public i streszczenie przed metodami interfejsu oraz słowa kluczowe public , static i final przed elementami danych. Właściwie, jeśli dodasz private lubprotected , pojawi się konflikt, a kompilator będzie narzekał na modyfikator dostępu komunikatem: „Modyfikator '<wybrany modyfikator>' nie jest tu dozwolony.” Dlaczego kompilator dodaje public , static i final zmienne w interfejsie? Rozwiążmy to:
  • public - interfejs umożliwia klientowi interakcję z obiektem. Gdyby zmienne nie były publiczne, klienci nie mieliby do nich dostępu.
  • static - nie można tworzyć interfejsów (a raczej ich obiektów), więc zmienna jest statyczna.
  • final - ponieważ interfejs służy do uzyskania 100% abstrakcji, zmienna ma swoją ostateczną postać (i nie będzie zmieniana).

11. Co to jest Classloader i do czego służy?

Classloader - lub Class Loader - zapewnia ładowanie klas Java. Dokładniej, ładowanie zapewniają jego potomkowie - ładowarki specyficznej klasy, ponieważ Sam ClassLoader jest abstrakcyjny. Za każdym razem, gdy ładowany jest plik .class, np. po wywołaniu konstruktora lub metody statycznej odpowiedniej klasy, tę akcję wykonuje jeden z potomków klasy ClassLoader . Istnieją trzy typy spadkobierców:
  1. Bootstrap ClassLoader to podstawowy moduł ładujący, zaimplementowany na poziomie JVM i nie ma sprzężenia zwrotnego ze środowiskiem wykonawczym, ponieważ jest częścią jądra JVM i napisany w natywnym kodzie. Ten moduł ładujący służy jako element nadrzędny wszystkich innych instancji ClassLoader.

    Odpowiada głównie za ładowanie wewnętrznych klas JDK, zwykle rt.jar i innych podstawowych bibliotek znajdujących się w katalogu $JAVA_HOME/jre/lib . Różne platformy mogą mieć różne implementacje modułu ładującego tę klasę.

  2. Extension Classloader jest modułem ładującym rozszerzenia, potomkiem klasy modułu ładującego podstawowego. Zajmuje się ładowaniem rozszerzenia standardowych klas bazowych Java. Ładowane z katalogu rozszerzeń JDK, zazwyczaj $JAVA_HOME/lib/ext lub dowolnego innego katalogu wymienionego we właściwości systemowej java.ext.dirs (tej opcji można użyć do kontrolowania ładowania rozszerzeń).

  3. System ClassLoader to moduł ładujący system zaimplementowany na poziomie JRE, który zajmuje się ładowaniem wszystkich klas poziomu aplikacji do maszyny JVM. Ładuje pliki znalezione w zmiennej środowiskowej klasy -classpath lub opcji wiersza poleceń -cp .

Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 15 - 2Moduły ładujące klasy są częścią środowiska wykonawczego Java. W momencie, gdy maszyna JVM zażąda klasy, moduł ładujący klasy próbuje znaleźć tę klasę i załadować definicję klasy do środowiska wykonawczego, używając w pełni kwalifikowanej nazwy klasy. Metoda java.lang.ClassLoader.loadClass() odpowiada za załadowanie definicji klasy w czasie wykonywania. Próbuje załadować klasę na podstawie jej pełnej nazwy. Jeśli klasa nie została jeszcze załadowana, deleguje żądanie do modułu ładującego klasę nadrzędną. Proces ten zachodzi rekurencyjnie i wygląda następująco:
  1. Systemowy moduł ładujący klasy próbuje znaleźć klasę w swojej pamięci podręcznej.

    • 1.1. Jeśli klasa zostanie znaleziona, ładowanie zakończy się pomyślnie.

    • 1.2. Jeśli klasa nie zostanie znaleziona, ładowanie jest delegowane do modułu ładującego klasy rozszerzenia.

  2. Rozszerzenie Classloader próbuje znaleźć klasę we własnej pamięci podręcznej.

    • 2.1. Jeśli klasa zostanie znaleziona, zakończy się pomyślnie.

    • 2.2. Jeśli klasa nie zostanie znaleziona, ładowanie jest delegowane do modułu ładującego klasy Bootstrap.

  3. Bootstrap Classloader próbuje znaleźć klasę we własnej pamięci podręcznej.

    • 3.1. Jeśli klasa zostanie znaleziona, ładowanie zakończy się pomyślnie.

    • 3.2. Jeśli klasa nie zostanie znaleziona, podstawowy moduł ładujący klasy Bootstrap spróbuje ją załadować.

  4. Jeśli ładujesz:

    • 4.1. Pomyślnie — ładowanie klasy zostało zakończone.

    • 4.2. Jeśli to się nie powiedzie, kontrola jest przekazywana do modułu ładującego klasy rozszerzenia.

  5. 5. Rozszerzenie Classloader próbuje załadować klasę, a jeśli ładuje:

    • 5.1. Pomyślnie — ładowanie klasy zostało zakończone.

    • 5.2. Jeśli się nie powiedzie, kontrola zostanie przekazana do modułu ładującego klasy systemowe.

  6. 6. System Classloader próbuje załadować klasę, a jeśli ładuje:

    • 6.1. Pomyślnie — ładowanie klasy zostało zakończone.

    • 6.2. Nie przeszło pomyślnie — generowany jest wyjątek — ClassNotFoundException.

Temat ładowarek klas jest obszerny i nie należy go zaniedbywać. Aby zapoznać się z nim bardziej szczegółowo, radzę przeczytać ten artykuł , a my nie będziemy zwlekać i iść dalej.

12. Co to są obszary danych wykonawczych?

Obszary danych wykonawczych — obszary danych wykonawczych JVM. JVM definiuje pewne obszary danych wykonawczych potrzebne podczas wykonywania programu. Niektóre z nich są tworzone podczas uruchamiania JVM. Inne są lokalne dla wątku i są tworzone tylko wtedy, gdy wątek jest tworzony (i niszczone, gdy wątek zostaje zniszczony). Obszary danych wykonawczych JVM wyglądają następująco: Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 15 - 3
  • Rejestr PC jest lokalny dla każdego wątku i zawiera adres instrukcji JVM, którą wątek aktualnie wykonuje.

  • Stos JVM to obszar pamięci używany jako magazyn zmiennych lokalnych i wyników tymczasowych. Każdy wątek ma swój własny, oddzielny stos: gdy tylko wątek się zakończy, stos ten również ulega zniszczeniu. Warto zauważyć, że przewagą stosu nad stertą jest wydajność, podczas gdy sterta z pewnością ma przewagę pod względem skali przechowywania.

  • Natywny stos metod — obszar danych przypadający na wątek, w którym przechowywane są elementy danych, podobne do stosu JVM, służące do wykonywania metod natywnych (innych niż Java).

  • Sterta - używana przez wszystkie wątki jako magazyn zawierający obiekty, metadane klas, tablice itp., które są tworzone w czasie wykonywania. Obszar ten jest tworzony podczas uruchamiania maszyny JVM i ulega zniszczeniu po jej zamknięciu.

  • Obszar metod — ten obszar wykonawczy jest wspólny dla wszystkich wątków i jest tworzony podczas uruchamiania maszyny JVM. Przechowuje struktury dla każdej klasy, takie jak Runtime Constant Pool, kod konstruktorów i metod, dane metod itp.

13. Co to jest obiekt niezmienny?

W tej części artykułu, w pytaniach 14 i 15, znajduje się już odpowiedź na to pytanie, więc zajrzyj tam, nie marnując czasu.

14. Co jest specjalnego w klasie String?

Wcześniej w analizie wielokrotnie omawialiśmy pewne cechy String (było na to osobną sekcję). Podsumujmy teraz funkcje String :
  1. Jest to najpopularniejszy obiekt w Javie i służy do różnych celów. Pod względem częstotliwości użytkowania nie ustępuje nawet prymitywnym typom.

  2. Obiekt tej klasy można utworzyć bez użycia słowa kluczowego new - bezpośrednio poprzez cudzysłów String str = „string”; .

  3. String jest klasą niezmienną : tworząc obiekt tej klasy, nie można zmienić jego danych (dodając + „kolejny ciąg” do określonego ciągu, w rezultacie otrzymasz nowy, trzeci ciąg). Niezmienność klasy String sprawia, że ​​jest ona bezpieczna dla wątków.

  4. Klasa String jest sfinalizowana (posiada ostateczny modyfikator ), więc nie można jej dziedziczyć.

  5. String ma własną pulę ciągów, czyli obszar pamięci na stercie, który buforuje tworzone przez siebie wartości ciągów. W tej części cyklu , w pytaniu 62, opisałem pulę strun.

  6. Java ma analogi String , również przeznaczone do pracy z ciągami znaków - StringBuilder i StringBuffer , ale z tą różnicą, że są one modyfikowalne. Więcej na ich temat przeczytasz w tym artykule .

Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 15 - 4

15. Co to jest kowariancja typu?

Aby zrozumieć kowariancję, spójrzmy na przykład. Powiedzmy, że mamy klasę zwierząt:
public class Animal {
 void voice() {
   System.out.println("*тишина*");
 }
}
I jakaś klasa Dog rozszerzająca to :
public class Dog extends Animal {

 @Override
 public void voice() {
   System.out.println("Гав, гав, гав!!!");
 }
}
Jak pamiętamy, łatwo możemy przypisać obiekty typu spadkobiercy do typu nadrzędnego:
Animal animal = new Dog();
To będzie nic innego jak polimorfizm. Wygodny i elastyczny, prawda? A co z listą zwierząt? Czy możemy dać listę z ogólnym Animalem listę z obiektami Dog ?
List<Dog> dogs = new ArrayList<>();
List<Animal> animals = dogs;
W takim przypadku linia przypisania listy psów do listy zwierząt zostanie podkreślona na czerwono, tj. kompilator nie przekaże tego kodu. Mimo że to przypisanie wydaje się całkiem logiczne (w końcu obiekt Dog możemy przypisać do zmiennej typu Animal ), to nie da się tego zrobić. Dzieje się tak dlatego, że gdyby było to dozwolone, moglibyśmy umieścić obiekt Animal na liście, która pierwotnie miała należeć do Dog , myśląc, że na liście mamy tylko psy . A potem na przykład użyjemy metody get() , aby pobrać obiekt z listy psów , myśląc, że to pies, i wywołamy na nim jakąś metodę obiektu Dog , której Animal nie ma . A jak rozumiesz, jest to niemożliwe - wystąpi błąd. Ale na szczęście kompilator nie przeoczył tego logicznego błędu przy przypisywaniu listy potomków do listy rodziców (i odwrotnie). W Javie obiekty list można przypisywać tylko do zmiennych list z pasującymi typami rodzajowymi. Nazywa się to inwariacją. Gdyby mogli to zrobić, nazywałoby się to i nazywa się kowariancją. Oznacza to, że kowariancja ma miejsce, gdy możemy ustawić obiekt typu ArrayList<Dog> na zmienną typu List<Animal> . Okazuje się, że kowariancja nie jest obsługiwana w Javie? Nieważne jak to jest! Ale odbywa się to na swój własny, szczególny sposób. Do czego służy projekt ? rozciąga się Zwierzę . Jest on umieszczany z rodzajem zmiennej, do której chcemy ustawić obiekt listy, z rodzajem potomka. Ta ogólna konstrukcja oznacza, że ​​zrobi to każdy typ będący potomkiem typu Animal (a typ Animal również podlega temu uogólnieniu). Z kolei Animal może być nie tylko klasą, ale także interfejsem (nie dajcie się zwieść słowie kluczowemu Extends ). Nasze poprzednie zadanie możemy wykonać w ten sposób: Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 15 - 5
List<Dog> dogs = new ArrayList<>();
List<? extends Animal> animals = dogs;
W rezultacie zobaczysz w IDE, że kompilator nie będzie narzekał na tę konstrukcję. Sprawdźmy funkcjonalność tego projektu. Powiedzmy, że mamy metodę, która powoduje, że wszystkie przekazane jej zwierzęta wydają dźwięki:
public static void animalsVoice(List<? extends Animal> animals) {
 for (Animal animal : animals) {
   animal.voice();
 }
}
Dajmy mu listę psów:
List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
dogs.add(new Dog());
dogs.add(new Dog());
animalsVoice(dogs);
W konsoli zobaczymy następujące dane wyjściowe:
Faj, fuj, fuj!!! Faj, fuj, fuj!!! Faj, fuj, fuj!!!
Oznacza to, że to podejście do kowariancji sprawdza się pomyślnie. Pragnę zauważyć, że ten lek generyczny znajduje się na liście ? rozszerza Animal nie możemy wstawić nowych danych żadnego typu: ani typu Dog , ani nawet typu Animal :
List<Dog> dogs = new ArrayList<>();
List<? extends Animal> animals = dogs;
animals.add(new Dog());
dogs.add(new Animal());
Właściwie w dwóch ostatnich liniach kompilator podświetli wstawienie obiektów na czerwono. Dzieje się tak dlatego, że nie możemy być w stu procentach pewni, która lista obiektów jakiego typu zostanie przypisana do listy z danymi przez rodzajowy <? rozciąga Zwierzę> . Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 15 - 6Chciałbym też porozmawiać o kontrawariancji , ponieważ zwykle to pojęcie zawsze idzie w parze z kowariancją i z reguły pyta się o nie łącznie. Koncepcja ta jest nieco przeciwieństwem kowariancji, ponieważ w tej konstrukcji zastosowano typ dziedzica. Powiedzmy, że potrzebujemy listy, do której można przypisać listę obiektów typu, które nie są przodkami obiektu Dog . Nie wiemy jednak z góry, jakie to będą konkretnie typy. W tym przypadku konstrukcja formularza ? super Dog , dla którego nadają się wszystkie typy - przodkowie klasy Dog :
List<Animal> animals = new ArrayList<>();
List<? super Dog> dogs = animals;
dogs.add(new Dog());
dogs.add(new Dog());
Możemy bezpiecznie dodać do listy obiekty typu Dog z takim typem generycznym , gdyż i tak posiada on wszystkie zaimplementowane metody któregokolwiek ze swoich przodków. Nie będziemy jednak mogli dodać obiektu typu Zwierzę , gdyż nie ma pewności, że w środku znajdą się obiekty tego typu, a nie np. Pies . Przecież możemy zażądać od elementu tej listy metody klasy Dog , której Animal nie będzie miał . W takim przypadku wystąpi błąd kompilacji. Ponadto, gdybyśmy chcieli zaimplementować poprzednią metodę, ale z tą ogólną:
public static void animalsVoice(List<? super Dog> dogs) {
 for (Dog dog : dogs) {
   dog.voice();
 }
}
otrzymalibyśmy błąd kompilacji w pętli for , ponieważ nie możemy być pewni, czy zwrócona lista zawiera obiekty typu Dog i czy możemy swobodnie korzystać z jej metod. Jeśli wywołamy na tej liście metodędogs.get (0) . - otrzymamy obiekt typu Object . Oznacza to, że aby metoda zwierzętaVoice() zadziałała , musimy przynajmniej dodać drobne manipulacje zawężając typ danych:
public static void animalsVoice(List<? super Dog> dogs) {
 for (Object obj : dogs) {
   if (obj instanceof Dog) {
     Dog dog = (Dog) obj;
     dog.voice();
   }
 }
}
Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 15 - 7

16. Jakie są metody w klasie Object?

W tej części serii, w paragrafie 11, odpowiedziałem już na to pytanie, dlatego gorąco radzę Ci go przeczytać, jeśli jeszcze tego nie zrobiłeś. Na tym dzisiaj zakończymy. Do zobaczenia w kolejnej części! Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 15 - 8
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION