Wątek przeniesiony 2022-01-19 11:11 z Kariera przez cerrato.

Java - pytania rekrutacyjne mid/senior

2

@PanamaJoe: Przykład praktycznego wykorzystania:
Piszesz aplikację, która ma wyświetlać listę ticketów w Jira.
Robisz "jakiś" mechanizm, który dba o to, żebyte tickety znalazły się w liście, były dociągane nowe, zamknięte usuwane itd.
Dorabiasz widok gdzie jakaś kontrolka ma wyświetlać aktualną zawartość listy. Widok rejestruje listenera w tej liście, i jest powiadamiany jeżeli zaistnieje konieczność odświeżenia tego co wyświetla.
Za chwilę pojawia się wymaganie biznesowe, że jak pojawi się coś nowego, to masz wyświetlić jakieś powiadomienie, a oprócz widoku listy masz też zrobić drugi panel, który wyświetli te dane ale w postaci kafelków - zmiana jest banalna.
Okazuje się, że aplikacja zaczyna lagować, bo wszystko zrobiłeś na pojedynczym wątku, a pobranie nowych ticketów stało się czasochłonne - podział na wątki również jest banalny.
Firma przepina się z Jira na RedMine ;) , zmieniasz jedynie mechanizm zasilający, cała reszta pozostaje bez zmian.
Trafiasz na ten jeden przypadek na milion, kiedy faktycznie ma znaczenie czy użyłeś ArrayListy, czy LinkedList zmieniasz jedną linijkę w kodzie.

W praktyce takie nasłuchiwanie zmian jest potrzebne dość często, przykład z listą jest akurat dlatego, że jest prosty do zrozumienia.

--edit
Dam też przykład jak można rozwiązać problem:


public class ObservableList<T> implements List<T>{
private final List<T> underlying;
private final List<ListListener> observers = new ArrayList<>();
//tu następuje implementacja wszystkich metod List:
public int length(){
  return underlying.length();
  }
........
//dla metod które coś zmieniają:
public void clear() {
  underlying.clear();
  observers.forEach(ListListener::onClear);
  }
}
//do tego dodanie możliwości rejestrowania listenerów
public void addListener(ListListener listener){
  observers.add(listener);
}

Pewnie warto byłoby trzymać te listenery dodatkowo zapakowane w WeakReference (bo o dodaniu to pamięta każdy, o wyrejestrowaniu już nie zawsze), można by to proxy rozdzielić na DefaultProxy z implementacjami 1:1 i dopiero dziedzicząc po nim nadpisywać konkretne metody.

0
piotrpo napisał(a):

@PanamaJoe: Przykład praktycznego wykorzystania:

Piszesz aplikację, która ma wyświetlać listę ticketów w Jira.
Robisz "jakiś" mechanizm, który dba o to, żebyte tickety znalazły się w liście, były dociągane nowe, zamknięte usuwane itd.
Dorabiasz widok gdzie jakaś kontrolka ma wyświetlać aktualną zawartość listy. Widok rejestruje listenera w tej liście, i jest powiadamiany jeżeli zaistnieje konieczność odświeżenia tego co wyświetla.
Za chwilę pojawia się wymaganie biznesowe, że jak pojawi się coś nowego, to masz wyświetlić jakieś powiadomienie, a oprócz widoku listy masz też zrobić drugi panel, który wyświetli te dane ale w postaci kafelków - zmiana jest banalna.

Sorry z góry, mogę głupoty pisać, ale czy nie było by prościej zamiast nasłuchiwać zmian to, co jakiś czas przejechać po liście i sprawdzić stan przełączników (wartości listy) i aktualizować stany tych kontrolek? A jeżeli to jest jakiś krytyczny system dla pocisków manewrujących (choć podejrzewam, że nie byłby w Javie pisany wtedy) to np. zrobić swoje rozszerzenie tej kolekcji, gdzie nadpisałoby się metody zmieniające jej elementy tak, że dodanie/update/usunięcie wyzwalałoby automatycznie jakąś reakcję?

2

@PanamaJoe: Zależy, jak pisałem przykład jest (jak właściwie każdy przykład na interview) trochę z d. Pytanie też co oznacza dla ciebie prościej. Jeżeli chodzi o szybkość pisania, to pewnie najszybciej byłoby mieć jakiś kawałek kodu typu:

tickets = jiraConnector.getTickets();
ui.update(tickets);

Ale ma to trochę wad:
Klasa w której to tworzysz (kontroler), ma spore szanse na rozwinięcie się w czasie do GodObject, bo będzie odpowiadać za kontrolowanie pozyskiwania danych i za co najmniej wyzwalanie zmian na UI.
Załóżmy, że na tym UI, użytkownik będzie mógł coś zrobić i powinien być responsywny. Jeżeli zrobisz tak jak wyżej, to znowu najprościej będzie robić to tak jak wyżej, co oznacza, że wszystko (UI, kontroler, komunikacja z Jira) będzie chodzić na jednym wątku. Przyjmuje się, że responsywność UI nie powinna przekraczać 100ms, czyli jeżeli najedziesz myszką na jakiś przycisk, on zmienia kolor i ta zmiana powinna zostać wykonana natychmiast, żeby użytkownik nie czuł lagowania aplikacji. Jeżeli w tym czasie akurat jesteś w trakcie pobierania ticketów z zewnętrznego źródła to nic innego nie zostanie zrobione. Jeżeli piszesz aplikację na Androida, to nawet ci się nie skompiluje, bo tam nie wolno robić komunikacji na głównym wątku.
To co opisałem to też nie jest jakaś wielka, samodzielnie wymyślona magia, poczytaj sobie o różnicach pomiędzy MVC i MVVM.

Odpytywanie co ileś tam sekund też ma sporo wad. Będzie to kod wykonywany nadmiarowo w większości przypadków, czyli albo zużyje jedynie trochę czasu CPU (desktop), albo dodatkowo baterię. W dodatku nawet w realnych zastosowaniach może ci zależeć na czasie (aplikacja do czatowania) i te 5s będzie wprowadzać dodatkowe opóźnienie w dostarczeniu wiadomości z jednego UI do drugiego.

Wreszcie, są moje prywatne oczekiwania w stosunku do osoby, która chcę mieć w zespole (mowa o seniorze) i tego co osobiście uważam za istotne przy tworzeniu oprogramowania. Rzeczywistość jest taka, że pracujemy ze zmieniającymi się w krótkim czasie wymaganiami klienta/rynku. Agile, to nie są sprinty, retrospektywy, daily i temu podobne bzdety, tylko zdolność do reagowania na dynamikę otoczenia. Jak spojrzysz sobie na te wszystkie SOLID, clean code/architecture, DDD, TDD, BDD, Design Pattern, CI, CD, to ich głównym, a przynajmniej celem jest zapewnienie, że kod, który tworzy zespół będzie utrzymywalny, co w delikatnym uproszczeniu oznacza podatność na zmiany. Na poziomie junior mogę zatrudnić kogoś z przyzwoitą znajomością Java, bo to wystarczy do tego żeby napisać kod, na poziomie senior chcę, żeby taka osoba wiedziała po co to robi i była w stanie w praktyce zastosować część tych wynalazków, co nie oznacza, że z radością wezmę do zespołu fanatyka któregoś podejścia.

7

Na większość z tych pytań odpowiedziałem https://lsdev.pl/posts/java-interview-pytania-rekrutacyjne/
może będzie pomocne dla kogoś. Jeżeli popelniłem jakiś błąd merytoryczny zachęcam do dyskusji ;)

0
lookacode1 napisał(a):

Na większość z tych pytań odpowiedziałem https://lsdev.pl/posts/java-interview-pytania-rekrutacyjne/

może będzie pomocne dla kogoś. Jeżeli popelniłem jakiś błąd merytoryczny zachęcam do dyskusji ;)

Mała sugestia - w pytaniu nr 19, o model pamięci w javie, warto dodać, że w off-heapie są też trzymane pola static

1
lookacode1 napisał(a):

Na większość z tych pytań odpowiedziałem https://lsdev.pl/posts/java-interview-pytania-rekrutacyjne/

może będzie pomocne dla kogoś. Jeżeli popelniłem jakiś błąd merytoryczny zachęcam do dyskusji ;)

To zabawne ale znając te odpowiedzi wciąż możesz
a) być kiepskim programistą
b) nie umieć pisać aplikacji
c) oba powyższe

2

Mała sugestia - w pytaniu nr 19, o model pamięci w javie, warto dodać, że w off-heapie są też trzymane pola static

Panie Pinku nieeeeeeee.
https://stackoverflow.com/questions/8387989/where-are-static-methods-and-static-variables-stored-in-java/8388068

3

@lookacode1:

Jeżeli popelniłem jakiś błąd merytoryczny zachęcam do dyskusji

pytanie 3

Nie ma to nic wspólnego ze Springiem i na dobrą sprawę Spring raczej incydentalnie wspiera tą adnotacje bo implementuje CDI, ale zaręczam ci że można tego używać nie mając żadnego Springa w projekcie.

pytanie 7

o_O chcesz listę ID przekazywać w URLu jako path variable? To raczej strasznie słaby pomysł, bo URL ma bardzo ograniczoną długość.

pytanie 9

Przy zmianie wymagań kod...

Ja bym jednak sugerował że przy nowych wymaganiach open-close ma sens. Jeśli wymagania uległy zmianie (funkcja ma zwracać 1 a wcześniej miała zwracać 2) to ja bym jednak kod zmienił i wyrzucił ten błędny.

pytanie 12

aby każdy kolejny odczyt zwracał ten sam wynik możemy zwiększyć poziom izolacji na REPEATABLE_READ.

phantom read chyba jednak sprawia że wynik niekoniecznie nie będzie taki sam ;)

pytanie 16

Sól pełni rolę mitygacji przed atakami słownikowymi

Nie, sól jest związana z rainbow tables a nie z atakiem słownikowym. Threat model zakłada że atakujacy zna wszystkie sole.

pytanie 17

dzięki parallelStream() możemy w prosty sposób zrównoleglić operacje na danych

No nie wiem, akurat takie użycie na pałe parallelStream to moze ci deadlockować aplikacje bardzo łatwo, albo zrobić bottleneck w miejscu w którym się tego nie spodziewasz ;)

wyrażenia (lambda) w map/filter itd. nie mogą rzucać tzw. checked exceptions co może być problematyczne np. jak przetwarzamy I/O

Istnienie Optionala czy Eithera sprawia ze ten problem nie istnieje

pytanie 20

Atak SQLI polega na zmanipulowaniu wejścia (np. nazwy użytkownika lub frazy wyszukiwania) w taki sposób aby silnik bazodanowy podczas parsowania tego zapytania zinterpretował je inaczej niż twórca aplikacji założył

No nie, SQLi to nie jest atak na system bazodanowy :D To jest atak na aplikacje która przygotowuje zapytanie do bazy. Silnik niczego tu nie parsuje i nie interpretuje źle. To aplikacja generuje zmanipulowane query do bazy.

0

@Shalom:
Thx część poprawiłem.

Nie ma to nic wspólnego ze Springiem i na dobrą sprawę Spring raczej incydentalnie wspiera tą adnotacje bo implementuje CDI, ale zaręczam ci że można tego używać nie mając żadnego Springa w projekcie.

Tak wiem i tak napisałem, że ta adnotacja pochodzi z specyfikacji a Spring ją wspiera. Generalnie piszę raczej w kontekście Springa z uwagi, że jest najpopularniejszy.

Istnienie Optionala czy Eithera sprawia ze ten problem nie istnieje

Nie każdy tego używa dlatego uznałem to jako wadę poza tym często wkurza.
Czasem chcesz tylko raz złapać exception i go zmapować na either a nie wszystko wrapować w Either.

No nie wiem, akurat takie użycie na pałe parallelStream to moze ci deadlockować aplikacje bardzo łatwo

Uznałem to jako zaletę bo lepiej jak jest taki feature niż jak go nie ma a kiedy go zastosować to już inna sprawa
natomiast na pewno w prostych przypadkach się przydaje kilka razy zastosowałem ;)

o_O chcesz listę ID przekazywać w URLu jako path variable? To raczej strasznie słaby pomysł, bo URL ma bardzo ograniczoną długość.

Racja chociaż jak tak odpowiedziałem rekruterowi to powiedział, że dobrze :D

0

Tak wiem i tak napisałem, że ta adnotacja pochodzi z specyfikacji a Spring ją wspiera. Generalnie piszę raczej w kontekście Springa z uwagi, że jest najpopularniejszy.

Tylko że Spring to akurat bardzo słaby przykład implementacji CDI bo ma kupę swoich udogodnień ;) Np. wg specyfikacji konstruktor do którego wstrzykujesz musi mieć @Inject a Spring pozwala na brak takiej adnotacji. Generalnie nie używałbym Springa jako przykładu do ilustracji JEE

Uznałem to jako zaletę bo lepiej jak jest taki feature niż jak go nie ma a kiedy go zastosować to już inna sprawa natomiast na pewno w prostych przypadkach się przydaje kilka razy zastosowałem

Sęk w tym że to jest dość ryzykowny ficzer, szczególnie właśnie użyty tak na pałe. Wynika to z tego jak on działa "pod spodem" - domyślnie puszcza to na standardowym fork join poolu, tylko ze ten sam fork join pool jest używany także w innych celach (np. do asynchronicznego uruchomienia CompletableFuture). W efekcie ten parallelStream w jednym miejscu w kodzie, może zablokować ci wykonanie jakiegoś CompletableFuture w zupełnie innym miejscu i odwrotnie jakieś długo działające CompletableFuture może ci zablokować tego streama.
A może być jeszcze gorzej, bo możesz zrobić kompletny deadlock, jeśli operacja którą odpalasz przez parallelStream woła pod spodem CompletableFuture (np. wrapujesz tak jakiś REST call) :D Bo nagle cały pool będzie zajęty streamem i aplikacja zablokuje się czekając na wątek żeby odpalić CompletableFuture :)

0

parallelStream w jednym miejscu w kodzie, może zablokować ci wykonanie jakiegoś CompletableFuture w zupełnie innym miejscu

A czy to jest coś dziwnego? Np. jak mamy pule połączeń do bazy to też kilka jakichś ciężkich zapytań może przyblokować inne itd. przez co pula się wyczerpie.
Z tego co wiem można podać dla parallelStream() customowy thread pool ale nigdy nie próbowałem.
Ja parallelStream wykorzystałem np. tutaj gdzie wiedziałem, że lista nie będzie jakaś duża
więc uznałem, że warto zrównoleglić.

1

A czy to jest coś dziwnego? Np. jak mamy pule połączeń do bazy to też kilka jakichś ciężkich zapytań może przyblokować inne itd. przez co pula się wyczerpie.

Problem jest w tym że jest to niewidzialne. Jesteś w stanie z głowy powiedzieć mi co w JVMie korzysta z tego samego forkjoinpoola i jest w stanie się tak deadlockować? :) W przypadku puli do bazy generalnie używasz jej gdzieś explicite, a tutaj nie ma to miejsca.

Ja parallelStream wykorzystałem np. tutaj gdzie wiedziałem, że lista nie będzie jakaś duża

To teraz wyobraź sobie że postanowiłeś to przetwarzać sobie teraz asynchronicznie i to RESTowe query które tam masz wrapujesz w jakieś CompletableFuture.suppyAsync() żeby wykonywało się w tle, bo nie potrzebujesz tych wyników od razu ;)

0

@Shalom:
Ok czyli wniosek taki żeby nie używać ForkJoinPool tylko tworzyć customowe thread poole?

1 użytkowników online, w tym zalogowanych: 0, gości: 1