JavaRush /Java блогы /Random-KK /Java әзірлеушісіне арналған сұхбаттардағы сұрақтар мен жа...
Константин
Деңгей

Java әзірлеушісіне арналған сұхбаттардағы сұрақтар мен жауаптарды талдау. 13-бөлім

Топта жарияланған
Сәлеметсіз бе!
Мақсатқа қарай жылжу – бұл ең алдымен қозғалыс.
Сондықтан бір нәрсеге қол жеткізгім келеді деп ойлау жеткіліксіз. Сізге бірдеңе істеу керек - тіпті ең кішкентай қадамдар - бірақ оларды күн сайын жасаңыз, сонда ғана сіз түпкілікті мақсатқа жетесіз. Сіз Java әзірлеушісі болу үшін осында болғандықтан, күн сайын Java туралы біліміңізді тереңдету үшін ең аз қадам жасауыңыз керек. Бүгінгі Java қадамы үшін әзірлеушілер үшін ең танымал сұхбат сұрақтарын талдаудың жаңа бөлігімен танысуды ұсынамын. Java әзірлеушісіне арналған сұхбаттардағы сұрақтар мен жауаптарды талдау.  13 - 1 бөлімБүгін біз кіші мамандарға арналған сұрақтардың практикалық бөлігін қарастырамыз. Сұхбаттағы практикалық тапсырма сирек емес. Мұндай жағдайда адаспау маңызды, салқын басын сақтауға және оңтайлы шешімді ұсынуға тырысыңыз, тіпті бірнеше. Сондай-ақ мәселені шешкен кезде үнсіз қалмай, өз ойларыңызға түсініктеме беріп, шешімін жазуды немесе жазғаннан кейін не істегеніңізді және не үшін жасағаныңызды сөзбен түсіндіруді ұсынамын. Бұл сізді үнсіз шешім қабылдаудан гөрі сұхбат алушыға ұнатады. Ендеше, бастайық!

111. Жіптер арасында мәліметтер қалай алмасуға болады?

Java әзірлеушісіне арналған сұхбаттардағы сұрақтар мен жауаптарды талдау.  13 - 2 бөлімАғындар арасында деректер алмасу үшін көптеген әртүрлі тәсілдер мен құралдарды пайдалануға болады: мысалы, атомдық айнымалыларды, синхрондалған жинақтарды және семафорды пайдаланыңыз. Бірақ бұл мәселені шешу үшін мен алмастырғышпен мысал келтіремін . Алмастырушы - ортақ синхрондау нүктесін жасау арқылы ағындар жұбының арасындағы элементтердің алмасуын жеңілдететін қатарлас пакеттен синхрондау класы . Оны пайдалану екі ағын арасындағы деректер алмасуды жеңілдетеді. Оның жұмыс істеу жолы өте қарапайым: ол Exchange() әдісін шақыру үшін екі бөлек ағынды күтеді . Олардың арасында алмасу нүктесі сияқты бірдеңе құрылады: бірінші жіп өз an objectісін қойып, орнына екіншісінің an objectісін алады, ал екіншісі, өз кезегінде, біріншінің an objectісін алып, өздікін қояды. Яғни, бірінші ағын Exchange() әдісін пайдаланады және басқа ағын сол нысандағы exchange() әдісін шақырмайынша және олардың арасында деректер алмаспайынша бос болады . Мысал ретінде Thread сыныбының келесі орындалуын қарастырыңыз :
public class CustomThread extends Thread {
 private String threadName;
 private String message;
 private Exchanger<String> exchanger;

 public CustomThread(String threadName, Exchanger<String> exchanger) {
   this.threadName = threadName;
   this.exchanger = exchanger;
 }

 public void setMessage(final String message) {
   this.message = message;
 }

 @Override
 public void run() {
   while (true) {
     try {
       message = exchanger.exchange(message);
       System.out.println(threadName + " поток получил сообщение: " + message);
       Thread.sleep(1000);
     } catch (Exception e) {
       e.printStackTrace();
     }
   }
 }
}
Жіп конструкторында біз String түріндегі нысандарды қабылдайтын Exchanger нысанын анықтаймыз және іске қосу кезінде ( іске қосу әдісінде) сол Exchanger ішінде осы әдісті пайдаланатын басқа ағынмен хабарлама алмасу үшін оның exchange() функциясын қолданамыз . Оны негізгі түрде іске қосайық :
Exchanger<String> exchanger = new Exchanger<>();
CustomThread first = new CustomThread("Первый ", exchanger);
first.setMessage("Сообщение первого потока");
CustomThread second = new CustomThread("Второй", exchanger);
second.setMessage("Сообщение второго потока");
first.start();
second.start();
Консоль келесіні көрсетеді:
Бірінші ағын хабарламаны алды: Екінші ағыннан хабарлама Екінші ағын хабарламаны алды: Бірінші ағыннан хабарлама Екінші ағын хабарламаны алды: Екінші ағыннан хабарлама Бірінші ағын хабарламаны алды: Бірінші ағыннан хабарлама Екінші жіп хабарламаны алды: Бірінші ағыннан хабарлама Бірінші ағын хабарламаны алды: Екінші ағыннан хабарлама... .
Бұл ағындар арасында деректер алмасу сәтті болғанын білдіреді.

112. Thread класы мен Runnable интерфейсінің айырмашылығы неде?

Бірінші айта кететін нәрсе, Thread - бұл класс, Runnable - интерфейс, бұл өте айқын айырмашылық =D Мен Thread Runnable (композиция) қолданатынын Java әзірлеушісіне арналған сұхбаттардағы сұрақтар мен жауаптарды талдау.  13 - 3 бөлімайтамын . Яғни, бізде екі жол бар:
  1. Thread ішінен мұраға алыңыз , іске қосу әдісін қайта анықтаңыз, содан кейін осы нысанды жасаңыз және ағынды start() әдісі арқылы бастаңыз .

  2. Белгілі бір сыныпта Runnable бағдарламасын іске қосыңыз , оның run() әдісін орындаңыз , содан кейін Runnable интерфейсінің осы нысан іске асырылуын оның конструкторына тағайындай отырып, Thread нысанын жасаңыз . Соңында start() әдісі арқылы Thread нысанын іске қосыңыз .

Не артықшылық береді? Кішкене ойланып көрейік:
  • Runnable интерфейсін іске асырған кезде ағынның әрекетін өзгертпейсіз. Негізінде сіз жіпке іске қосу үшін бір нәрсе бересіз. Және бұл өз кезегінде жақсы тәсіл деп саналатын біздің композиция.

  • Runnable бағдарламасын енгізу сыныпқа көбірек икемділік береді. Егер сіз Thread ішінен мұра алсаңыз , орындалатын әрекет әрқашан ағында болады. Бірақ Runnable қолданбасын іске асырсаңыз, ол жай ғана ағын болуы керек емес. Өйткені, сіз оны ағында іске қоса аласыз немесе оны қандай да бір орындаушы қызметіне бере аласыз. Немесе оны бір ағынды қолданбадағы тапсырма ретінде бір жерге жіберіңіз.

  • Runnable пайдалану тапсырманы орындауды ағынды басқару логикасынан логикалық түрде бөлуге мүмкіндік береді.

  • Java тілінде тек бір ғана мұрагерлік мүмкін, сондықтан тек бір сыныпты кеңейтуге болады. Сонымен қатар, кеңейтілетін интерфейстердің саны шексіз (жақсы, мүлдем шектеусіз емес, бірақ 65535 , бірақ сіз бұл шектеуге жетуіңіз екіталай).

Нені пайдаланған дұрыс екенін өзіңіз шешесіз ^^

113. Т1, Т2 және Т3 жіптері бар. Оларды ретімен қалай жүзеге асыруға болады?Java әзірлеушісіне арналған сұхбаттардағы сұрақтар мен жауаптарды талдау.  13 - 4 бөлім

Ең бірінші және ең қарапайым нәрсе - join() әдісін пайдалану . Ол ағымдағы ағынның (әдіс деп аталатын) орындалуын әдіс деп аталатын ағын орындалуды аяқтағанша тоқтатады. Өзіміздің ағынды іске асыруды жасайық:
public class CustomThread extends Thread {
private String threadName;

 public CustomThread(final String  threadName){
   this.threadName = threadName;
 }

 @Override
 public void run() {
   System.out.println(threadName + " - начал свою работу");
   try {
     // происходит некая логика
     Thread.sleep(1000);
   } catch (InterruptedException e) {
     e.printStackTrace();
   }

   System.out.println(threadName + " - закончил свою работу");
 }
}
join() арқылы үш ағынды бір-бірден бастайық :
CustomThread t1 = new CustomThread("Первый поток");
t1.start();
t1.join();
CustomThread t2 = new CustomThread("Второй поток");
t2.start();
t2.join();
CustomThread t3 = new CustomThread("Третий поток");
t3.start();
t3.join();
Консоль шығысы:
Бірінші жіп - жұмысын бастады Бірінші жіп - жұмысын аяқтады Екінші жіп - жұмысын бастады Екінші жіп - жұмысын аяқтады Үшінші жіп - жұмысын бастады Үшінші жіп - жұмысын аяқтады
Бұл тапсырманы орындадық деген сөз. Содан кейін біз тікелей Junior деңгейіндегі практикалық тапсырмаларға көшеміз .

