Interview preparation
JVM nədir?
JVM (Java Virtual Machine) — Java proqramlarının işlədiyi virtual mühitdir. Sadə dillə desək, JVM sənin yazdığın Java kodunu (yəni .java
faylını) kompilyasiya edildikdən sonra yaranan bytecode-u (.class
faylları) oxuyur və icra edir. JVM-in əsas məqsədi "Write once, run anywhere" prinsipini təmin etməkdir. Yəni sən eyni Java kodunu yazırsan, fərqli əməliyyat sistemlərində (Windows, Linux, macOS) heç dəyişiklik etmədən işləyir, çünki hər sistem üçün ayrıca JVM var.
JVM necə işləyir?
JVM-in işləmə prosesi 3 əsas mərhələdən ibarətdir:
-
Kompilyasiya mərhələsi (Java Compiler)
-
javac
sənin yazdığın.java
faylını götürür və onu bytecode-a (.class
faylı) çevirir. -
Bu bytecode platformadan asılı deyil. Yəni Windows-da da, Linux-da da eyni fayl işləyəcək.
-
-
Class Loader
-
JVM işə düşəndə lazımi
.class
fayllarını və ya JAR paketlərini yaddaşa yükləyir. -
ClassLoader dinamik yükləmə edir, yəni hansı class-a ehtiyac varsa, o anda yüklənir. Bu, yaddaşın optimallaşdırılmasına kömək edir.
-
-
Execution Engine (İcra mühərriki)
-
Burada işin əsas hissəsi baş verir:
-
Interpreter – bytecode-u sətir-sətir oxuyub icra edir.
-
JIT (Just-In-Time Compiler) – performansı artırmaq üçün tez-tez işləyən bytecode parçalarını maşın koduna çevirir və daha sürətli icra edir.
-
-
Yəni JVM daim balans qurur: həm çeviklik (interpretasiya), həm də sürət (JIT kompilyasiyası).
-
-
Yaddaş idarəetməsi (Memory Management)
-
JVM-də xüsusi yaddaş bölgüsü var: Heap, Stack, Metaspace və s.
-
Garbage Collector (GC) avtomatik olaraq lazımsız obyektləri silir, bu da developerin əlinə düşən böyük yükü götürür.
-
Real Həyat Analogiyası
Təsəvvür et ki, sənin yazdığın kod ingilis dilində kitabdır. JVM isə tərcüməçi kimidir:
-
Kitabı götürür (bytecode),
-
Hər bir ölkədə (OS-də) həmin ölkənin dilinə (maşın koduna) tərcümə edib oxuyur.
-
Sən isə bircə dəfə ingilis dilində yazmısan, JVM isə hər yerdə onu başa salır.
Nəticə
-
JVM sənin Java kodunu platformadan asılı olmayan hala gətirir.
-
O, həm təhlükəsizlik (class loader + sandboxing), həm performans (JIT), həm də yaddaş idarəetməsi (GC) təmin edir.
-
Bu səbəbdən Java “bir dəfə yaz, hər yerdə işlət” fəlsəfəsini reallaşdırır.
İndi sənə sual: İstəyirsən mən sənə JVM-in memory areas (Heap, Stack, Metaspace, PC register) barədə də ayrıca izah verim? Bu, çox vaxt intervülərdə ayrıca sual kimi soruşulur.
Əla 👍 Gəlin sənə Skeletal Implementation Pattern-in real işdə necə görünəcəyini konkret kodla yazaq. Domen olaraq sənin yaxşı bildiyin ödəniş sistemi (Payment) götürək.
🔹 Step 1 – Interface (Contract)
👉 Burada yalnız “nə var” deyirik.
public interface PaymentProcessor {
void validate(double amount);
void execute(double amount);
void printReceipt(double amount);
}
➡️ PaymentProcessor
müqavilədir. İstənilən ödəniş prosessoru bunu implement etməlidir.
🔹 Step 2 – Abstract Skeletal Implementation
👉 Burada ümumi kodu yazırıq. Konkret detallar alt siniflərdə qalır.
public abstract class AbstractPaymentProcessor implements PaymentProcessor {
@Override
public void validate(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}
System.out.println("Validation passed for: " + amount);
}
@Override
public void printReceipt(double amount) {
System.out.println("Receipt: paid " + amount);
}
// execute() hələ burda yoxdur, çünki hər ödəniş tipi fərqli icra olunur
}
➡️ Burada validate
və printReceipt
ümumi skeletdir.
➡️ Amma execute
abstrakt saxlanır, çünki hər ödəniş tipi fərqli qaydada icra olunur.
🔹 Step 3 – Concrete Implementation-lar
👉 Burada sadəcə əsas fərqi yazırıq. Qalan kodu skeletdən miras alırıq.
public class CardPaymentProcessor extends AbstractPaymentProcessor {
@Override
public void execute(double amount) {
System.out.println("Charging credit card with amount: " + amount);
}
}
public class PaypalPaymentProcessor extends AbstractPaymentProcessor {
@Override
public void execute(double amount) {
System.out.println("Processing PayPal transfer of: " + amount);
}
}
🔹 Step 4 – İstifadə
public class PaymentDemo {
public static void main(String[] args) {
PaymentProcessor card = new CardPaymentProcessor();
card.validate(100);
card.execute(100);
card.printReceipt(100);
PaymentProcessor paypal = new PaypalPaymentProcessor();
paypal.validate(200);
paypal.execute(200);
paypal.printReceipt(200);
}
}
Çıxış:
Validation passed for: 100
Charging credit card with amount: 100
Receipt: paid 100
Validation passed for: 200
Processing PayPal transfer of: 200
Receipt: paid 200
🔹 İndi “interview cavabı” kimi öyrənməli olduğun hissə
Skeletal Implementation Pattern o deməkdir ki,
Müqavilə (contract) interface-də saxlanır.
Ümumi, təkrar kod abstract class-da toplanır.
Konkret fərqlər isə alt siniflərdə implement olunur.
Bu yanaşma Java Collections API-də istifadə olunur:List
(interface),AbstractList
(skeletal),ArrayList
/LinkedList
(concrete).
Mən real layihədə də məsələn ödəniş modullarında eyni yanaşmanı tətbiq edə bilərəm —PaymentProcessor
interfeys,AbstractPaymentProcessor
skelet, müxtəlif provider-lər isə konkret implementasiya.
⚡ Dostum, istəyirsən mən sənə bunun Collections Framework-də (List, AbstractList, ArrayList) necə göründüyünü də kodla göstərim? Yoxsa artıq encapsulation-a keçək?
Отличный вопрос! Абстрактные классы - это очень важная концепция в ООП. Давайте разберем детально, почему они нужны и где используются.
Зачем нужны абстрактные классы?
1. Определение общего контракта с частичной реализацией
// Абстрактный класс определяет общее поведение для всех фигур
public abstract class Shape {
protected String color;
protected double x, y; // координаты
public Shape(String color, double x, double y) {
this.color = color;
this.x = x;
this.y = y;
}
// Общая реализация для всех фигур
public void move(double deltaX, double deltaY) {
this.x += deltaX;
this.y += deltaY;
System.out.println("Фигура перемещена в позицию (" + x + ", " + y + ")");
}
// Общий метод отображения
public void display() {
System.out.println("Отображаю " + color + " фигуру в позиции (" + x + ", " + y + ")");
draw(); // Вызываем абстрактный метод
System.out.println("Площадь: " + calculateArea());
}
// Абстрактные методы - ДОЛЖНЫ быть реализованы в наследниках
public abstract double calculateArea();
public abstract double calculatePerimeter();
public abstract void draw();
// Геттеры
public String getColor() { return color; }
public double getX() { return x; }
public double getY() { return y; }
}
// Конкретная реализация - Круг
public class Circle extends Shape {
private double radius;
public Circle(String color, double x, double y, double radius) {
super(color, x, y);
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
@Override
public double calculatePerimeter() {
return 2 * Math.PI * radius;
}
@Override
public void draw() {
System.out.println("Рисую круг с радиусом " + radius);
}
}
// Конкретная реализация - Прямоугольник
public class Rectangle extends Shape {
private double width, height;
public Rectangle(String color, double x, double y, double width, double height) {
super(color, x, y);
this.width = width;
this.height = height;
}
@Override
public double calculateArea() {
return width * height;
}
@Override
public double calculatePerimeter() {
return 2 * (width + height);
}
@Override
public void draw() {
System.out.println("Рисую прямоугольник " + width + "x" + height);
}
}
2. Template Method Pattern (Шаблонный метод)
public abstract class DataProcessor {
// Шаблонный метод - определяет алгоритм
public final void processData() {
System.out.println("=== Начало обработки данных ===");
// Шаги алгоритма
loadData();
validateData();
transformData();
saveData();
cleanup();
System.out.println("=== Обработка завершена ===");
}
// Общая реализация
protected void cleanup() {
System.out.println("Очистка временных файлов...");
}
// Абстрактные шаги - каждый наследник реализует по-своему
protected abstract void loadData();
protected abstract void validateData();
protected abstract void transformData();
protected abstract void saveData();
}
// Обработка XML файлов
public class XmlDataProcessor extends DataProcessor {
@Override
protected void loadData() {
System.out.println("Загрузка данных из XML файла");
}
@Override
protected void validateData() {
System.out.println("Валидация XML против XSD схемы");
}
@Override
protected void transformData() {
System.out.println("Преобразование XML в объекты Java");
}
@Override
protected void saveData() {
System.out.println("Сохранение в базу данных");
}
}
// Обработка CSV файлов
public class CsvDataProcessor extends DataProcessor {
@Override
protected void loadData() {
System.out.println("Загрузка данных из CSV файла");
}
@Override
protected void validateData() {
System.out.println("Проверка формата CSV и обязательных полей");
}
@Override
protected void transformData() {
System.out.println("Парсинг CSV и создание объектов");
}
@Override
protected void saveData() {
System.out.println("Сохранение в файл JSON");
}
}
3. Реальные примеры использования в Enterprise приложениях
// Абстрактный сервис для работы с базой данных
public abstract class BaseService<T, ID> {
protected Logger logger = LoggerFactory.getLogger(this.getClass());
// Общие методы для всех сервисов
public List<T> findAll() {
logger.info("Получение всех записей для " + getEntityName());
List<T> entities = getRepository().findAll();
logger.info("Найдено {} записей", entities.size());
return entities;
}
public T findById(ID id) {
logger.info("Поиск {} по ID: {}", getEntityName(), id);
validateId(id);
T entity = getRepository().findById(id);
if (entity == null) {
throw new EntityNotFoundException(getEntityName() + " с ID " + id + " не найден");
}
return entity;
}
public T save(T entity) {
logger.info("Сохранение {}", getEntityName());
validateEntity(entity);
T savedEntity = getRepository().save(entity);
logger.info("{} успешно сохранен с ID: {}", getEntityName(), getId(savedEntity));
return savedEntity;
}
// Общая валидация ID
protected void validateId(ID id) {
if (id == null) {
throw new IllegalArgumentException("ID не может быть null");
}
}
// Абстрактные методы - каждый сервис реализует по-своему
protected abstract Repository<T, ID> getRepository();
protected abstract String getEntityName();
protected abstract void validateEntity(T entity);
protected abstract ID getId(T entity);
}
// Конкретный сервис для пользователей
@Service
public class UserService extends BaseService<User, Long> {
@Autowired
private UserRepository userRepository;
@Override
protected Repository<User, Long> getRepository() {
return userRepository;
}
@Override
protected String getEntityName() {
return "User";
}
@Override
protected void validateEntity(User user) {
if (user.getEmail() == null || !user.getEmail().contains("@")) {
throw new IllegalArgumentException("Некорректный email");
}
if (user.getName() == null || user.getName().trim().isEmpty()) {
throw new IllegalArgumentException("Имя пользователя не может быть пустым");
}
}
@Override
protected Long getId(User user) {
return user.getId();
}
// Дополнительные методы специфичные для пользователей
public User findByEmail(String email) {
logger.info("Поиск пользователя по email: {}", email);
return userRepository.findByEmail(email);
}
}
// Конкретный сервис для продуктов
@Service
public class ProductService extends BaseService<Product, Long> {
@Autowired
private ProductRepository productRepository;
@Override
protected Repository<Product, Long> getRepository() {
return productRepository;
}
@Override
protected String getEntityName() {
return "Product";
}
@Override
protected void validateEntity(Product product) {
if (product.getName() == null || product.getName().trim().isEmpty()) {
throw new IllegalArgumentException("Название продукта не может быть пустым");
}
if (product.getPrice() < 0) {
throw new IllegalArgumentException("Цена не может быть отрицательной");
}
}
@Override
protected Long getId(Product product) {
return product.getId();
}
// Специфичные методы для продуктов
public List<Product> findByCategory(String category) {
return productRepository.findByCategory(category);
}
public List<Product> findByPriceRange(double minPrice, double maxPrice) {
return productRepository.findByPriceBetween(minPrice, maxPrice);
}
}
4. Когда использовать абстрактные классы vs Интерфейсы
// Интерфейс - только контракт
public interface Drawable {
void draw();
void resize(double scale);
}
// Абстрактный класс - контракт + частичная реализация
public abstract class GraphicalElement implements Drawable {
protected double x, y;
protected String color;
// Общая реализация
public void move(double deltaX, double deltaY) {
this.x += deltaX;
this.y += deltaY;
}
// Общая реализация изменения размера
@Override
public void resize(double scale) {
if (scale <= 0) {
throw new IllegalArgumentException("Масштаб должен быть положительным");
}
doResize(scale);
}
// Абстрактный метод для конкретной реализации изменения размера
protected abstract void doResize(double scale);
// draw() остается абстрактным из интерфейса
}
// Конкретная реализация
public class Button extends GraphicalElement {
private double width, height;
@Override
public void draw() {
System.out.println("Рисую кнопку " + width + "x" + height + " в позиции (" + x + ", " + y + ")");
}
@Override
protected void doResize(double scale) {
width *= scale;
height *= scale;
System.out.println("Размер кнопки изменен до " + width + "x" + height);
}
}
Основные преимущества абстрактных классов:
- Код переиспользование - общая логика в базовом классе
- Принуждение к реализации - абстрактные методы должны быть реализованы
- Шаблоны поведения - Template Method pattern
- Эволюция API - можно добавлять новые конкретные методы без поломки наследников
- Инкапсуляция общего состояния - protected поля доступны наследникам
Где используются в реальных проектах:
- Spring Framework - AbstractController, AbstractTransactionalTestNGSpringContextTests
- Servlet API - HttpServlet (абстрактный класс)
- Collections Framework - AbstractList, AbstractSet, AbstractMap
- GUI фреймворки - Component классы в Swing/AWT
- ORM фреймворки - базовые Entity классы
- Архитектурные паттерны - базовые сервисы, репозитории, контроллеры
Абстрактные классы помогают создать хорошо структурированную иерархию классов, где общее поведение реализовано один раз, а специфичное поведение делегируется наследникам.
Отличный вопрос! Действительно, с Java 8 в интерфейсы добавили default и static методы, а в Java 9 - private методы. Это сильно изменило различия между интерфейсами и абстрактными классами. Давайте разберем подробно!
Современные возможности интерфейсов (Java 8+)
public interface ModernInterface {
// Константы (public static final по умолчанию)
String CONSTANT = "Константа интерфейса";
// Абстрактные методы (как и раньше)
void abstractMethod();
// Default методы (Java 8) - имеют реализацию
default void defaultMethod() {
System.out.println("Default метод в интерфейсе");
helperMethod(); // можем вызывать private методы
}
default String processData(String data) {
if (data == null) {
return "Данные отсутствуют";
}
return "Обработано: " + data.toUpperCase();
}
// Static методы (Java 8) - вызываются через имя интерфейса
static void staticMethod() {
System.out.println("Static метод в интерфейсе");
}
static String formatMessage(String message) {
return "[ИНТЕРФЕЙС] " + message;
}
// Private методы (Java 9) - для внутреннего использования
private void helperMethod() {
System.out.println("Private helper метод");
}
}
Сравнение: Интерфейсы vs Абстрактные классы в современной Java
1. Состояние (поля/переменные)
// ИНТЕРФЕЙС - только константы
public interface PaymentProcessor {
// Все поля автоматически public static final
String DEFAULT_CURRENCY = "USD";
int MAX_RETRY_ATTEMPTS = 3;
default void processPayment(double amount) {
// НЕ МОЖЕМ иметь поля экземпляра!
// String instanceField; // ОШИБКА КОМПИЛЯЦИИ!
System.out.println("Обработка платежа: " + amount + " " + DEFAULT_CURRENCY);
}
}
// АБСТРАКТНЫЙ КЛАСС - может иметь любые поля
public abstract class AbstractPaymentProcessor {
// Поля экземпляра - разных модификаторов доступа
protected String processorName;
private int retryCount = 0;
protected static final String DEFAULT_CURRENCY = "USD";
public AbstractPaymentProcessor(String processorName) {
this.processorName = processorName;
}
// Можем изменять состояние
protected void incrementRetryCount() {
this.retryCount++;
}
protected int getRetryCount() {
return retryCount;
}
public abstract void processPayment(double amount);
}
2. Конструкторы
// ИНТЕРФЕЙС - НЕТ конструкторов
public interface Drawable {
default void draw() {
// Нет доступа к конструктору!
System.out.println("Рисую объект");
}
}
// АБСТРАКТНЫЙ КЛАСС - ЕСТЬ конструкторы
public abstract class Shape {
protected double x, y;
protected String color;
// Конструктор по умолчанию
public Shape() {
this(0, 0, "black");
}
// Параметризованный конструктор
public Shape(double x, double y, String color) {
this.x = x;
this.y = y;
this.color = color;
System.out.println("Создана фигура в позиции (" + x + ", " + y + ")");
}
public abstract void draw();
}
public class Circle extends Shape {
private double radius;
public Circle(double x, double y, String color, double radius) {
super(x, y, color); // Вызов конструктора родителя
this.radius = radius;
}
@Override
public void draw() {
System.out.println("Рисую " + color + " круг");
}
}
3. Множественное наследование
// ИНТЕРФЕЙСЫ - множественная реализация возможна
public interface Flyable {
default void fly() {
System.out.println("Летаю в небе");
}
default void land() {
System.out.println("Приземляюсь");
}
}
public interface Swimmable {
default void swim() {
System.out.println("Плаваю в воде");
}
default void dive() {
System.out.println("Ныряю под воду");
}
}
public interface Walkable {
default void walk() {
System.out.println("Иду по земле");
}
}
// Класс может реализовать много интерфейсов
public class Duck implements Flyable, Swimmable, Walkable {
@Override
public void fly() {
System.out.println("Утка летит над озером");
}
@Override
public void swim() {
System.out.println("Утка плавает по озеру");
}
@Override
public void walk() {
System.out.println("Утка идет по берегу");
}
public void demonstrateAbilities() {
fly();
swim();
walk();
land(); // default метод из Flyable
dive(); // default метод из Swimmable
}
}
// АБСТРАКТНЫЕ КЛАССЫ - только одиночное наследование
public abstract class Animal {
protected String name;
protected int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void sleep() {
System.out.println(name + " спит");
}
public abstract void makeSound();
}
public abstract class Bird extends Animal {
protected boolean canFly;
public Bird(String name, int age, boolean canFly) {
super(name, age);
this.canFly = canFly;
}
public void chirp() {
System.out.println(name + " чирикает");
}
}
// Можем наследоваться только от одного абстрактного класса
public class Sparrow extends Bird implements Flyable {
public Sparrow(String name, int age) {
super(name, age, true);
}
@Override
public void makeSound() {
System.out.println("Воробей чик-чирик");
}
@Override
public void fly() {
if (canFly) {
System.out.println(name + " летит быстро и ловко");
}
}
}
4. Diamond Problem и его решение
public interface A {
default void method() {
System.out.println("Метод из A");
}
}
public interface B {
default void method() {
System.out.println("Метод из B");
}
}
// Diamond Problem - какой default метод выбрать?
public class DiamondClass implements A, B {
// ОБЯЗАНЫ переопределить метод при конфликте!
@Override
public void method() {
// Можем выбрать конкретную реализацию
A.super.method(); // Вызов метода из интерфейса A
// или
// B.super.method(); // Вызов метода из интерфейса B
// или своя реализация
System.out.println("Собственная реализация");
}
}
5. Практический пример: когда использовать что
// ИНТЕРФЕЙС - для определения контракта поведения
public interface CacheService {
String DEFAULT_CACHE_NAME = "defaultCache";
int DEFAULT_TTL = 3600; // 1 час
// Абстрактные методы - основной контракт
void put(String key, Object value);
Object get(String key);
void remove(String key);
// Default метод - общее поведение
default void put(String key, Object value, int ttlSeconds) {
put(key, value); // Базовая реализация без TTL
scheduleExpiration(key, ttlSeconds);
}
default boolean exists(String key) {
return get(key) != null;
}
// Static метод - утилита
static String generateKey(String prefix, Object... parts) {
return prefix + ":" + String.join(":",
Arrays.stream(parts).map(Object::toString).toArray(String[]::new));
}
// Private метод - внутренняя логика
private void scheduleExpiration(String key, int ttlSeconds) {
// Логика планирования удаления
System.out.println("Запланировано удаление " + key + " через " + ttlSeconds + " сек");
}
}
// АБСТРАКТНЫЙ КЛАСС - когда нужно состояние и общая логика
public abstract class AbstractCacheService implements CacheService {
protected final String cacheName;
protected final Map<String, CacheEntry> cache;
protected final Object lock = new Object();
protected long hitCount = 0;
protected long missCount = 0;
protected static class CacheEntry {
final Object value;
final long timestamp;
final int ttl;
CacheEntry(Object value, int ttl) {
this.value = value;
this.timestamp = System.currentTimeMillis();
this.ttl = ttl;
}
boolean isExpired() {
if (ttl <= 0) return false;
return System.currentTimeMillis() - timestamp > ttl * 1000L;
}
}
public AbstractCacheService(String cacheName) {
this.cacheName = cacheName;
this.cache = createCacheStorage(); // Абстрактный метод
}
// Общая реализация с использованием состояния
@Override
public Object get(String key) {
synchronized (lock) {
CacheEntry entry = cache.get(key);
if (entry == null || entry.isExpired()) {
missCount++;
if (entry != null && entry.isExpired()) {
cache.remove(key);
}
return null;
}
hitCount++;
return entry.value;
}
}
// Общие методы с состоянием
public double getHitRatio() {
long total = hitCount + missCount;
return total == 0 ? 0.0 : (double) hitCount / total;
}
public void clearStats() {
synchronized (lock) {
hitCount = 0;
missCount = 0;
}
}
// Абстрактные методы - специфичная реализация
protected abstract Map<String, CacheEntry> createCacheStorage();
public abstract void clear();
}
// Конкретная реализация
public class InMemoryCacheService extends AbstractCacheService {
public InMemoryCacheService(String cacheName) {
super(cacheName);
}
@Override
protected Map<String, CacheEntry> createCacheStorage() {
return new ConcurrentHashMap<>();
}
@Override
public void put(String key, Object value) {
synchronized (lock) {
cache.put(key, new CacheEntry(value, 0));
}
}
@Override
public void remove(String key) {
synchronized (lock) {
cache.remove(key);
}
}
@Override
public void clear() {
synchronized (lock) {
cache.clear();
clearStats();
}
}
}
Современные различия (после Java 8+)
Аспект | Интерфейс | Абстрактный класс |
---|---|---|
Поля экземпляра | ❌ Нет | ✅ Есть |
Константы | ✅ public static final | ✅ Любые static |
Конструкторы | ❌ Нет | ✅ Есть |
Методы с телом | ✅ default, static, private | ✅ Любые |
Множественное наследование | ✅ Да | ❌ Нет |
Состояние объекта | ❌ Нет | ✅ Есть |
Инициализация | ❌ Нет | ✅ Через конструктор |
Когда что использовать?
Используйте ИНТЕРФЕЙСЫ когда:
- Нужно определить контракт поведения
- Требуется множественная реализация
- Нет необходимости в состоянии объекта
- Хотите обеспечить гибкость архитектуры
Используйте АБСТРАКТНЫЕ КЛАССЫ когда:
- Нужно общее состояние (поля экземпляра)
- Требуется инициализация через конструктор
- Есть общая логика, которая использует состояние
- Применяете паттерн Template Method
- Нужен контроль над созданием объектов
В современной Java часто используют комбинацию: интерфейс определяет контракт, а абстрактный класс предоставляет базовую реализацию с состоянием.
Комментарии
Отправить комментарий