JavaRush /Java блог /Random /Кофе-брейк #141. Порядок инициализации в Java. Зачем нужн...

Кофе-брейк #141. Порядок инициализации в Java. Зачем нужна инкапсуляция в Java?

Статья из группы Random

Порядок инициализации в Java

Источник: Medium В этой статье вы узнаете, что такое порядок инициализации в Java и согласно какой очередности загружаются компоненты в класс. Кофе-брейк #141. Порядок инициализации в Java. Зачем нужна инкапсуляция в Java? - 1 Порядок инициализации — это последовательность, в которой компоненты загружаются в класс Java. Вот перечень компонентов, которые играют в этом ключевую роль: 1. Статические блоки:

static Map timezones = new HashMap<>();
static {
    timezones.put("MDT","US/Mountain Daylight");
    timezones.put("IST","Indian Standard Time");
    timezones.put("MST","US/Mountain Standard");
}
2. Блоки экземпляров:

private LocalDate todayDate ;
{
    todayDate = LocalDate.now();
}
3. Конструктор:

class Foo {
    public Foo() {
        System.out.println("Default constructor");
    }
}
Теперь рассмотрим, как наследование влияет на загрузку этих компонентов. Допустим, Foo — это суперкласс Bar. Оба этих класса имеют статические блоки, блоки экземпляров и конструкторы (по умолчанию и аргумент). Давайте посмотрим на пример, а затем узнаем, что в нем происходит. Класс Foo выглядит так:

class Foo {
    static { System.out.print("Static 1, "); }

    { System.out.print("Instance 1, "); }

    public Foo() {
        System.out.print("Foo Constructor, ");
    }

    public Foo(int a, String b) {
        System.out.print("Foo args-constructor, ");
    }
}
Класс Bar:

class Bar extends Foo {
    static { System.out.print("Static 2, "); }

    { System.out.print("Instance 2, "); }

    public Bar() {
        System.out.print("Bar Constructor, ");
    }

    public Bar(int a,String b) {
        System.out.println("Bar args-constructor");
    }
}
Теперь давайте вызовем конструктор Bar из основного метода (пример кода #1):

public class Test {
    public static void main(String[] args) {
        Bar b1 = new Bar();
    }
}
Output:
Static 1, Static 2, Instance 1, Foo Constructor, Instance 2, Bar Constructor,
А сейчас вызовем параметризированный конструктор Bar из основного метода (пример кода #2):

public class Test {
    public static void main(String[] args) {
        Bar b2 = new Bar(10,"Ashutosh");
    }
}
Output:
Static 1, Static 2, Instance 1, Foo Constructor, Instance 2, Bar args-constructor
Итак, когда у нас есть дочерний класс, и мы вызываем конструктор дочернего класса, будет соблюдаться следующий порядок загрузки:
  • Статические блоки суперкласса и дочернего класса.
  • Блок экземпляров суперкласса.
  • Конструктор суперкласса.
  • Блок экземпляра дочернего класса.
  • Конструктор дочернего класса.
(Примечание: Конструктор загрузится, только если загружены все блоки экземпляра) Теперь, если мы проанализируем вывод на примере кода #1, то увидим, что загружается статический блок Foo, за которым следует статический блок Bar, затем блок экземпляра и конструктор Foo, за которым следуют блок экземпляра и конструктор Bar. Здесь следует отметить, что если бы у нас было несколько статических блоков и блоков экземпляров, то они были бы выполнены в той же последовательности, в которой они были написаны в классе. Конструктор любого класса загрузится после того, как все блоки экземпляра этого класса будут выполнены. Теперь давайте проанализируем вывод на примере кода #2. Вывод очень похож на предыдущий, но с небольшим отклонением. Когда у нас есть конструктор по умолчанию и параметризованный конструктор в суперклассе и дочернем классе, то параметризованный конструктор в дочернем классе неявно вызывает конструктор по умолчанию.

class Bar extends Foo {
    public Bar(int a,String b) {
        super();  //super() is invoked implicity at run-time
        System.out.println("Bar args-constructor");
}
По этой причине на примере кода #2 мы находим в выходных данных Foo constructor вместо Foo args-constructor. Если бы мы явно вызвали параметризованный суперконструктор, то в выводе мы бы нашли Foo args-constructor, как показано в приведенных ниже фрагментах.

class Bar extends Foo {
    public Bar(int a,String b) {
        super(a,b);
        System.out.println("Bar args-constructor");
    }
    //existing codes
}
Вывод основного метода:

public class Test {
    public static void main(String[] args) {
        Bar b2 = new Bar(10,"Ashutosh");
    }
}
Output:
Static 1, Static 2, Instance 1, Foo args-constructor, Instance 2, Bar args-constructor
Надеюсь, что эта статья смогла донести до вас эту концепцию инициализации. Успехов в кодировании!

Зачем нужна инкапсуляция в Java?

Источник: Medium Эта публикация поможет вам лучше понять, для чего нужна инкапсуляция в Java и в решении каких проблем она помогает. Инкапсуляция нам необходима для поддержки кода. Чтобы было понятно, давайте возьмем пример автомобиля. Периодически владелец машины меняет автомобильное масло, воздушные фильтры или дворники. Это нужно, чтобы автомобиль работал лучше и служил дольше. Представьте, если бы замена скромного стеклоочистителя могла сломать автомобиль. Ой! Какой кошмар, правда! Вы бы не стали доверять такой машине свою жизнь. Точно так же, чтобы код Java работал лучше и дольше, не нарушая работу всего вашего приложения или других классов, обращающихся к вашему классу, вам нужна ремонтопригодность и гибкость при изменении кода. Ремонтопригодность и гибкость не встроены ни в один язык программирования. Вам нужно спроектировать свой код таким образом, чтобы он наследовал эти функции.

Как этого добиться?

В Java мы достигаем этого, ограничивая доступ к переменным данных в классе. В общедоступных классах объявите все переменные закрытыми (или защищенными) и предоставьте общедоступные методы (методы доступа) для работы с этими переменными. Поступая таким образом, вы контролируете, как будет осуществляться доступ к данным, а также определяете, как будет вести себя код, обеспечивая при этом согласованный интерфейс для данных. Это дает вам возможность улучшать и поддерживать код, а также предотвращает возникновение ошибок при обращении других классов к этому коду.

Итак, что же такое инкапсуляция в Java?

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

Что еще нужно знать?

Когда один класс расширяет другой класс, это нарушает инкапсуляцию. Когда подкласс наследует суперкласс, это позволяет нарушить инкапсуляцию подкласса без изменения подкласса событием. Если вы измените метод в суперклассе в следующем релизе, то в подклассе, зависящем от этого метода, может появиться ошибка, даже если вы даже не никак не затронете этот подкласс. Например, в следующем релизе программы вы решили создать новый метод в суперклассе. Однако у вас уже есть метод с такой же сигнатурой, но другим типом возвращаемого значения в вашем подклассе. И вот теперь этот подкласс даже не скомпилируется.

Что же тогда делать, избегать наследования?

Технически да. Вам следует избегать расширения одного класса от другого, если вы не уверены на 100%, что ваш подкласс является типом вашего суперкласса. Скажем, класс B должен расширять класс A, только если каждый класс B действительно является классом A.

Что-то еще?

Сериализация ограничивает инкапсуляцию. Как упоминалось выше, инкапсуляция необходима для обслуживания и развития вашего класса без нарушения экосистемы вокруг него. Однако, если сериализация по умолчанию является нормой, а позже было решено изменить класс, JVM может использовать другую версию сериализации для сериализации и десериализации одного и того же класса.
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