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ęść 14

Opublikowano w grupie Random-PL
Sztuczne ognie! Świat w ciągłym ruchu i my w ciągłym ruchu. Wcześniej, aby zostać programistą Java, wystarczyło znać trochę składnię Java, a reszta sama przyjdzie. Z biegiem czasu poziom wiedzy wymaganej, aby zostać programistą Java, znacznie wzrósł, podobnie jak konkurencja, która w dalszym ciągu podnosi dolną poprzeczkę wymaganej wiedzy. Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 14 - 1Jeśli naprawdę chcesz zostać programistą, musisz przyjąć to za oczywistość i dokładnie przygotować się, aby wyróżnić się wśród początkujących takich jak Ty.To, co dzisiaj zrobimy, a mianowicie będziemy kontynuować analizę ponad 250 pytań . W poprzednich artykułach analizowaliśmy wszystkie pytania na poziomie młodszym, a dzisiaj zajmiemy się pytaniami na poziomie średnim. Choć zaznaczam, że nie są to w 100% pytania na poziomie średnim, to większość z nich można spotkać na rozmowie kwalifikacyjnej na poziomie juniorskim, bo to właśnie na takich rozmowach następuje szczegółowe sondowanie bazy teoretycznej, natomiast w przypadku gimnazjalisty pytania skupiają się bardziej na badaniu jego doświadczenia. Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 14 - 2Ale bez zbędnych ceregieli, zacznijmy.

Środek

Są pospolite

1. Jakie są zalety i wady OOP w porównaniu z programowaniem proceduralnym/funkcjonalnym?

To pytanie pojawiło się w analizie pytań dla Juniora i dlatego już na nie odpowiedziałem. Poszukaj tego pytania i odpowiedzi w tej części artykułu, pytaniach 16 i 17.

2. Czym agregacja różni się od kompozycji?

W OOP istnieje kilka rodzajów interakcji pomiędzy obiektami, zjednoczonych w ramach ogólnej koncepcji „relacji Has-A”. Zależność ta wskazuje, że jeden obiekt jest składnikiem innego obiektu. Jednocześnie istnieją dwa podtypy tej relacji: Kompozycja – jeden obiekt tworzy inny obiekt, a czas życia innego obiektu zależy od czasu życia twórcy. Agregacja - obiekt otrzymuje w procesie budowy łącze (wskaźnik) do innego obiektu (w tym przypadku czas życia drugiego obiektu nie jest zależny od czasu życia twórcy). Dla lepszego zrozumienia spójrzmy na konkretny przykład. Mamy pewną klasę samochodu - Car , która z kolei posiada wewnętrzne pola typu - Engine oraz listę pasażerów - List<Passenger> , posiada także metodę rozpoczęcia ruchu - startMoving() :
public class Car {

 private Engine engine;
 private List<Passenger> passengers;

 public Car(final List<Passenger> passengers) {
   this.engine = new Engine();
   this.passengers = passengers;
 }

 public void addPassenger(Passenger passenger) {
   passengers.add(passenger);
 }

 public void removePassengerByIndex(Long index) {
   passengers.remove(index);
 }

 public void startMoving() {
   engine.start();
   System.out.println("Машина начала своё движение");
   for (Passenger passenger : passengers) {
     System.out.println("В машине есть пассажир - " + passenger.getName());
   }
 }
}
W tym przypadku Kompozycja jest połączeniem pomiędzy Car i Engine , ponieważ wydajność samochodu zależy bezpośrednio od obecności obiektu silnika, ponieważ jeśli Engine = null , otrzymamy wyjątek NullPointerException . Z kolei silnik nie może istnieć bez maszyny (po co nam silnik bez maszyny?) i nie może należeć do kilku maszyn w tym samym czasie. Oznacza to, że jeśli usuniemy obiekt Car , nie będzie już żadnych odniesień do obiektu Engine i wkrótce zostanie on usunięty przez Garbage Collector . Jak widać, zależność ta jest bardzo ścisła (silna). Agregacja to połączenie pomiędzy Samochodem i Pasażerem , ponieważ działanie Samochodu w żaden sposób nie zależy od obiektów typu Pasażer i ich liczby. Mogą albo opuścić samochód - usuńPassengerByIndex(Long indeks) lub wprowadzić nowe - addPassenger(Passenger pasażer) , mimo to samochód będzie nadal działał prawidłowo. Z kolei obiekty Passenger mogą istnieć bez obiektu Car . Jak rozumiesz, jest to znacznie słabsze połączenie niż widzimy w kompozycji. Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 14 - 3Ale to nie wszystko, obiekt, który jest połączony poprzez agregację z innym, może mieć także dane połączenie z innymi obiektami w tym samym momencie. Na przykład Ty, jako student Java, uczestniczysz w kursach języka angielskiego, OOP i logarytmów w tym samym czasie, ale jednocześnie nie jesteś ich krytycznie niezbędną częścią, bez której normalne funkcjonowanie nie jest możliwe (np. nauczyciel).

3. Jakie wzorce GoF wykorzystałeś w praktyce? Daj przykłady.

Odpowiedziałem już na to pytanie wcześniej, więc zostawię tylko link do analizy , patrz pierwsze pytanie. Znalazłem także wspaniały artykuł ze ściągawką na temat wzorców projektowych, który gorąco polecam mieć pod ręką.

4. Co to jest obiekt proxy? Daj przykłady

Serwer zastępczy to strukturalny wzorzec projektowy, który umożliwia zastępowanie specjalnych obiektów zastępczych, czyli innymi słowy obiektów zastępczych, zamiast obiektów rzeczywistych. Te obiekty proxy przechwytują wywołania do obiektu oryginalnego, umożliwiając wstawienie logiki przed lub po przekazaniu wywołania do oryginału. Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 14 - 4Przykłady użycia obiektu proxy:
  • Jako zdalny serwer proxy - używany, gdy potrzebujemy obiektu zdalnego (obiektu w innej przestrzeni adresowej), który musi być reprezentowany lokalnie. W tym przypadku proxy zajmie się tworzeniem połączenia, kodowaniem, dekodowaniem itp., natomiast klient będzie z niego korzystał tak, jakby był oryginalnym obiektem znajdującym się w przestrzeni lokalnej.

  • Jako wirtualny serwer proxy - używany, gdy potrzebny jest obiekt wymagający dużych zasobów. W tym przypadku obiekt proxy pełni funkcję czegoś w rodzaju obrazu rzeczywistego obiektu, który w rzeczywistości jeszcze nie istnieje. Kiedy do tego obiektu zostanie wysłane prawdziwe żądanie (wywołanie metody), dopiero wtedy zostanie załadowany oryginalny obiekt i wykonana zostanie metoda. Takie podejście nazywa się także leniwą inicjalizacją; może być bardzo wygodne, ponieważ w niektórych sytuacjach oryginalny obiekt może nie być przydatny i wtedy jego utworzenie nie będzie wymagało żadnych kosztów.

  • Jako serwer proxy bezpieczeństwa - używany, gdy trzeba kontrolować dostęp do jakiegoś obiektu w oparciu o uprawnienia klienta. Oznacza to, że jeśli klient z brakującymi prawami dostępu spróbuje uzyskać dostęp do oryginalnego obiektu, serwer proxy przechwyci go i nie pozwoli.

Spójrzmy na przykład wirtualnego serwera proxy: Mamy interfejs obsługi:
public interface Processor {
 void process();
}
Której implementacja zużywa zbyt wiele zasobów, ale jednocześnie nie może być wykorzystana przy każdym uruchomieniu aplikacji:
public class HiperDifficultProcessor implements Processor {
 @Override
 public void process() {
   // некоторый сверхсложная обработка данных
 }
}
Klasa proxy:
public class HiperDifficultProcessorProxy implements Processor {
private HiperDifficultProcessor processor;

 @Override
 public void process() {
   if (processor == null) {
     processor = new HiperDifficultProcessor();
   }
   processor.process();
 }
}
Uruchommy to w trybie głównym :
Processor processor = new HiperDifficultProcessorProxy();
// тут тяжеловсеного оригинального obiektа, ещё не сущетсвует
// но при этом есть obiekt, который его представляет и у которого можно вызывать его методы
processor.process(); // лишь теперь, obiekt оригинал был создан
Zauważam, że wiele frameworków korzysta z proxy i dla Springa jest to kluczowy wzór (Spring jest zszyty nim od wewnątrz i na zewnątrz). Więcej o tym wzorze przeczytasz tutaj . Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 14 - 5

5. Jakie innowacje zapowiedziano w Javie 8?

Innowacje wprowadzone przez Java 8 są następujące:
  • Dodano funkcjonalne interfejsy, o tym co to za bestia przeczytasz tutaj .

  • Wyrażenia lambda, które są ściśle powiązane z interfejsami funkcjonalnymi, więcej o ich zastosowaniu przeczytasz tutaj .

  • Dodano Stream API do wygodnego przetwarzania zbiorów danych, czytaj więcej tutaj .

  • Dodano linki do metod .

  • Do interfejsu Iterable dodano metodę forEach() .

  • Dodano nowe API daty i godziny w pakiecie java.time , szczegółowa analiza tutaj .

  • Ulepszone współbieżne API .

  • Dodając opcjonalną klasę opakowania , która służy do poprawnej obsługi wartości null, doskonały artykuł na ten temat znajdziesz tutaj .

  • Dodanie możliwości korzystania przez interfejsy z metod statycznych i domyślnych (co w istocie przybliża Javę do wielokrotnego dziedziczenia), więcej szczegółów tutaj .

  • Dodano nowe metody do klasy Collection(removeIf(), spliterator()) .

  • Drobne ulepszenia Java Core.

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

6. Czym jest wysoka spójność i niskie sprzężenie? Daj przykłady.

Wysoka spójność lub wysoka spójność to koncepcja, w której pewna klasa zawiera elementy, które są ze sobą ściśle powiązane i połączone zgodnie ze swoim przeznaczeniem. Na przykład wszystkie metody w klasie User powinny reprezentować zachowanie użytkownika. Klasa ma niską spójność, jeśli zawiera niepowiązane elementy. Przykładowo klasa User zawierająca metodę sprawdzania poprawności adresu e-mail:
public class User {
private String name;
private String email;

 public String getName() {
   return this.name;
 }

 public void setName(final String name) {
   this.name = name;
 }

 public String getEmail() {
   return this.email;
 }

 public void setEmail(final String email) {
   this.email = email;
 }

 public boolean isValidEmail() {
   // некоторая логика валидации емейла
 }
}
Klasa użytkownika może być odpowiedzialna za przechowywanie adresu e-mail użytkownika, ale nie za jego sprawdzanie i wysyłanie wiadomości e-mail. Dlatego też, chcąc osiągnąć dużą spójność, metodę walidacji przenosimy do osobnej klasy użyteczności:
public class EmailUtil {
 public static boolean isValidEmail(String email) {
   // некоторая логика валидации емейла
 }
}
I używamy go w razie potrzeby (na przykład przed zapisaniem użytkownika). Low Coupling lub Low Coupling to koncepcja opisująca niską współzależność pomiędzy modułami oprogramowania. Zasadniczo współzależność polega na tym, że zmiana jednego wymaga zmiany drugiego. Dwie klasy mają silne powiązanie (lub ścisłe powiązanie), jeśli są blisko spokrewnione. Na przykład dwie konkretne klasy, które przechowują wzajemne odniesienia i wywołują wzajemne metody. Klasy luźno powiązane są łatwiejsze do rozwijania i utrzymywania. Ponieważ są one od siebie niezależne, można je rozwijać i testować równolegle. Co więcej, można je zmieniać i aktualizować bez wzajemnego wpływu. Spójrzmy na przykład klas silnie sprzężonych. Mamy klasę studencką:
public class Student {
 private Long id;
 private String name;
 private List<Lesson> lesson;
}
Który zawiera listę lekcji:
public class Lesson {
 private Long id;
 private String name;
 private List<Student> students;
}
Każda lekcja zawiera link do uczniów uczestniczących w zajęciach. Niesamowicie mocny chwyt, nie sądzisz? Jak możesz to zmniejszyć? Najpierw upewnijmy się, że uczniowie nie mają listy przedmiotów, ale listę ich identyfikatorów:
public class Student {
 private Long id;
 private String name;
 private List<Long> lessonIds;
}
Po drugie, klasa lekcyjna nie musi wiedzieć o wszystkich uczniach, więc usuńmy całkowicie ich listę:
public class Lesson {
 private Long id;
 private String name;
}
Więc wszystko stało się znacznie łatwiejsze, a połączenie znacznie słabsze, nie sądzisz? Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 14 - 7

Ups

7. Jak zaimplementować wielokrotne dziedziczenie w Javie?

Dziedziczenie wielokrotne to cecha koncepcji obiektowej, w której klasa może dziedziczyć właściwości od więcej niż jednej klasy nadrzędnej. Problem pojawia się, gdy istnieją metody o tym samym podpisie zarówno w nadklasie, jak i podklasie. Podczas wywoływania metody kompilator nie może określić, która metoda klasy powinna zostać wywołana, a nawet podczas wywoływania metody klasy, która ma pierwszeństwo. Dlatego Java nie obsługuje wielokrotnego dziedziczenia! Istnieje jednak pewna luka, o której porozmawiamy dalej. Jak wspomniałem wcześniej, wraz z wydaniem Java 8 do interfejsów dodano możliwość posiadania metod domyślnych . Jeżeli klasa implementująca interfejs nie nadpisze tej metody, wówczas zostanie użyta ta domyślna implementacja (nie jest konieczne przesłonięcie metody domyślnej, takiej jak implementacja abstrakcyjnej). W takim przypadku możliwe jest zaimplementowanie różnych interfejsów w jednej klasie i wykorzystanie ich metod domyślnych. Spójrzmy na przykład. Mamy interfejs ulotki z domyślną metodą fly() :
public interface Flyer {
 default void fly() {
   System.out.println("Я лечу!!!");
 }
}
Interfejs walkera z domyślną metodą walk() :
public interface Walker {
 default void walk() {
   System.out.println("Я хожу!!!");
 }
}
Interfejs pływaka z metodą swim() :
public interface Swimmer {
 default void swim() {
   System.out.println("Я плыву!!!");
 }
}
Cóż, teraz zaimplementujmy to wszystko w jednej klasie kaczki:
public class Duck implements Flyer, Swimmer, Walker {
}
I przeprowadźmy wszystkie metody naszej kaczki:
Duck donald = new Duck();
donald.walk();
donald.fly();
donald.swim();
W konsoli otrzymamy:
Idę!!! Latam!!! Pływam!!!
Oznacza to, że poprawnie przedstawiliśmy dziedziczenie wielokrotne, chociaż tak nie jest. Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 14 - 8Zauważam również, że jeśli klasa implementuje interfejsy z metodami domyślnymi, które mają te same nazwy metod i te same argumenty w tych metodach, to kompilator zacznie narzekać na niezgodność, ponieważ nie rozumie, której metody tak naprawdę należy użyć. Jest kilka wyjść:
  • Zmień nazwy metod w interfejsach tak, aby się od siebie różniły.
  • Zastąp takie kontrowersyjne metody w klasie implementacji.
  • Dziedzicz po klasie, która implementuje te kontrowersyjne metody (wtedy Twoja klasa użyje dokładnie jej implementacji).

8. Jaka jest różnica pomiędzy metodami final, final i finalize()?

final to słowo kluczowe używane do nałożenia ograniczenia na klasę, metodę lub zmienną, co oznacza:
  • Dla zmiennej - po wstępnej inicjalizacji, zmienna nie może być ponownie zdefiniowana.
  • W przypadku metody nie można jej zastąpić w podklasie (klasie następczej).
  • W przypadku klasy — klasy nie można dziedziczyć.
wreszcie jest słowem kluczowym poprzedzającym blok kodu, używanym podczas obsługi wyjątków, w połączeniu z blokiem try i razem (lub zamiennie) z blokiem catch. Kod w tym bloku jest wykonywany w każdym przypadku, niezależnie od tego, czy został zgłoszony wyjątek, czy nie. W tej części artykułu, w pytaniu 104, omówiono wyjątkowe sytuacje, w których blok ten nie zostanie wykonany. finalize() to metoda klasy Object , wywoływana przed usunięciem każdego obiektu przez moduł zbierający elementy bezużyteczne. Metoda ta zostanie wywołana (ostatnio) i służy do oczyszczenia zajętych zasobów. Więcej informacji na temat metod klasy Object , które dziedziczy każdy obiekt, znajdziesz w pytaniu 11 w tej części artykułu. Cóż, na tym dzisiaj zakończymy. Do zobaczenia w kolejnej części! Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 14 - 9
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION