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

Opublikowano w grupie Random-PL
Hej wszystkim! Programowanie jest pełne pułapek. I praktycznie nie ma tematu, w którym się nie potkniesz i nie dostaniesz nierówności. Jest to szczególnie prawdziwe w przypadku początkujących. Jedynym sposobem, aby to zmniejszyć, jest nauka. W szczególności dotyczy to szczegółowych analiz zagadnień najbardziej podstawowych. Dziś nadal analizuję ponad 250 pytań z wywiadów z programistami Java, które dobrze obejmują podstawowe tematy. Pragnę zaznaczyć, że na liście znajdują się także pytania niestandardowe, które pozwalają spojrzeć na popularne tematy z innej perspektywy.Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 7 - 1

62. Co to jest pula ciągów i dlaczego jest potrzebna?

W pamięci w Javie (Sterta, o której porozmawiamy później) znajduje się obszar - Pula ciągów lub pula ciągów. Jest przeznaczony do przechowywania wartości ciągów. Innymi słowy, kiedy tworzysz określony ciąg znaków, na przykład poprzez podwójne cudzysłowy:
String str = "Hello world";
sprawdzane jest, czy pula ciągów ma podaną wartość. Jeśli tak, zmiennej str przypisywane jest odwołanie do tej wartości w puli. Jeśli tak się nie stanie, w puli zostanie utworzona nowa wartość, a referencja do niej zostanie przypisana do zmiennej str . Spójrzmy na przykład:
String firstStr = "Hello world";
String secondStr = "Hello world";
System.out.println(firstStr == secondStr);
prawda zostanie wyświetlona na ekranie . Pamiętamy, że == porównuje referencje — co oznacza, że ​​te dwa odniesienia odnoszą się do tej samej wartości z puli ciągów. Robi się to po to, aby nie tworzyć w pamięci wielu identycznych obiektów typu String , bo jak pamiętamy, String jest klasą niezmienną i jeśli mamy wiele odniesień do tej samej wartości, nie ma w tym nic złego. Nie jest już możliwa sytuacja, w której zmiana wartości w jednym miejscu powoduje zmianę dla kilku innych łączy jednocześnie. Niemniej jednak, jeśli utworzymy ciąg znaków za pomocą new :
String str = new String("Hello world");
w pamięci zostanie utworzony oddzielny obiekt, który będzie przechowywał tę wartość ciągu (i nie ma znaczenia, czy mamy już taką wartość w puli ciągów). Jako potwierdzenie:
String firstStr = new String("Hello world");
String secondStr = "Hello world";
String thirdStr = new String("Hello world");
System.out.println(firstStr == secondStr);
System.out.println(firstStr == thirdStr);
Otrzymamy dwie fałszywe wartości , co oznacza, że ​​mamy tutaj trzy różne wartości, do których się odwołujemy. Właściwie dlatego zaleca się tworzenie ciągów znaków po prostu za pomocą podwójnych cudzysłowów. Możesz jednak dodać (lub uzyskać odniesienie) wartości do puli ciągów podczas tworzenia obiektu za pomocą new . W tym celu używamy metody klasy string - intern() . Ta metoda wymusza utworzenie wartości w puli ciągów lub uzyskanie łącza do niej, jeśli jest już tam przechowywana. Oto przykład:
String firstStr = new String("Hello world").intern();
String secondStr = "Hello world";
String thirdStr = new String("Hello world").intern();
System.out.println(firstStr == secondStr);
System.out.println(firstStr == thirdStr);
System.out.println(secondStr == thirdStr);
W rezultacie w konsoli otrzymamy trzy wartości true , co oznacza, że ​​wszystkie trzy zmienne odnoszą się do tego samego ciągu znaków.Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 7 - 2

63. Jakie wzorce GOF są używane w puli strun?

W puli strunowej wyraźnie widoczny jest wzór GOF – flyweight , zwany inaczej osadnikiem. Jeśli widzisz tutaj inny szablon, udostępnij go w komentarzach. Cóż, porozmawiajmy o lekkim szablonie. Lekki to wzorzec projektu konstrukcyjnego, w którym obiekt, który prezentuje się jako unikalna instancja w różnych miejscach programu, w rzeczywistości taki nie jest. Lekkość oszczędza pamięć, dzieląc między sobą współdzielony stan obiektów, zamiast przechowywać te same dane w każdym obiekcie. Aby zrozumieć istotę, spójrzmy na najprostszy przykład. Załóżmy, że mamy interfejs pracownika:
public interface Employee {
   void work();
}
I są pewne wdrożenia, na przykład prawnik:
public class Lawyer implements Employee {

   public Lawyer() {
       System.out.println("Юрист взят в штат.");
   }

   @Override
   public void work() {
       System.out.println("Решение юридических вопросов...");
   }
}
I księgowy:
public class Accountant implements Employee{

   public Accountant() {
       System.out.println("Бухгалтер взят в штат.");
   }

   @Override
   public void work() {
       System.out.println("Ведение бухгалтерского отчёта....");
   }
}
Metody są bardzo warunkowe: musimy tylko dopilnować, aby zostały wdrożone. Ta sama sytuacja dotyczy konstruktora. Dzięki wynikom konsoli zobaczymy, kiedy powstają nowe obiekty. Posiadamy również dział pracowniczy, którego zadaniem jest wydanie żądanego pracownika, a jeśli go tam nie ma, zatrudnienie go i wydanie w odpowiedzi na żądanie:
public class StaffDepartment {
   private Map<String, Employee> currentEmployees = new HashMap<>();

   public Employee receiveEmployee(String type) throws Exception {
       Employee result;
       if (currentEmployees.containsKey(type)) {
           result = currentEmployees.get(type);
       } else {
           switch (type) {
               case "Бухгалтер":
                   result = new Accountant();
                   currentEmployees.put(type, result);
                   break;
               case "Юрист":
                   result = new Lawyer();
                   currentEmployees.put(type, result);
                   break;
               default:
                   throw new Exception("Данный сотрудник в штате не предусмотрен!");
           }
       }
       return result;
   }
}
Czyli logika jest prosta: jeśli jest dana jednostka, zwróć ją, jeśli nie, utwórz ją, umieść w magazynie (coś w rodzaju skrytki) i oddaj. Zobaczmy teraz jak to wszystko działa:
public static void main(String[] args) throws Exception {
   StaffDepartment staffDepartment = new StaffDepartment();
   Employee empl1  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl2  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
   Employee empl3  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl4  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
   Employee empl5  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl6  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
   Employee empl7  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl8  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
   Employee empl9  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl10  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
}
Odpowiednio w konsoli pojawi się wyjście:
Prawnik został zatrudniony. Rozwiązywanie problemów prawnych... Zatrudniono księgową. Prowadzenie raportu księgowego.... Rozwiązywanie problemów prawnych... Prowadzenie raportu księgowego.... Rozwiązywanie problemów prawnych... Prowadzenie raportu księgowego.... Rozwiązywanie problemów prawnych... Prowadzenie raportu księgowego.... Rozwiązywanie problemów prawnych. .. Prowadzenie raportów księgowych...
Jak widać powstały tylko dwa obiekty, które były wielokrotnie wykorzystywane. Przykład jest bardzo prosty, ale wyraźnie pokazuje, jak użycie tego szablonu może zaoszczędzić nasze zasoby. Cóż, jak zauważyłeś, logika tego wzorca jest bardzo podobna do logiki puli ubezpieczeniowej. Więcej o rodzajach wzorców GOF przeczytasz w tym artykule .Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 7 - 3

64. Jak podzielić ciąg na części? Podaj przykład odpowiedniego kodu

Oczywiście to pytanie dotyczy metody podziału . Klasa String ma dwie odmiany tej metody:
String split(String regex);
I
String split(String regex);
regex to separator linii — jakieś wyrażenie regularne dzielące ciąg znaków na tablicę ciągów, na przykład:
String str = "Hello, world it's Amigo!";
String[] arr = str.split("\\s");
for (String s : arr) {
  System.out.println(s);
}
Na konsolę zostaną wypisane następujące informacje:
Witaj, świecie, tu Amigo!
Oznacza to, że nasza wartość ciągu została podzielona na tablicę ciągów, a separatorem była spacja (w celu rozdzielenia moglibyśmy użyć wyrażenia regularnego innego niż spacja „\\s” i po prostu wyrażenia łańcuchowego „ „ ). Druga, przeciążona metoda posiada dodatkowy argument – ​​limit . limit — maksymalna dozwolona wartość wynikowej tablicy. Oznacza to, że gdy ciąg znaków został już podzielony na maksymalną dozwoloną liczbę podciągów, nie będzie już dalszego podziału, a ostatni element będzie zawierał „resztę” prawdopodobnie niedostatecznie podzielonego ciągu. Przykład:
String str = "Hello, world it's Amigo!";
String[] arr = str.split(" ", 2);
for (String s : arr) {
  System.out.println(s);
}
Wyjście konsoli:
Witaj, świecie, tu Amigo!
Jak widać, gdyby nie ograniczenie limit = 2 , ostatni element tablicy można by podzielić na trzy podciągi.Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 7 - 4

65. Dlaczego tablica znaków jest lepsza do przechowywania hasła niż ciąg znaków?

Istnieje kilka powodów, dla których podczas przechowywania hasła preferujemy tablicę zamiast łańcucha: 1. Pula ciągów i niezmienność ciągów. Używając tablicy ( char[] ), możemy jawnie usunąć dane, gdy już z nimi skończymy. Możemy też dowolnie przepisywać tablicę, a prawidłowego hasła nie będzie nigdzie w systemie, nawet przed wyrzuceniem śmieci (wystarczy zmienić kilka komórek na nieprawidłowe wartości). Jednocześnie String jest klasą niezmienną. Oznacza to, że jeśli będziemy chcieli zmienić jego wartość, otrzymamy nową, a stara pozostanie w puli ciągów. Jeśli chcemy usunąć wartość String z hasła, może to być bardzo trudne zadanie, ponieważ potrzebujemy modułu zbierającego elementy bezużyteczne, aby usunąć wartość z puli String i istnieje duże prawdopodobieństwo, że ta wartość String pozostanie tam przez dłuższy czas długi czas. Oznacza to, że w tej sytuacji String jest gorszy od tablicy char pod względem bezpieczeństwa przechowywania danych. 2. Jeśli wartość String zostanie przypadkowo wyprowadzona do konsoli (lub dzienników), sama wartość zostanie wyświetlona:
String password = "password";
System.out.println("Пароль - " + password);
Wyjście konsoli:
Hasło
Jednocześnie, jeśli przypadkowo wyślesz tablicę do konsoli:
char[] arr = new char[]{'p','a','s','s','w','o','r','d'};
System.out.println("Пароль - " + arr);
W konsoli pojawi się niezrozumiały bełkot:
Hasło - [C@7f31245a
Właściwie to nie gobbledygook, ale: [C to nazwa klasy to tablica znaków , @ to separator, po którym następuje 7f31245a to szesnastkowy kod skrótu. 3. Oficjalny dokument, Przewodnik po architekturze Java Cryptography, wyraźnie wspomina o przechowywaniu haseł w char[] zamiast String : „Wydaje się logiczne zbieranie i przechowywanie haseł w obiekcie typu java.lang.String . Istnieje jednak jedno zastrzeżenie: obiekty String są niezmienne, tj. nie ma zdefiniowanych metod pozwalających na modyfikację (nadpisanie) zawartości obiektu String lub wyzerowanie go po użyciu. Ta funkcja sprawia, że ​​obiekty String nie nadają się do przechowywania poufnych informacji, takich jak hasła użytkowników. Zamiast tego powinieneś zawsze zbierać i przechowywać poufne informacje dotyczące bezpieczeństwa w tablicy znaków.Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 7 - 5

Wyliczenie

66. Podaj krótki opis Enum w Javie

Wyliczenie jest wyliczeniem, czyli zbiorem stałych łańcuchowych połączonych wspólnym typem. Deklarowane poprzez słowo kluczowe - enum . Oto przykład z wyliczeniem - prawidłowymi rolami w określonej szkole:
public enum Role {
   STUDENT,
   TEACHER,
   DIRECTOR,
   SECURITY_GUARD
}
Słowa pisane wielkimi literami są tymi samymi stałymi wyliczeniowymi, które są zadeklarowane w sposób uproszczony, bez użycia operatora new . Korzystanie z wyliczeń znacznie upraszcza życie, ponieważ pomaga uniknąć błędów i zamieszania w nazewnictwie (ponieważ może istnieć tylko określona lista wartości). Osobiście uważam je za bardzo wygodne, gdy są stosowane w projekcie logicznym Switcha .

67. Czy Enum może implementować interfejsy?

Tak. W końcu wyliczenia muszą reprezentować coś więcej niż tylko zbiory pasywne (takie jak role). W Javie mogą reprezentować bardziej złożone obiekty z pewną funkcjonalnością, więc może być konieczne dodanie do nich dodatkowej funkcjonalności. Umożliwi to również wykorzystanie możliwości polimorfizmu poprzez podstawienie wartości wyliczeniowej w miejscach, gdzie wymagany jest typ zaimplementowanego interfejsu.

68. Czy Enum może rozszerzyć klasę?

Nie, nie może, ponieważ wyliczenie jest domyślną podklasą klasy ogólnej Enum <T> , gdzie T reprezentuje ogólny typ wyliczeniowy. Jest to nic innego jak wspólna klasa bazowa dla wszystkich typów wyliczeniowych języka Java. Konwersja wyliczenia na klasę jest wykonywana przez kompilator Java w czasie kompilacji. To rozszerzenie nie jest wyraźnie wskazane w kodzie, ale zawsze jest obecne w sposób niewidoczny.

69. Czy można utworzyć Enum bez instancji obiektów?

Jeśli chodzi o mnie, pytanie jest trochę dziwne, albo nie do końca je zrozumiałem. Mam dwie interpretacje: 1. Czy może istnieć wyliczenie bez wartości – tak, oczywiście byłoby to coś w rodzaju pustej klasy – bez znaczenia:
public enum Role {
}
I dzwonienie:
var s = Role.values();
System.out.println(s);
W konsoli otrzymamy:
[Lwaga.Rola;@9f70c54
(pusta tablica wartości Role ) 2. Czy można utworzyć wyliczenie bez operatora new - oczywiście, że tak. Jak powiedziałem powyżej, nie musisz używać operatora new dla wartości wyliczeniowych (wyliczeń) , ponieważ są to wartości statyczne.

70. Czy możemy zastąpić metodę toString() dla Enum?

Tak, oczywiście możesz zastąpić metodę toString() , aby zdefiniować konkretny sposób wyświetlania wyliczenia podczas wywoływania metody toString (podczas tłumaczenia wyliczenia na zwykły ciąg znaków, na przykład w celu wyprowadzenia danych do konsoli lub dzienników).
public enum Role {
   STUDENT,
   TEACHER,
   DIRECTOR,
   SECURITY_GUARD;

   @Override
   public String toString() {
       return "Выбрана роль - " + super.toString();
   }
}
To tyle na dziś, do następnej części!Analiza pytań i odpowiedzi z rozmów kwalifikacyjnych dla programisty Java.  Część 7 - 6
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION