EQUALS

Теория

Что напечатает следующий код?

public class Point {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public boolean equals(Point other) {
        if (this == other) return true;

        return this.x == other.x && this.y == other.y;
    }

    public int hashCode() {
        return Objects.hash(x, y);
    }
}

Point p1 = new Point(1, 2);
Point p2 = new Point(1, 2);
Point p3 = new Point(2022, 2023);

System.out.println(p1.equals(p2));
System.out.println(p1.equals(p3));

Set<Point> set = new HashSet<>();
set.add(p1);

System.out.println(set.contains(p2));
System.out.println(set.contains(p3));

true false false false

Метод boolean equals(Point other) не переопределяет Object.equals, а перегружает его.
Плюс не определен метод hashcode(), что осложняет нахождение объекта в HashSet.


Какое свойство equals нарушается в этом случае? (если нарушается)

public class Point {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    // getters

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Point)) return false;

        Point other = (Point) o;

        return x == other.x && y == other.y;
    }
}

public class ColourPoint extends Point {
    private final String colour;

    public ColourPoint(int x, int y, String colour) {
        super(x, y);
        this.colour = colour;
    }

    // getters

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof ColourPoint)) return false;

        ColourPoint other = (ColourPoint) o;

        return this.getX() == other.getX() &&
               this.getY() == other.getY() &&
               this.colour.equals(other.colour);
    }

}

Симметрия

INSTANCEOF

Что будет выведено в консоль?

class A {}
class B extends A {}
class C extends B {}
class D extends C {}

public class Loader {
    public static void main(String[] args) {
        B b = new C();
        A a = b;
        if (a instanceof A) System.out.println("A");
        if (a instanceof B) System.out.println("B");
        if (a instanceof C) System.out.println("C");
        if (a instanceof D) System.out.println("D");
    }
}

A B C

Method Overloading (Перегрузка методов)

Что будет выведено в консоль?

class SuperBase {
    public int i = 3;

    public void foo (Object o) {
        System.out.println("Object " + i);
    }
    public void foo (String s) {
        System.out.println("String " + i);
    }
}

class Base extends SuperBase {
    public Base() {
        i = 5;
    }

    public static void main(String[] args) {
        SuperBase sb = new Base();
        Object o = "";
        sb.foo(o);
        sb.foo("");
    }
}

Object 5 String 5

Inheritance (наследование)

Теория

Каким будет результат кода?

class Parent {}
class DeriveOne extends Parent {}
class DeriveTwo extends Parent {}
Parent p = new Parent();
DeriveOne d1 = new DeriveOne();
DeriveTwo d2 = new DeriveTwo();
d1 = (DeriveOne) d2;

Ошибка компиляции.

Компилятору сразу понятно, что невозможно кастировать d2 к классу DeriveOne, т.к. d2 не является наследником DeriveOne

Параметризирование

Есть класс

public class Sample<T> {
    T data;
    public Sample(T data) {
        this.data = data;
    }
}

Его используют так, что не так? что смущает?

public class App {
        public Sample method(Integer value1, String value2) { //1
        var sample1 = new Sample<Integer>(value1);
        if (value2 != null) {
            var sample2 = (Sample) sample1; //2
            sample2.data = value2;
            return sample2;
        }
        return sample1;
    }
}

Первое 1 и 2 - не параметризированны

Использование сырого типа лишает вас преимуществ дженериков и типобезопасности.

Зачем вообще строка 2? Можно удалить

Если хочется поумничать про проблемы:
Типобезопасность: Переменной типа Sample<Integer> присваивается значение типа Sample<String>, что может приводить к ошибкам в runtime. Дженерики в JAVA предназначены для обеспечения типобезопасности, и данная конструкция его нарушает.
Неконсистентность данных: Объект Sample создается с одним типом данных (Integer), но позже его содержимое перезаписывается другим типом данных (String). Это может привести к путанице и сложности в поддержке кода.
Нарушенные принципы SOLID:
- OCP: Код не открыт для расширения и изменение поведения метода требует изменения существующего кода, причём делает это небезопасно.
- LSP: нарушен, поскольку использование объекта с различными видами данных не ведет себя предсказуемо.

Cигнатура выглядит очень странно? Надо задать вопрос, что мы хотим?

Скажут обернуть второе если есть (первое типа есть всегда)

Тут надо сказать "мухи отдельно", давайте оборачивать отдельно и выбирать во что обернуть то же, и родить подобный код с перегрузкой метода

    public Sample<Integer> wrap(Integer value1) {
        return new Sample<>(value1);
    }

    public Sample<String> wrap(String value2) {
        return new Sample<>(value2);
    }

    public Sample<?> method(Object value1, String value2) {
        return value2 != null ? wrap(value2) : wrap(value1);
    }

После это, глядя на это, можно спросить - а будут ли использоваться другие типы?

Если да, то можно продолжить "шалить" и написать типизированный метод с использованием дженериков

   public <T> Sample<T> method(T value) {
        return new Sample<>(value);
    }

Index DB

Будет ли использоваться индекс в запросе?

1.

CREATE index ON post (owner_id);
SELECT *
FROM post
WHERE likes_count > ?
  AND owner_id = ?;

Да, индекс будет использоваться в запросе.

В данном запросе имеется условие owner_id = ?, которое соответствует индексу, созданному на owner_id.
Индекс на owner_id может помочь улучшить производительность запроса, делая выборку более быстрой.
Условие likes_count > ? может быть вторичным, но главный фильтрующий критерий здесь — это owner_id.

2.

CREATE index ON post (owner_id, likes_count);
SELECT *
FROM post
WHERE owner_id = ?;

Да, индекс будет использоваться в запросе.

В данном запросе имеется условие owner_id = ?, которое соответствует первой колонке составного индекса (owner_id, likes_count).
Индексы могут использоваться даже если условие соответствует только первой колонке составного индекса.

3.

CREATE index ON post (owner_id, likes_count);
SELECT *
FROM post
WHERE likes_count = ?;

Нет, индекс не будет использован в этом запросе.

Поскольку условие фильтрации затрагивает только likes_count, а индекс начинается с owner_id, то индекс (owner_id, likes_count) не будет полезен.
Составной индекс может быть использован, только если условия фильтрации затрагивают первый столбец (или первый и второй) в индексе.

4.

CREATE index ON post (owner_id);
CREATE index ON post (likes_count);
SELECT *
FROM post
WHERE owner_id = ?
  AND likes_count = ?;

Да, индекс будет использоваться в запросе.

В этом случае оба индекса (owner_id и likes_count) могут быть использованы.
СУБД может использовать оба индекса для более эффективного выполнения запроса.
Например, некоторые системы управления базами данных могут выполнить пересечение индексов (bitmap index merge)
или выбрать наиболее селективный индекс для начала поиска и затем использовать другой индекс для дальнейшей фильтрации.

5.

CREATE index ON post (owner_id);
CREATE index ON post (likes_count);
SELECT *
FROM post
WHERE owner_id = ?
   OR likes_count = ?;

Здесь ответ может зависеть от конкретной СУБД и её возможностей оптимизации. В некоторых случаях, особенно для сложных OR-условий, использование индексов может быть ограниченным.

Обычно:
- Некоторые СУБД могут использовать индексы для обеих колонок при запросах с OR-условиями, выполняя два индексных поиска и объединяя результаты.
- Другие СУБД могут выбрать полный скан таблицы, если считают, что это будет более эффективно по времени.
Таким образом, хотя технически оба индекса потенциально могут быть использованы,
всё зависит от подсистемы оптимизации запросов конкретной СУБД.
В большинстве случаев конкретная база данных будет принимать решение, исходя из своих алгоритмов оптимизации.

JMM

1.

class VolatileExample {
    private static boolean running = true; // Не использовано volatile

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            int count = 0;
            while (running) {
                count++;
            }
            System.out.println("Stopped at count: " + count);
        });

        thread.start();
        Thread.sleep(1000);

        running = false;
        thread.join();
        System.out.println("Main thread completed.");
    }
}

Что выведет программа в конце работы?

В таком виде программа не закончится.

т.к. не использовано volatile, while (running) может быть преобразовано в while (true)


2.

class ReorderingExample {
    private int x = 0;
    private boolean flag = false;  // тут нужен volatile

    public void writer() {
        x = 42;  // (1)
        flag = true;  // (2)
    }

    public void reader() {
        if (flag) {  // (3)
            System.out.println(x);  // (4)
        }
    }

    public static void main(String[] args) {
        ReorderingExample example = new ReorderingExample();

        Thread writerThread = new Thread(example::writer);
        Thread readerThread = new Thread(example::reader);

        writerThread.start();
        readerThread.start();
    }
}

Что выведет программа в конце работы?

0, 42 или ничего

0 - потому-что оптимизатор может поменять местами строки 1 и 2

Что сделать что бы получить ожидаемое?

Добавить volatile для флага


3.

Как правильно создать синглтон

class Singleton {
    private static Singleton instance; // Тут еще нужно вкрячить volatile

    private Singleton() {
        // Private constructor
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Наиболее распространенный способ — «Double-Checked Locking»

volatile тут нужен, что бы мы отдали полностью созданный и инициализированный объект, иначе может быть ситуация что ссылка на объект опубликована, а объект еще не создан, или объект создан, но его поля еще не проинициализированны.

статейка на тему


4.

class UnsafePublication {
    private Object resource; // нужен volatile что бы решить проблему

    public UnsafePublication() {
        new Thread(() -> {
            resource = new Object();
        }).start();
    }

    public Object getResource() {
        return resource;
    }

    public static void main(String[] args) throws InterruptedException {
        UnsafePublication unsafe = new UnsafePublication();
        Thread.sleep(100);
        if (unsafe.getResource() != null) {
            System.out.println("Resource is initialized");
        } else {
            System.out.println("Resource is null");
        }
    }
}

Что тут не так?

Небезопасная публикация

Можем отдать не полностью проинициализированный объект, т.е. объект не null, но еще не все поля проинициализированы

статья на тему

SQL

У нас есть следующие таблицы.
Требуется вывести id людей городов в которых нет мостов.

CREATE TABLE town
(
    id   bigserial PRIMARY KEY,
    name text
);

CREATE TABLE person
(
    id      bigserial PRIMARY KEY,
    name    text,
    town_id bigint not null REFERENCES town (id)
);

CREATE TABLE bridge
(
    id      bigserial PRIMARY KEY,
    name    text,
    town_id bigint not null REFERENCES town (id)
);

SELECT p.id
FROM person p
        LEFT JOIN bridge b on p.town_id = b.town_id
WHERE b.id IS NULL;

Структуры данных

Реализовать RandomSet что бы все методы работали за O(1)
- boolean add(el)
- boolean contains(el)
- boolean contains(el)
- el getRandom()

Быстро даром не бывает - значит что тут будет использовано много памяти

Тут надо понять что одной структурой не обойтись

Определившись со структурами подумать или вспомнить

как удалить из массива за O(1)

Мой вариант кода (уже с оптимизацией и типизацией)

Public class RandomSet<T> {

    private final HashMap<T, Integer> map = new HashMap<>();
    private final List<T> list = new ArrayList<>();
    private final Random random = new Random();

    public boolean add(T element) {
        int size = list.size();
        return map.computeIfAbsent(element, t -> {
            list.add(element);
            return list.size() - 1;
        }) == size;
    }

    public boolean contains(T element) {
        return map.containsKey(element);
    }

    public boolean remove(T element) {
        Integer removeIndex = map.remove(element);
        if (removeIndex != null) {
            int lastIndex = list.size() - 1;
            T lastElement = list.get(lastIndex);
            list.set(removeIndex, lastElement);
            if (removeIndex > 0) map.put(lastElement, removeIndex);
            list.remove(lastIndex);
            return true;
        }
        return false;
    }

    public T getRandom() {
        return list.get(random.nextInt(list.size()));
    }

}