Практикалық тапсырмалар

114. Матрицалық диагональды қосынды (Leetcode мәселесі)

Шарты: Бас диагональдағы барлық элементтердің және негізгі диагональдың бөлігі болып табылмайтын қосымша диагональдағы барлық элементтердің қосындысын есептеңіз. Java әзірлеушісіне арналған сұхбаттардағы сұрақтар мен жауаптарды талдау.  13 - 5 бөлім1. Пішіннің матрицасымен: mat = [[1,2,3], [4,5,6], [7,8,9]] Шығару - 25 болуы керек 2. Матрицамен - mat = [[1,1 ,1,1], [1,1,1,1], [1,1,1,1], [1,1,1,1]] Шығару - 8 болуы керек 3. a матрица - mat = [[ 5]] Қорытынды мынадай болуы керек - 5 Оқуды тоқтатыңыз және шешіміңізді орындаңыз. Менің шешімім келесідей болады:
public static int countDiagonalSum(int[][] matrix) {
 int sum = 0;
 for (int i = 0, j = matrix.length - 1; i < matrix.length; i++, j--) {
   sum += matrix[i][i];
   if (j != i) {
     sum += matrix[i][j];
   }
 }
 return sum;
}
Барлығы массив арқылы бір өту кезінде болады, оның барысында бізде есеп үшін екі индекс бар: i - массив жолдары мен негізгі диагональ бағандарын хабарлау үшін, j - қосымша диагональ бағандарын хабарлау үшін. Егер негізгі диагональ мен қосымша ұяшық сәйкес келсе, онда қосындыны есептеу кезінде мәндердің бірі еленбейді. Шарттағы матрицаларды пайдаланып тексерейік:
int[][] arr1 = {
   {1, 2, 3},
   {4, 5, 6},
   {7, 8, 9}};
System.out.println(countDiagonalSum(arr1));

int[][] arr2 = {
   {1, 1, 1, 1},
   {1, 1, 1, 1},
   {1, 1, 1, 1},
   {1, 1, 1, 1}};
System.out.println(countDiagonalSum(arr2));

int[][] arr3 = {{5}};
System.out.println(countDiagonalSum(arr3));
Консоль шығысы:
25 8 5

115. Нөлдерді жылжыту (Leetcode тапсырмасы)

Шарты: бүтін массивте нөлдік емес элементтердің салыстырмалы тәртібін сақтай отырып, барлық 0-ді соңына дейін жылжытыңыз. 1. Массивпен: [0,1,0,3,12] Шығару келесідей болуы керек: [1,3,12,0,0] 2. Жиыммен: [0] Шығару келесідей болуы керек: [0] Тоқтап, шешімімді жазыңыз... Менің шешімім:
public static void moveZeroes(int[] nums) {
 int counterWithoutNulls = 0;
 int counterWithNulls = 0;
 int length = nums.length;
 while (counterWithNulls < length) {
   if (nums[counterWithNulls] == 0) {// находим нулевые элементы и увеличиваем счётчик
     counterWithNulls++;
   } else { // сдвигаем элементы на количество найденных нулевых элементов слева
     nums[counterWithoutNulls++] = nums[counterWithNulls++];
   }
 }
 while (counterWithoutNulls < length) {
   nums[counterWithoutNulls++] = 0;// заполняем последние элементы массива нулями согласно счётчику нулей
 }
}
Емтихан:
int[] arr1 = {1, 2, 0, 0, 12, 9};
moveZeroes(arr1);
System.out.println(Arrays.toString(arr1));

int[] arr2 = {0};
moveZeroes(arr2);
System.out.println(Arrays.toString(arr2));
Консоль шығысы:
[1, 2, 12, 9, 0, 0] [0]

116. Берілген тізім <String> атаулары. Әрбір атаудан бірінші әріпті алып тастап, сұрыпталған тізімді бұрыңыз

1. Еске түсетін бірінші нәрсе - коллекцияларға арналған көптеген көмекші әдістерді қамтитын Collections класының әдістері:
public static List<String> processTheList(List<String> nameList) {
 for (int i = 0; i < nameList.size(); i++) {
   nameList.set(i, nameList.get(i).substring(1));
 }
 Collections.sort(nameList);
 return nameList;
}
2. Сондай-ақ, егер біз Java 8 және одан жоғары нұсқасын қолданатын болсақ, біз шешімді ағындар арқылы көрсетуіміз керек:
public static List<String> processTheList(List<String> nameList) {
 return nameList.stream()
     .map(x -> x.substring(1))
     .sorted().collect(Collectors.toList());
}
Таңдалған шешімге қарамастан, тексеру келесідей болуы мүмкін:
List<String> nameList = new ArrayList();
nameList.add("John");
nameList.add("Bob");
nameList.add("Anna");
nameList.add("Dmitriy");
nameList.add("Peter");
nameList.add("David");
nameList.add("Igor");

System.out.println(processTheList(nameList));
Консоль шығысы:
[avid, eter, gor, mitriy, nna, ob, ohn]

117. Жиымды аударыңыз

1-шешім Тағы да бірінші кезекте Collections көмекші утorтасының әдістерін қолдану керек . Бірақ бізде массив болғандықтан, алдымен оны жинаққа (тізімге) түрлендіру керек:
public static Integer[] reverse(Integer[] arr) {
 List<Integer> list = Arrays.asList(arr);
 Collections.reverse(list);
 return list.toArray(arr);
}
2-шешім Сұрақ массив туралы болғандықтан, менің ойымша, бұл шешімді қораптан тыс дайын функционалдылықты қолданбай көрсету керек және былайша айтқанда, классиктер:
public static Integer[] reverse(Integer[] arr) {
 for (int i = 0; i < arr.length / 2; i++) {
   int temp = arr[i];
   arr[i] = arr[arr.length - 1 - i];
   arr[arr.length - 1 - i] = temp;
 }
 return arr;
}
Емтихан:
Integer[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
System.out.println(Arrays.toString(reverse(arr)));
Консоль шығысы:
[9, 8, 7, 6, 5, 4, 3, 2, 1]

118. Жолдың палиндром екенін тексеріңіз

Java әзірлеушісіне арналған сұхбаттардағы сұрақтар мен жауаптарды талдау.  13 - 6 бөлім1-шешім StringBuilder бағдарламасын бірден еске түсірген жөн : ол қарапайым String- ге қарағанда икемді және әртүрлі әдістерге бай . Бізді әсіресе кері әдіс қызықтырады :
public static boolean isPalindrome(String string) {
 string = string.toLowerCase(); //приводит всю строку к нижнему регистру
 StringBuilder builder = new StringBuilder();
 builder.append(string);
 builder.reverse(); // перевочиваем строку методом Builder-а
 return (builder.toString()).equals(string);
}
Шешім: Келесі тәсіл қораптағы «саңылауларды» қолданбай болады. Біз жолдың артқы жағындағы таңбаларды алдыңғы жағындағы сәйкес таңбалармен салыстырамыз:
public static boolean isPalindrome(String string) {
  string = string.toLowerCase();
 int length = string.length();
 int fromBeginning = 0;
 int fromEnd = length - 1;
 while (fromEnd > fromBeginning) {
   char forwardChar = string.charAt(fromBeginning++);
   char backwardChar = string.charAt(fromEnd--);
   if (forwardChar != backwardChar)
     return false;
 }
 return true;
}
Және біз екі тәсілді де тексереміз:
boolean isPalindrome = isPalindrome("Tenet");
System.out.println(isPalindrome);
Консоль шығысы:
рас

119. Қарапайым сұрыптау алгоритмін жазыңыз (Bubble, Selection немесе Shuttle). Оны қалай жақсартуға болады?

Іске асырудың қарапайым алгоритмі ретінде мен таңдауды сұрыптауды таңдадым - Таңдауды сұрыптау:
public static void selectionSorting(int[] arr) {
 for (int i = 0; i < arr.length - 1; i++) {
   int min = i;
   for (int j = i + 1; j < arr.length; j++) {
     if (arr[j] < arr[min]) {
       min = j; // выбираем минимальный элемент в текущем числовом отрезке
     }
   }
   int temp = arr[min]; // меняем местами минимальный элемент с элементом под индексом i
   arr[min] = arr[i]; // так How отрезок постоянно уменьшается
   arr[i] = temp; // и выпадающие из него числа будут минимальными в текущем отрезке
 } // и How итог - числа оставшиеся вне текущей итерации отсортированы от самого наименьшего к большему
}
Жақсартылған нұсқа келесідей болады:
public static void improvedSelectionSorting(int[] arr) {
 for (int i = 0, j = arr.length - 1; i < j; i++, j--) { // рассматриваемый отрезок с каждой итерацией
   // будет уменьшаться с ДВУХ сторон по одному элементу
   int min = arr[i];
   int max = arr[i];
   int minIndex = i;
   int maxIndex = i;
   for (int n = i; n <= j; n++) { // выбираем min и max на текущем отрезке
     if (arr[n] > max) {
       max = arr[n];
       maxIndex = n;
     } else if (arr[n] < min) {
       min = arr[n];
       minIndex = n;
     }
   }
   // меняем найденный минимальный элемент с позиции с индексом min на позицию с индексом i
   swap(arr, i, minIndex);

   if (arr[minIndex] == max) {// срабатывает, если элемент max оказался смещен предыдущей перестановкой -
     swap(arr, j, minIndex); // на старое место min, поэтому с позиции с индексом min смещаем его на позицию j
   } else {
     swap(arr, j, maxIndex); // простое обмен местами элементов с индексами max и j
   }
 }
}

static int[] swap(int[] arr, int i, int j) {
 int temp = arr[i];
 arr[i] = arr[j];
 arr[j] = temp;
 return arr;
}
Енді сұрыптау шынымен жақсарғанына көз жеткізуіміз керек. Өнімділікті салыстырайық:
long firstDifference = 0;
long secondDifference = 0;
long primaryTime;
int countOfApplying = 10000;
for (int i = 0; i < countOfApplying; i++) {
 int[] arr1 = {234, 33, 123, 4, 5342, 76, 3, 65,
     3, 5, 35, 75, 255, 4, 46, 48, 4658, 44, 22,
     678, 324, 66, 151, 268, 433, 76, 372, 45, 13,
     9484, 499959, 567, 774, 473, 3, 32, 865, 67, 43,
     63, 332, 24, 1};
 primaryTime = System.nanoTime();
 selectionSorting(arr1);
 firstDifference += System.nanoTime() - primaryTime;

 int[] arr2 = {234, 33, 123, 4, 5342, 76, 3, 65,
     3, 5, 35, 75, 255, 4, 46, 48, 4658, 44, 22,
     678, 324, 66, 151, 268, 433, 76, 372, 45, 13,
     9484, 499959, 567, 774, 473, 3, 32, 865, 67, 43,
     63, 332, 24, 1};
 primaryTime = System.nanoTime();
 improvedSelectionSorting(arr2);
 secondDifference += System.nanoTime() - primaryTime;
}

System.out.println(((double) firstDifference / (double) secondDifference - 1) * 100 + "%");
Екі түр де бір циклде басталды, өйткені егер бөлек циклдар болса, жоғарыдағы code бойынша сұрыптау екінші орынға қойылғаннан гөрі нашар нәтиже көрсетеді. Бұл бағдарламаның «жылытуына», содан кейін сәл жылдамырақ жұмыс істеуіне байланысты. Бірақ мен тақырыптан сәл ауытқып кеттім. Консольде осы тексеруді бес рет орындағаннан кейін мен өнімділіктің жоғарылауын көрдім: 36,41006735635892% 51,46131097160771% 41,88918834013988% 48,0919807057437014% 48,09198014 , мен үшін бұл. өте жақсы нәтиже. Java әзірлеушісіне арналған сұхбаттардағы сұрақтар мен жауаптарды талдау.  13 - 7 бөлім

120. Int типті литералды byte типті литералмен құру алгоритмін (әрекеттер тізбегін) жазыңыз. Жадқа не болатынын түсіндіріңіз

  1. byte мәні int түрлендіріледі. Ол үшін 1 byte жад бөлінбейді, бірақ барлық int мәндері сияқты - 4, егер бұл мән әлі int стекінде болмаса. Егер бар болса, оған сілтеме жай ғана алынады.

  2. Екі int мәні қосылады, ал үшіншісі алынады. Ол үшін жадтың жаңа бөлімі бөлінеді - 4 byte (немесе int стекінен бар мәнге сілтеме алынады).

    Бұл жағдайда екі инт жады әлі де бос болады және олардың мәндері сәйкесінше int стегінде сақталады.

Біздің тізімдегі кіші деңгей сұрақтары осымен аяқталады. Келесі мақаладан бастап біз орта деңгейдегі мәселелерді түсінеміз. Орта деңгей сұрақтары бастауыш деңгейдегі әзірлеушілерге де белсенді түрде қойылатынын атап өткім келеді - Junior. Сондықтан хабардар болыңыз. Міне, бүгін бәрі: кездескенше!Java әзірлеушісіне арналған сұхбаттардағы сұрақтар мен жауаптарды талдау.  13 - 8 бөлім
Сериядағы басқа материалдар:
Пікірлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION