Насколько насущный для людей уже имеющих какой-никакой опыт в Java вопрос о повышении уровня и движении дальше? Есть люди, которые выбирают долгий и тернистый путь Back-End разработчика штудируя все новые и новые для себя языки программирования, углубляясь в аппаратную или даже DevOps часть. Безусловно, это все полезные навыки и честь и хвала тому кто выбрал для себя этот путь. Но ведь есть и такие которые "всем сердцем любят" Java и хотят достичь высот именно с этим языком, не "отвлекаясь на всякие" Ruby-on-Rails, Python, C# или node.js.
При достижении определенного пика на уровне junior сложно понять, что именно надо знать и понимать для повышения своего скила. А ведь есть ряд вопросов которые, по моему мнению, являются базовыми, от которых уже можно "танцевать" и выстраивать логические цепочки в хитросплетениях этого богатого языка. Конечно это не отменяет того, что для повышения своего скила, а как следствие и уровня оплаты труда, полезно знать множество "сопроводительных" технологий, таких как ElasticSearch, Queue, AWS или GCS. Но это все специфика проекта на который вы собеседуетесь. А базовые знания для этого уровня, как ни странно, примерно одни. Это не значит что Вам можно "забить" на изучение других технологий и штудировать эти(либо подобные) вопросы - всю спецификацию проекта знать(или хотя бы понимать для чего и как используется та или иная технология) необходимо, однако знание базы на собеседовании даст вам необходимый плюс перед другими кандидатами.
И так, я бы выделил несколько тем которые следует знать, если не досконально, то хотя бы на достаточно высоком уровне: Java core, Java Collection Framework, Multithreading, OOP, SQL ну и конечно же Spring Framework. Каждая из этих тем встречается довольно часто и на собеседованиях на junior уровень, но для уровня middle нужны более углубленные знания. Там где junior скажет что в Java есть два типа памяти и каждая из них хранит свой тип данных, middle разработчик должен подробно объяснить структуру и принципы работы. В общем если junior должен знать что это такое, то middle уже должен еще и знать и понимать "с чем его едят". Естественно вопросы типа "Как перебрать все элементы HashMap?" должны вызывать у вас улыбку и незамедлительный и правильный ответ.
Java Core
Java Core обширная тема, разговаривая по которой можно уходить далеко в "дебри" языка. Вас, безусловно, будут спрашивать о Java Core на любом из собеседований, это база отталкиваясь от которой можно составить первое впечатление о собеседнике и выстроить процесс дальнейшего общения. Если Вы ответите на все вопросы связанные с Java Core, то и по другим темам самой Java вас будут спрашивать куда меньше. Ниже приведены примеры вопросов которые могут встретиться на собеседовании.
Какие бывают типы памяти в Java?
В Java существует два вида памяти - heap и stack. Каждый из них хранит свой вид данных, имеет свою структуру и работает по своей схеме. Heap выделяет память под объекты и JRE классы, stack в свою очередь хранит ссылки на объекты и примитивы.
В чем разница между heap и stack?
Heap используется всеми частями приложения, в то время как stack используется лишь главным потоком выполнения программы.
Каждый раз когда мы создаем объект(практически всегда используя ключевое слово new
) - этот объект хранится в heap, а в stack хранится ссылка на этот объект:
Person person = new Person();
тут мы создаем новый объект Person(), после чего он сразу же помещается в heap, а person становится ссылкой на этот объект и хранится в stack.
Stack - упорядоченная структура и работает по схеме LIFO(Last-In-First-Out). у heap весь доступ к объектам происходит по ссылкам из stack.
Heap в разы больше stack, stack работает быстрее благодаря структуре, stack "живет" короткий промежуток времени, в то время как heap "живет" весь "жизненный цикл" программы.
Какая структура у heap?
Heap имеет структуру пирамиды. Это означает что самый большой элемент всегда будет занимать самую высокую позицию. Иллюстрация приведена ниже на рисунке.
Вставляемый новый узел помещается в конец пирамиды. Но согласно условия что дочерний узел не может быть больше родительского, идет проверка с родительским узлом. Если новый элемент меньше - операция вставки закончена, если нет - новый элемент меняется местами с родительским, до момента пока родительского узла не останется(либо родительский элемент не будет больше дочернего).
Благодаря своей пирамидной структуре(если угодно - структура бинарного дерева) вставка/удаление элемента происходит за время O(logN).
Что такое packege access level?
В Java существует 4 модификатора доступа: 3 из них явных и один неявный. Private/public/protected - явные модификаторы, это означает что каждый из них нужно объявлять в коде. Так же существует четвёртый модификатор доступа - packege access level. Его необязательно объявлять в коде, данный модификатор означает что поле/метод будут доступны на уровне пакета или наследникам данного класса.
Что означает понятие JIT?
Аббревиатура JIT означает Just-In-Time. В JRE существует подобный компилятор, который компилирует байт-код в собственный машинный код прямо во время выполнения программы. Таким образом повышается производительность Java-программ. В свою очередь JIT компилятор повышает непредсказуемость работы программы и повышает сложность отслеживания причины багов и ошибок. Так же такому компилятору необходимо гораздо больше памяти в сравнении с интерпретируемой компиляцией.
Для чего нужна Double Brace инициализация в Java?
Double Brace инициализация используется в Java для наполнения коллекций(set, list, map, queue) одновременно с их объявлением. В случае если вам необходимо создать unmodifiable коллекцию, без Double Brace инициализации нам будет необходимо создать список, положить туда необходимое кол-во элементов и создать из этого списка unmodifiableList используя Collections класс. Используя Double Brace инициализацию мы имеем возможность "положить" в unmodifiableList все необходимые элементы сразу при объявлении.
Что такое WeakReference и SoftReference в Java? Какая разница между этими понятиями?
WeakReference и SoftReference упоминается в контексте сборки мусора.
SoftReference используется при кешировании внутри системы и будет удален сборщиком мусора в случае если осталось мало памяти, а на объект существуют только ссылки SoftReference. Это критично при кэшировании, так как наш сборщик мусора самостоятельно освободит нам память в критичной ситуации, но до этого момента не будет "трогать" эти объекты.
WeakReference - в случае если на объект ссылаются только через weak-ссылки, то объект будет удален сразу при обходе сборщиком мусора.
Это критические отличия между этими двумя типами ссылок.
Для чего используют слово static в Java? Возможно ли переопределить статический метод?
Ключевым словом static
в Java можно пометить поле, метод, блок или внутренний класс и это будет означать что этот субъект принадлежит классу, а не объекту. Иными словами - при изменении субъекта, он одинаково изменится для всех объектов данного класса.
Мы можем перегрузить статический метод, но мы не можем его переопределить, так как он принадлежит классу.
Java Collection Framework
Так как Java язык для обработки большого количества данных, то и коллекции Вам знать просто необходимо! В целом, как правило, на этой теме внимания не заостряют, но Вы должны знать это и быть готовым вопросам сложнее чем просто "В чем разница между Set и List?" - это и так Вам должно быть известно и если Вам задали такой вопрос при собеседовании на должность middle, то стоит задуматься о квалификации того кто задал вопрос и о его компании в целом. Я выбрал несколько вопросов, которые помогут Вам более детально понять в каком направлении следует "копать".
В чем различия ArrayList и LinkedList?
В первую очередь в самой структуре - ArrayList "под коробкой" использует массив, следовательно, каждый раз при вставке элемента будет создаваться новый массив размера n+1(где n размер массива до вставки). Из-за этого для вставки элемента в середину списка потребуется сначала "освободить" место под новый элемент, "сдвинув" все элементы после места вставки влево. Вставка в конец массива происходит за константное время. Доступ же к элементам происходит быстрее благодаря индексу элемента.
В свою очередь в LinkedList каждый элемент "знает" своих соседей. Следовательно, что бы вставить новый элемент(удалить старый) в середину все что нам надо это записать ссылки на соседей и перезаписать у соседей. Однако для доступа по индексу необходимо "перебрать" все элементы и найти нужный индекс. Так же при использовании LinkedList следует помнить про затраты памяти на хранение связи между элементами.
В чем особенность Map в сравнении с Set и List?
Map не является наследником интерфейсов Collection или Iterable. Так же Map хранит данные в виде ключ-значение. Соответственно доступ к элементам происходит не по индексу, а по уникальному ключу. При добавлении нового элемента всегда нужно создавать для него уникальный ключ(например при использовании HashMap и не правильном заполнении ключа могут возникать коллизии - ситуации когда по ключу доступен совершенно не тот элемент который нужен).
В чем отличие между HashMap и Hashtable?
Hashtable потокобезопасна и синхронизирована, но из-за этого теряется производительность. Кроме того Вы не сможете записать парой ключ-значение null-null, в HashMap это доступно. У HashMap есть альтернатива - ConcurrentHashMap которая является потокобезопасной. В целом Hashtable более старый класс и появился в Java до введения Collection framework в целом и HashMap в частности.
Какая есть особенность у Queue? Какие порядки упорядочивания элементов Queue вы знаете?
Queue - коллекция, хранящая элементы в порядке очереди. Выделяют два порядка для упорядочивания элементов - LIFO(last-in-first-out) и FIFO(first-in-first-out) - основные отличия между ними в том что при LIFO элемент который был вставлен последний, первый же и будет возвращен/удален. При FIFO первый вставленный элемент будет возвращен(методы element() и peek()) либо удален(remove() и poll()).
Multithreading
Многопоточность - важнейшая тема при собеседовании на должность middle Java developer. Вот тут следует ждать и каверзных вопросов, вопросов с подвохами и перехода на более сложные темы. Крайне сложно вести разговор на равных по этой теме если Вы до этого не работали с многопоточностью или вся Ваша работа с ней заключалась в том что бы описать один поток и запустить его. Но понимание отдельных тем даст Вам необходимый баланс и Вы сможете произвести впечатление человека, который, если не досконально владеет темой, то как минимум не "плавает" в ней, а разбирается. Этот ряд вопросов поможет Вам сориентироваться в теме многопоточности и поможет Вам понять что нужно "подтянуть":
Какими способами можно создать потоки в Java?
В Java доступны три варианта создания потока из класса - расширить класс Thread либо реализовать интерфейсы Callable или Runnable.
При использовании Thread мы запускаем поток методом start(), а описываем логику выполнения в методе run().
При использовании Runnable мы должны вызвать метод run(), а у Callable - метод call(). Так как и Runnable и Callable - интерфейсы, мы определим логику выполнения внутри их методов(Runnable и Callable являются функциональными интерфейсами которые имеют только один метод - run() и call() соответственно). Разница в этих двух интерфейсов в том что Callable возвращает результат.
Что такое монитор?
В случае если мы, например, имеем синхронизированный метод(помеченный ключевым словом syncronized
) и один из наших потоков заходит в этот метод, то этот поток блокирует этот метод для других потоков и любой другой поток которому нужно будет воспользоваться нашим методом будет "ждать" пока первый поток не закончит свою работу с методом. Про такую ситуацию говорят: "данный метод занят монитором". Монитор в Java - объект, который обеспечивает правильную работу в многопоточной среде - отметка что синхронизированный блок кода временно стал недоступен, отметка что блок кода стал доступен после выполнения, ожидание если синхронизированный блок занят другим потоком.
Какие есть способы синхронизации в Java?
В Java существует несколько способов синхронизации:
по методу/блоку инициализации - с помощью ключевого слова
syncronized
по переменной - используя ключевое слово
volatile
методы wait/notify/notifyAll
используя классы из пакета java.util.concurrent - в этом пакете собраны классы работа которых основана на атомарных операциях.
В чем разница между sleep() и wait(), notify() и notifyAll() методами?
Метод sleep()
заставляет поток "заснуть" на определенное время(указывается в миллисекундах) после чего поток продолжит свою работу. Метод wait()
освобождает монитор занятый потоком так что другие потоки могут использовать указанный блок кода и поток переходит в состояние waiting - поток будет ждать вызова метода notify()
или notifyAll()
другим потоком. Разница между notify()
и notifyAll()
в том что notify()
"высвободит" один поток(какой именно определить нельзя) а notifyAll()
"освободит" все потоки из состояния waiting в состояние running.
Что такое Dead lock?
Случай когда у нас выполняются два потока(A, B) и наш первый поток(А) заблокировал метод a() и второй поток(В) заблокировал метод b() и в то же время поток А пытается получить доступ к методу b(), а поток B пытается получить доступ к методу а() называют dead lock.
Зачем используют потоки-демоны в Java?
Потоки-демоны это "служебные" потоки, которые работают в фоновом процессе. Они служат для "обеспечения всем необходимым" основные потоки выполнения. В случае завершения всех основных потоков потоки-демоны завершатся автоматически, не дожидаясь окончания их выполнения.
Что такое Future в Java?
Future - это интерфейс Java который находится в пакете java.util.concurrent. Future представляет методы для определения того была ли задача выполнена, ожидание выполнения для получения результата, получение результата уже выполненной задачи. В глобальном плане Future можно описать следующим образом - мы определяем задачу для Future, Future выполняет эту задачу независимо от нас, мы получаем результат.
OOP
Java язык объектно-ориентированного программирования, значит и ООП будет рассмотрен уже под более детальным углом. Если на junior уровень вам было достаточно рассказать про три основные принципы ООП, то для middle Вы можете смело поправить собеседника фразой "А их разве всего три? Куда тогда деть абстракцию?". Ниже приведен ряд вопросов для того что бы Вы поняли чего Вы не знаете и что бы Вам стало понятнее что гуглить.
Дайте определение абстракции
Абстракция - процесс выделения общих значений и характеристик объекта, исключая незначительные, которые будут определены в конкретных наследниках.
В чем преимущество ООП?
Главный плюс ООП подхода в том что мы характеризуем и описываем реальные объекты из мира - счет в банке, товар в магазине, машина и прочее. Так же ряд преимуществ нам дает наследование, полиморфизм, инкапсуляция и абстракция. Так наследование позволяет нам уйти от повторного написания одного и того же кода, абстракция позволяют нам выделить общие характеристики, а полиморфизм дает возможность использовать эти общие характеристики для разных реализаций.
В чем разница между композицией и агрегацией?
Агрегация - процесс при котором мы не строго связываем объекты, то есть наш корневой объект может существовать и правильно функционировать и без инициализации объектов-полей.
Композиция - процесс при котором мы связываем объекты строго, что значит что объект не только является частью другого объекта, но и не может быть частью других объектов.
Для полного понимания следует обратиться к примерам: наш корневой объект - машина(Car
). У машины есть двигатель(Engine
) и пассажиры(Passenger[]
). Мы можем высадить всех пассажиров(стереть массив Passenger
, либо самому массиву, либо каждому месту передать значение null
) и машина будет функционировать и без них - это называют агрегацией. В то же время машина не может функционировать без двигателя и данный конкретный двигатель не может "обслуживать" другую машину - если наша Car
Нonda Сivic, то у нее двигатель может быть только 2л и на Honda Accord мы его уже не поставим - это называется композиция.
Что такое динамическое связывание?
Что бы ответить на этот вопрос, необходимо понимать что такое само по себе связывание. И так, связывание - это наличие связи между ссылкой и кодом. Пример: ссылка на которую Вы ссылаетесь привязана к коду в котором она определена. Так же и метод привязан к месту в коде в котором он определен.
Что же такое динамическое связывание? Динамическое связывание означает что метод привязывается к конкретному коду в момент вызова, на этапе выполнения программы, на этапе создания объектов, а не при компиляции программы в байт-код.
Перечислите SOLID принципы
SOLID - это аббревиатура. Расшифровывается так:
S - Single Responsibility - означает что класс должен отвечать только за операции одного типа.
O - Open-Closed - описываемый класс должен быть открыт для расширения, но закрыт для изменений.
L - Liskov Substitution - если наш класс А является наследником класса В, то любой объект класса В может быть заменен объектом класса А без негативных последствий и нарушения функционала.
I - Interface Segregation - класс должен выполнять только те функции и задачи которые на него возложены, в ином случае это производит к потере ресурсов и появлению багов.
D - Dependency Inversion - зависимости самого низкого уровня не должны зависеть от зависимостей высшего уровня и наоборот - зависимости высшего уровня НЕ могут существовать без зависимостей низшего уровня.
Что значит Liskov Substitution?
Рассмотрим ситуацию когда мы имеем класс А - пусть это будет класс грузовик(Truck
) и его наследник прицепной грузовик(Trailer
). Назначение что одного, что другого - перевозка грузов. Используя принцип подстановки Барбары Лисков мы можем заменить любой Truck
на Trailer
без потери производительности, без негативных последствий и нарушений функционала в любом месте в программе.
SQL
Java - язык для работы с большими объемами данных. Все эти данные хранятся в базах данных разного рода. Безусловно, NoSQL базы сейчас обрели популярность и встречаются в проектах все чаще. Но 80% существующих проектов работают именно с реляционной моделью. Секрет этого весьма прост - почти все Java проекты - высоконагруженные системы, требующие обработки большого количества данных и средства Java, в целом, конечно справляются с возложенными задачами, но аппаратная часть требует больших затрат на обработку этих данных. Тут нам и помогает SQL готовый принять на себя часть этих действий, тем самым облегчая жизнь для самой Java. Разные сервера баз данных предлагают разный функционал, но базовые вещи выделяются во всех и как раз они и приведены в этих вопросах:
Что такое хранимая процедура?
Хранимая процедура - это набор инструкций который следует выполнить с данными. Например, если мы сохраняем купленный товар, нам надо проверить есть ли этот товар в наличии, есть ли на товар скидка, нет ли предзаказа на данный товар и так далее и на основании этого решить сохранять купленный товар или нет. Мы можем нагрузить наш код проверками, можем даже обернуть это все в одну транзакцию, но гораздо правильнее будет инкапсулировать все эти проверки в хранимую процедуру и вызывать ее в определенный момент не заботясь о проверках.
Какие вы знаете основные свойства транзакции?
Основные свойства транзакций баз данных сгруппированы и представлены в виде аббревиатуры ACID:
А - Atomicity(Атомарность) - гарантирует нам что каждая транзакция будет выполнена полностью, либо не будет выполнена совсем.
С - Consistency(Согласованность) - каждая успешная транзакция фиксирует только допустимые результаты не нарушая консистентность базы. Иными словами если мы сохраняем записи в разные связанные таблицы мы можем выбрать путь сохранения в каждую таблицу по отдельности, но в таком случае нам придется следить за успешным выполнением каждой из запросов что бы не нарушить целостность базы или же мы можем обернуть все запросы в одну транзакцию и не беспокоиться о том что при одной не успешной транзакции мы не должны проводить ни каких дополнительных манипуляций.
I - Isolation(Изолированность) - при выполнении одной транзакции параллельные транзакции не должны оказывать влияния на результат.
D - Durability(Надежность) - если система дала ответ об успешной транзакции, то мы не должны переживать о том что транзакция будет отменена при любом сбое. Транзакция успешно завершена, в ходе выполнения программы выпадает критическая ошибка, но транзакция уже выполнена и данные сохранены.
Что такое индексы? Как и где их применяют?
Индексы в БД - это специальные таблицы, которые используются поисковым двжком БД. В целом, индексы в БД схожи с оглавлением в книге. В случае если нам приоритетен поиск по таблице, а не вставка или изменение, мы можем создать индекс как для таблицы в целом так для одной либо нескольких столбцов. Выбор столбцов, ровно, как и типа индекса(одноколоночный/многоколоночный) зависит от того что именно, мы чаще всего будем использовать в условном операторе.
Что означает нормализация базы данных?
Нормализация БД - процесс при котором мы организовываем данные в базе, путем создания новых таблиц и связей между ними. Каждый шаг должен проходить в соответствии с правилами обеспечивающими защиту данных. В конечном итоге мы должны получить гибкую базу данных, без избыточности и несогласованных зависимостей.
Выделяют шесть нормальных форм баз данных. Переходя, например, от первой нормальной формы ко второй мы получим более гибкую базу, разделенную необходимыми связями между таблицами позволяющую гибко использовать тот или иной функционал.
Разница между HAVING и WHERE?
Самое главное отличие - используя HAVING
мы можем писать условие с использованием агрегатных функций. СHAVING
мы должны использовать GROUP BY
так как HAVING
используется после формирований групп. То есть, сначала используется фильтр WHERE
, затем формируются группыGROUP BY,
после чего можно накладывать агрегации на условие HAVING
. Следовательно - полностью заменить WHERE
мы не можем так как на этапе исполнения WHERE
мы еще не имеем групп по которым мы сможем фильтровать. А на момент выполнения HAVING
мы уже имеем необходимые группы. Это спецификация выполнения SQL запроса.
Spring Framework
Spring хорош не только тем что дает нам необходимые механизмы для создания приложений(подчеркну - не только web! Сейчас Spring используется повсеместно и даже какие либо не сложные десктопные приложения лучше создавать используя Spring), но и сама концепция Spring, ее понимание, не только владение аннотациями Spring, а и понимание того как они вставляются, расширяет наш кругозор в Java. Например, IoC и DI - паттерны проектирования, которые используются не только в Spring. Знание того что использует Spring под коробкой даст вам необходимый уровень на собеседовании не только по теме самого Spring, но и Java в целом.
Что такое IoC? Как это используется в Spring?
IoC - один из принципов ООП. Расшифровывается как Inversion of Control и дает нам возможность просто описать класс, не заботясь о его инициализации. Иными словами - мы создаем классы, отдаем эти класс под управление системой, а система уже сама создает экземпляры этого класса и сама решает какой экземпляр и где его использовать.
Например, мы описываем класс, помечаем его аннотацией и далее уже можем его использовать в том или ином контексте, не озадачиваясь тем что нам еще необходимо его правильно проинициализировать, либо этот класс должен быть singlton, либо еще чем то. Мы просто его используем. И все.
Spring использует IoC-контейнер для инициализации классов, контроля за их использованием. Когда мы помечаем один из классов как @Service
мы "отдаем" этот самый класс под власть Spring и дальше сам Spring будет отвечать за создание экземпляра этого класса и внедрение этого самого экземпляра в нужном месте.
Что такое AOP? Как это используется в Spring?
АОР - аспектно-ориентированное программирование, парадигма программирования, призванное решить те задачи для которого ООП кажется избыточным. В основе идеи лежит выделение сквозной функциональности. Грубо говоря - мы выносим наш функционал, не относящийся к бизнес логике в отдельное место и даем ему набор правил когда и с кем работать. Сумбурно? Пример: мы не можем писать код без логов. Но логи - это не часть бизнес логики, это вспомогательный функционал. Мы можем описать всю логику логов в отдельном классе и затем каждый раз инициализировать этот класс для выведения лога или же мы можем создать отдельный класс(аспект) и навесить набор правил когда и где использовать этот код. Имеем класс Service, метод method() внутри и аспект LogAspect. Используя аннотации мы можем указать методам внутри LogAspect что они должны быть вызваны перед, либо после вызова Service.method(). Так же мы можем указать выполнение после возврата результата либо после ошибки.
Spring использует АОР, например, при проверке security при доступе на тот или иной эндпоинт.
Что такое Spring Bean?
Spring Bean - самый обычный объект Java. Можно сказать, что это обыкновенный POJO, с одной единственной оговоркой - этот объект управляется Spring и только им.
Жизненный цикл Spring Beans?
Жизненным циклом бина в Spring управляет контейнер Spring. После запуска контейнера он начинает создавать необходимые бины(компоненты) и внедрять зависимости. Когда контейнер завершает свою работу уничтожаются и бины связанные с ним. Весь цикл жизни бина можно разделить на 6 этапов:
Инстанцирование объекта - технический старт жизни любого объекта, работа конструктора.
Установка свойств и внедрение зависимостей - выполнение конфигурационных свойств бина и внедрение необходимых ему зависимостей.
Уведомление aware-интерфейсов - если бин реализует
Aware
интерфейс, он будет вызываться путем передачи имени бинаset*()
метод.Пре-инициализация - метод
postProcessBeforeInitialization()
интерфейсаBeanPostProcessor
.Инициализация - сначала выполняется метод бина с аннотацией
PostConstruct
, затем методafterPropertiesSet()
в случае если бин реализует InitializingBean, методinit()
.Пост-инициализация - метод
postProcessAfterInitialization()
интерфейсаBeanPostProcessor
.
Какие есть scope Spring Beans?
Scope бинов - области видимости бинов. Последняя версия Spring(на сентябрь 2022 - 5.3.19) включает в себя шесть областей видимости:
Singleton - область видимости установленная по умолчанию, в случае если не установлен никакая другая видимость. Работает как любой паттерн проектирования Singleton.
Prototype - означает что каждый раз при обращении к контейнеру будет возвращен разные объекты.
Request - создает бин для одного HTTP запроса.
Session - аналогичен Request, но работает на уровне сессии.
Application - жизненный цикл ServletContext.
Websocket - создает бин для сессии веб-сокета
У вас несколько бинов реализующих один интерфейс. Какой из них будет внедрятся?
Для правильного внедрения зависимостей существуют несколько способов и подходов. Самый первый из них - аннотация @Primary
означает что данный бин будет внедряться по умолчанию. Так же существует аннотация @Qualifier
- позволяет указать имя бина который необходим в данном месте. Так же мы можем внедрять list/map бинов. При указании имен для всех бинов-наследников одного интерфейса мы можем внедрить map где ключом будет имя бина, а значением сам бин. При внедрении list мы получим список всех бинов.
Как объявить сервис в Spring? Назовите все способы которые знаете
Сервис в Spring - часть бизнес логики, но на самом абстрактном уровне для самого Spring все бины мало чем отличаются - каждый из них это, в первую очередь, это @Bean
который можно объявить в классе конфигураций. Далее мы можем объявить наш класс как @Component
. Используя аннотации мы можем объявить наш класс @Service
что "облегчит" работу Spring при инициализации бина(все бины инициализируются в очередности от тех кто не требует ни каких внедрений и до тех кто "не может жить" без зависимостей. Сервисные слои инициализируются "посередине" и мы даем явно понять Spring когда создавать этот бин). Кроме того мы можем объявлять бины используя xml конфигурацию Spring.
Какой класс в Spring отвечает за анализ входящих http-запросов и их направления в определенный контроллер?
В Spring за "разведение" http-запросов по соответствующим контейнерам отвечает класс DispatcherServlet. После получения запроса DispatcherServlet обращается к интерфейсу HandlerMapping, который и рассказывает DispatcherServlet какой именно котроллер отвечает за этот запрос. После обращения к контреллеру DispatcherServlet, получивший имя представления(View), обращается к ViewResolver для получения View по его имени. После создания View DispatcherServlet отправляет данные Модели в View который и отобразится в браузере.
Что такое Spring Security? В чем разница между авторизацией и аутентификацией?
Spring Security - часть Spring Framework представляющий механизмы для контроля за авторизацией пользователей. Для управления Spring Security необходимо создать конфигурационный класс-наследник WebSecurityAdapter.
Аутентификация - процедура проверки подлинности, например проверка подлинности пользователя путем сравнения введенного им пароля с паролем, сохраненным в базе данных
Авторизация - предоставление определенному лицу или группе лиц прав на выполнение определенных действий.
Подведем итог
Я не гарантирую что каждый из этих вопросов попадется Вам на собеседовании, но внимательно прочитав эту статью вы сможете определить ряд вопросов для себя и уменьшить круг поисков. Более того, возможно, на собеседовании уделяться время будет как раз таки другим технологиям - микросервисной архитектуре, работе облачными технологиями, работе с Docker, либо Вас будут гонять по NoSQL базам данных. Но будьте уверены - темы из в этой статьи будут затронуты тем или иным образом на собеседовании. Так же не затронул банальные вещи типа web, паттерны проектирования, логирование и тому подобные вещи которые Вы и так должны знать, более того Вы должны знать, что Вы должны это знать! Спасибо за внимание :-)
Комментарии (34)
Alexandroppolus
12.09.2022 18:16В параграфе про SOLID, ответы для пп. 4 и 5 прямо совсем мимо.
FruTb
13.09.2022 01:20Ага. А ещё прям бесит что из-за того что на подстановку так "затрачиваются" - забыли все остальное что аж выразили это в DRY. (Это я не буду тут ещё рассуждать почему ооп - недоказуемая концепция)
panzerfaust
12.09.2022 18:34+2Этот ряд вопросов поможет Вам сориентироваться в теме многопоточности и поможет Вам понять что нужно "подтянуть"
Честное слово, сколько можно мурыжить эти wait-notify в 2022 году? Для меня такие вопросы на собесе просто красный флаг, что в данной команде никакой экспертизой по многопоточке даже не пахнет. Иначе бы более практические вещи обсуждали.
Что значит Liskov Substitution?
И я не понимаю, зачем продолжают придумывать эти вымученные примеры (к тому же ошибочные), если можно просто спросить кандидата примеры из JDK, где этот LSP нарушается.
S - Single Responsibility - означает что класс должен отвечать только за операции одного типа.
А потом эти люди имеют CRUD-репозиторий (CRUD это же один тип операций) на 2000 строк и святую уверенность, что написали все по солиду.
frideviloop
13.09.2022 17:44Допустим, у нас есть Берун, Кладун и Переберун. Если Переберун сделан на основе Трогуна или Щупуна, то где-то тихонечко плачет Клаус Песцов, ведь Берун сделан на основе Поднималы (см. Отрывалу), а Кладун на основе Отпускалы. Но зубрить надо обязательно эту англосаксонскую ересь, которую запомнить невозможно, а искать непереведено, и ересь эта изначально должна была отвечать только за микрокод и ни за что больше. Мы же о языке высокого уровня говорим, не так ли?
ASCIIx0E-0F
Но вы меня поправьте, если я не прав, буду весьма признателен.
Trump
12.09.2022 19:34+2Спасибо за публикацию.
"Мы можем перегрузить статический метод, но мы не можем его переопределить, так как он принадлежит классу. "
Не можем переопределить, т.к. статика не принимает участие в полиморфизме, а не потому, что статический метод принадлежит классу, т.к. класс наследник - это уже фактически другой класс.
onets
12.09.2022 21:37+1Кому из мидлов реально пригодилось знание структуры кучи?
Каждый раз когда мы создаем объект(практически всегда используя ключевое слово
new
) - этот объект хранится в heapВ яве нет способа разместить ссылочные типы на стеке?
Trump
12.09.2022 22:59По структуре кучи и в целом памяти бывает полезно ориентироваться, когда тюнингуешь GC
snuk182
13.09.2022 00:12+1Явно - нет. Расположение зависит от реализации JVM, оптимизации кода под конкретное железо, и много от чего еще. Знание структуры кучи может пригодиться для реализации высокопроизводительных алгоритмов, чтобы было как можно меньше поводов гонять GC, останавливая или замедляя мир.
saboteur_kiev
13.09.2022 03:42Главный плюс ООП подхода в том что мы характеризуем и описываем реальные объекты из мира - счет в банке, товар в магазине, машина и прочее. Так же ряд преимуществ нам дает наследование, полиморфизм, инкапсуляция и абстракция. Так наследование позволяет нам уйти от повторного написания одного и того же кода, абстракция позволяют нам выделить общие характеристики, а полиморфизм дает возможность использовать эти общие характеристики для разных реализаций.
Да ладно, это вообще не плюс ООП подхода, и вообще суть ООП из этого абзаца неясно.
Главный плюс ООП - это интуитивно понятная логика по нарезанию большого кода на кусочки, поддержка и модернизация которых вызывает минимум конфликтов.
А именно:
ООП говорит, что надо объединить данные с методами, которые именно эти данные обрабатывают в одно целое (в объект).Ни модульный ни другой подходы такой явной логики не дает, и когда проект разрастается до размера в десятки и сотни разработчиков - кроме ООП мало что может выжить. Микросервисы - это тоже своего рода ООП, но уже уровнем выше.
А все эти наследования и полиморфизмы - это следствие того, что ООП изначальный неидеален, они никак не главные в ООП.GospodinKolhoznik
13.09.2022 08:13Смотрите: 1. Все методы static(вообще все); 2. Никаких переменных у объектов (вообще никаких); 3. Все необходимые данные каждый метод получает только из аргументов; 4. Все результаты своей работы каждый метод выдает только в виде ответа, т.е. не меняет переменные своего или других объектов (а куда ему деваться, переменных у объектов то нет); 5. Конструкторы не нужны (какое облегчение); 6. Наследование тоже не нужно (больше проблем, чем пользы); 7. Интерфейсы нужны (ибо это хорошо); 8. Все данные, которая обрабатывает программа объедены в иерархические структуры (и их немного, может быть вообще одна единая структура на всю программу).
Никакого объединения кода и данных и программа становится более устойчива. Потому что нет конструкторов и инициализаторов, которые всегда точки риска и потенциальных проблем, и у методов нет состояния, а значит их результат работы предсказуем и зависит только от входных аргументов, а не от того, что там в переменных модуля кто-то когда-то раньше понапихал.
Получилось вообще не ООП, но понятность и прозрачность кода только повысилась.
dopusteam
13.09.2022 08:25Интерфейсы нужны (ибо это хорошо)
Статичный метод может реализовать интерфейс?
Потому что нет конструкторов и инициализаторов, которые всегда точки риска и потенциальных проблем
Представляете сколько параметров будут принимать сервисы при таком подходе? Конструктор - как раз решение проблем, а не источник, имхо
А как это всё тестировать, кстати?
GospodinKolhoznik
13.09.2022 09:38Статичный метод может реализовать интерфейс?
В java нельзя? Это печально, рука-лицо. В чем сакральный смысл такого запрета? А в скале можно, я щас проверил.
Представляете сколько параметров будут принимать сервисы при таком подходе?
Один, два может быть три? Параметры ведь можно упаковать в струкруты.
А как это всё тестировать, кстати?
Так в том то и дело, что методы не зависящие от внешнего состояния, а зависящие только лишь от входных параметров тестировать гораздо проще и надежнее.
dopusteam
14.09.2022 08:09Упаковывать параметры в структуры просто, чтоб было меньше параметров? Это не решение, это костыль.
Смотрите. У меня есть метод сервиса. Он ожидает на вход некий коннектор к БД и вызывает внутри его метод. Теперь мне нужно при вызове сервиса передать туда не только коннектор, но и строку подключения, чтоб сервис мог работать с БД. Это нормально?
И в тему тестов, статичные методы можно мокать?
GospodinKolhoznik
14.09.2022 09:56Да, именно так. Нормально. Гораздо лучше передавать параметры подключения каждый раз, чем хранить их внутри объекта (Ну это мое имхо, разумеется). Да, надо будет предварительно потратить усилия на то, чтобы продумать иерархическую структуру данных, ещё до того, как начать писать код. Ну а потом у вас все данные программы по сути будут объеденины в несколько крупных структур, которые будут в себе содержать всё. Стандартный подход, это 3 структуры: 1я содержит в себе только неизменяимую информацию (как параметры подключения к БД) у нее доступны только механизмы чтения; 2я обычная - содержит изменяемую информацию; 3я содержит только инфу, которую можно лишь добавлять, но не удалять и изменять (например логи и т.п.).
Ну не для всей программы, а скорее для каждого из компонентов есть свой собственный набор структур данных, и для некоторых компонетнов эти структуры могут быть общими.
Т.е. при таком подходе все методы будут вместо сигнатуры f : X -> Y будут иметь сигнатуру f : APP<X> -> APP<Y>, где APP<X> содержит в себе Params, Logger и собственно сам X. (Почти все методы могут обойтись без изменяемой структуры, там где без нее не обойтись надо будет передавать и ее)
GospodinKolhoznik
14.09.2022 10:04Главная проблема в другом. В том, что любая либа будет в качестве пареметров интерфейса взаимодействия просить обычные объекты - инициализированные, где поля наполненны какими то там данными, и т.п. И принимать она захочет такие объекты и возвращать их будет. А это значит, что придется почти для каждой библиотеки городить собственную прослойку, подменяющие все методы библиотеки на аналогичные методы но уже только с данными, без объектов. Вот это главная боль.
Ну а какие преимущества? Вы попробуйте написать какой ни будь небольшой собственный проектик в такой концепции. Вы сами увидите, что у вас программа получается целостная, устойчивая и не рассыпающаяся от внесения изменениий. К тому же при таком подходе вы большую часть мыслительных ресурсов будете тратить на продумывание структуры данных и алгоритмов программы, т.е. именно на решение задачи, и меньше тратить сил и времени на продумывание "архитектурных излишеств".
dopusteam
14.09.2022 10:15Гораздо лучше передавать параметры подключения каждый раз, чем хранить их внутри объекта
Чем лучше то?
Тем, что нарушается D из SOLID, т.к. мой условный сервис уровня приложения принимает на вход строку подключения (и ещё кучу низкоуровневых деталей)? Тем, что мне нужно для любого сервиса таскать весь конфиг всегда с собой и все необходимые сервисы (http, db, различные бизнесовые, всё что угодно)?
Ну и не забывайте, что в условных c# и java, статичные методы не могут быть реализацией интерфейса.
Покажите как это выглядит в скале, я что то бегло не нагуглил такое.
panzerfaust
13.09.2022 08:30+1Все методы static(вообще все);
Интерфейсы нужны (ибо это хорошо)
Не бьется как-то. Что будут делать интерфейсы, если методы прибиты гвоздями к классам?
Собственно, вы описали самый примитивный кейс использования Java + Spring. И все хорошо, пока вам не понадобились например паттерны "стратегия" или "pub-sub". Вот тут и будут нужны наследования и полиморфизмы в реальной жизни. А вовсе не для наследования foo от bar, как нас учат странные статейки.
GospodinKolhoznik
13.09.2022 09:45Что будут делать интерфейсы, если методы прибиты гвоздями к классам?
Не понял вопроса. Вы же можете один и тот же интерфейс для разных ооп-шных классов реализовывать по разному. Задача интерфейсов заключается в определении интерфейса взаимодействия между компонентами программы. Каждый компонент программы это API для других компонентов. Интерфейс задаёт этот API. Можно и без него, но с интерфейсом лучше.
Вот тут и будут нужны наследования и полиморфизмы в реальной жизни
Нужны будут наследования интерфейсов (т.е. просто расширение функционала интерфейса), а не наследование ооп-шных классов.
saboteur_kiev
14.09.2022 14:08Никаких переменных у объектов (вообще никаких);
А где тогда хранятся данные? Все переменные глобальные?
Тогда при любом изменении программы как минимум все разработчики будут одновременно править файл с этими глобальными переменными.Все данные, которая обрабатывает программа объедены в иерархические структуры (и их немного, может быть вообще одна единая структура на всю программу).
Как вы поделите это на отдельные файлы, чтобы в большом проекте 10-ти разным девелоперам было удобно править 10 разных задач и не пересекаться по файлам или минимизировать это пересечение?
dopusteam
13.09.2022 07:53+1зависимости высшего уровня НЕ могут существовать без зависимостей низшего уровня
Где вы такое определение DI то нашли? И что оно значит в вашем мире?
класс должен выполнять только те функции и задачи которые на него возложены, в ином случае это производит к потере ресурсов и появлению багов.
Что это значит? Как класс может выполнять задачи, которые на него не возложены?
процесс выделения общих значений и характеристик объекта, исключая незначительные, которые будут определены в конкретных нанаследниках
Незначительные будут определены в наследниках, я правильно понял?
iliazeus
13.09.2022 09:07+7Какая структура у heap?
Heap имеет структуру пирамиды. Это означает что самый большой элемент всегда будет занимать самую высокую позицию.
Очень похоже, что автор перепутал кучу как область памяти и кучу как структуру данных.
ptyrss
13.09.2022 10:47+3К вопросу "В чем различия ArrayList и LinkedList?"
" каждый раз при вставке элемента будет создаваться новый массив размера n+1(где n размер массива до вставки)."Что-то не верится, а там нет умножения на 1.5 (2) размерности? Вот реально каждый раз переаллокация?)
alekseeyn91
13.09.2022 17:43-1Здравствуйте, Вам наверное стоит перепроверить информацию и сравнить некоторые утверждения с официальной документацией. Например:
"Так же существует четвёртый модификатор доступа - packege access level. Его необязательно объявлять в коде, данный модификатор означает что поле/метод будут доступны на уровне пакета или наследникам данного класса."
Это неверно т.к package-private доступен только внутри класса или пакета
aleksandy
14.09.2022 10:03Справедливости ради, если наследник будет находиться в том же пакете, то поле/метод будут видны и в наследнике.
Гораздо больше раздражает, что protected поля/методы видны в рамках пакета вообще всем.
zzzzzzzzzzzz
14.09.2022 11:57Почему раздражает? В рамках пакета же могут быть всякие "дружественные" классы. Например, в энтэрпрайзном стиле - класс с protected конструктором и отдельный класс фабрики объектов.
Да, возможно, было бы интереснее отдельно задавать права для наследников, для пакета и для ещё кого-нибудь. Но если изобретать свой язык и стараться учесть все варианты этих "кого-нибудь", то уже слишком сложно всё получится. Интересно, кто-нибудь таким заморачивался?
Teolen
13.09.2022 17:49ArrayList "под коробкой" использует массив, следовательно, каждый раз при вставке элемента будет создаваться новый массив размера n+1
Эм... А Вы точно специалист-то, про grow() не слышали?(и это только вторая минута чтения)
Viacheslav1991
14.09.2022 13:11Секундочку! А разве Stack не создается для каждого потока? И почему в таком случае к нему есть доступ только у главного?
dymmasja
14.09.2022 14:21Про double braces спрашивают на собеседованиях? Статические анализаторы кода это помечают как баг, пишут, что это - антипаттерн и требуют исправить.
К тому же с Java 9 для создания неизменяемых Set и Map есть более удобная конструкция: Set.of(...), Map.of(...).
Seldon
Какая-то странная статья, ваши вопросы по java скорее тянут на джуна, но никак не на мидла. От вас как мидла будут уже хотеть реальный опыт разработки приложений, понимание микросервисной архитектуры, rest, grpc очереди и тд.
aelaa
В этом и нюанс: перечисленные Вами
никакого отношения к Java не имеют.
Уровень программиста (junior-middle-senior) ортогонален языку (и технологии в целом) на котором он пишет. Давайте еще Senior Spring Boot 2.7.1 Developer определим.
Seldon
Вы перестаньте жить в вашем мире фантазий, на уровне мидл нет "Java разработчика", который знает java и больше ничего, такой разработчик ничего не стоит, у вас уже есть конкретная специальность (backend разработчик веб приложений, разработчик ui приложений, андройд девелопер и тд) вас берут для решения конкретной задачи иначе смысла от вас нет. И даже если вы решите будучи middle backend developer перейти в android developer вас никто никогда не наймет, пока вы не научитесь проходить интервью для андройд девелопера, там будут вопросы по Java но это далеко даже не 50% интервью.
И то что вы написали, спросят скорее для начала разговора и лишь частично, да и все эти знания должен иметь любой junior developer