JavaRush /Blog Java /Random-PL /50 najpopularniejszych pytań i odpowiedzi do rozmów kwali...
Roman Beekeeper
Poziom 35

50 najpopularniejszych pytań i odpowiedzi do rozmów kwalifikacyjnych dotyczących języka Java Core. Część 3

Opublikowano w grupie Random-PL
50 najpopularniejszych pytań i odpowiedzi do rozmów kwalifikacyjnych dotyczących języka Java Core. Część 1 50 najważniejszych pytań i odpowiedzi podczas rozmowy kwalifikacyjnej Java Core. Część 2

Wielowątkowość

37. Jak utworzyć nowy wątek (przepływ) w Javie?

Tak czy inaczej, tworzenie odbywa się poprzez użycie klasy Thread. Ale tutaj mogą być opcje...
  1. Dziedziczymy odjava.lang.Thread
  2. Implementujemy interfejs , którego obiekt akceptuje klasę java.lang.RunnablekonstruktoraThread
Porozmawiajmy o każdym z nich.

Dziedziczymy z klasy Thread

Aby to zadziałało, w naszej klasie dziedziczymy z java.lang.Thread. Zawiera metamfetaminę run(), czyli dokładnie to, czego potrzebujemy. Całe życie i logika nowego wątku będzie w tej metodzie. Jest to swego rodzaju mainmetoda na nowy wątek. Następnie pozostaje już tylko stworzyć obiekt naszej klasy i wykonać metodę start(), która utworzy nowy wątek i uruchomi zapisaną w nim logikę. Spójrzmy:
/**
* Пример того, Jak создавать треды путем наследования {@link Thread} класса.
*/
class ThreadInheritance extends Thread {

   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName());
   }

   public static void main(String[] args) {
       ThreadInheritance threadInheritance1 = new ThreadInheritance();
       ThreadInheritance threadInheritance2 = new ThreadInheritance();
       ThreadInheritance threadInheritance3 = new ThreadInheritance();
       threadInheritance1.start();
       threadInheritance2.start();
       threadInheritance3.start();
   }
}
Dane wyjściowe na konsolę będą wyglądać następująco:

Thread-1
Thread-0
Thread-2
Oznacza to, że nawet tutaj widzimy, że wątki są wykonywane nie po kolei, ale zgodnie z decyzją JVM)

Implementacja interfejsu Runnable

Jeśli jesteś przeciwny dziedziczeniu i/lub już dziedziczysz jedną z pozostałych klas, możesz użyć metody java.lang.Runnable. Tutaj w naszej klasie implementujemy ten interfejs i implementujemy metodę run(), tak jak to było w tym przykładzie. Musisz tylko stworzyć więcej obiektów Thread. Wydawać by się mogło, że im więcej linii, tym gorzej. Ale wiemy, jak szkodliwe jest dziedziczenie i że lepiej go za wszelką cenę unikać ;) Spójrzmy:
/**
* Пример того, Jak создавать треды из интерфейса {@link Runnable}.
* Здесь проще простого - реализуем этот интерфейс и потом передаем в конструктор
* экземпляр реализуемого obiektа.
*/
class ThreadInheritance implements Runnable {

   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName());
   }

   public static void main(String[] args) {
       ThreadInheritance runnable1 = new ThreadInheritance();
       ThreadInheritance runnable2 = new ThreadInheritance();
       ThreadInheritance runnable3 = new ThreadInheritance();

       Thread threadRunnable1 = new Thread(runnable1);
       Thread threadRunnable2 = new Thread(runnable2);
       Thread threadRunnable3 = new Thread(runnable3);

       threadRunnable1.start();
       threadRunnable2.start();
       threadRunnable3.start();
   }
}
I wynik wykonania:

Thread-0
Thread-1
Thread-2

38. Jaka jest różnica pomiędzy procesem a wątkiem?

50 najpopularniejszych pytań i odpowiedzi do rozmów kwalifikacyjnych dotyczących języka Java Core.  Część 3 - 1Istnieją następujące różnice pomiędzy procesem a wątkiem:
  1. Wykonywany program nazywany jest procesem, natomiast wątek jest podzbiorem procesu.
  2. Procesy są niezależne, podczas gdy wątki stanowią podzbiór procesu.
  3. Procesy mają różną przestrzeń adresową w pamięci, podczas gdy wątki mają wspólną przestrzeń adresową.
  4. Przełączanie kontekstu jest szybsze między wątkami w porównaniu do procesów.
  5. Komunikacja między procesami jest wolniejsza i droższa niż komunikacja między wątkami.
  6. Wszelkie zmiany w procesie nadrzędnym nie wpływają na proces potomny, natomiast zmiany w wątku nadrzędnym mogą wpływać na wątek potomny.

39. Jakie są zalety wielowątkowości?

50 najpopularniejszych pytań i odpowiedzi do rozmów kwalifikacyjnych dotyczących języka Java Core.  Część 3 - 2
  1. Wielowątkowość umożliwia aplikacji/programowi zawsze reagowanie na wprowadzane dane, nawet jeśli wykonuje już pewne zadania w tle;
  2. Wielowątkowość umożliwia szybsze wykonywanie zadań, ponieważ wątki wykonują się niezależnie;
  3. Wielowątkowość zapewnia lepsze wykorzystanie pamięci podręcznej, ponieważ wątki współdzielą wspólne zasoby pamięci;
  4. Wielowątkowość zmniejsza ilość wymaganego serwera, ponieważ jeden serwer może jednocześnie uruchamiać wiele wątków.

40. Jakie są stany w cyklu życia wątku?

50 najpopularniejszych pytań i odpowiedzi do rozmów kwalifikacyjnych dotyczących języka Java Core.  Część 3 - 3
  1. Nowość: W tym stanie obiekt klasy Threadjest tworzony przy użyciu operatora new, ale wątek nie istnieje. Wątek nie rozpocznie się, dopóki nie wywołamy metody start().
  2. Runnable: W tym stanie wątek jest gotowy do uruchomienia po wywołaniu metody początek(). Jednak nie został on jeszcze wybrany przez planistę wątków.
  3. Uruchomiony: w tym stanie program planujący wątki wybiera wątek ze stanu gotowości i uruchamia go.
  4. Oczekiwanie/Zablokowany: W tym stanie wątek nie jest uruchomiony, ale nadal żyje lub czeka na zakończenie innego wątku.
  5. Martwy/Zakończony: Po zakończeniu metody run()wątek jest w stanie zakończonym lub martwym.

41. Czy można rozpocząć wątek dwa razy?

Nie, nie możemy zrestartować wątku, ponieważ po uruchomieniu i wykonaniu wątek przechodzi w stan Martwy. Jeśli więc spróbujemy uruchomić wątek dwukrotnie, zgłosi wyjątek runtimeException „ java.lang.IllegalThreadStateException ”. Spójrzmy:
class DoubleStartThreadExample extends Thread {

   /**
    * Имитируем работу треда
    */
   public void run() {
	// что-то происходит. Для нас не существенно на этом этапе
   }

   /**
    * Запускаем тред дважды
    */
   public static void main(String[] args) {
       DoubleStartThreadExample doubleStartThreadExample = new DoubleStartThreadExample();
       doubleStartThreadExample.start();
       doubleStartThreadExample.start();
   }
}
Gdy tylko praca osiągnie drugi początek tego samego wątku, nastąpi wyjątek. Spróbujcie sami ;) Lepiej raz zobaczyć niż sto razy usłyszeć.

42. Co się stanie, jeśli wywołasz metodę run() bezpośrednio, bez wywoływania metody start()?

Tak, run()oczywiście możesz wywołać metodę, ale nie spowoduje to utworzenia nowego wątku i wykonania go jako osobnego wątku. W tym przypadku jest to prosty obiekt wywołujący prostą metodę. Jeśli mówimy o metodzie start(), to jest to inna sprawa. Uruchamiając tę ​​metodę, runtimeuruchamia nową, a ona z kolei uruchamia naszą metodę ;) Jeśli mi nie wierzysz, spróbuj:
class ThreadCallRunExample extends Thread {

   public void run() {
       for (int i = 0; i < 5; i++) {
           System.out.print(i);
       }
   }

   public static void main(String args[]) {
       ThreadCallRunExample runExample1 = new ThreadCallRunExample();
       ThreadCallRunExample runExample2 = new ThreadCallRunExample();

       // просто будут вызваны в потоке main два метода, один за другим.
       runExample1.run();
       runExample2.run();
   }
}
Dane wyjściowe na konsolę będą wyglądać następująco:

0123401234
Widać, że nie powstał żaden wątek. Wszystko działało jak na normalnych zajęciach. Najpierw zadziałała metoda pierwszej klasy, potem druga.

43. Co to jest wątek demona?

50 najpopularniejszych pytań i odpowiedzi do rozmów kwalifikacyjnych dotyczących języka Java Core.  Część 3 - 4Wątek demona (zwany dalej wątkiem demona) to wątek wykonujący zadania w tle w stosunku do innego wątku. Oznacza to, że jego zadaniem jest wykonywanie zadań pomocniczych, które należy wykonać tylko w połączeniu z innym (głównym) wątkiem. Istnieje wiele wątków demonów, które działają automatycznie, takich jak Garbage Collector, finalizator itp.

Dlaczego Java zamyka wątek demona?

Jedynym celem wątku demona jest świadczenie usług wątkowi użytkownika w celu wykonania zadania obsługi w tle. Dlatego też, jeśli wątek główny zostanie zakończony, środowisko wykonawcze automatycznie zamknie wszystkie wątki demona.

Metody pracy w klasie Thread

Klasa java.lang.Threadudostępnia dwie metody pracy z demonem wątku:
  1. public void setDaemon(boolean status)- wskazuje, że będzie to wątek demona. Wartość domyślna to false, co oznacza, że ​​zostaną utworzone wątki inne niż demony, chyba że określono to osobno.
  2. public boolean isDaemon()- zasadniczo jest to moduł pobierający zmienną daemon, którą ustawiliśmy przy użyciu poprzedniej metody.
Przykład:
class DaemonThreadExample extends Thread {

   public void run() {
       // Проверяет, демон ли этот поток Lub нет
       if (Thread.currentThread().isDaemon()) {
           System.out.println("daemon thread");
       } else {
           System.out.println("user thread");
       }
   }

   public static void main(String[] args) {
       DaemonThreadExample thread1 = new DaemonThreadExample();
       DaemonThreadExample thread2 = new DaemonThreadExample();
       DaemonThreadExample thread3 = new DaemonThreadExample();

       // теперь thread1 - поток-демон.
       thread1.setDaemon(true);

       System.out.println("демон?.. " + thread1.isDaemon());
       System.out.println("демон?.. " + thread2.isDaemon());
       System.out.println("демон?.. " + thread3.isDaemon());

       thread1.start();
       thread2.start();
       thread3.start();
   }
}
Wyjście konsoli:

демон?.. true
демон?.. false
демон?.. false
daemon thread
user thread
user thread
Z wyników widzimy, że wewnątrz samego wątku, stosując currentThread()metodę statyczną, możemy z jednej strony dowiedzieć się, który to wątek, z drugiej strony, jeśli mamy odniesienie do obiektu tego wątku, możemy się tego dowiedzieć bezpośrednio z niego. Daje to niezbędną elastyczność konfiguracji.

44. Czy można uczynić wątek demonem po jego utworzeniu?

NIE. Jeśli to zrobisz, zgłosi wyjątek IllegalThreadStateException. Dlatego wątek demona możemy utworzyć jedynie przed jego uruchomieniem. Przykład:
class SetDaemonAfterStartExample extends Thread {

   public void run() {
       System.out.println("Working...");
   }

   public static void main(String[] args) {
       SetDaemonAfterStartExample afterStartExample = new SetDaemonAfterStartExample();
       afterStartExample.start();

       // здесь будет выброшено исключение
       afterStartExample.setDaemon(true);
   }
}
Wyjście konsoli:

Working...
Exception in thread "main" java.lang.IllegalThreadStateException
	at java.lang.Thread.setDaemon(Thread.java:1359)
	at SetDaemonAfterStartExample.main(SetDaemonAfterStartExample.java:14)

45. Co to jest hak zamykający?

Shutdownhook to wątek, który jest wywoływany niejawnie przed zamknięciem JVM (wirtualnej maszyny Java). Możemy więc użyć go do oczyszczenia zasobu lub zapisania stanu, gdy wirtualna maszyna Java wyłącza się normalnie lub nagle. Możemy dodać shutdown hookza pomocą następującej metody:
Runtime.getRuntime().addShutdownHook(new ShutdownHookThreadExample());
Jak pokazano na przykładzie:
/**
* Программа, которая показывает Jak запустить shutdown hook тред,
* который выполнится аккурат до окончания работы JVM
*/
class ShutdownHookThreadExample extends Thread {

   public void run() {
       System.out.println("shutdown hook задачу выполнил");
   }

   public static void main(String[] args) {

       Runtime.getRuntime().addShutdownHook(new ShutdownHookThreadExample());

       System.out.println("Теперь программа засыпает, нажмите ctrl+c чтоб завершить ее.");
       try {
           Thread.sleep(60000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
   }
}
Wyjście konsoli:

Теперь программа засыпает, нажмите ctrl+c чтоб завершить ее.
shutdown hook задачу выполнил

46. ​​​​Co to jest synchronizacja?

Synchronizacja w Javie to możliwość kontrolowania dostępu wielu wątków do dowolnego współdzielonego zasobu. Gdy wiele wątków próbuje wykonać to samo zadanie, istnieje możliwość uzyskania błędnego wyniku, dlatego aby przezwyciężyć ten problem, Java wykorzystuje synchronizację, dzięki czemu w danym momencie może pracować tylko jeden wątek. Synchronizację można osiągnąć na trzy sposoby:
  • Metoda synchronizacji
  • Synchronizując konkretny blok
  • Synchronizacja statyczna

Synchronizacja metod

Metoda synchronized służy do blokowania obiektu dla dowolnego udostępnionego zasobu. Gdy wątek wywołuje metodę zsynchronizowaną, automatycznie nabywa blokadę na tym obiekcie i zwalnia ją, gdy wątek zakończy swoje zadanie. Aby to zadziałało, musisz dodać słowo kluczowe synchronized . Zobaczmy, jak to działa na przykładzie:
/**
* Пример, где мы синхронизируем метод. То есть добавляем ему слово synchronized.
* Есть два писателя, которые хотят использовать один принтер. Они подготовLub свои поэмы
* И конечно же не хотят, чтоб их поэмы перемешались, а хотят, чтоб работа была сделана по * * * очереди для каждого из них
*/
class Printer {

   synchronized void print(List<String> wordsToPrint) {
       wordsToPrint.forEach(System.out::print);
       System.out.println();
   }

   public static void main(String args[]) {
       // один obiekt для двух тредов
       Printer printer  = new Printer();

       // создаем два треда
       Writer1 writer1 = new Writer1(printer);
       Writer2 writer2 = new Writer2(printer);

       // запускаем их
       writer1.start();
       writer2.start();
   }
}

/**
* Писатель номер 1, который пишет свою поэму.
*/
class Writer1 extends Thread {
   Printer printer;

   Writer1(Printer printer) {
       this.printer = printer;
   }

   public void run() {
       List<string> poem = Arrays.asList("Я ", this.getName(), " Пишу", " Письмо");
       printer.print(poem);
   }

}

/**
* Писатель номер 2, который пишет свою поэму.
*/
class Writer2 extends Thread {
   Printer printer;

   Writer2(Printer printer) {
       this.printer = printer;
   }

   public void run() {
       List<String> poem = Arrays.asList("Не Я ", this.getName(), " Не пишу", " Не Письмо");
       printer.print(poem);
   }
}
I wyjście na konsolę:

Я Thread-0 Пишу Письмо
Не Я Thread-1 Не пишу Не Письмо

Blok synchronizacji

Blok synchronized może zostać użyty do przeprowadzenia synchronizacji dowolnego zasobu metody. Powiedzmy, że w dużej metodzie (tak, tak, nie można takich rzeczy pisać, ale czasami się to zdarza) z jakiegoś powodu trzeba zsynchronizować tylko małą część. Jeśli umieścisz wszystkie kody metody w zsynchronizowanym bloku, będzie ona działać tak samo jak metoda zsynchronizowana. Składnia wygląda następująco:
synchronized (“obiekt для блокировки”) {
   // сам kod, который нужно защитить
}
Aby nie powtarzać poprzedniego przykładu, wątki będziemy tworzyć poprzez klasy anonimowe – czyli od razu implementując interfejs Runnable.
/**
* Вот Jak добавляется блок синхронизации.
* Внутри нужно указать у кого будет взят мьютекс для блокировки.
*/
class Printer {

   void print(List<String> wordsToPrint) {
       synchronized (this) {
           wordsToPrint.forEach(System.out::print);
       }
       System.out.println();
   }

   public static void main(String args[]) {
       // один obiekt для двух тредов
       Printer printer = new Printer();

       // создаем два треда
       Thread writer1 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Я ", "Writer1", " Пишу", " Письмо");
               printer.print(poem);
           }
       });
       Thread writer2 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Не Я ", "Writer2", " Не пишу", " Не Письмо");
               printer.print(poem);
           }
       });

       // запускаем их
       writer1.start();
       writer2.start();
   }
}

}
i wyprowadź na konsolę

Я Writer1 Пишу Письмо
Не Я Writer2 Не пишу Не Письмо

Synchronizacja statyczna

Jeśli zsynchronizujesz metodę statyczną, blokada będzie dotyczyć klasy, a nie obiektu. W tym przykładzie stosujemy słowo kluczowe synchronized do metody statycznej, aby przeprowadzić synchronizację statyczną:
/**
* Вот Jak добавляется блок синхронизации.
* Внутри нужно указать у кого будет взят мьютекс для блокировки.
*/
class Printer {

   static synchronized void print(List<String> wordsToPrint) {
       wordsToPrint.forEach(System.out::print);
       System.out.println();
   }

   public static void main(String args[]) {

       // создаем два треда
       Thread writer1 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Я ", "Writer1", " Пишу", " Письмо");
               Printer.print(poem);
           }
       });
       Thread writer2 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Не Я ", "Writer2", " Не пишу", " Не Письмо");
               Printer.print(poem);
           }
       });

       // запускаем их
       writer1.start();
       writer2.start();
   }
}
i wyjście na konsolę:

Не Я Writer2 Не пишу Не Письмо
Я Writer1 Пишу Письмо

47. Co to jest zmienna zmienna?

Słowo kluczowe volatilejest używane w programowaniu wielowątkowym, aby zapewnić bezpieczeństwo wątków, ponieważ modyfikacja jednej zmiennej zmiennej jest widoczna dla wszystkich pozostałych wątków, więc jedna zmienna może być używana przez jeden wątek na raz. Używając słowa kluczowego, volatilemożesz zagwarantować, że zmienna będzie bezpieczna dla wątków i będzie przechowywana w pamięci współdzielonej, a wątki nie zabiorą jej do swojej pamięci podręcznej. Jak to wygląda?
private volatile AtomicInteger count;
Po prostu dodajemy do zmiennej volatile. Ale to nie oznacza całkowitego bezpieczeństwa wątków... W końcu operacje na zmiennej nie mogą być atomowe. Można jednak użyć Atomicklas, które wykonują operację atomowo, czyli w jednym wykonaniu przez procesor. Wiele takich klas można znaleźć w pakiecie java.util.concurrent.atomic.

48. Co to jest impas

Zakleszczenie w Javie jest częścią wielowątkowości. Zakleszczenie może wystąpić w sytuacji, gdy wątek oczekuje na blokadę obiektu przejętą przez inny wątek, a drugi wątek oczekuje na blokadę obiektu przejętą przez pierwszy wątek. Zatem te dwa wątki czekają na siebie i nie będą kontynuować wykonywania swojego kodu. 50 najpopularniejszych pytań i odpowiedzi do rozmów kwalifikacyjnych dotyczących języka Java Core.  Część 3 - 5Spójrzmy na przykład, w którym istnieje klasa implementująca Runnable. Akceptuje dwa zasoby w swoim konstruktorze. Wewnątrz metody run() pobierana jest dla nich blokada jeden po drugim, więc jeśli utworzysz dwa obiekty tej klasy i przeniesiesz zasoby w różnej kolejności, możesz łatwo natknąć się na blokadę:
class DeadLock {

   public static void main(String[] args) {
       final Integer r1 = 10;
       final Integer r2 = 15;

       DeadlockThread threadR1R2 = new DeadlockThread(r1, r2);
       DeadlockThread threadR2R1 = new DeadlockThread(r2, r1);

       new Thread(threadR1R2).start();
       new Thread(threadR2R1).start();
   }
}

/**
* Класс, который принимает два ресурса.
*/
class DeadlockThread implements Runnable {

   private final Integer r1;
   private final Integer r2;

   public DeadlockThread(Integer r1, Integer r2) {
       this.r1 = r1;
       this.r2 = r2;
   }

   @Override
   public void run() {
       synchronized (r1) {
           System.out.println(Thread.currentThread().getName() + " захватил ресурс: " + r1);

           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }

           synchronized (r2) {
               System.out.println(Thread.currentThread().getName() + " захватил ресурс: " + r2);
           }
       }
   }
}
Wyjście konsoli:

Первый тред захватил первый ресурс
Второй тред захватывает второй ресурс

49. Jak uniknąć impasu?

Na podstawie tego, co wiemy, jak powstaje impas, możemy wyciągnąć pewne wnioski...
  • Jak pokazano w powyższym przykładzie, zakleszczenie wynikało z zagnieżdżenia zamków. Oznacza to, że w jednym zamku znajduje się inny lub więcej. Można tego uniknąć w następujący sposób - zamiast zagnieżdżać, trzeba dodać na wierzch nową abstrakcję i nadać blokadę na wyższy poziom, a zagnieżdżone blokady usunąć.
  • Im więcej blokad, tym większa szansa, że ​​nastąpi impas. Dlatego za każdym razem, gdy dodajesz zamek, musisz zastanowić się, czy jest on naprawdę potrzebny i czy da się uniknąć dodawania nowego.
  • Używa Thread.join(). Zakleszczenie można również wykonać, gdy jeden wątek czeka na inny. Aby uniknąć tego problemu, można rozważyć ustawienie limitu czasu dla join()metody.
  • Jeśli mamy jeden wątek to nie będzie impasu ;)

50. Co to jest stan wyścigu?

Jeśli w prawdziwych wyścigach samochody się sprawdzają, to w terminologii wyścigowej wielowątkowości wątki sprawdzają się w wyścigach. Ale dlaczego? Istnieją dwa wątki, które są uruchomione i mogą mieć dostęp do tego samego obiektu. Mogą jednocześnie próbować zaktualizować stan. Na razie wszystko jest jasne, prawda? Zatem wątki pracują albo realnie równolegle (jeśli w procesorze jest więcej niż jeden rdzeń), albo warunkowo równolegle, gdy procesor przydziela krótki okres czasu. Nie możemy kontrolować tych procesów, więc nie możemy zagwarantować, że gdy jeden wątek odczyta dane z obiektu, będzie miał czas na ich zmianę ZANIM zrobi to jakiś inny wątek. Takie problemy zdarzają się, gdy zachodzi taka kombinacja testu i działania. Co to znaczy? Przykładowo mamy ifwyrażenie w treści którego zmienia się sam warunek, czyli:
int z = 0;

// проверь
if (z < 5) {
//действуй
   z = z + 5;
}
Może więc dojść do sytuacji, gdy dwa wątki jednocześnie wejdą do tego bloku kodu w momencie, gdy z jest jeszcze równe zero i wspólnie zmieniają tę wartość. I ostatecznie nie otrzymamy oczekiwanej wartości 5, ale 10. Jak tego uniknąć? Musisz zablokować przed i po wykonaniu. Oznacza to, że aby pierwszy wątek mógł wejść do bloku if, wykonaj wszystkie czynności, zmień go zi dopiero wtedy daj szansę następnemu wątkowi, aby to zrobił. Ale następny wątek nie wejdzie do bloku if, ponieważ zbędzie już równy 5:
// получить блокировку для z
if (z < 5) {
   z = z + 5;
}
// выпустить из блокировки z
===================================================

Zamiast wyjścia

Chcę podziękować wszystkim, którzy przeczytali do końca. To była długa podróż i udało Ci się! Nie wszystko może być jasne. Jest okej. Gdy tylko zacząłem uczyć się języka Java, nie mogłem pojąć, czym jest zmienna statyczna. Ale nic, przespałem się z tą myślą, przeczytałem jeszcze kilka źródeł i w końcu zrozumiałem. Przygotowanie do rozmowy kwalifikacyjnej to bardziej kwestia akademicka niż praktyczna. Dlatego przed każdą rozmową kwalifikacyjną musisz powtórzyć i odświeżyć pamięć o rzeczach, których być może nie używasz zbyt często.

I jak zawsze przydatne linki:

Dziękuję wszystkim za przeczytanie. Do zobaczenia wkrótce) Mój profil na GitHubie
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION