CodeGym/Blog Java/Random-PL/Przeglądanie pytań i odpowiedzi z rozmowy kwalifikacyjnej...
John Squirrels
Poziom 41
San Francisco

Przeglądanie pytań i odpowiedzi z rozmowy kwalifikacyjnej na stanowisko programisty Java. Część 12

Opublikowano w grupie Random-PL
Cześć! Wiedza to potęga. Im więcej wiedzy zdobędziesz podczas pierwszej rozmowy kwalifikacyjnej, tym pewniej się poczujesz. Przeglądanie pytań i odpowiedzi z rozmowy kwalifikacyjnej na stanowisko programisty Java.  Część 12 - 1Jeśli zaangażujesz się w rozmowę z dużym mózgiem pełnym wiedzy, rozmówcy nie będzie trudno wprowadzić Cię w błąd i prawdopodobnie będzie mile zaskoczony. Zatem bez zbędnych ceregieli, dzisiaj będziemy nadal wzmacniać Twoją bazę teoretyczną, przeglądając pytania do programisty Java.

103. Jakie zasady obowiązują przy sprawdzaniu wyjątków podczas dziedziczenia?

Jeśli dobrze rozumiem pytanie, pytają o zasady pracy z wyjątkami podczas dziedziczenia. Odpowiednie zasady są 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.
Załóżmy na przykład, że mamy interfejs Animal z metodą zgłaszającą wyjątek IOException :
public interface Animal {
   void speak() throws IOException;
}
Implementując ten interfejs, nie możemy ujawnić bardziej ogólnego wyjątku, który można zgłosić (np.Exception , Throwable ) , ale możemy zastąpić istniejący wyjątek podklasą, taką jak FileNotFoundException :
public class Cat implements Animal {
   @Override
   public void speak() throws FileNotFoundException {
// Some implementation
   }
}
  • Klauzula rzutów konstruktora podklasy musi zawierać wszystkie klasy wyjątków zgłoszone przez konstruktor nadklasy wywołany w celu utworzenia obiektu.
Załóżmy, że konstruktor klasy Animal zgłasza wiele wyjątków:
public class Animal {
  public Animal() throws ArithmeticException, NullPointerException, IOException {
  }
Następnie konstruktor podklasy również musi je rzucić:
public class Cat extends Animal {
   public Cat() throws ArithmeticException, NullPointerException, IOException {
       super();
   }
Lub, podobnie jak w przypadku metod, możesz określić inne, bardziej ogólne wyjątki. W naszym przypadku możemy wskazać Wyjątek , ponieważ jest on bardziej ogólny i jest wspólnym przodkiem wszystkich trzech wyjątków wskazanych w nadklasie:
public class Cat extends Animal {
   public Cat() throws Exception {
       super();
   }

104. Czy możesz napisać kod, w którym blok final nie zostanie wykonany?

Najpierw przypomnijmy sobie, co w końcu jest. Wcześniej sprawdziliśmy mechanizm wyłapywania wyjątków: blok try wskazuje, gdzie zostaną przechwycone wyjątki, a blok(i) catch to kod, który zostanie wywołany po przechwyceniu odpowiedniego wyjątku. Trzeci blok kodu oznaczony słowem kluczowym last może zastąpić bloki catch lub wystąpić po nich. Ideą tego bloku jest to, że jego kod jest zawsze wykonywany niezależnie od tego, co dzieje się w bloku try lub catch (niezależnie od tego, czy istnieje wyjątek, czy nie). Przypadki, w których ten blok nie jest wykonywany, są rzadkie i nietypowe. Najprostszym przykładem jest wywołanie System.exit(0) przed blokiem last, co kończy działanie programu:
try {
   throw new IOException();
} catch (IOException e) {
   System.exit(0);
} finally {
   System.out.println("This message will not be printed on the console");
}
Istnieją również inne sytuacje, w których blok final nie zostanie uruchomiony:
  • Na przykład nieprawidłowe zakończenie programu spowodowane krytycznymi błędami systemu lub jakiś błąd powodujący awarię aplikacji (na przykład StackOverflowError występujący w przypadku przepełnienia stosu aplikacji).

  • Inna sytuacja ma miejsce, gdy wątek demona wchodzi do bloku try-finally , ale wtedy główny wątek programu kończy się. W końcu wątki demonów służą do pracy w tle, która nie ma wysokiego priorytetu ani jest obowiązkowa, więc aplikacja nie będzie czekać na ich zakończenie.

  • Najbardziej bezmyślnym przykładem jest nieskończona pętla wewnątrz bloku try or catch — gdy już znajdziesz się w środku, wątek utknie tam na zawsze:

    try {
       while (true) {
       }
    } finally {
       System.out.println("This message will not be printed on the console");
    }
To pytanie jest bardzo popularne podczas rozmów kwalifikacyjnych z młodszymi programistami, dlatego dobrze jest zapamiętać kilka takich wyjątkowych sytuacji. Przeglądanie pytań i odpowiedzi z rozmowy kwalifikacyjnej na stanowisko programisty Java.  Część 12 - 2

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

1) Nie jestem pewien, czy to pytanie zostało zadane poprawnie. O ile rozumiem, to pytanie odnosi się do kilku bloków catch i jednej próby :
try {
  throw new FileNotFoundException();
} catch (FileNotFoundException e) {
   System.out.print("Oops! There was an exception: " + e);
} catch (IOException e) {
   System.out.print("Oops! There was an exception: " + e);
} catch (Exception e) {
   System.out.print("Oops! There was an exception: " + e);
}
Jeśli w bloku try zostanie zgłoszony wyjątek , wówczas powiązane bloki catch próbują go przechwycić, sekwencyjnie od góry do dołu. Gdy wyjątek pasuje do jednego z bloków catch , żadne pozostałe bloki nie będą już w stanie go przechwycić i obsłużyć. Wszystko to oznacza, że ​​węższe wyjątki są umieszczone powyżej bardziej ogólnych w zestawie bloków catch . Przykładowo, jeśli nasz pierwszy blok catch przechwyci klasę Wyjątek , to kolejne bloki nie będą przechwytywać sprawdzanych wyjątków (czyli pozostałe bloki z podklasami Wyjątku będą całkowicie bezużyteczne). 2) A może pytanie zostało zadane poprawnie. W takim przypadku moglibyśmy obsłużyć wyjątki w następujący sposób:
try {
  throw new NullPointerException();
} catch (Exception e) {
   if (e instanceof FileNotFoundException) {
       // Some handling that involves a narrowing type conversion: (FileNotFoundException)e
   } else if (e instanceof ArithmeticException) {
       // Some handling that involves a narrowing type conversion: (ArithmeticException)e
   } else if(e instanceof NullPointerException) {
       // Some handling that involves a narrowing type conversion: (NullPointerException)e
   }
Po użyciu catch do przechwycenia wyjątku, następnie próbujemy odkryć jego konkretny typ za pomocą operatora instancjaof , który sprawdza, czy obiekt należy do określonego typu. Dzięki temu możemy śmiało wykonać konwersję typu zawężającego bez obawy o negatywne konsekwencje. W tej samej sytuacji moglibyśmy zastosować oba podejścia. Wyraziłem wątpliwości co do tego pytania tylko dlatego, że drugiej opcji nie nazwałbym dobrym podejściem. Z mojego doświadczenia wynika, że ​​nigdy się z tym nie spotkałem, a pierwsze podejście polegające na wielu blokach catch jest szeroko rozpowszechnione.

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

Użyłem go już kilkukrotnie w powyższych przykładach, ale powtórzę jeszcze raz: słowo kluczowe rzut . Przykład ręcznego zgłoszenia wyjątku:
throw new NullPointerException();

107. Czy metoda main może zgłosić wyjątek? Jeśli tak, to dokąd to zmierza?

Przede wszystkim chcę zauważyć, że główna metoda to nic innego jak zwykła metoda. Tak, jest wywoływany przez maszynę wirtualną w celu rozpoczęcia wykonywania programu, ale poza tym można go wywołać z dowolnego innego kodu. Oznacza to, że podlega również zwykłym zasadom dotyczącym wskazywania sprawdzanych wyjątków po słowie kluczowym rzuca :
public static void main(String[] args) throws IOException {
W związku z tym może zgłaszać wyjątki. Gdy main zostanie wywołany jako punkt startowy programu (a nie inną metodą), wówczas każdy zgłoszony wyjątek zostanie obsłużony przez UncaughtExceptionHandler . Każdy wątek ma jedną taką procedurę obsługi (to znaczy, że w każdym wątku istnieje jedna taka procedura obsługi). Jeśli to konieczne, możesz utworzyć własny moduł obsługi i ustawić go, wywołując publiczną statyczną pustkę main(String[] args) zgłasza wyjątek IOException {metoda setDefaultUncaughtExceptionHandler na publicznej statycznej pustce main(String[] args) zgłasza wyjątek IOException {obiekt wątku.

Wielowątkowość

Przeglądanie pytań i odpowiedzi z rozmowy kwalifikacyjnej na stanowisko programisty Java.  Część 12 - 3

108. Jakie znasz mechanizmy pracy w środowisku wielowątkowym?

Podstawowe mechanizmy wielowątkowości w Javie to:
  • Słowo kluczowe synchronized , które umożliwia wątkowi zablokowanie metody/bloku po wejściu, uniemożliwiając wejście innym wątkom.

  • Słowo kluczowe volatile zapewnia spójny dostęp do zmiennej, do której uzyskują dostęp różne wątki. Oznacza to, że gdy ten modyfikator zostanie zastosowany do zmiennej, wszystkie operacje przypisywania i odczytywania tej zmiennej stają się niepodzielne. Innymi słowy, wątki nie skopiują zmiennej do swojej pamięci lokalnej i nie zmienią jej. Zmienią jego pierwotną wartość.

  • Runnable — Możemy zaimplementować ten interfejs (który składa się z pojedynczej metody run() ) w jakiejś klasie:

    public class CustomRunnable implements Runnable {
       @Override
       public void run() {
           // Some logic
       }
    }

    Kiedy już utworzymy obiekt tej klasy, możemy rozpocząć nowy wątek, przekazując nasz obiekt konstruktorowi Thread , a następnie wywołując metodę start() :

    Runnable runnable = new CustomRunnable();
    new Thread(runnable).start();

    Metoda start uruchamia zaimplementowaną metodę run() w osobnym wątku.

  • Thread — Możemy dziedziczyć tę klasę i nadpisywać jej metodę uruchamiania :

    public class CustomThread extends Thread {
       @Override
       public void run() {
           // Some logic
       }
    }

    Nowy wątek możemy rozpocząć tworząc obiekt tej klasy, a następnie wywołując metodę start() :

    new CustomThread().start();

  • Współbieżność — Jest to pakiet narzędzi do pracy w środowisku wielowątkowym.

    Składa się ona z:

    • Kolekcje współbieżne — jest to kolekcja kolekcji stworzona specjalnie do pracy w środowisku wielowątkowym.

    • Kolejki — wyspecjalizowane kolejki dla środowiska wielowątkowego (blokujące i nieblokujące).

    • Synchronizatory — są to wyspecjalizowane narzędzia do pracy w środowisku wielowątkowym.

    • Executory — mechanizmy tworzenia pul wątków.

    • Blokady — mechanizmy synchronizacji wątków bardziej elastyczne niż standardowe (synchronizacja, czekanie, powiadamianie, powiadamianie wszystkich).

    • Atomics — klasy zoptymalizowane pod kątem wielowątkowości. Każda z ich operacji jest atomowa.

109. Opowiedz nam o synchronizacji pomiędzy wątkami. Do czego służą metody Wait(), Notify(), NotifyAll() i Join()?

Synchronizacja między wątkami dotyczy słowa kluczowego synchronized . Modyfikator ten można umieścić bezpośrednio na bloku:
synchronized (Main.class) {
   // Some logic
}
Lub bezpośrednio w sygnaturze metody:
public synchronized void move() {
   // Some logic }
Jak powiedziałem wcześniej, synchronized to mechanizm blokowania bloku/metody z innymi wątkami po wejściu jednego wątku. Pomyślmy o bloku/metodzie kodu jak o pokoju. Jakaś nić zbliża się do pokoju, wchodzi do niego i zamyka drzwi na klucz. Kiedy inne wątki zbliżają się do pokoju, widzą, że drzwi są zamknięte i czekają w pobliżu, aż pokój stanie się dostępny. Gdy pierwszy wątek zakończy swoje sprawy w pokoju, otwiera drzwi, opuszcza pokój i zwalnia klucz. Wspomniałem o kluczu kilka razy nie bez powodu — ponieważ naprawdę istnieje coś analogicznego. Jest to specjalny obiekt, który ma stan zajęty/wolny. Każdy obiekt w Javie ma taki obiekt, więc gdy używamy bloku synchronized , musimy użyć nawiasów, aby wskazać obiekt, którego muteks zostanie zablokowany:
Cat cat = new Cat();
synchronized (cat) {
   // Some logic
}
Możemy również użyć muteksu powiązanego z klasą, tak jak to zrobiłem w pierwszym przykładzie ( Main.class ). W końcu, gdy używamy metody synchronized , nie określamy obiektu, który chcemy zablokować, prawda? W tym przypadku dla metod niestatycznych muteksem, który zostanie zablokowany jest obiekt this , czyli bieżący obiekt klasy. W przypadku metod statycznych muteks powiązany z bieżącą klasą ( this.getClass(); ) jest zablokowany. Wait() to metoda, która zwalnia muteks i umieszcza bieżący wątek w stanie oczekiwania, tak jakby był podłączony do bieżącego monitora (coś w rodzaju kotwicy). Z tego powodu tę metodę można wywołać tylko z synchronizowanego bloku lub metody. W przeciwnym razie na co by czekał i co zostałby uwolniony?). Należy również pamiętać, że jest to metoda klasy Object . No cóż, nie jeden, ale trzy:
  • Wait() umieszcza bieżący wątek w stanie oczekiwania do czasu, aż inny wątek wywoła na tym obiekcie metodę notify() lub notifyAll() (o tych metodach porozmawiamy później).

  • Wait(long timeout) ustawia bieżący wątek w stan oczekiwania do czasu, aż inny wątek wywoła metodę notify() lub notifyAll() na tym obiekcie lub upłynie przedział czasu określony przez timeout.

  • Wait(long timeout, int nanos) działa jak poprzednia metoda, ale tutaj nanos pozwala określić nanosekundy (bardziej precyzyjny limit czasu).

  • notify() pozwala obudzić jeden losowy wątek oczekujący na bieżący blok synchronizacji. Ponownie, tę metodę można wywołać tylko w zsynchronizowanym bloku lub metodzie (w końcu w innych miejscach nie byłoby kogo obudzić).

  • notifyAll() budzi wszystkie wątki oczekujące na bieżącym monitorze (używane również tylko w zsynchronizowanym bloku lub metodzie).

110. Jak zatrzymać wątek?

Pierwszą rzeczą do powiedzenia jest to, że gdy run() zakończy się, wątek zakończy się automatycznie. Ale czasami chcemy zabić wątek przed terminem, zanim metoda zostanie wykonana. Więc co robimy? Być może moglibyśmy użyć metody stop() na obiekcie Thread ? Nie! Ta metoda jest przestarzała i może powodować awarie systemu. Przeglądanie pytań i odpowiedzi z rozmowy kwalifikacyjnej na stanowisko programisty Java.  Część 12 - 4No i co wtedy? Można to zrobić na dwa sposoby: Najpierw użyj wewnętrznej flagi logicznej. Spójrzmy na przykład. Mamy naszą implementację wątku, który powinien wyświetlać określoną frazę na ekranie, aż wątek całkowicie się zatrzyma:
public class CustomThread extends Thread {
private boolean isActive;

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

   @Override
   public void run() {
       {
           while (isActive) {
               System.out.println("The thread is executing some logic...");
           }
           System.out.println("The thread stopped!");
       }
   }

   public void stopRunningThread() {
       isActive = false;
   }
}
Wywołanie metody stopRunningThread() ustawia flagę wewnętrzną na wartość false, co powoduje zakończenie metody run() . Nazwijmy to w main :
System.out.println("Program starting...");
CustomThread thread = new CustomThread();
thread.start();
Thread.sleep(3);
// As long as our main thread is asleep, our CustomThread runs and prints its message on the console
thread.stopRunningThread();
System.out.println("Program stopping...");
W efekcie w konsoli zobaczymy coś takiego:
Uruchamianie 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ę... Zatrzymywanie programu... Wątek się zatrzymał!
Oznacza to, że nasz wątek został uruchomiony, wydrukował kilka komunikatów na konsoli, a następnie pomyślnie się zatrzymał. Należy pamiętać, że liczba wyświetlanych komunikatów będzie się różnić w zależności od uruchomienia. Czasami wątek pomocniczy może w ogóle nic nie wyświetlać. Konkretne zachowanie zależy od tego, jak długo główny wątek śpi. Im dłużej śpi, tym mniejsze jest prawdopodobieństwo, że wątek pomocniczy nie zdąży nic wyświetlić. Przy czasie uśpienia wynoszącym 1 ms prawie nigdy nie zobaczysz wiadomości. Ale jeśli ustawisz go na 20 ms, komunikaty będą prawie zawsze wyświetlane. Kiedy czas snu jest krótki, wątek po prostu nie ma czasu na uruchomienie i wykonanie swojej pracy. Zamiast tego zostaje natychmiast zatrzymany. Drugi sposób polega na użyciu metody przerwanej() na obiekcie Thread . Zwraca wartość wewnętrznej flagi przerwania, która domyślnie ma wartość false . Lub jego metoda przerwania() , która ustawia tę flagę na 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("The thread is executing some logic...");
           }
           System.out.println("The thread stopped!");
       }
   }
}
Działa w trybie głównym :
System.out.println("Program starting...");
Thread thread = new CustomThread();
thread.start();
Thread.sleep(3);
thread.interrupt();
System.out.println("Program stopping...");
Efekt uruchomienia jest taki sam jak w pierwszym przypadku, jednak bardziej podoba mi się to podejście: napisaliśmy mniej kodu i wykorzystaliśmy więcej gotowych, standardowych funkcjonalności. Cóż, to tyle na dziś!
Komentarze
  • Popularne
  • Najnowsze
  • Najstarsze
Musisz się zalogować, aby dodać komentarz
Ta strona nie ma jeszcze żadnych komentarzy