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

Opublikowano w grupie Random-PL
Cześć! Wiedza to potęga. Im większą wiedzę posiadasz przed pierwszą rozmową kwalifikacyjną, tym pewniej się będziesz czuć. Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 12 - 1Mając odpowiednią wiedzę trudno będzie Cię zmylić, a jednocześnie będziesz w stanie miło zaskoczyć rozmówcę. Dlatego dzisiaj, bez zbędnych ceregieli, będziemy nadal wzmacniać Twoją bazę teoretyczną, badając ponad 250 pytań dla programisty Java . Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 12 - 2

103. Jakie są zasady sprawdzania wyjątków w spadku?

Jeśli dobrze rozumiem pytanie, pytają o zasady pracy z wyjątkami podczas dziedziczenia i są one następujące:
  • Przesłonięta lub zaimplementowana metoda w potomku/implementacji nie może generować sprawdzonych wyjątków, które znajdują się wyżej w hierarchii niż wyjątki w metodzie nadklasy/interfejsie.
Oznacza to, że jeśli mamy określony interfejs Animal z metodą zgłaszającą wyjątek IOException :
public  interface Animal {
   void voice() throws IOException;
}
Implementując ten interfejs, nie możemy zgłosić bardziej ogólnego zgłoszonego wyjątku (na przykład wyjątkiem , Throwable ), ale możemy zastąpić go wyjątkiem potomnym, takim jak FileNotFoundException :
public class Cat implements Animal {
   @Override
   public void voice() throws FileNotFoundException {
// некоторая реализация
   }
}
  • Konstruktor podklasy musi uwzględnić w swoim bloku rzutów wszystkie klasy wyjątków zgłoszone przez konstruktor nadklasy wywoływany podczas tworzenia obiektu.
Załóżmy, że konstruktor klasy Animal zgłasza wiele wyjątków:
public class Animal {
  public Animal() throws ArithmeticException, NullPointerException, IOException {
  }
Następnie spadkobierca klasy musi je również wskazać w konstruktorze:
public class Cat extends Animal {
   public Cat() throws ArithmeticException, NullPointerException, IOException {
       super();
   }
Lub, tak jak w przypadku metod, możesz określić nie te same wyjątki, ale bardziej ogólne. W naszym przypadku wystarczy podać bardziej ogólny wyjątek - Wyjątek , ponieważ jest to wspólny przodek wszystkich trzech rozważanych wyjątków:
public class Cat extends Animal {
   public Cat() throws Exception {
       super();
   }

104. Czy mógłbyś napisać kod informujący, kiedy blok final nie zostanie wykonany?

Najpierw przypomnijmy sobie, co w końcu jest . Wcześniej przyjrzeliśmy się mechanizmowi wychwytywania wyjątków: blok try wyznacza obszar przechwytywania, natomiast blok(i) catch to kod, który zadziała, gdy zostanie zgłoszony określony wyjątek. Wreszcie jest trzeci blok kodu po końcu , który jest wymienny z catch , ale nie wyklucza się wzajemnie. Istotą tego bloku jest to, że zawarty w nim kod zawsze działa, niezależnie od wyniku try lub catch ( niezależnie od tego, czy został zgłoszony wyjątek, czy nie). Przypadki jego awarii są bardzo rzadkie i mają charakter nietypowy. Najprostszym przypadkiem niepowodzenia jest wywołanie w powyższym kodzie metody System.exit(0) , która kończy działanie programu (wygasza go):
try {
   throw new IOException();
} catch (IOException e) {
   System.exit(0);
} finally {
   System.out.println("Данное сообщение не будет выведенно в консоль");
}
Są też inne sytuacje, w których ostatecznie nie zadziała:
  • Nieprawidłowe zakończenie programu spowodowane krytycznymi problemami z systemem lub pojawieniem się jakiegoś błędu, który spowoduje „zawieszenie” aplikacji (przykładem błędu może być ten sam błąd StackOwerflowError , który pojawia się, gdy pamięć stosu jest pełna).
  • Kiedy wątek demona przechodzi przez blok ry...finally i równolegle z tym, program się kończy. W końcu wątek demona jest wątkiem dla działań w tle, czyli nie jest priorytetowy i obowiązkowy, a aplikacja nie będzie czekać na zakończenie swojej pracy.
  • Najbardziej powszechna nieskończona pętla w try lub catch , w której przepływ pozostanie tam na zawsze:

    try {
       while (true) {
       }
    } finally {
       System.out.println("Данное сообщение не будет выведенно в консоль");
    }

To pytanie jest dość popularne w wywiadach dla nowicjuszy, dlatego warto zapamiętać kilka z tych wyjątkowych sytuacji. Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 12 - 3

105. Napisz przykład obsługi wielu wyjątków w jednym bloku catch

1) Być może pytanie zostało zadane niepoprawnie. O ile rozumiem, to pytanie oznacza wiele połowów dla jednego bloku próbnego :
try {
  throw new FileNotFoundException();
} catch (FileNotFoundException e) {
   System.out.print("Упс, у вас упало исключение - " + e);
} catch (IOException e) {
   System.out.print("Упс, у вас упало исключение - " + e);
} catch (Exception e) {
   System.out.print("Упс, у вас упало исключение - " + e);
}
Jeśli w bloku try wystąpi wyjątek , wówczas bloki catch naprzemiennie próbują go przechwycić od góry do dołu. Jeżeli określony blok catch powiedzie się, uzyskuje prawo do obsługi wyjątku, podczas gdy pozostałe bloki poniżej nie będą już w stanie spróbować go uchwycić i przetworzyć na swój własny sposób. Dlatego węższe wyjątki są umieszczane wyżej w łańcuchu bloków catch , a szersze wyjątki są umieszczane niżej. Przykładowo, jeśli w naszym pierwszym bloku catch zostanie przechwycony wyjątek klasy Wyjątek , to sprawdzone wyjątki nie będą mogły przedostać się do pozostałych bloków (pozostałe bloki z potomkami Wyjątków będą całkowicie bezużyteczne). 2) Pytanie zostało zadane poprawnie.W takim przypadku nasze przetwarzanie będzie wyglądało następująco:
try {
  throw new NullPointerException();
} catch (Exception e) {
   if (e instanceof FileNotFoundException) {
       // некоторая обработка с сужением типа (FileNotFoundException)e
   } else if (e instanceof ArithmeticException) {
       // некоторая обработка с сужением типа (ArithmeticException)e
   } else if(e instanceof NullPointerException) {
       // некоторая обработка с сужением типа (NullPointerException)e
   }
Po złapaniu wyjątku metodą catch staramy się poznać jego konkretny typ poprzez metodę instancjiof , która służy do sprawdzenia, czy obiekt należy do określonego typu, aby później móc zawęzić go do tego typu bez negatywnych konsekwencji. Obydwa rozważane podejścia można zastosować w tej samej sytuacji, przy czym stwierdziłem, że pytanie jest błędne, ponieważ drugiej opcji nie nazwałbym dobrą i nigdy się z nią nie spotkałem w swojej praktyce, a jednocześnie powszechna jest metoda pierwsza z multicatchami uwaga, rozprzestrzenianie się. Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 12 - 4

106. Który operator pozwala wymusić zgłoszenie wyjątku? Napisz przykład

Użyłem go już kilka razy powyżej, ale mimo to powtórzę to słowo kluczowe - rzut . Przykładowe użycie (wymuszenie wyjątku):
throw new NullPointerException();

107. Czy metoda główna może zgłosić wyjątek zgłaszający? Jeśli tak, to gdzie zostanie przeniesiony?

Przede wszystkim chcę zauważyć, że main to nic innego jak zwykła metoda i tak, jest wywoływana przez maszynę wirtualną, aby rozpocząć wykonywanie programu, ale poza tym można ją wywołać z dowolnego innego kodu. Oznacza to, że podlega również zwykłym zasadom określania sprawdzanych wyjątków po rzutach :
public static void main(String[] args) throws IOException {
W związku z tym mogą również wystąpić w nim wyjątki. Jeśli main nie został wywołany w jakiejś metodzie, ale został uruchomiony jako punkt uruchomienia programu, to zgłoszony przez niego wyjątek zostanie obsłużony przez przechwytywacz .UncaughtExceptionHandler . Ta procedura obsługi jest jedna na wątek (to znaczy jedna procedura obsługi w każdym wątku). Jeśli zajdzie taka potrzeba, możesz utworzyć własny moduł obsługi i ustawić go za pomocą metody setDefaultUncaughtExceptionHandler wywoływanej w obiekcie Thread .

Wielowątkowość

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

108. Jakie znasz narzędzia do pracy z wielowątkowością?

Podstawowe/podstawowe narzędzia do korzystania z wielowątkowości w Javie:
  • Synchronized to mechanizm zamykania (blokowania) metody/bloku, gdy wątek do niej wchodzi z innych wątków.
  • Volatile to mechanizm zapewniający spójny dostęp do zmiennej przez różne wątki, czyli przy obecności tego modyfikatora na zmiennej wszystkie operacje przypisania i odczytu muszą być niepodzielne. Innymi słowy, wątki nie skopiują tej zmiennej do swojej pamięci lokalnej i nie zmienią jej, ale zmienią jej pierwotną wartość.
Przeczytaj więcej o lotności tutaj .
  • Runnable to interfejs, który można zaimplementować (w szczególności jego metodę uruchamiania) w określonej klasie:
public class CustomRunnable implements Runnable {
   @Override
   public void run() {
       // некоторая логика
   }
}
Po utworzeniu obiektu tej klasy możesz rozpocząć nowy wątek, ustawiając ten obiekt w konstruktorze nowego obiektu Thread i wywołując jego metodę start() :
Runnable runnable = new CustomRunnable();
new Thread(runnable).start();
Metoda start uruchamia zaimplementowaną metodę run() w osobnym wątku.
  • Thread jest klasą, z której dziedziczy (przy nadpisywaniu metody run ):
public class CustomThread extends Thread {
   @Override
   public void run() {
       // некоторая логика
   }
}
Tworząc obiekt tej klasy i uruchamiając go za pomocą metody start() , uruchomimy w ten sposób nowy wątek:
new CustomThread().start();
  • Concurrency to pakiet z narzędziami do pracy w środowisku wielowątkowym.
Składa się ona z:
  • Kolekcje współbieżne - zestaw kolekcji wyspecjalizowanych do pracy w środowisku wielowątkowym.
  • Kolejki - wyspecjalizowane kolejki dla środowiska wielowątkowego (blokujące i nieblokujące).
  • Synchronizatory to wyspecjalizowane narzędzia do pracy w środowisku wielowątkowym.
  • Executory to mechanizmy służące do tworzenia pul wątków.
  • Blokady - mechanizmy synchronizacji wątków (bardziej elastyczne niż standardowe - synchronizacja, czekanie, powiadamianie, powiadamianie wszystkich).
  • Atomics to klasy zoptymalizowane pod kątem wykonywania wielowątkowego; każda operacja jest niepodzielna.
Przeczytaj więcej o pakiecie równoległym tutaj .

109. Porozmawiajmy o synchronizacji pomiędzy wątkami. Do czego służą metody wait(), notify() - notifyAll() Join()?

O ile rozumiem pytanie, synchronizacja między wątkami dotyczy kluczowego modyfikatora - synchronized . Modyfikator ten można umieścić bezpośrednio obok bloku:
synchronized (Main.class) {
   // некоторая логика
}
Lub bezpośrednio w sygnaturze metody:
public synchronized void move() {
   // некоторая логика}
Jak powiedziałem wcześniej, synchronized to mechanizm, który pozwala zamknąć blok/metodę z innych wątków, gdy jeden wątek już do niego wszedł. Pomyśl o bloku/metodzie jak o pokoju. Jakiś strumień, gdy do niego dotrze, wejdzie do niego i zamknie go, inne strumienie, przyjdą do pokoju i zobaczą, że jest zamknięte, będą czekać w pobliżu niego, aż będzie wolne. Po wykonaniu swoich obowiązków pierwszy wątek opuszcza pokój i zwalnia klucz. I nie bez powodu ciągle mówiłem o kluczu, ponieważ on naprawdę istnieje. Jest to specjalny obiekt, który ma stan zajęty/wolny. Obiekt ten jest dołączony do każdego obiektu Java, więc korzystając z bloku zsynchronizowanego, musimy wskazać w nawiasie obiekt, dla którego muteksu chcemy zamknąć drzwi:
Cat cat = new Cat();
synchronized (cat) {
   // некоторая логика
}
Możesz także użyć muteksu klasy, tak jak to zrobiłem w pierwszym przykładzie ( Main.class ). Kiedy używamy synchronizacji w metodzie, nie określamy obiektu, na którym chcemy zamknąć, prawda? W tym przypadku, w przypadku metody niestatycznej, zostanie ona zamknięta na muteksie tego obiektu , czyli bieżącym obiekcie tej klasy. Statyczny zostanie zamknięty na muteksie bieżącej klasy ( this.getClass(); ). Więcej o mutexie możesz przeczytać tutaj . Cóż, przeczytaj o synchronizacji tutaj . Wait() to metoda, która zwalnia muteks i przełącza bieżący wątek w tryb gotowości, tak jakby był podłączony do bieżącego monitora (coś w rodzaju kotwicy). Z tego powodu tę metodę można wywołać tylko z zsynchronizowanego bloku lub metody (w przeciwnym razie, co powinna zwolnić i czego się spodziewać). Należy również pamiętać, że jest to metoda klasy Object . Dokładniej, nie jeden, ale nawet trzy:
  • Wait() — ustawia bieżący wątek w tryb oczekiwania, dopóki inny wątek nie wywoła metody notify() lub notifyAll() dla tego obiektu (o tych metodach porozmawiamy później).

  • Czekaj (długi limit czasu) — przełącza bieżący wątek w tryb oczekiwania do czasu, aż inny wątek wywoła metodę notify() lub notifyAll() na tym obiekcie lub upłynie określony limit czasu .

  • Wait (long timeout, int nanos) - podobnie jak poprzednio, tylko nanos pozwala na określenie nanosekund (bardziej precyzyjne ustawienie czasu).

  • Notify() to metoda pozwalająca obudzić jeden losowy wątek bieżącego bloku synchronizacji. Ponownie można go wywołać tylko w zsynchronizowanym bloku lub metodzie (w końcu w innych miejscach nie będzie miał kogo odmrozić).

  • NotifyAll() to metoda, która budzi wszystkie oczekujące wątki na bieżącym monitorze (używana również tylko w zsynchronizowanym bloku lub metodzie).

110. Jak zatrzymać przepływ?

Pierwszą rzeczą, którą należy powiedzieć, jest to, że gdy metoda run() zostanie w pełni wykonana , wątek zostanie automatycznie zniszczony. Ale czasami trzeba go zabić przed terminem, zanim ta metoda się zakończy. Co zatem powinniśmy zrobić? Być może obiekt Thread powinien mieć metodę stop() ? Nieważne jak to jest! Ta metoda jest uważana za przestarzałą i może prowadzić do awarii systemu. Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 12 - 6No i co wtedy? Można to zrobić na dwa sposoby: Pierwszym jest użycie wewnętrznej flagi logicznej. Spójrzmy na przykład. Mamy własną implementację wątku, który powinien wyświetlać określoną frazę na ekranie, aż do całkowitego zatrzymania:
public class CustomThread extends Thread {
private boolean isActive;

   public CustomThread() {
       this.isActive = true;
   }

   @Override
   public void run() {
       {
           while (isActive) {
               System.out.println("Поток выполняет некую логику...");
           }
           System.out.println("Поток остановлен!");
       }
   }

   public void stopRunningThread() {
       isActive = false;
   }
}
W przypadku użycia metody stopRunning() flaga wewnętrzna staje się fałszywa i metoda uruchamiania przestaje działać. Uruchommy to w trybie głównym :
System.out.println("Начало выполнения программы");
CustomThread thread = new CustomThread();
thread.start();
Thread.sleep(3);
// пока наш основной поток спит, вспомогательный  CustomThread работает и выводит в коноль своё сообщение
thread.stopRunningThread();
System.out.println("Конец выполнения программы");
W efekcie w konsoli zobaczymy coś takiego:
Rozpoczęcie wykonywania programu Wątek wykonuje jakąś logikę... Wątek wykonuje jakąś logikę... Wątek wykonuje jakąś logikę... Wątek wykonuje jakąś logikę... Wątek wykonuje jakąś logikę... wątek wykonuje jakąś logikę... Koniec wykonywania programu Wątek został zatrzymany!
Oznacza to, że nasz wątek zadziałał, wysłał określoną liczbę komunikatów do konsoli i został pomyślnie zatrzymany. Zauważam, że liczba wysyłanych komunikatów będzie się różnić w zależności od uruchomienia; czasami dodatkowy wątek nawet niczego nie wyświetlał. Jak zauważyłem, zależy to od czasu uśpienia głównego wątku, im dłuższy, tym mniejsza szansa, że ​​dodatkowy wątek nic nie wygeneruje. Przy czasie uśpienia wynoszącym 1 ms komunikaty prawie nigdy nie są wysyłane, ale jeśli ustawisz go na 20 ms, prawie zawsze działa. Być może, gdy czasu jest mało, wątek po prostu nie ma czasu na rozpoczęcie i rozpoczęcie pracy i zostaje natychmiast zatrzymany. Drugi sposób polega na użyciu metody przerwania() na obiekcie Thread , która zwraca wartość wewnętrznej flagi przerwania ( domyślnie ta flaga ma wartość false ) i jej innej metody przerwania() , która ustawia tę flagę na wartość true (kiedy flaga jest prawdziwa , wątek powinien przestać działać). Spójrzmy na przykład:
public class CustomThread extends Thread {

   @Override
   public void run() {
       {
           while (!Thread.interrupted()) {
               System.out.println("Поток выполняет некую логику...");
           }
           System.out.println("Поток остановлен!");
       }
   }
}
Uruchom w głównym :
System.out.println("Начало выполнения программы");
Thread thread = new CustomThread();
thread.start();
Thread.sleep(3);
thread.interrupt();
System.out.println("Конец выполнения программы");
Wynik wykonania będzie taki sam jak w pierwszym przypadku, jednak bardziej podoba mi się to podejście: piszemy mniej kodu i korzystamy z większej ilości gotowych, standardowych funkcjonalności. Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 12 - 7Na tym dzisiaj się zatrzymamy.Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 12 - 8
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION