17. Podaj przykłady udanego i nieudanego użycia Opcjonalnego
Załóżmy, że mamy pewną serię wartości, przez które przechodzimy przez strumień, i na koniec otrzymujemy w efekcie Opcjonalne :Optional<String> stringOptional = Stream.of("a", "ab", "abc", "abcd")
.filter(str -> str.length() >= 3)
.findAny();
Zgodnie z oczekiwaniami musimy uzyskać wartość z tego Option . Samo użycie get() jest złym sposobem:
String result = stringOptional.get();
Ale ta metoda ma pobrać wartość z Opcjonalnego i zwrócić ją nam? To oczywiście prawda, ale jeśli ma to jakieś znaczenie. No cóż, jeśli wartości w strumieniu były różne i na koniec otrzymaliśmy pusty Opcjonalny , to przy próbie pobrania z niego wartości metodą get() zostanie wyrzucony komunikat: Co nie jest dobre. W takim przypadku lepiej zastosować następujące konstrukcje:
-
String result = null; if (stringOptional.isPresent()) { stringOptional.get(); }
W tym przypadku sprawdzamy, czy element znajduje się w Opcjonalnym . Jeśli nie, wynikowy ciąg znaków ma swoją starą wartość.
-
String result = stringOptional.orElse("default value");
W tym przypadku podajemy jakąś wartość domyślną, która zostanie nadana wynikowemu stringowi w przypadku pustej Opcjonalnie .
-
String result = stringOptional.orElseThrow(() -> new CustomException());
W tym przypadku sami zgłaszamy wyjątek, gdy Option jest pusta .
18. Czy można zadeklarować metodę główną jako ostateczną?
Tak, oczywiście, nic nie stoi na przeszkodzie, abyśmy zadeklarowali metodę main() jako ostateczną . Kompilator nie będzie generował błędów. Warto jednak pamiętać, że każda metoda po zadeklarowaniu jej jako ostateczna stanie się metodą ostatnią - niezastąpioną. Chociaż kto na nowo zdefiniuje main ???19. Czy można dwukrotnie zaimportować tę samą paczkę/klasę? Jakie mogą być konsekwencje?
Tak, możesz. Konsekwencje? Będziemy mieli kilka niepotrzebnych importów, które Intelijj IDEA wyświetli na szaro, tj. nie używany.20. Co to jest casting? Kiedy możemy uzyskać wyjątek ClassCastException?
Rzutowanie lub rzutowanie typu to proces konwertowania jednego typu danych na inny typ danych: ręcznie (rzutowanie niejawne) lub automatycznie (rzutowanie typu jawnego). Konwersja automatyczna jest wykonywana przez kompilator, a konwersja ręczna przez programistę. Rzutowanie typów dla prymitywów i klas jest nieco inne, więc rozważymy je osobno. Typy pierwotne Przykład automatycznego rzutowania typów pierwotnych:int value = 17;
double convertedValue = value;
Jak widać, nie są tu potrzebne żadne dodatkowe manipulacje poza znakiem = . Przykład ręcznego rzutowania typów pierwotnych:
double value = 17.89;
int convertedValue = (int)value;
W tym przypadku możemy zaobserwować ręczne rzutowanie, które jest realizowane za pomocą (int) , przy czym część znajdująca się po przecinku zostanie odrzucona, a konwertowanaValue będzie miała wartość -17. Więcej o rzutowaniu typów pierwotnych przeczytasz w tym artykule . Cóż, teraz przejdźmy do obiektów. Typy referencyjne W przypadku typów referencyjnych możliwe jest automatyczne rzutowanie klas potomnych na klasy nadrzędne. Nazywa się to również polimorfizmem . Załóżmy, że mamy klasę Lion , która dziedziczy po klasie Cat . W takim przypadku automatyczna konwersja będzie wyglądać następująco:
Cat cat = new Lion();
Ale w przypadku jawnego rzutowania wszystko jest nieco bardziej skomplikowane, ponieważ nie ma funkcji odcinania nadmiaru, jak w przypadku prymitywów. I po prostu robię jawną konwersję formularza:
Lion lion= (Lion)new Cat();
Otrzymasz błąd: W rzeczywistości możesz dodać do klasy potomnej Lion metody , które pierwotnie nie znajdowały się w klasie Cat , a następnie spróbować je wywołać, ponieważ typem obiektu stanie się Lion . No cóż, nie ma w tym żadnej logiki. Dlatego zawężanie typów jest możliwe tylko wtedy, gdy oryginalny obiekt był typu Lion , ale później został rzutowany na klasę nadrzędną:
Lion lion = new Lion();
Cat cat = lion;
Lion newLion = (Lion)cat;
Ponadto, dla większej niezawodności, zaleca się zawężanie rzutowania obiektów przy użyciu konstrukcji instancyjnej :
if (cat instanceof Lion) {
newLion = (Lion)new Cat();
}
Przeczytaj więcej na temat rzutowań typu referencyjnego w tym artykule .
21. Dlaczego współczesne frameworki używają głównie tylko niesprawdzonych wyjątków?
Myślę, że to wszystko dlatego, że obsługa sprawdzonych wyjątków to nadal kod spaghetti, który powtarza się wszędzie, ale nie jest tak naprawdę potrzebny we wszystkich przypadkach. W takich przypadkach łatwiej jest wykonać przetwarzanie wewnątrz frameworku, aby nie zrzucać tego ponownie na barki programistów. Tak, oczywiście, może wystąpić sytuacja awaryjna, ale te same niesprawdzone wyjątki można obsłużyć w wygodniejszy sposób, bez zawracania sobie głowy przetwarzaniem w trybie try-catch i bez przekazywania ich dalej przez metody. Wystarczy przekonwertować wyjątek na jakąś odpowiedź HTTP w wyjątkuHandler .22. Co to jest import statyczny?
Korzystając z danych statycznych (metod, zmiennych) nie można stworzyć samego obiektu, lecz zrobić to po nazwie klasy, choć i w tym przypadku potrzebne jest odwołanie do klasy. Wszystko jest z nim proste: dodaje się go za pomocą zwykłego importu. Ale co, jeśli zaczniemy używać metody statycznej bez wpisywania nazwy klasy, tak jakby była to metoda statyczna bieżącej klasy? Jest to możliwe dzięki importowi statycznemu! W takim przypadku musimy napisać import statyczny i link do tej metody. Tak na przykład statyczna metoda klasy Math służąca do obliczania wartości cosinusa:import static java.lang.Math.cos;
Dzięki temu możemy skorzystać z metody bez podawania nazwy klasy:
double result = cos(60);
Możemy także po prostu załadować wszystkie statyczne metody klasy na raz, używając importu statycznego:
import static java.lang.Math.*;
23. Jaki jest związek pomiędzy metodami hashCode() i Equals()?
Według Oracle zasada jest następująca: jeśli dwa obiekty są równe (tj. metoda równości() zwraca wartość true ), muszą mieć ten sam kod skrótu. Jednocześnie nie zapominaj, że dwa różne obiekty mogą mieć ten sam kod skrótu. Aby zrozumieć, dlaczego równania() i hashCode() są zawsze nadpisywane parami, rozważ następujące przypadki:-
Obie metody są zastępowane.
W tym przypadku dwa różne obiekty o tych samych stanach wewnętrznych zwrócą równa() - true , podczas gdy oba obiekty hashCode() zwrócą tę samą liczbę.
Okazuje się, że wszystko jest w porządku, bo zasada jest przestrzegana.
-
Obie metody nie są zastępowane.
W tym przypadku dwa różne obiekty o tych samych stanach wewnętrznych zwrócą false , gdy równa się() , ponieważ porównanie odbywa się przez odniesienie za pomocą operatora == .
Metoda hashCode() również zwróci inne wartości (najprawdopodobniej), ponieważ generuje przekonwertowaną wartość adresu lokalizacji pamięci. Ale dla tego samego obiektu wartość ta będzie taka sama, podobnie jak funkcjaquals() w tym przypadku zwróci wartość true tylko wtedy, gdy odniesienia wskazują na ten sam obiekt.
Okazuje się, że w tym przypadku wszystko jest w porządku i zasada jest spełniona.
-
Przesłonięty równa się() , nie zastąpiony hashCode() .
W tym przypadku dla dwóch różnych obiektów o tych samych stanach wewnętrznych funkcjaquals() zwróci true , a hashCode() zwróci (najprawdopodobniej) różne wartości.
Jest to naruszenie zasady, dlatego nie zaleca się tego robić.
-
równa się() nie jest przesłonięta , hashCode() jest przesłonięta .
W tym przypadku dla dwóch różnych obiektów o tych samych stanach wewnętrznych funkcjaquals() zwróci wartość false , a hashCode() zwróci te same wartości.
Nastąpiło naruszenie zasady, więc podejście jest nieprawidłowe.
24. Kiedy używane są klasy BufferedInputStream i BufferedOutputStream?
InputStream służy do odczytywania danych bajt po bajcie z jakiegoś zasobu, a OutputStream służy do zapisywania danych bajt po bajcie. Jednak operacje bajtowe mogą być bardzo niewygodne i wymagają dodatkowego przetwarzania (w celu normalnego odczytu/zapisu tekstów). Właściwie, aby uprościć takie rekordy bajtowe, wprowadzono BufferedOutputStream i BufferedInputStream do odczytu . Klasy te to nic innego jak bufory gromadzące dane, pozwalające na pracę z danymi nie bajt po bajcie, ale całymi pakietami danych (tablicami). Po utworzeniu BufferedInputStream przyjmuje do swojego konstruktora instancję typu WejścieStream , z której odczytywane są dane:BufferedInputStream bufferedInputStream = new BufferedInputStream(System.in);
byte[] arr = new byte[100];
bufferedInputStream.read(arr);
System.in jest obiektem wejściowym , który odczytuje dane z konsoli. Oznacza to, że używając tego obiektu BufferedInputStream , możemy odczytać dane z wejściowego strumienia , zapisując je do przekazanej tablicy. Okazuje się, że jest to rodzaj opakowania klasy WejścieStream . Tablica arr z tego przykładu to tablica, która odbiera dane z BufferedInputStream . To z kolei odczytuje dane ze strumienia wejściowego za pomocą innej tablicy, która domyślnie ma rozmiar 2048 bajtów. Podobnie jest w przypadku BufferedOutputStream : do konstruktora należy przekazać instancję typu OutputStream , do której zapiszemy dane w całych tablicach:
byte[] arr = "Hello world!!!".getBytes();
BufferedOutputStream bufferedInputStream = new BufferedOutputStream(System.out);
bufferedInputStream.write(arr);
bufferedInputStream.flush();
System.out to obiekt OutputStream , który zapisuje dane do konsoli. Metoda Flush() wysyła dane ze strumienia BufferedOutputStream do strumienia OutputStream , opróżniając w ten sposób strumień BufferedOutputStream . Bez tej metody nic nie zostanie zapisane. I podobnie jak w poprzednim przykładzie: arr to tablica, z której dane są zapisywane do BufferedOutputStream . Stamtąd są zapisywane do OutputStream w innej tablicy, która domyślnie ma rozmiar 512 bajtów. Więcej o tych dwóch klasach przeczytasz w artykule .
25. Jaka jest różnica pomiędzy klasami java.util.Collection i java.util.Collections?
Kolekcja to interfejs będący głową hierarchii kolekcji. Wprowadza klasy, które pozwalają tworzyć, zawierać i modyfikować całe grupy obiektów. Istnieje wiele metod, które służą do tego, np. add() , usuwania() , zawiera() i inne. Główne interfejsy klasy Collection :-
Zestaw to interfejs opisujący zestaw zawierający nieuporządkowane, unikalne (niepowtarzające się) elementy.
-
Lista to interfejs opisujący strukturę danych przechowującą uporządkowaną sekwencję obiektów. Obiekty te otrzymują własny indeks (numer), za pomocą którego można z nimi wchodzić w interakcję: pobierać, usuwać, zmieniać, nadpisywać.
-
Kolejka to interfejs opisujący strukturę danych, w której przechowywane są elementy w postaci kolejki zgodnej z zasadą FIFO – First In First Out .
-
addAll(Collection<? super T> kolekcja, T...element) - dodaje przekazane elementy typu T do kolekcji .
-
copy(List<? super T> dest, List<? Extends T> src) - kopiuje wszystkie elementy z listy src na listę w dest .
-
pustyList() - zwraca pustą listę.
-
max(Collection<? Extends T> kolekcja, Comparator<? super T> comp) - Zwraca maksymalny element danej kolekcji zgodnie z kolejnością określoną przez podany komparator.
-
unmodibleList(List<? Extends T> list) - zwraca niemodyfikowalną reprezentację przekazanej listy.
GO TO FULL VERSION