Dla tych, którzy po raz pierwszy słyszą słowo Java Core, są to podstawowe podstawy tego języka. Mając tę wiedzę możesz bezpiecznie udać się na staż/staż.
Dzięki tym pytaniom odświeżysz swoją wiedzę przed rozmową kwalifikacyjną lub nauczysz się czegoś nowego dla siebie. Aby zdobyć praktyczne umiejętności, studiuj w JavaRush . Artykuł oryginalny Linki do innych części: Java Core. Pytania do rozmowy kwalifikacyjnej, część 1 Java Core. Pytania do rozmowy kwalifikacyjnej, część 3
Dlaczego należy unikać metody finalize()?
Wszyscy znamy stwierdzenie, żefinalize()
moduł zbierający elementy bezużyteczne wywołuje metodę przed zwolnieniem pamięci zajmowanej przez obiekt. Oto przykładowy program, który dowodzi, że wywołanie metody finalize()
nie jest gwarantowane:
public class TryCatchFinallyTest implements Runnable {
private void testMethod() throws InterruptedException
{
try
{
System.out.println("In try block");
throw new NullPointerException();
}
catch(NullPointerException npe)
{
System.out.println("In catch block");
}
finally
{
System.out.println("In finally block");
}
}
@Override
protected void finalize() throws Throwable {
System.out.println("In finalize block");
super.finalize();
}
@Override
public void run() {
try {
testMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class TestMain
{
@SuppressWarnings("deprecation")
public static void main(String[] args) {
for(int i=1;i< =3;i++)
{
new Thread(new TryCatchFinallyTest()).start();
}
}
}
Dane wyjściowe: W bloku try W bloku catch W bloku wreszcie W bloku try W bloku catch W bloku wreszcie W bloku try W bloku catch W bloku wreszcie W bloku finalnie metoda finalize
nie została wykonana dla żadnego wątku. To potwierdza moje słowa. Myślę, że powodem jest to, że finalizatory są wykonywane przez oddzielny wątek modułu zbierającego elementy bezużyteczne. Jeśli wirtualna maszyna Java zakończy działanie zbyt wcześnie, moduł zbierający elementy bezużyteczne nie będzie miał wystarczająco dużo czasu na utworzenie i wykonanie finalizatorów. Innymi powodami niestosowania tej metody finalize()
mogą być:
- Metoda
finalize()
nie działa z łańcuchami takimi jak konstruktory. Oznacza to, że gdy wywołasz konstruktor klasy, konstruktory nadklasy zostaną wywołane bezwarunkowo. Jednak w przypadku metodyfinalize()
tak się nie stanie. Metodę nadklasyfinalize()
należy wywołać jawnie. - Każdy wyjątek zgłoszony przez metodę
finalize
jest ignorowany przez wątek modułu zbierającego elementy bezużyteczne i nie będzie dalej propagowany, co oznacza, że zdarzenie nie zostanie zapisane w logach. To bardzo źle, prawda? -
finalize()
Jeśli metoda jest obecna w Twojej klasie, otrzymujesz również znaczną obniżkę wydajności . W „Effective Programming” (wyd. 2) Joshua Bloch powiedział:
„Tak i jeszcze jedno: używanie finalizatorów powoduje duży spadek wydajności. Na moim komputerze czas tworzenia i niszczenia prostych obiektów wynosi około 5,6 nanosekundy.
Dodanie finalizatora zwiększa czas do 2400 nanosekund. Innymi słowy, tworzenie i usuwanie obiektu za pomocą finalizatora jest około 430 razy wolniejsze.
Dlaczego nie należy używać HashMap w środowisku wielowątkowym? Czy może to spowodować nieskończoną pętlę?
Wiemy, żeHashMap
jest to niezsynchronizowana kolekcja, której zsynchronizowanym odpowiednikiem jest HashTable
. Tak więc, gdy uzyskujesz dostęp do kolekcji w środowisku wielowątkowym, w którym wszystkie wątki mają dostęp do pojedynczej instancji kolekcji, bezpieczniej jest używać HashTable
z oczywistych powodów, takich jak unikanie brudnych odczytów i zapewnienie spójności danych. W najgorszym przypadku to wielowątkowe środowisko spowoduje nieskończoną pętlę. Tak, to prawda. HashMap.get()
może spowodować nieskończoną pętlę. Zobaczmy jak? Jeśli spojrzysz na kod źródłowy metody HashMap.get(Object key)
, wygląda to tak:
public Object get(Object key) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
Entry e = table[i];
while (true) {
if (e == null)
return e;
if (e.hash == hash && eq(k, e.key))
return e.value;
e = e.next;
}
}
while(true)
zawsze może paść ofiarą nieskończonej pętli w wielowątkowym środowisku wykonawczym, jeśli z jakiegoś powodu e.next
może wskazywać na siebie. Spowoduje to nieskończoną pętlę, ale w jaki sposób e.next
będzie wskazywała na siebie (to znaczy na e
)? Może się to zdarzyć w przypadku metody void transfer(Entry[] newTable)
wywoływanej podczas zmiany jej HashMap
rozmiaru.
do {
Entry next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
Ten fragment kodu może utworzyć nieskończoną pętlę, jeśli zmiana rozmiaru nastąpi w tym samym czasie, gdy inny wątek będzie próbował zmienić instancję mapy ( HashMap
). Jedynym sposobem uniknięcia tego scenariusza jest użycie synchronizacji w kodzie lub jeszcze lepiej użycie zsynchronizowanej kolekcji.
Wyjaśnij abstrakcję i enkapsulację. Jak są połączone?
W prostych słowach „ Abstrakcja wyświetla tylko te właściwości obiektu, które są istotne dla bieżącego widoku ” . W teorii programowania obiektowego abstrakcja obejmuje zdolność definiowania obiektów reprezentujących abstrakcyjnych „aktorów”, którzy mogą wykonywać pracę, zmieniać i raportować zmiany swojego stanu oraz „wchodzić w interakcję” z innymi obiektami w systemie. Abstrakcja w dowolnym języku programowania działa na wiele sposobów. Można to zobaczyć na podstawie tworzenia procedur definiujących interfejsy dla poleceń języka niskiego poziomu. Niektóre abstrakcje próbują ograniczyć zakres ogólnej reprezentacji potrzeb programisty, całkowicie ukrywając abstrakcje, na których są zbudowane, takie jak wzorce projektowe. Zazwyczaj abstrakcję można postrzegać na dwa sposoby: Abstrakcja danych to sposób tworzenia złożonych typów danych i udostępniania tylko znaczących operacji do interakcji z modelem danych, przy jednoczesnym ukrywaniu wszystkich szczegółów implementacji przed światem zewnętrznym. Abstrakcja wykonania to proces identyfikowania wszystkich znaczących instrukcji i eksponowania ich jako jednostki pracy. Zwykle używamy tej funkcji, gdy tworzymy metodę wykonania jakiejś pracy. Zamykanie danych i metod w klasach w połączeniu z ukrywaniem (za pomocą kontroli dostępu) jest często nazywane enkapsulacją. Wynikiem jest typ danych z charakterystyką i zachowaniem. Hermetyzacja zasadniczo obejmuje również ukrywanie danych i ukrywanie implementacji. „Zawrzyj wszystko, co może się zmienić” . Ten cytat jest dobrze znaną zasadą projektowania. W każdej klasie zmiany danych mogą nastąpić w czasie wykonywania, a zmiany w implementacji mogą nastąpić w przyszłych wersjach. Zatem enkapsulacja dotyczy zarówno danych, jak i implementacji. Można je zatem połączyć w następujący sposób:- Abstrakcja to głównie to, co może zrobić klasa [Pomysł]
- Enkapsulacja to więcej Jak osiągnąć tę funkcjonalność [Wdrożenie]
Różnice między interfejsem a klasą abstrakcyjną?
Główne różnice można wymienić w następujący sposób:- Interfejs nie może implementować żadnych metod, ale klasa abstrakcyjna może.
- Klasa może implementować wiele interfejsów, ale może mieć tylko jedną nadklasę (abstrakcyjną lub nieabstrakcyjną)
- Interfejs nie jest częścią hierarchii klas. Niepowiązane klasy mogą implementować ten sam interfejs.
Cat
i Dog
może dziedziczyć po klasie abstrakcyjnej Animal
, a ta abstrakcyjna klasa bazowa będzie implementować metodę void Breathe()
– Breathe, którą dzięki temu wszystkie zwierzęta wykonają w ten sam sposób. Jakie czasowniki można zastosować do mojej klasy i można je zastosować do innych? Utwórz interfejs dla każdego z tych czasowników. Na przykład wszystkie zwierzęta mogą jeść, więc stworzę interfejs IFeedable
i sprawię, że będzie on Animal
implementowany. Tylko wystarczająco dobry, aby zaimplementować interfejs Dog
( który może mnie polubić), ale nie wszystkie. Ktoś powiedział: główna różnica polega na tym, gdzie chcesz wdrożyć. Tworząc interfejs, możesz przenieść implementację do dowolnej klasy, która implementuje Twój interfejs. Tworząc klasę abstrakcyjną, możesz udostępnić implementację wszystkich klas pochodnych w jednym miejscu i uniknąć wielu złych rzeczy, takich jak duplikowanie kodu. Horse
ILikeable
W jaki sposób StringBuffer oszczędza pamięć?
KlasaString
jest zaimplementowana jako niezmienny obiekt, co oznacza, że kiedy początkowo zdecydujesz się umieścić coś w obiekcie String
, maszyna wirtualna przydziela tablicę o stałej długości dokładnie o rozmiarze oryginalnej wartości. Będzie to następnie traktowane jako stała wewnątrz maszyny wirtualnej, co zapewnia znaczną poprawę wydajności, jeśli wartość ciągu nie ulegnie zmianie. Jeśli jednak zdecydujesz się w jakikolwiek sposób zmienić zawartość ciągu, tak naprawdę maszyna wirtualna skopiuje zawartość oryginalnego ciągu do przestrzeni tymczasowej, wprowadzi zmiany, a następnie zapisze je w nowej tablicy pamięci. Zatem dokonanie zmian w wartości ciągu po inicjalizacji jest kosztowną operacją. StringBuffer
, natomiast jest realizowany w postaci dynamicznie rozwijającej się tablicy wewnątrz maszyny wirtualnej, co oznacza, że dowolna operacja modyfikacji może nastąpić na istniejącej komórce pamięci, a nowa pamięć zostanie przydzielona w miarę potrzeb. Jednak maszyna wirtualna nie ma możliwości przeprowadzenia optymalizacji, StringBuffer
ponieważ jej zawartość jest uważana za niespójną w każdej instancji.
Dlaczego metody Wait i Notify są zadeklarowane w klasie Object zamiast w Thread?
Metodywait
, notify
, notifyAll
są potrzebne tylko wtedy, gdy chcesz, aby Twoje wątki miały dostęp do współdzielonych zasobów, a współdzielonym zasobem może być dowolny obiekt Java na stercie. Zatem metody te są zdefiniowane w klasie bazowej, Object
dzięki czemu każdy obiekt ma kontrolę, która pozwala wątkom czekać na swoim monitorze. Java nie ma żadnego specjalnego obiektu używanego do współużytkowania współdzielonego zasobu. Nie zdefiniowano takiej struktury danych. Dlatego obowiązkiem klasy jest Object
móc stać się zasobem współdzielonym i zapewnić metody pomocnicze, takie jak wait()
, notify()
, notifyAll()
. Java opiera się na koncepcji monitorów Charlesa Hoare’a. W Javie wszystkie obiekty mają monitor. Wątki oczekują na monitorach, więc aby wykonać oczekiwanie, potrzebujemy dwóch parametrów:
- wątek
- monitor (dowolny obiekt).
wait
). To dobry projekt, ponieważ jeśli uda nam się zmusić inny wątek do oczekiwania na konkretnym monitorze, spowoduje to „inwazję”, co utrudni projektowanie/programowanie programów równoległych. Pamiętaj, że w Javie wszystkie operacje zakłócające inne wątki są przestarzałe (na przykład stop()
).
Napisz program tworzący zakleszczenie w Javie i naprawiający go
W Javiedeadlock
jest to sytuacja, w której co najmniej dwa wątki blokują różne zasoby i oba czekają, aż drugi zasób stanie się dostępny, aby zakończyć swoje zadanie. I żaden z nich nie jest w stanie pozostawić blokady na przechowywanym zasobie. Przykładowy program:
package thread;
public class ResolveDeadLockTest {
public static void main(String[] args) {
ResolveDeadLockTest test = new ResolveDeadLockTest();
final A a = test.new A();
final B b = test.new B();
// Thread-1
Runnable block1 = new Runnable() {
public void run() {
synchronized (a) {
try {
// Добавляем задержку, чтобы обе нити могли начать попытки
// блокирования ресурсов
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Thread-1 заняла A но также нуждается в B
synchronized (b) {
System.out.println("In block 1");
}
}
}
};
// Thread-2
Runnable block2 = new Runnable() {
public void run() {
synchronized (b) {
// Thread-2 заняла B но также нуждается в A
synchronized (a) {
System.out.println("In block 2");
}
}
}
};
new Thread(block1).start();
new Thread(block2).start();
}
// Resource A
private class A {
private int i = 10;
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
}
// Resource B
private class B {
private int i = 20;
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
}
}
Uruchomienie powyższego kodu spowoduje zakleszczenie z bardzo oczywistych powodów (wyjaśnionych powyżej). Teraz musimy rozwiązać ten problem. Wierzę, że rozwiązanie każdego problemu leży u podstaw samego problemu. W naszym przypadku głównym problemem jest model dostępu do A i B. Dlatego, aby go rozwiązać, po prostu zmieniamy kolejność operatorów dostępu do zasobów współdzielonych. Po zmianie będzie to wyglądać następująco:
// Thread-1
Runnable block1 = new Runnable() {
public void run() {
synchronized (b) {
try {
// Добавляем задержку, чтобы обе нити могли начать попытки
// блокирования ресурсов
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Thread-1 заняла B но также нуждается в А
synchronized (a) {
System.out.println("In block 1");
}
}
}
};
// Thread-2
Runnable block2 = new Runnable() {
public void run() {
synchronized (b) {
// Thread-2 заняла B но также нуждается в А
synchronized (a) {
System.out.println("In block 2");
}
}
}
};
Uruchom tę klasę ponownie, a teraz nie zobaczysz impasu. Mam nadzieję, że pomoże Ci to uniknąć zakleszczeń i pozbyć się ich, jeśli je napotkasz.
Co się stanie, jeśli Twoja klasa implementująca interfejs Serializable zawiera komponent, którego nie można serializować? Jak to naprawić?
W takim przypadku zostanie on wyrzuconyNotSerializableException
podczas wykonywania. Aby rozwiązać ten problem, istnieje bardzo proste rozwiązanie — zaznacz te pola transient
. Oznacza to, że zaznaczone pola nie będą serializowane. Jeśli chcesz także przechowywać stan tych pól, musisz wziąć pod uwagę zmienne referencyjne, które już implementują Serializable
. Może być również konieczne użycie metod readResolve()
i writeResolve()
. Podsumujmy:
- Najpierw spraw, aby Twoje pole nie podlegało serializacji
transient
. - Najpierw
writeObject
wywołajdefaultWriteObject
wątek, aby zapisać wszystkie inne niżtransient
pola, a następnie wywołaj pozostałe metody, aby serializować poszczególne właściwości obiektu, którego nie można serializować. - W
readObject
, najpierw wywołajdefaultReadObject
strumień, aby odczytać wszystkietransient
pola inne niż, a następnie wywołaj inne metody (odpowiadające tym, które dodałeś wwriteObject
), aby deserializować swój obiekt niebędącytransient
obiektem.
Wyjaśnij przejściowe i niestabilne słowa kluczowe w Javie
„Słowo kluczowetransient
służy do wskazania pól, które nie będą serializowane.” Zgodnie ze specyfikacją języka Java: Zmienne można oznaczyć wskaźnikiem przejściowym, aby wskazać, że nie są częścią trwałego stanu obiektu. Na przykład możesz zawierać pola pochodzące z innych pól i lepiej jest uzyskać je programowo, niż przywracać ich stan poprzez serializację. Na przykład w klasie BankPayment.java
pola takie jak principal
(dyrektor) i rate
(stawka) mogą być serializowane, a interest
(naliczone odsetki) mogą być obliczane w dowolnym momencie, nawet po deserializacji. Jeśli pamiętamy, każdy wątek w Javie ma własną pamięć lokalną i wykonuje operacje odczytu/zapisu w tej pamięci lokalnej. Po wykonaniu wszystkich operacji zapisuje zmodyfikowany stan zmiennej do pamięci współdzielonej, skąd wszystkie wątki uzyskują dostęp do zmiennej. Zazwyczaj jest to normalny wątek wewnątrz maszyny wirtualnej. Jednak modyfikator volatile informuje maszynę wirtualną, że dostęp wątku do tej zmiennej musi zawsze odpowiadać jego własnej kopii tej zmiennej z główną kopią zmiennej w pamięci. Oznacza to, że za każdym razem, gdy wątek chce odczytać stan zmiennej, musi wyczyścić stan pamięci wewnętrznej i zaktualizować zmienną z pamięci głównej. Volatile
najbardziej przydatne w algorytmach bez blokad. Oznaczasz zmienną przechowującą udostępnione dane jako niestabilną, następnie nie używasz blokad, aby uzyskać dostęp do tej zmiennej, a wszystkie zmiany dokonane przez jeden wątek będą widoczne dla innych. Lub jeśli chcesz utworzyć relację „zdarzenie późniejsze”, aby mieć pewność, że obliczenia nie zostaną powtórzone, ponownie, aby zapewnić widoczność zmian w czasie rzeczywistym. Volatile powinno być używane do bezpiecznego publikowania niezmiennych obiektów w środowisku wielowątkowym. Deklaracja pola public volatile ImmutableObject
gwarantuje, że wszystkie wątki zawsze zobaczą aktualnie dostępne odwołanie do instancji.
Różnica między Iteratorem a ListIteratorem?
Możemy używać lub doIterator
iteracji po elementach . Można go jednak używać tylko do iteracji po elementach . Inne różnice opisano poniżej. Możesz: Set
List
Map
ListIterator
List
- iteruj w odwrotnej kolejności.
- uzyskać indeks w dowolnym miejscu.
- dodaj dowolną wartość w dowolnym miejscu.
- ustawić dowolną wartość w bieżącej pozycji.
GO TO FULL VERSION