Въпроси и информация за подготовка за интервю за работа като Java Developer

Какво представляват SOLID принципите?
Какво представлява Single Responsibility Principle (SRP)
Един клас трябва да има само една отговорност и само една причина да бъде променян.
Ползите включват по-лесно тестване, по-ниско nиво на coupling и по-добра организация.
Пример:
public class Book {private String name;private String author;private String text;// constructor, getters, and setters}
Какво представлява принципът Open-Closed (OCP)
Софтуерните компоненти (класове, модули, функции) трябва да бъдат отворени за разширяване, но затворени за промяна.
Това се постига чрез използване на interfaces, abstract classes и inheritance.
Пример:
interface Shape {double area();}class Circle implements Shape { /* implementation / } class Rectangle implements Shape { / implementation */ }
Какво представлява Liskov Substitution Principle (LSP)
Подтиповете трябва да могат да заместят своите базови типове, без да променят коректността на програмата. Това следва връзката „is-a“.
Пример:
class Bird { /* common bird behavior / } class Sparrow extends Bird { / specific sparrow behavior */ }
Какво представлява Interface Segregation Principle (ISP)
Клиентите не трябва да бъдат принуждавани да зависят от interfaces, които не използват. Разделяй големи interfaces на по-малки и по-специфични.
Пример:
interface Worker {void work();}interface Eater {void eat();}class Robot implements Worker { /* implementation */ }
Какво представлява Dependency Inversion Principle (DIP)
Модулите от по-високо ниво не трябва да зависят от модули от по-ниско ниво; и двата вида трябва да зависят от абстракции. Абстракциите не трябва да зависят от детайли; детайлите трябва да зависят от абстракции.
// Abstractioninterface MessageSender {void sendMessage(String message);}// High-level moduleclass NotificationService {private MessageSender messageSender;public NotificationService(MessageSender messageSender) {this.messageSender = messageSender; }public void send(String message) {messageSender.sendMessage(message); }}// Low-level moduleclass EmailSender implements MessageSender {@Overridepublic void sendMessage(String message) {System.out.println("Sending Email: " + message); }}// Another Low-level moduleclass SmsSender implements MessageSender {@Overridepublic void sendMessage(String message) {System.out.println("Sending SMS: " + message); }}// Use SmsSenderMessageSender smsSender = new SmsSender();NotificationService notificationService2 = new NotificationService(smsSender);notificationService2.send("Hello via SMS!");
Интерфейсът MessageSender служи като абстракцията. Модулът от по-високо ниво NotificationService зависи от тази абстракция вместо от конкретни имплементации.
Модулите от по-ниско ниво като EmailSender и SmsSender също зависят от абстракцията, като имплементират интерфейса MessageSender.
Какво представлява Normalization в SQL
Какво е Normalization?
Normalization включва структуриране на данните в релационна база данни, за да се елиминират аномалии като redundancy. То разбива големи, сложни таблици на по-малки, като същевременно запазва релациите между данните.
Защо Normalization е важна?
Намалява redundancy: Като разделяме данните в по-малки таблици, избягваме съхранението на една и съща информация многократно.
Подобрява query performance: По-малките normalized таблици водят до по-бързо изпълнение на заявки.
Минимизира update anomalies: Промените могат да се правят, без да се засягат други записи.
Подобрява data integrity: Осигурява последователни и точни данни.
Сценарии за прилагане на Normalization
Data integrity: Предотвратява несъответствия при обновяване на данни (например, когато възрастта на клиент се промени).
Efficient querying: Опростява сложни заявки чрез разбиване на данните.
Storage optimization: Премахва излишната redundancy, за да се спести място.
Какво е Microservices monolith?
Какво е monolith?
Monolithic application е изградено като единен, цялостен модул. То стегнато свързва всички компоненти (като client-side UI, database и server-side application) в една обща code base.
Advantages:
Easy Deployment: Тъй като е един единствен executable файл или директория, deployment-ът е лесен и директен.
Development Speed: В ранните етапи от живота на даден проект monolith позволява бързо разработване благодарение на своята простота.
Performance: Централизираната code база може да обработва различни функции ефективно.
Какво представляват Microservices?
Microservices разбиват приложението на по-малки, независимо deploy-ваеми услуги. Всяка услуга е фокусирана върху конкретен бизнес домейн.
Предимства:
Scalability: Microservices позволяват скалиране на отделните компоненти независимо един от друг.
Flexibility: Разработчиците могат да избират различни технологии за всяка услуга.
Isolation: Грешки в една услуга не засягат останалите.
Предизвикателства:
Complexity: Управлението на множество услуги изисква надеждна оркестрация.
Network Overhead: Комуникацията между услугите въвежда latency.
Deployment Complexity: Координирането на updates през множество услуги може да бъде предизвикателство.
Какво е Polymorphism?
Method Overriding:
Когато даден subclass предоставя конкретна имплементация за метод, който вече е дефиниран в неговия superclass.
Method Overloading:
Когато един class има множество методи със същото име, но с различни parameters (различен брой или типове arguments).
Какво е предназначението на Private constructors?
Private constructors се използват в utility класове или constant класове.
Тези класове съдържат само static методи или членове.
Избягване на наследяване (Avoiding Subclassing)
Constructor Delegation:
Private constructors позволяват constructor delegation, при което конкретни конструктори извикват общ private constructor.
Singleton Pattern
За JVM
Compilation:
Когато компилираш .java файл, Java compiler генерира .class файлове, които съдържат bytecode (intermediate representation).
Bytecode е platform-independent и може да се изпълнява на всяка система с съвместима JVM.
Class Loader Subsystem:
Отговаря за зареждането на класовете в паметта.
Чете .class файловете, генерира двоични данни и ги съхранява в method area.
Създава Class object, който представя заредения клас в heap memory.
Example: Student s1 = new Student(); Class c1 = s1.getClass();
Linking:
Състои се от три стъпки:
Verification: Проверява коректността на .class файла.
Preparation: За side памет за class variables и ги инициализира.
Resolution (optional): Разрешава symbolic references към други класове или methods.
Initialization:
Изпълнява static initializers (напр. static blocks) и инициализира static variables.
Instance variables се инициализират, когато се създаде object.
Execution Engine:
Преобразува bytecode в machine code.
Включва Just-In-Time (JIT) compilation за оптимизация на performance-а.
Накратко, JVM позволява Java програмите да се изпълняват навсякъде, като интерпретира bytecode и управлява memory-то.
Какво представлява private abstract метод?
Use Cases за private методи в abstract класове:
Въпреки че abstract методите не могат да бъдат private, можеш да използваш private методи в abstract клас за различни цели:
Helper Methods: Имплементират обща функционалност, която се споделя от други методи.
Encapsulation: Скриват детайлите на имплементацията от subclasses.
Struct-like класове: Създават малки класове с private методи, които служат като data containers
What is Garbage collector?
Heap Memory:
Java обектите се създават в heap паметта, специално отделена памет зона.
Някои обекти стават недостъпни (вече не са реферирани от програмата).
Garbage Collector (GC):
GC идентифицира и изтрива неизползвани обекти, за да освободи памет.
Работи автоматично на заден фон.
Два типа:
Minor GC: Премахва недостъпните обекти в young generation.
Major GC (Full GC): Изчиства обектите в old generation.
Benefits:
Developers нямат нужда ръчно да изтриват обекти.
GC осигурява наличност на памет и предотвратява OutOfMemoryErrors.
What is OOP?
Four Principles of OOP:
Encapsulation: Групиране на данни (attributes) и методи (functions) заедно в един class, за да се скрият детайлите по имплементацията и да се осигури изчистен interface.
Inheritance: Създаване на нови classes на базата на вече съществуващи, като се наследяват техните attributes и behavior. Това насърчава code reuse.
Polymorphism: Позволява на objects от различни classes да бъдат третирани по един и същи начин чрез общ interface. Това дава гъвкавост и прави системата по-лесна за разширяване (extensibility).
Abstraction: Фокусиране върху важните характеристики, като се скриват ненужните сложности. Abstraction опростява дизайна и го прави по-лесен за поддръжка и управление.
ACID properties играят ключова роля за гарантиране на надеждността и консистентността на транзакциите.
Atomicity:
Транзакциите се изпълняват или изцяло, или изобщо не се изпълняват.
Няма частично изпълнение: ако една транзакция се провали по средата, всички промени, направени в базата данни, се връщат назад (rollback).
Пример: Да разгледаме прехвърляне на $100 от сметка X към сметка Y. Ако транзакцията се провали след като сумата е изтеглена от X, но преди да бъде добавена към Y, базата данни остава консистентна.
Consistency:
Базата данни трябва да поддържа своите integrity constraints преди и след дадена транзакция.
Пример: Ако общата сума преди транзакцията е $700 (X: $500, Y: $200), тя трябва да остане същата и след транзакцията (X: $400, Y: $300).
Isolation:
Множество транзакции могат да се изпълняват едновременно, без да си влияят една на друга.
Промените, направени от една транзакция, не са видими за останалите, докато не бъдат commit-нати.
Гарантира се резултат, еквивалентен на такъв, сякаш транзакциите са изпълнени последователно (serially).
Пример: Изолирането на транзакции предотвратява неконсистентни състояния, причинени от преплетени операции.
Durability:
След като една транзакция бъде commit-ната, нейните промени стават постоянни.
Промените оцеляват при системни повреди или crash-ове.
Гарантира се дълготрайно съхранение на данните.
Пример: Дори след restart на системата, commit-натите промени остават запазени.
Запомни, ACID properties осигуряват основата за надеждни и консистентни операции с бази данни!
Какво представлява Strategy шаблонът?
Описание: Дефинира семейство от algorithms, капсулира всеки от тях и ги прави взаимозаменяеми. Това позволява даден algorithm да се променя независимо от clients, които го използват.
Пример:
// Interface for eat behaviorinterface EatBehavior {void eat();}// Concrete implementations of eat behaviorclass NormalDiet implements EatBehavior {@Overridepublic void eat() {// Normal food }}class LosingWeightDiet implements EatBehavior {@Overridepublic void eat() {// Healthy food }}// Cat class with eat behaviorabstract class Cat {EatBehavior eatBehavior;public void eat() { eatBehavior.eat(); }public void setEatBehavior(EatBehavior eatBehavior) { this.eatBehavior = eatBehavior; }}// UsageCat cat = new Cat();cat.setEatBehavior(new LosingWeightDiet());cat.eat(); // Cat eats with a healthier diet
Creational Patterns
Тези шаблони са фокусирани върху инстанцирането на класове.
Примери включват
Factory Method,
Problem Statement: Да предположим, че искаме да създаваме различни типове продукти (напр. cars, bikes, cycles), без да обвързваме плътно кода си с конкретни имплементации.
Key Components:
Product: Абстрактен клас или interface, който дефинира общите операции за обектите, които factory-то ще създава.
Concrete Products: Реални класове, които имплементират Product interface-а и представят конкретни типове обекти.
Creator: Абстрактен клас или interface, който декларира factory method-а. Той е отговорен за създаването на Product обекти, но делегира реалното създаване на подкласи.
Concrete Creators: Подкласове на Creator, които имплементират factory method-а. Те решават кой конкретен Concrete Product да създадат, базирано на входни параметри или конфигурация.
Factory Method: Метод, дефиниран в Creator класа, който създава Product обекти. Обикновено е деклариран като abstract в Creator и е имплементиран в Concrete Creators.
Example in Java: Да кажем, че имаме Factory, което произвежда различни типове vehicles. Ето един опростен пример:
// Abstract Productinterface Product {void display();}// Concrete Productsclass Car implements Product {public void display() {System.out.println("Car created"); }}class Bike implements Product {public void display() {System.out.println("Bike created"); }}// Creator (Factory)interface Factory {Product factoryMethod();}// Concrete Creatorsclass ConcreteFactoryA implements Factory {public Product factoryMethod() {return new Car(); }}class ConcreteFactoryB implements Factory {public Product factoryMethod() {return new Bike(); }}public class FactoryMethodExample {public static void main(String[] args) {Factory factoryA = new ConcreteFactoryA();Product productA = factoryA.factoryMethod();productA.display();Factory factoryB = new ConcreteFactoryB(); Product productB = factoryB.factoryMethod(); productB.display();}
Builder
Builder Pattern е creational design pattern, който позволява да конструирате сложни обекти стъпка по стъпка,
като разделяте тяхната конструкция от тяхната репрезентация. Ето един прост пример in Java:
// Define the class you want to buildclass Post {private final String title;private final String text;private final String category;Post(Builder builder) { this.title = builder.title; this.text = builder.text; this.category = builder.category;}// Getters for propertiespublic String getTitle() { return title;}public String getText() { return text;}public String getCategory() { return category;}// Builder classpublic static class Builder { private String title; private String text; private String category; public Builder title(String title) { this.title = title; return this; } public Builder text(String text) { this.text = text; return this; } public Builder category(String category) { this.category = category; return this; } public Post build() { return new Post(this); }}// Usagepublic class Main {public static void main(String[] args) {Post post = new Post.Builder().title("Java Builder Pattern").text("Explaining how to implement the Builder Pattern in Java").category("Design Patterns").build();System.out.println("Title: " + post.getTitle()); System.out.println("Text: " + post.getText()); System.out.println("Category: " + post.getCategory()); }}
Prototype
Prototype
Prototype Pattern е creational design pattern, който позволява да създавате нови обекти, като копирате вече съществуващ обект.
Вместо да създавате нови инстанции от нулата, вие clone-вате съществуващ prototype, което може да бъде по-ефективно и по-гъвкаво. Нека разгледаме пример в Java:
Define the Prototype Interface: Първо създаваме abstract class или interface, наречен Tree, с abstract метод copy():
public abstract class Tree {public abstract Tree copy();}
Concrete Prototypes: След това имплементираме два concrete класа, които extend-ват Tree: PlasticTree и PineTree. Тези класове представят конкретни видове дървета и предоставят своя собствена имплементация на метода copy():
public class PlasticTree extends Tree {@Overridepublic Tree copy() {PlasticTree plasticTreeClone = new PlasticTree(this.getMass(), this.getHeight());plasticTreeClone.setPosition(this.getPosition());return plasticTreeClone; }}public class PineTree extends Tree {@Overridepublic Tree copy() {PineTree pineTreeClone = new PineTree(this.getMass(), this.getHeight());pineTreeClone.setPosition(this.getPosition());return pineTreeClone; }}
В тези класове ние custom-изираме процеса на cloning според конкретния тип дърво. Например копираме mass, height и position на оригиналното дърво.
Usage: Сега нека създадем инстанции на нашите concrete prototypes:
Java
public class Main {public static void main(String[] args) {Tree originalPlasticTree = new PlasticTree(10, 5);Tree clonedPlasticTree = originalPlasticTree.copy(); Tree originalPineTree = new PineTree(15, 8); Tree clonedPineTree = originalPineTree.copy(); // Use the cloned trees as needed // ... }}
Какво представляват Structural Patterns?
Тези шаблони се отнасят до структурата и композицията на един клас.
Adapter Pattern
Adapter Pattern е structural design pattern, който позволява на два несъвместими интерфейса да работят заедно.
Той действа като мост между тези интерфейси, като им позволява да си сътрудничат, без да се променя техният source code.
Object Adapter:
При този подход използваме composition, за да делегираме логиката към adapter-а.
Adapter-ът съдържа Adaptee и делегира метода request() към метода specificRequest() в Adaptee.
class Adaptee {void specificRequest() {System.out.println("Adaptee's specific request"); }}// Target interfaceinterface Target {void request();}// Adapter (using composition)class Adapter implements Target {private final Adaptee adaptee;Adapter(Adaptee adaptee) { this.adaptee = adaptee;}@Overridepublic void request() { adaptee.specificRequest(); }}
Class Adapter:
Изисква multiple inheritance (директно невъзможно в Java).
Можем да го постигнем, използвайки interfaces с default methods.
Adapter-ът наследява както Target, така и Adaptee класовете.
// Target interfaceinterface Target {void request();}// Adaptee (existing class)class Adaptee {void specificRequest() {System.out.println("Adaptee's specific request"); }}// Adapter (using class inheritance)class Adapter extends Adaptee implements Target {@Overridepublic void request() {specificRequest(); }}
Benefits and Trade-Offs:
Class Adapter работи добре при one-to-one mapping на методи.
Object Adapter осигурява повече гъвкавост и избягва модификация на source code.
Decorator Pattern
Decorator Pattern е creational design pattern, който позволява динамично да добавяш функционалност и поведение към даден обект, без
да влияеш на поведението на други съществуващи обекти в същия клас. Той е особено полезен, когато искаш да разшириш възможностите на един обект,
без да променяш неговата структура.
Overview:
Decorator Pattern предоставя разширен интерфейс към оригиналния обект, като го „обвива“ с допълнителна функционалност.
Предпочитаме composition пред inheritance, за да намалим overhead-а от subclassing.
Decorators могат да бъдат добавяни по време на изпълнение (runtime), което осигурява гъвкавост.
Example Scenario: Да приемем, че имаме ChristmasTree обект и искаме да го декорираме с различни елементи като garland, tinsel и bubble lights. Ще следваме оригиналните Gang of Four design конвенции.
Implementation:
First, create a ChristmasTree interface:Javapublic interface ChristmasTree {String decorate();}Implement the interface with a basic tree:Javapublic class ChristmasTreeImpl implements ChristmasTree {@Overridepublic String decorate() {return "Christmas tree"; }}Next, create an abstract TreeDecorator class that implements ChristmasTree and holds the decorated tree:Javapublic abstract class TreeDecorator implements ChristmasTree {private final ChristmasTree tree;public TreeDecorator(ChristmasTree tree) { this.tree = tree;}@Overridepublic String decorate() { return tree.decorate(); }}Now create specific decorators. For example, BubbleLights:Javapublic class BubbleLights extends TreeDecorator {public BubbleLights(ChristmasTree tree) { super(tree); }@Overridepublic String decorate() { return super.decorate() + " with Bubble Lights"; }}public class Garland extends TreeDecorator {public Garland(ChristmasTree tree) {super(tree);}@Overridepublic String decorate() { return super.decorate() + " with Garland";}}Usage:Create decorated trees:public class DecoratorPatternDemo {public static void main(String[] args) {ChristmasTree tree1 = new Garland(new ChristmasTreeImpl());System.out.println(tree1.decorate()); // Output: "Christmas tree with Garland"ChristmasTree tree2 = new BubbleLights(new Garland(new Garland(new ChristmasTreeImpl()))); System.out.println(tree2.decorate()); // Output: "Christmas tree with Garland with Garland // with Bubble Lights" }}
Pattern-ът позволява добавяне на decorators по време на изпълнение, което дава гъвкавост да декорираш според нуждите.
Запомни, Decorator Pattern е широко използван в ситуации, в които искаш да разшириш поведението на даден обект, без да модифицираш неговата основна структура.
Facade Pattern
Facade Pattern е структурен design pattern, който опростява взаимодействието с комплексен subsystem, като предоставя унифициран interface.
Той действа като „front door“, за да скрие вътрешната complexност и да направи system-а по-лесен за използване от client-ите.
Composite
Composite Pattern е структурен design pattern, който позволява да композирате обекти в tree структури, за да представите part-whole йерархии.
Основната идея зад Composite Pattern е да се изгради tree структура от обекти, при която отделните (leaf) обекти и composite обектите споделят общ interface.
Base Component (Department): Ще дефинираме прост Department interface като наш base component:
public interface Department {void printDepartmentName();}Leaf Components (FinancialDepartment and SalesDepartment): Let’s create classes for financial and sales departments, both implementing the Department interface:public class FinancialDepartment implements Department { // Implementation details (e.g., id, name) public void printDepartmentName() { System.out.println(getClass().getSimpleName()); }}public class SalesDepartment implements Department { // Implementation details (e.g., id, name) public void printDepartmentName() { System.out.println(getClass().getSimpleName()); }}These leaf classes implement the printDepartmentName() method and don’t contain references to other Department objects.Composite Element (HeadDepartment): As a composite class, let’s create a HeadDepartment that holds a collection of Department components:import java.util.ArrayList;import java.util.List;public class HeadDepartment implements Department { private Integer id; private String name; private List childDepartments; public HeadDepartment(Integer id, String name) { this.id = id; this.name = name; this.childDepartments = new ArrayList<>(); } public void printDepartmentName() { childDepartments.forEach(Department::printDepartmentName); } public void addDepartment(Department department) { childDepartments.add(department); } public void removeDepartment(Department department) { childDepartments.remove(department); }}
HeadDepartment класът имплементира метода printDepartmentName() и предоставя методи за добавяне и премахване на child departments.
Употреба: Можете да създадете инстанции на leaf departments (например FinancialDepartment, SalesDepartment) и composite department (например HeadDepartment). Добавете leaf departments към composite-а и принтирайте техните имена.
public class Main {public static void main(String[] args) { Department finance = new FinancialDepartment(); Department sales = new SalesDepartment(); HeadDepartment headDepartment = new HeadDepartment(1, "Company Departments"); headDepartment.addDepartment(finance); headDepartment.addDepartment(sales); headDepartment.printDepartmentName(); }}Output:FinancialDepartmentSalesDepartment
The Composite Pattern allows you to create flexible tree structures where
individual objects and compositions (composites) can be treated uniformly.
Какво е HASH функция !!!
Hash функцията е математическа функция, която приема вход (или „message“) и връща низ от символи с фиксирана дължина,
който обикновено е последователност от числа и букви. Този изход се нарича „hash value“ или просто „hash“.
Hash функциите се използват широко в computer science и cryptography и имат няколко важни свойства:
Как се използва hash функция и как работи?
Deterministic: Един и същ вход винаги ще генерира един и същ hash.
Fast: Функцията може бързо да изчисли hash стойността за произволен вход.
Unique (almost): Два различни входа трябва да генерират различни hash стойности (стремим се да минимизираме „collisions“).
Irreversible (One-way): При даден hash трябва да е изчислително неизпълнимо да възстановим оригиналния вход.
Uniform: Hash стойностите са равномерно разпределени в изходното пространство.
Hash функциите се използват широко в различни приложения, като например:
Data Integrity: За да се гарантира, че данните не са били променени или фалшифицирани, чрез сравняване на hash стойности.
Password Storage: За сигурно съхранение на hashed пароли вместо реалните пароли.
Cryptography: Използват се в digital signatures и други cryptographic алгоритми.
Hash Tables: За ефективно извличане на данни в програмирането.
Дай ми пример за hash map – как би го имплементирал?
SHA (Secure Hash Algorithm):
Включва варианти като SHA-1, SHA-256, SHA-384 и SHA-512.
SHA-256 и SHA-512 се използват широко в cryptography и blockchain технологии (напр. Bitcoin).
import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;public class HashExample {public static void main(String[] args) {try {// The input string to be hashedString input = "Hello, world!"; // Create a MessageDigest instance for SHA-256 MessageDigest digest = MessageDigest.getInstance("SHA-256"); // Compute the hash byte[] hash = digest.digest(input.getBytes()); // Convert the hash bytes to a hexadecimal string StringBuilder hexString = new StringBuilder(); for (byte b : hash) { String hex = String.format("%02x", b); hexString.append(hex); } // Print the resulting hash System.out.println("SHA-256 Hash: " + hexString.toString()); } catch (NoSuchAlgorithmException e) { System.err.println("Error: " + e.getMessage()); } }}
Input Data: Променливата за вход съдържа данните, които искаме да „hash“-нем.
MessageDigest Initialization: Инстанцията на MessageDigest се създава чрез getInstance(“SHA-256”), което указва алгоритъма за хеширане SHA-256.
Hashing: Методът digest изчислява hash стойността на входните данни.
Hexadecimal Conversion: Тъй като hash стойността е в двоичен формат, използваме цикъл, за да я преобразуваме в по-четим hexadecimal низ.
Put and Patch ? Кога да ги използваме ?
PUT и PATCH са HTTP методи, използвани за обновяване на ресурси на сървъра, но се различават по начина, по който прилагат промените:
PUT:
Пълно обновяване:
PUT се използва, за да замени напълно съществуващ ресурс.
Целият ресурс трябва да бъде изпратен в заявката, дори ако се променя само част от него.
Ако ресурсът не съществува, PUT често го създава (макар че това поведение може да варира).
Idempotent:
Повтарящите се PUT заявки със същите данни винаги водят до едно и също състояние на ресурса.
Пример: обновяване на потребителски профил:
jsonPUT /users/123{"name": "Velislav","email": "velislav@example.com","age": 30}
Ако се променя само name, но email и age остават същите, те пак трябва да присъстват в заявката.
PATCH:
Частично обновяване:
PATCH се използва за частично обновяване на съществуващ ресурс.
В заявката се изпращат само полетата, които се променят.
Idempotent (Mostly):
Подобно на PUT, повтарящите се PATCH заявки със същите данни обикновено водят до едно и също състояние на ресурса.
Въпреки това поведението може да зависи от имплементацията на сървъра.
Пример: обновяване само на потребителското name:
jsonPATCH /users/123{"name": "Velislav"}
Тук се обновява само полето name, а останалите полета остават непроменени.
Основна разлика:
PUT замества целия ресурс, докато PATCH променя само конкретно посочените полета на ресурса.
HTTP methods с кратко описание.
GET: Използва се за извличане на данни от сървъра без да ги променя. Често се използва за fetch на resources или информация.
POST: Използва се за изпращане на данни към сървъра, обикновено за създаване на нов resource. Например, при изпращане на form или upload на file.
PUT: Използва се за update или пълна подмяна на съществуващ resource на сървъра. Ако resource не съществува, може да бъде създаден.
PATCH: Използва се за частичен update на съществуващ resource, като се модифицират само посочените полета.
DELETE: Използва се за изтриване на resource от сървъра.
HEAD: Подобен на GET, използва се за извличане само на headers без body на resource.
OPTIONS: Показва кои HTTP methods са позволени.
TRACE: Used mainly for diagnostic purposes by echoing the request back to the client
CONNECT: Establishes a tunnel to the server, often used for SSL/TLS connections.
Functional Interface
Functional interface в Java е interface, който съдържа точно един abstract method. Тези interfaces се използват основно с lambda expressions и method references, като позволяват по-кратък и по-четим functional programming стил.
Основни характеристики:
- Single Abstract Method: Functional interface има само един abstract method. Това е същността на неговата функционалност.
- Default и Static Methods: Functional interfaces могат да съдържат default и static methods, но те не се броят като единствения abstract method.
- @FunctionalInterface Annotation: Макар тази анотация да е по избор, тя помага, като показва, че даденият interface е предназначен да бъде functional. Ако към interface-а се добавят допълнителни abstract methods, компилаторът хвърля грешка.
Пример:
@FunctionalInterfaceinterface Calculator { int calculate(int a, int b);}// Using lambda expressionpublic class Main { public static void main(String[] args) { // Implementing the calculate method using a lambda Calculator addition = (a, b) -> a + b; Calculator multiplication = (a, b) -> a * b; System.out.println("Addition: " + addition.calculate(5, 3)); // Output: 8 System.out.println("Multiplication: " + multiplication.calculate(5, 3)); // Output: 15 }}
Use Case:
Интерфейсът Calculator в примера има единствен abstract method calculate, което го прави functional interface.
Това ни позволява да използваме lambda expressions за по-кратка и елегантна имплементация.
Често използвани Functional Interfaces в Java:
Java предоставя няколко вградени functional interfaces в пакета java.util.function, като например:
- Predicate: Приема вход и връща boolean.
- Function: Приема вход и връща резултат от тип R.
- Supplier: Осигурява стойност без вход.
- Consumer: Приема стойност и извършва действие.
Functional interfaces улесняват работата, особено в сценарии като работа с collections, streams или asynchronous tasks.
Те позволяват на Java да възприема functional programming парадигми, като същевременно запазва своя object-oriented характер.
Какво е higher-order function?
Higher-order function е функция, която или приема други функции като аргументи, или връща функция като свой резултат. С други думи, тя оперира с функции, третирайки ги като “first-class citizens”. Това е мощна възможност във функционалните програмни парадигми, която позволява абстракция, повторно използване на код и composition.
Пример за Higher-Order Function в Java:
import java.util.function.Function;public class Main { public static void main(String[] args) { // Higher-order function: applies a function to a value Function<Integer, Integer> squareFunction = x -> x * x; int result = applyFunction(5, squareFunction); System.out.println("Square of 5: " + result); // Output: 25 } public static int applyFunction(int value, Function<Integer, Integer> function) { return function.apply(value); }}
В горния пример:
- Методът
applyFunctionе higher-order function, защото приема друга функция (Function<Integer, Integer>) като параметър.
Характеристики:
- Accepts Functions as Arguments: Higher-order functions могат да приемат други функции като вход, за да изпълнят конкретни операции.
- Returns Functions: Те могат динамично да генерират и връщат нови функции.
- Encourages Code Reuse: Насърчава модулен и многократно използваем код, тъй като функциите могат да се предават и комбинират помежду си.
Use Case:
В Java higher-order functions се използват често във функционалното програмиране със streams, lambdas и method references. Например:
List<String> names = Arrays.asList("John", "Jane", "Jake");names.stream() .filter(name -> name.startsWith("J")) // Takes a lambda function as argument .forEach(System.out::println); // Takes a method reference as argument
Higher-order functions придават гъвкавост и мощ на Java програмите, позволявайки на разработчиците да пишат чист, стегнат и изразителен код!
Няколко реални примера с отговори от интервюта
- Разбира основите като polymorphism, static methods и String pool
- Запознат е с collections (напр. HashSet, TreeSet) и техните use cases.
- Правилно идентифицира bean scopes (singleton, prototype)
- Осъзнава какво представлява dependency injection и неговата цел.
- Знае как да имплементира many-to-many relationships чрез junction tables.
- Разбира предназначението на indexes (макар и повърхностно)
- Може да разграничава между monolithic и microservice architectures
- Разбира базовите trade-offs (scalability, maintainability)
- Не разбира правилно Liskov Substitution Principle и Dependency Inversion
- Трудно дава конкретни примери за Open/Closed Principle.
- Няма задълбочени познания за bean lifecycle management.
- Не му е ясно advanced dependency injection.
- Има повърхностно разбиране за indexes (composite indexes, query optimization).
- Не е запознат с isolation levels и optimistic/pessimistic locking.
Pros:
- Работил е с legacy code в голяма и сложна система.
- Познат е със SOLID.
- Има познания за streams.
- Обясни indexes и ACID.
- Добро ниво на английски
Cons: - Няма отговорност за deployment или infrastructure, участие в architecture design.
- Има известни познания за OOP принципите, но изпита сериозни затруднения да ги обясни и обърка някои концепции.
- Общо познаване на collections, но с пропуски и доста несигурен.
- Не разбира разликата между concurrency и parallelism, няма особено силни познания за concurrency.
- Не е разглеждал подробно microservice архитектура.
- Неяснота при някои database концепции (дискусията за indexes беше повърхностна).
- Не можа да обясни livelock.
- Има пропуски в разбирането на finally block.
- Ограничени познания за спецификите на exception handling.
- Бърка се при access modifiers (protected vs. package private)
Общото мнение е, че
не е подходящ за позицията Mid-Java developer поради пропуските в техническите си познания. - Обясни добре SOLID.
- Обясни добре OOP.
- Обясни composition (но първоначално се затрудни малко).
- Отлично разбиране на collections.
- Обясни разликата между lists и sets.
- Обсъди HashSet.
- Обсъди HashMap.
- Обсъди ConcurrentHashMap.
- Добро разбиране на data structures – stacks, queues, trees.
- Добри познания за JVM архитектурата.
- Обясни добре concurrency vs parallelism, демонстрира добри познания за multithreaded приложения.
- Добро разбиране на REST.
- Обясни добре db indexes.
- Обясни различните видове joins.

Leave a Reply