воскресенье, 23 октября 2022 г.

Наследование в Java

   «родительское/дочернее»

    Наследование (inheritance) - свойство системы, позволяющее описать (создать) новый класс на основе уже существующего с частично или полностью заимствующейся функциональностью.
    Класс, от которого производится наследование, называется базовым, родительским или суперклассом, а новый класс - потомком, наследником, дочерним или производным классом. 
    Используется для быстрой и безопасной организации родственных понятий: чтобы было достаточно на каждом иерархическом шаге учитывать только изменения, не дублируя всё остальное, учтённое на предыдущих шагах.

    Общий смысл наследования заключается в том, что если несколько классов имеют сходное поведение, то нет смысла дублировать их описание (писать с нуля), лучше выделить у них общие признаки и объединить их в общем для них родительском классе. При этом в описании самих классов оставить только различающиеся элементы (методы, поля) либо переопределить имеющиеся новой реализацией.

 

    Для обозначения наследования в Java служит слово extends.

    Так или иначе, но наследование всегда используется при создании любого класса, пусть и в не явном виде, т.к. любой класс в Java автоматически становится производным от суперкласса Object. Таким образом мы получаем доступ ко всем полям и методам этого класса.

   Чем дальше вверх по иерархии наследования, тем более универсальными и абстрактными становятся классы. Такие классы становятся основой для других классов. И, как правило, запрещается создавать их экземпляры.

 

В Java есть два вида наследования:

  • наследование классов: каждый наследник может иметь только одного родителя;
  • наследование интерфейсов: интерфейс может иметь сколько угодно родителей.

Порядок инициализации объектов при наследовании

  • память, выделенная под новый объект, заполняется двоичными нулями;

  • в начале идет подъем до корня иерархии, а потом сверху вниз вызываются конструкторы один за другим вплоть до класса, конструируемого объекта;

  • инициализируются поля класса в порядке их записи;

  • вызывается тело конструктора нужного объекта.

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

 

Ограничения

  • при наследовании доступ из методов класса-потомка к приватным полям родительского класса напрямую запрещен. Кроме того, данные поля не наследуются. Но благодаря специальным публичным методам, которые называются get/геттеры и set/сеттеры можно совершенно свободно обращаться к данным полям родительского класса из класса-потомка;

  • приватные методы, как и приватные поля также не наследуются. Это значит, что создание метода в классе-потомке с именем, аналогичным имени метода класса-предка — создаст совершенно новый метод и компилятор не предупредит вас об этом. Во избежание таких коллизий при наследовании методов желательно использовать аннотацию @Override. Благодаря данной аннотации компилятор сможет проконтролировать ваш код и выдать предупреждение, если переопределяемый метод не будет найден в родительском классе.

     

Достоинства

  • способствует уменьшению повторяемости кода, т.е. имеет место быть его переиспользование (англ. code reuse);

  • ускоряет разработку. тк наследование позволяет взять готовый класс, "клонировать" его в новый класс-потомок, т.е. получить весь функционал класса-предка, а затем расширить его, добавив новые методы и поля;

Недостатки

  • большое значение имеет правильное построение иерархии классов. Т.к на поздних этапах разработки, когда иерархия классов построена и на её основе разработано большое количество кода, оказывается трудно или даже невозможно внести какие-либо изменения в код базовых классов иерархии;

  • при внесении изменений в базовые классы — классы наследники об этом могут ничего не знать;

  • данный механизм требует, чтобы точный тип объекта был известен уже на стадии компиляции, что делает код, зависящим от реализации;

  • подкласс зависит от реализации родительского класса, что делает код сильно связанным.

Вместо наследования можно использовать композицию / агрегацию, т.к. этот механизм более гибок, так как позволяет динамически выбирать тип.

 

Когда нужно применять наследование

    Для того что бы определить стоит ли применять наследование нужно для предка и предполагаемого производного класса попробовать установить отношение "является" ("is a").

    Отношение "является" служит признаком наследования.


    Наследование применяют для описания объектов, незначительно отличающихся друг от друга

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

суббота, 1 октября 2022 г.

Форматирование строк в Java. System.out.printf . Описание спецификаторов формата %

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

    В Java существуют, например, следующие методы для форматирования строк:

 

   1 метод format класса String:

public static String format(String format, Object… args)

   (возвращает строку, отформатированную из строки format с помощью остальных аргументов args)

 

   1 String.format()

   2 System.out.printf()

   3 System.out.format()

 

   Метод format класса String: public static String format(String format, Object… args) возвращает строку, отформатированную из строки format с помощью остальных аргументов args.


   Метод printf() это часть java.io.PrintStream класса.

   Синтакс может быть следующим:

      System.out.printf(format, arguments);
      System.out.printf(locale, format, arguments);


   Внутри printf() использует класс java.util.Formatter для разбора строки формата и создания вывода. Дополнительные параметры строки формата можно найти в документации Formatter Javadoc.


   Правила форматирования определяются параметром форматирования (format)

   Правила (спецификаторы) формата начинаются с символа % и заканчиваются символом, указывающим тип аргумента, который нужно отформатировать (conversion-character). Спецификаторы формата могут (опционально) содержать такие параметры как flags, width, precision.

   %[flags][width][.precision]conversion-character

 

[flags] - флаги - определяют стандартные способы изменения вывода и чаще всего используются для форматирования целых чисел и чисел с плавающей запятой.

[width] - ширина - указывает ширину поля для вывода аргумента. Он представляет собой минимальное количество символов, записываемых на выходе.

[.precision] - точность - указывает количество разрядов точности при выводе значений с плавающей запятой. Кроме того, мы можем использовать его для определения длины подстроки для извлечения из строки.

 

   Пример: System.out.printf("Hello %s!%n", "World");

 

   %s для форматирования (вывода) объектов - строк

   %d для вывода целого числа (int. byte, short, int, long, BigInteger)

   %f для вывода десятичного числа с плавающей точкой 

    %b для вывода любого типа, который будет приведен к boolean

    %t для форматирования значений даты/времени

    %n используется для разделения строки на линии (как Enter)


   %s

   Если попробовать вставить, к примеру, double в место, в котором прописан объект строки, double будет приведен к строке:

   То есть данная запись

String str = String.format("Вася - %s! Он сейчас %s", 18.1, "на работе");

   Выведет следующее:

   Вася - 18.1! Он сейчас на работе

 

   %d

      Пример:

         String.format("Поймал рыб штук %d!",25)

         Результат: Поймал рыб штук 25!

 

   %f

      System.out.printf("%f", Math.PI); // 3,141593

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

   Например, для десятичного числа с 3 символами после запятой:

      System.out.printf("%.3f", Math.PI); // 3,142

 

    %b

      (true — если значение не null, false — если null)

      Пример:

         String.format("2x2=5 %b!",null)

         Результат: 2x2=5 false!

 


   Равнозначные примеры:

1 String str = String.format("Вася - %s! Он сейчас %s", "инженер", "на работе");

   System.out.println(str);

2 System.out.printf("Вася - %s! Он сейчас %s", "инженер", "на работе");

3 System.out.format("Вася - %s! Он сейчас %s", "инженер", "на работе");

   Все три примера выведут в консоль следующее:

   Вася - инженер! Он сейчас на работе

 

   Дополнительную информацию можно посмотреть здесь:

https://www.baeldung.com/java-printstream-printf

среда, 10 августа 2022 г.

Оператор ветвления if..else в Java

   Оператор с условием "Если" - if.

    В Java оператор if может сопровождаться дополнительным оператором else, который выполняется при ложном логическом выражениив результате.

   Синтаксис:

   if (Логическое выражение) { 

      //Выполняется, если истинно

    } else { //Выполняется, если ложно }

 

   Объединяющие условие "и" выглядит так:

 if (a && b)

 

   Объединяющие условие "или" выглядит так:

if (a || b)

суббота, 6 августа 2022 г.

Операции в Java

Операция присвоения =
 
  Операция "=" позволяет присвоить значение переменной
   (Пример: int x=3; )
 

Арифметические преобразования

   Арифметические преобразования обозначаются так:

   +   сложение
   -   вычитание
   *   умножение
   /   деление
   %   взятие остатка от деления: выдает остаток от деления: (5%3=2); (24%7=3);

   (Пример: double x3 = 1.1 * 2 + 1;  )

   NB!: При выполнении арифметических операций над операндами с различными типами данных результат будет иметь наибольший тип из типов данных операнд.
    При выполнении арифметических операций операнды всегда преобразуются как
минимум в int (например при умножении двух переменных типа byte оба значения сначала преобразуются в int, и результат выражения будет int).

   При выполнении арифметической операции над операндами разных типов результат операции будет иметь наибольший тип, Что можно описать следующими правилами:

 1. Если один из операндов имеет тип double, то результат выражения
    имеет тип double, иначе смотри пункт 2.
 2. Если один из операндов имеет тип float, то результат выражения имеет
    тип float, иначе смотри пункт 3.
 3. Если один из операндов имеет тип long, то результат выражения имеет
    тип long, иначе результат выражения имеет тип int.

(например, при сложении int  и long  результат будет иметь тип long, а при сложении long  и float  результат будет иметь тип float, а при сложении float  и double  результат будет иметь тип double).

   Если результат операции с целочисленными данными выходит за диапазон, то старшие биты отбрасываются, и результирующее значение будет совершенно неверным. При попытке деления на 0 возникает исключение java.lang.ArithmeticException/zero.

   При выполнении операций с плавающей точкой при выходе за верхнюю или
нижнюю границу диапазона получается +Infinity  (
Double.POSITIVE_INFINITY  и Float.POSITIVE_INFINITY) и -Infinity  (
Double.NEGATIVE_INFINITY  и Float.NEGATIVE_INFINITY ) соответственно, а при получении слишком маленького числа, которое не может быть нормально сохранено в этом типе данных получается -0.0 или +0.0.

   При выполнении операций с плавающей точкой результат NaN  ( Double.NaN и Float.NaN) получается в следующих случаях:

  * Когда один из операндов |NaN|
  * В неопределённых результатах:
      o Деления 0/0, ∞/∞, ∞/−∞, −∞/∞,  −∞/−∞
      o Умножения 0×∞ and 0×−∞
      o Степень 1^∞
      o сложения ∞ + (−∞), (−∞) + ∞ и эквивалентные вычитания.
  * Операции с комплексными результатами:
      o Квадратный корень из отрицательного числа
      o Логарифм отрицательного числа
      o Тангенс 90 градусов и ему подобных (или π/2 радиан)
      o Обратный синус и косинус от числа меньше −1 и больше +1.



Унарные операции

   Унарными называются операции, которые имеют только один операнд. Унарные
операции бывают префиксные и постфиксные.

   Постфиксные унарные операции ставятся после операнда:

  * Инкремент (увеличение на 1) ++
  * Декремент (уменьшение на 1) --


Примеры:

Java
int x = 3;
short y = 100;
x++; // после выполнения x становится равным 4.
y--; // после выполнения y становится равным 99.

    Префиксные унарные операции ставятся перед операндом:
 
+    Унарный плюс (обозначает положительные числа, хотя числа положительными будут и без него)
-    Унарный минус (обозначает отрицательные числа)
!    Логическое НЕ (инвертирует значение логического типа, превращая true  в false и наоборот)
++ Префиксный инкремент (увеличивает значение на 1)
--   Префиксный декремент (уменьшает значение на 1)


Примеры:

Java
int x1 = +10; // положительная десятка
int x2 = -x1; // -10

boolean b1 = true
boolean b2 = !b1; // false

++x1; // теперь x1 равен 11.
--x2; // теперь x2 равен -11


Отличие постфиксного и префиксного инкремента и декремента


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

Пример:

Main.java
Java
class Main {
    public static void main(String[] args) {
        int x1 = 100;
        int x2 = 145;
       
        int y1 = ++x1;
        int y2 = --x2;

        // Вывод для префиксных операций
        System.out.println("\nPrefix ++, -- test");
        System.out.println("x1=" + x1 + "; y1=" + y1);
        System.out.println("x2=" + x2 + "; y2=" + y2);

        // Возвращаем исходные значения
        x1 = 100;
        x2 = 145;
  
        int z1 = x1--;
        int z2 = x2++;

        // Вывод для постфиксных операций
        System.out.println("\nPostfix ++, -- test");
        System.out.println("x1=" + x1 + "; z1=" + z1);
        System.out.println("x2=" + x2 + "; z2=" + z2);
    }
}


Этот пример выводит в консоль следующее:

Prefix ++, -- test
x1=101; y1=101
x2=144; y2=144

Postfix ++, -- test
x1=99; z1=100
x2=146; z2=145


    Как видно из примера y1 и y2 стали равны  значениям x1 и x2, которые получились после осуществления операций инкремента и декремента соответственно, а z1  и z2  стали равны значениям x1 и x2, которые были до операций инкремента и декремента.
 


Операции сравнения

   Операции сравнения позволяют проверить, больше ли один операнд другого, либо что один операнд равен другому и т. д.

Вот список операций сравнения в Java:

   = =  равенство (нужно использовать два символа равно для сравнения, а не один)
   !=  неравенство
   >  больше
   >=  больше или равно
   <  меньше
   <=  меньше или равно
 
   Данныеоперации сравнения используются для чисел, т.к. оператор == сравнивает не свойства объектов (строк), а ссылки (адреса в памяти).


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

Пример:

class Main {
    public static void main(String[] args) {
        int x = 3;
        double d = 3.1;
        System.out.println(x == d); // false
        System.out.println(x > d);  // false
        System.out.println(x < d);  // true
    }
}

При сравнении используются следующие правила:

  * Если один из операндов NaN, то результат false.
  * -Infinity  меньше +Infinity
  * -0.0 с плавающей точкой равен +0.0 с плавающей точкой
  * При сравнении примитивов разных типов значение меньшего типа
    преобразуется в больший тип.



Логические операторы в Java
 
&      Логическое AND (И)
&&   Сокращённое AND
|        Логическое OR (ИЛИ)
||        Сокращённое OR
^       Логическое XOR (исключающее OR (ИЛИ))
!       Логическое унарное NOT (НЕ)
&=   AND с присваиванием
|=      OR с присваиванием
^=     XOR с присваиванием
==     Равно
!=      Не равно
?:       Тернарный (троичный) условный оператор
 
 
   Логические операторы &, |, ^ действуют применительно к значениям типа boolean точно так же, как и по отношению к битам целочисленных значений.
 
 
 
Логические И и ИЛИ

   Логическое И &&  и Логическое ИЛИ || ведут себя вполне ожидаемо для логического И или логического ИЛИ:

Java
boolean b1 = true && true; //true
boolean b2 = true && false; //false
boolean b3 = true || false; // true
boolean b4 = false || false; //false

System.out.println(b1);
System.out.println(b2);
System.out.println(b3);
System.out.println(b4);


    Логическое И &&  вычисляет свой правый операнд только в том случае, если левый равен true. Если левый операнд равен false, то сразу возвращается false. Логическое ИЛИ ||  вычисляет правый операнд только в том случае, если левый равен false. Если левый операнд равен true, то сразу возвращается true. Эти два правила сокращения вычислений позволяют сразу откинуть последующие вычисления, если результат всего выражения уже известен. Это можно использовать для проверки на null  перед проверкой результата какого-либо метода объекта (будет описано в дальнейшем):

Java
if (obj != null && obj.method1()) { // obj.method1() будет вызывать только
                                    // если проверка obj!= null вернула true.
}
 
 
 
Логическое НЕ
 
   Логический оператор ! инвертирует (меняет на противоположный) булево состояние: !true == false и !false == true.



Тернарная операция

   Операция «?:» называется тернарной, потому что он принимает три операнда.

<выражение_boolean> ? <выражение1> : <выражение2>
1
   
<выражение_boolean> ? <выражение1> : <выражение2>

   Тернарная операция вычисляет <выражение_boolean>, если оно равно true, то вычисляет и возвращает <выражение1>, а если false, то <выражение2> .

Java
class Main {
    public static void main(String[] args) {
        int x = 3 > 2 ? 5 : -3; // 5
        String str1 = 3 == 2 ? "YES": "NO"; //"NO"
        System.out.println(x);
        System.out.println(str1);
    }
}
 
 
 
 
Операция instanceof

   Операция instanceof  проверяет, является ли объект экземпляром класса или экземпляром дочернего класса или экземпляром класса, реализующего интерфейс.


obj1 instanceof A

Возвращается true, если obj1  не null  и является экземпляром класса A
или экземпляром дочернего класса A  или экземпляром класса, реализующего
интерфейс A.


Object obj1 = new String("test1");
if (obj1 instanceof String) {
    System.out.println("YES");
}

Если левый операнд равен null, то результатом будет false. Код ниже
выведет “NO”:

Object obj1 = null;
if (obj1 instanceof String) {
    System.out.println("YES");
} else {
    System.out.println("NO");
}
 


Битовые (побитовые) операции

   Битовые операции в Java используются редко, но знать их нужно. Работают они также, как и в Javascript.
   Побитовые операции в Java можно проводить только над целочисленными типами данных. То есть long, int, short, char, byte. Разница в работе с целочисленными значениями только в том какой они хранят в себе диапазон допустимых значений.

Битовые операции в Java:

  <<     Битовый сдвиг влево
  <<=   Битовый сдвиг влево с присваиванием
  >>     Битовый знаковый сдвиг вправо
  >>=   Битовый знаковый сдвиг вправо с присваиванием
  >>>   Беззнаковый битовый сдвиг вправо с заполнением нулями. Он отличается от >>  тем, что ставит 0 в самую левую позицию, а >> ставит то, что было в знаковом бите
  >>>=   Беззнаковый битовый сдвиг вправо с заполнением нулями с присваиванием
  ~       Побитовый унарный оператор NOT - инвертация бит (меняет 0 на 1 и 1 на 0 во всех битах)
  &      Побитовый AND (применяет битовую операцию И)
  &=    Побитовый AND с присваиванием
  |        Побитовый OR. Применяет побитовую операцию ИЛИ
  |=      Побитовый OR с присваиванием
  ^       Побитовый исключающий OR. Применяет XOR (исключающее или)
  ^=     Побитовый исключающий OR с присваиванием

   Эти операнды работают так же, как и из аналоги в других языках программирования.

Main.java

class Main {
    public static void main(String[] args) {
        int n1 = 4; // 100 в двоичной системе
        System.out.println("n1 >> 1 = " + (n1 >> 1)); //2 или 10
                                                  // в двоичной системе.


        System.out.println("n1 << 1 = " + (n1 << 1)); ;// 8 или 100
                                                  // в двоичной системе.

        System.out.println("0b101 & 0b100 = " + (0b101 & 0b100)); // 4  (0b100)
        System.out.println("0b001 | 0b100 = " + (0b001 | 0b100)); // 5  (0b101)
        System.out.println("0b1110 ^ 0b1011 = " + (0b1110 ^ 0b1011)); //5 (0b101);

        System.out.println("-2 >> 1 = " + (-2 >> 1)); // -1 (единица со знака
                               // сдвинется вправо, так что знак не поменяется)
                                                     

        System.out.println("-2 >>> 1= " + (-2 >>> 1)); // 2147483647 (сменит
                                // знак, так как левый бит заполнится нулём).

        System.out.println("~1 = " + ~1) ;  // -2 (0b000...001
                                          // превратится в 0b1111..10)     
       
    }
}

   Результаты выполнения побитовых операций:
   Дополнительная информация по битовым операциям: ссылка

 
 
Присвоение с выполнением другой операции
    (Сокращённая запись операций))

    Сокращённые записи операций позволяют сразу выполнить операции и присвоить результат другой переменной:
 
   +=    (сложение с присвоением)
    -=    (вычитание с присвоением)
   *=    (умножение с присвоением)
    /=    (деление с присвоением)
    %=  (взятие остатка с присвоением)
    &=   (битовый И с присвоением)
    ^=    (битовое исключающее ИЛИ с присвоением)
    |=     (битовое ИЛИ с присвоением)
    <<=  (сдвиг влево с присвоением)
    >>=  (знаковый сдвиг вправо с присвоением)
    >>>=  (беззнаковый сдвиг вправо с присвоением)


   Пример: num += 7;  // это эквивалентно num = num + 7



Они работают так:

E1 compop E2
1
   
E1 compop E2
 
что эквивалентно

E1 = (T) E1 op E2;
1
   
E1 = (T) E1 op E2;

, где T  — это тип переменной E1.

То есть intx1+=x3 эквивалентно intx1=(int)x1+x2.

Main.java
Java
class Main {
    public static void main(String[] args) {
        int x1 = 100;
        byte x2 = 100;
        int x3 = 100;
       
        x1 += 300; // эквивалентно x1 = (int) x1 + 300;
        x2 += 300; // эквивалентно x2 = (byte) x2 + 300;      
        x3 += 300.1; // эквивалентно x3 = (int) x3 + 300.1;

        System.out.println("x1=" + x1);  // 400
        System.out.println("x2=" + x2);  // -112
        System.out.println("x3=" + x3);  // 400
    }
}
 
 
 

Приоритеты операций


 
   Все операции вычисляются слева направо (сначала вычисляется левый операнд, затем правый и затем сама операций, кроме операции присваивания. Операция присваивания вычисляется справа налево.

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

Пример 1:

Java
int z = 200 * (3 + 4);


Последовательность вычисляения такая:

 1. 3+4 = 7
 2. 200 * 7 = 1 400
 3. z = 1 400

Пример 2:

Java
int x;
int y;
int z = x = y = 10000 + 20000 >> 1 + 3 * 2;


Последовательность вычисления такая:

 1. 10 000 + 20 000 = 30 000 (Присвоение вычисляется справа налево,
    поэтому сначала смотрится y=10000+20000>>1+3*2 , и вычисляется
    правая часть. В правой части ( 10000+20000>>1+3*2 ) вычисление идёт
    слева направо, и берётся 10000+20000 (выбирается среди 10000+20000 ,
    20000>>1 , 1+3  и 3*2 ), которое вычисляется перед сдвигом, так как
    у сложения приоритет выше.)
 2. 3 * 2 = 6 (В выражении 30000>>1+3*2 вычисление идёт слева направо, и
    выбирается умножение(среди 30000>>1 , 1+3  и 3*2 ), так как у него
    приоритет выше, что означает, что сложение будет выполнено раньше.)
 3. 1 + 6 = 7 (В выражении 30000>>1+6 вычисление идёт слева направо и
    сложение вычисляется раньше сдвига, так как приоритет у сложения выше.)
 4. 30 000 >> 7 = 234   (0b00…111010100110000 сдвигаем на 7 бит вправо и
    получаем 0b00…0011101010)
 5. y = 234
 6. x = 234
 7. z = 234



Таблица приоритетов операций

Группа операций     Приоритет
Группировка     |/(/ ... /)/|
Доступ к члену     |/.../ . /.../|
постфиксные     |/expr/++ /expr/--|
унарные     |++/expr/ --/expr/ +/expr/ -/expr/ ~ !|
мультипликативные     |* / %|
аддитивные     |+ -|
сдвиги     |<< >> >>>|
сравнения     |< > <= >= instanceof|
равенства     |== !=|
бинарный И     |&|
бинарный исключающее ИЛИ     |^|
бинарный ИЛИ     |||
логический И     |&&|
логический ИЛИ     ||||
тернарный     |? :|
лямбда     |->|
присваивания     |= += -= *= /= %= &= ^= |= <<= >>= >>>=|

вторник, 15 февраля 2022 г.

1.1 Java SE (Java Platform Standard Edition)

       Java SE (Java Platform Standard Edition) - стандартная редакция Java, которая используется для разработки простых Java приложений.

 

      У Oracle  есть два продукта, которые имплементируют (обеспечивают воплощение в жизнь) Java SE: Java SE Development Kit (JDK) и Java SE Runtime Environment (JRE).

 

   О том, что такое JVM, JRE, JDK, IDK и как они устроены, можно прочитать здесь: https://javaika.blogspot.com/2020/09/JVM-JRE-JDK.html

вторник, 17 ноября 2020 г.

Что такое классы в Java

    Класс представляет собой контейнер, содержащий программный код.


    (модель для создания объектов определённого типа, описывающая их структуру (набор полей и их начальное состояние) и определяющая алгоритмы (функции или методы) для работы с этими объектами)

   Код внутри класса представляет собой набор элементов - описание полей, констант, методов, интерфейсов и других классов.
   Класс - по сути является шаблоном (описанием, образцом) для объекта: он определяет, как объект будет выглядеть и какими функциями обладать.
   Класс представляется (объявляется) в исходном коде так:
   (Объявление класса состоит из заголовка (первая строка, содержащая ключевое слово class) и тела класса, заключённого между фигурными скобками {}).

   МодификаторыКласса class ИмяКласса extends КлассРодитель implements РеализуемыеИнтерфейсы
{
Тело класса

   Здесь class - ключевое слово, сообщающее о том, что данная конструкция описывает класс, а не что-то другое.
   Имя класса принято начинать с большой буквы и использовать для него CamelCase.
   Имена класса и файла с Java-кодом класса с расширением .java должны совпадать,в т.ч.их регистр.

 
 
   Содержание заголовка класса

   В заголовке сначала указываются модификаторы класса, затем – ключевое слово class, затем – имя класса.
   Также класс может быть объявлен как final – в этом случае не допускается создание наследников такого класса, и на своей ветке наследования он является последним.
 
   Далее заголовок может содержать ключевое слово extends, после которого должно быть указано имя доступного не-final класса, от которого наследуется объявляемый класс.
   Если выражение extends не применяется, то класс наследуется напрямую от базового класса Object, при этом выражение extends Object допускается, но игнорируется.
   (Все классы в Java унаследованы от класса Object, который находится на вершине иерархии наследования и не является чьим-либо наследником)
   Если компилятор обнаруживает, что класс является своим наследником, то возникает ошибка компиляции. Наследование более чем от одного класса (множественное наследование) в языке Java запрещено.
 
   Далее в заголовке может быть указано ключевое слово implements, за которым должно следовать перечисление через запятую имен доступных интерфейсов, которые реализует данный класс.
   Класс может реализовывать любое количество интерфейсов.
   Если выражение implements отсутствует, то класс действительно не реализует никаких интерфейсов.

 
Содержание тела класса
 
   Тело класса, заключенное в пару фигурных скобок { }, может быть пустым или содержать описание тела класса.
 
   Тело класса может содержать объявление элементов класса (полей, методов и внутренних типов) и остальных допустимых конструкций (конструкторов, инициализаторов, статических инициализаторов).
   Класс может иметь один или несколько методов, которые должны быть объявлены внутри класса (между фигурными скобками).
 
   Элементами класса являются элементы, описанные в объявлении тела класса и переданные по наследству от класса-родителя и всех реализуемых интерфейсов при условии достаточного уровня доступа.
   Элементы класса имеют имена и передаются по наследству, областью видимости элементов является все объявление тела класса. Для элементов простые имена указываются при объявлении, составные формируются из имени класса или имени переменной объектного типа и простого имени элемента.Допускается применение к ним любого из модификаторов доступа.
   Поля и методы могут иметь одинаковые имена, поскольку обращение к полям всегда записывается без скобок, а к методам - всегда со скобками. Конструкторы, инициализаторы и статические инициализаторы не обладают именами, а потому не могут быть вызваны явно – их вызывает сама виртуальная машина. По той же причине они не обладают и модификаторами доступа
 
 

    Полное имя класса - это имя, состоящее из всех пакетов, перечисленных через точку и имени класса. Например, полное имя класса FileOutputStream из пакета java.io выглядит так:
   java.io.FileOutputStream
 
   Чтобы использовать класс в своём коде, необходимо указывать его полное имя, либо краткое (т.е. только лишь имя класса), но для этого нужно предварительно "импортировать" пакет, в котором содержится данный класс - указать его имя перед объявлением класса, со словом import.   Пример импорта пакетов:
      import java.io.FileInputStream;
      import java.io.FileOutputStream; 
      import java.io.IOException; 

   Классы из пакета java.lang импортируются по умолчанию: их указывать не обязательно.
 
   Кроме обычных переменных есть Переменные экземпляра (поля) - переменные, созданные в классе, которые будут у каждого экземпляра (объекта) класса. Они записываются через точку: maiskii.price.

  
   Объект - это экземпляр какого-либо класса, обладающий характеристиками в виде полей (fields) и поведением (функционалом) в виде методов (methods).
 
   Для создания экземпляра класса (объекта) используется оператор  new.
   Пример:
 
public class Tea {
   String name;
   int price;
 
   public static void main(String[] args) {
     Tea maiskii = new Cat();
     maiskii.price = 50;
     maiskii.name = "Assam";
  } 
}
 
 
   Все классы в Java унаследованы от класса Object.
   Класс Object, в свою очередь, имеет метод toString(), который вызывается, когда объект нужно преобразовать к строке (все объекты в Java могут быть преобразованы в строку).
   Стандартный метод toString() класса Object возвращает строку, состоящую из имени класса и адреса объекта в памяти (в шестнадцатеричном виде). Пример выводимого результата:
   “Tea is com.pack.lesson3.Tea@1fb8ee3”
 
   Однако, можно в своём классе написать свою реализацию метода toString(), и вызываться будет именно он.

   Три приведённых примера эквивалентны:

   1) Tea tea = new Tea("Assam"); System.out.println("Tea is " + tea);

   2) Tea tea = new Tea("Assam"); System.out.println("Tea is " + tea.toString());

   3) Tea tea = new Tea("Assam"); String teaText = tea.toString(); System.out.println("Tea is " + teaText);

 
 
  Абстрактные классы и методы
 
   Для абстрактных методов и классов используется модификатор abstract.
 
   Иногда бывает удобным описать только заголовок метода, без его тела, и таким образом объявить, что такой метод будет существовать в этом классе и его потомках, а реализацию этого метода (то есть его тело) описать позже в наследниках данного класса.
 
   Так как абстрактный метод не имеет тела, после описания его заголовка ставится точка с запятой.
   Так как у абстрактного метода нет тела, то к нему нельзя обращаться, пока его наследники не опишут реализацию - то есть нельзя создавать экземпляры класса, у которого есть абстрактные методы.
 
   Такой класс сам объявляется абстрактным.Класс может быть абстрактным и в случае, если у него нет абстрактных методов, но должен быть абстрактным, если такие методы есть.
   Разработчик может указать ключевое слово abstract в списке модификаторов класса, если хочет запретить создание экземпляров этого класса.
   Классы-наследники должны реализовать все абстрактные методы своего абстрактного родителя (если они есть), чтобы их можно было объявлять неабстрактными и порождать от них экземпляры.
   Конечно, классы и методы не могут быть одновременно abstract и final.
   Кроме того, абстрактный метод не может быть private, native, static.
   Сам класс может без ограничений пользоваться своими абстрактными методами, поскольку метод класса может быть вызван только у объекта, а объект при этом может быть порожден только от неабстрактного класса, который является его наследником  должен реализовать все абстрактные методы.
   По этой же причине можно объявлять переменные типа абстрактный класс. Они могут иметь значение null или ссылаться на объект, порожденный от неабстрактного наследника этого класса.
 
   О служебных словах-моификаторах доступа можно прочитать здесь: https://javaika.blogspot.com/2020/09/public-private-protected-access.html

Ключевое слово final

   Ключевое слово final (завершённый) может использоваться при объявлении классов, методов, переменных (в том числе аргументов методов).

 

   Для класса это означает, что класс не сможет иметь подклассов, т.е. запрещено наследование класса. (не допускается создание наследников такого класса, и на своей ветке наследования он является последним)

   (такой класс не является предком какого-либо класса).

 

   Попытка расширить final-класс приведёт к ошибке компиляции.

   Объявление класса завершенным неявно делает завершенными и все его методы.

   Это полезно при создании immutable (неизменяемых) объектов, например, класс String объявлен, как final.

   Следует также отметить, что к абстрактным классам (с ключевым словом abstract), нельзя применить модификатор final, т.к. это взаимоисключающие понятия.

(Одновременное объявление класса как abstract и final недопустимо).

 

   Поле (переменная класса) также может быть объявлено final, что означает, что оно инициализируется ровно один раз, и больше не будет менять своего значения.

   Статические поля класса также могут быть объявлены как final, что означает, что они должны быть проинициализированы строго один раз, и затем уже больше не менять своего значения.

 

   Локальный класс (класс, объявленный в блоке операторов между фигурными скобками, например, в теле метода, цикла или оператора ветвления) имеет доступ к членам класса, в котором он объявлен, а также к локальным переменным, объявленным как final.

   Локальные классы, как и внутренние, не могут содержать статические методы и статические поля, не объявленные как final, и могут обращаться только к статическим полям внешнего класса.


   Для интерфейса все поля интерфейса должны быть public static final, поэтому эти модификаторы указывать необязательно и даже нежелательно, чтобы не загромождать код.

   (К полям интерфейса по умолчанию применяются модификаторы public static final).

 

   Для метода final означает, что такой метод не может быть переопределен в подклассах (в наследниках) (устанавливается чтобы запретить переопределение метода в классах наследниках).

   (он не может быть переопределён потомками данного класса) 


   Это полезно, когда необходимо, чтобы исходную реализацию нельзя было переопределить.

   Если класс А содержит завершенный метод, который не может быть переопределен в классе наследнике Б, то при попытке возникнет ошибка компиляции.

 

    Можно считать, что все методы final-класса, а также все private-методы любого класса являются final:

   Не имеет смысла объявлять метод private как final, так как private метод не виден в наследниках, соответственно не может быть переопределен.

 

   Также конструктор не может быть объявлен как final.

   Для каждого аргумента метода можно указать ключевое слово final перед указанием его типа - в этом случае такой параметр не может менять своего значения в теле метода.

    Статические методы могут быть объявлены как final, что означает, что их нельзя переопределять в классах-наследниках.

 

   Для объекта (например, массива) final означает, что после присвоения ссылки на объект, уже нельзя ее изменить, но можно изменять состояние объекта.

 

   Для переменной объявление её final позволяет предотвратить изменение содержимого переменной, сделав ее, по существу, константой.

   Ключевое слово final указывают перед типом переменной, которую в этом случае необходимо сразу проинициализировать и впоследствии никогда не менять ее значение. Если попытаться изменить значение final-переменной, это приведет к ошибке компиляции. Таким образом, final-переменные становятся чем-то вроде констант, но, на самом деле, некоторые инициализаторы могут вычисляться только во время исполнения программы.

   final переменная класса, объявленная как не static, должна инициализироваться при объявлении или в теле конструктора или блоке инициализации, иначе произойдет ошибка компиляции.

   final переменные, объявленные как static, должны быть проинициализированы при объявлении или в блоке инициализации, также объявленном как static. В противном случае, опять получится ошибка компиляции. 

   Кроме переменных, объявленными как final могут быть параметры метода и локальные переменные. 

   Константы Java Константы – это переменные, значение которых не меняется. Константами в Java принято называть public static final переменные класса. Имена констант принято задавать только заглавными буквами, а слова в имени разделять знаком подчеркивания: NAME_SURNAME. 

   Преимущества от введения констант: если необходимо изменить значение константы, то это можно сделать в одном месте, также имя константы непосредственно объясняет значение хранимых данных.

 

   Существует понятие - effectively final. Применяется оно только к переменным (в том числе аргументам методов). Суть в том, что не смотря на явное отсутствие ключевого слова final, значение переменной не изменяется после инициализации. Другими словами, к такой переменной можно подставить слово final без ошибки компиляции.

  effectively final переменные могут быть использованы внутри локальных классов (Local Inner Classes), анонимных классов (Anonymous Inner Classes), стримах (Stream API).

Наследование в Java

   «родительское/дочернее»     Наследование (inheritance) - свойство системы, позволяющее описать (создать) новый класс на основе уже су...