Зачем нужны абстрактные методы?
Как мы уже говорили, абстрактный метод не реализуется в абстрактном классе, но класс-наследник должен реализовать его. Зачем нам это? Это нужно если метод обязательно должен быть у всех наследников.
Например, каждое оружие наносит урон — без этого оно уже не оружие. Или, еще пример, каждый навык должен применяться… а животные, скажем, подавать голос Но каждый наследник будет это делать по-своему, поэтому нам как бы и нет смысла реализовывать его в абстрактном родителе.
Давайте покажем это в коде. Представим, что у нас есть абстрактный класс Animal, в котором лежит абстрактный метод voice():
abstract class Animal {
abstract void voice();
}
1 |
abstractclassAnimal{ abstractvoidvoice(); } |
У нас будут три наследника — Cat, Dog и Cow. Как можно догадаться, «голос» у каждого животного разный:
- кошка мяукает
- собака гавкает
- корова мычит
Если мы создаем Cat, который наследует Animal, мы обязаны будем реализовать voice():
Таким образом, давайте пропишем свой метод voice() для каждого класса:
public class Cat extends Animal {
void voice() {
System.out.println(«Meow!»);
}
}
1 |
publicclassCatextendsAnimal{ voidvoice(){ System.out.println(«Meow!»); } } |
public class Dog extends Animal {
void voice() {
System.out.println(«Woof!»);
}
}
1 |
publicclassDogextendsAnimal{ voidvoice(){ System.out.println(«Woof!»); } } |
public class Cow extends Animal {
void voice() {
System.out.println(«Mooo!»);
}
}
1 |
publicclassCowextendsAnimal{ voidvoice(){ System.out.println(«Mooo!»); } } |
Создадим объекты этих классов и вызовем метод voice():
public static void main(String[] args) {
Cat myCat = new Cat();
Dog myDog = new Dog();
Cow myCow = new Cow();
myCat.voice();
myDog.voice();
myCow.voice();
}
}
1 |
publicclassTest{ publicstaticvoidmain(Stringargs){ Cat myCat=newCat(); Dog myDog=newDog(); Cow myCow=newCow(); myCat.voice(); myDog.voice(); myCow.voice(); } } |
Получим:
Таким образом, каждый класс имеет свой метод voice(). Собака гавкает, а кошка мяукает — все как надо При этом нам не нужно было прописывать этот метод в классе Animal чтобы потом переопределить его. Мы же с самого начала знаем, что он будет меняться.
Итак, вот мы и применили абстрактный метод. Надеемся Вы поняли суть.
Подведем итог:
- Чтобы создать абстрактный класс, перед словом «class» нужно дописать модификатор «abstract»
- Вы не можете создать объект абстрактного класса
- В абстрактном классе можно создавать абстрактные методы, а можно и «обычные»
- Абстрактные методы могут быть только в абстрактном классе
- «Обычные» методы абстрактного класса наследуются так же, как и для неабстрактных классов
Надеемся, что наша статья была Вам полезна. Можно записаться к нам на курсы по Java на сайте.
Тип среды выполнения по сравнению с статический тип
Давайте быстро рассмотрим предыдущий пример. Когда мы вызываем метод str.getClass () , мы получаем тип времени выполнения объекта str . С другой стороны, String.class вычисляет String класс статически . В этом примере тип среды выполнения story и String.class одинаковы.
Однако они могут отличаться, если к партии присоединяется наследование классов. Давайте рассмотрим два простых класса:
public class Animal { protected int numberOfEyes; } public class Monkey extends Animal { // monkey stuff }
Теперь давайте создадим экземпляр объекта класса Animal и проведем еще один тест:
@Test public void givenClassInheritance_whenGettingRuntimeTypeAndStaticType_thenGetDifferentResult() { Animal animal = new Monkey(); Class runtimeType = animal.getClass(); Class staticType = Animal.class; assertSame(staticType, runtimeType); }
Если мы запустим тест выше, мы получим сбой теста:
java.lang.AssertionError: .... Expected :class com.baeldung.getclassobject.Animal Actual :class com.baeldung.getclassobject.Monkey
В тестовом методе, даже если мы создали экземпляр объекта animal с помощью Animal Monkey(); вместо Monkey Monkey(); , тип времени выполнения объекта animal по-прежнему Monkey. Это связано с тем, что объект animal является экземпляром Monkey во время выполнения.
Однако, когда мы получаем статический тип класса Animal , тип всегда Animal .
Использование Утверждений Java
Чтобы добавить утверждения, просто используйте ключевое слово assert и задайте ему логическое условие :
public void setup() { Connection conn = getConnection(); assert conn != null; }
Java также предоставляет второй синтаксис для утверждений, который принимает строку, которая будет использоваться для построения AssertionError , если она будет выдана:
public void setup() { Connection conn = getConnection(); assert conn != null : "Connection is null"; }
В обоих случаях код проверяет, возвращает ли соединение с внешним ресурсом ненулевое значение. Если это значение равно null, JVM автоматически выдаст Ошибку утверждения .
Во втором случае исключение будет содержать дополнительные сведения, которые будут отображаться в трассировке стека и могут помочь в отладке проблемы.
Давайте посмотрим на результат запуска нашего класса с включенными утверждениями:
Exception in thread "main" java.lang.AssertionError: Connection is null at com.baeldung.assertion.Assertion.setup(Assertion.java:15) at com.baeldung.assertion.Assertion.main(Assertion.java:10)
Абстрактные классы
Вы можете рассматривать абстрактные классы как смесь интерфейсов и обычных классов. Абстрактные классы могут иметь все, что есть в интерфейсах, а также свойства и конструкторы. Следовательно, вы можете правильно удерживать состояние в абстрактных классах, но не можете создать экземпляр абстрактного класса.
Вы можете думать об абстрактном классе как о шаблоне. Подумайте о каталоге шаблонов WordPress, где вы можете выбрать конкретный шаблон со всеми функциями, необходимыми для создания веб-сайта.
Возможно, вам нужен веб-сайт электронной коммерции с различными встроенными характеристиками, такими как раздел обзоров, корзина и список продуктов. Вы выбираете шаблон, который вам подходит, и редактируете несколько позиций, чтобы сделать его именно таким, каким вы хотите. Выполнено!
Что ж, это было намного проще, чем создать сайт с нуля.
Абстрактные классы делают то же самое. Они обеспечивают общее поведение для всех субклассов. Используйте абстрактный класс, чтобы создать группу характеристик, которые являются общими для многих классов.
Методы класса
В начале статьи я упомянул, что наши домашние животные могут перемещаться и есть. В отличие от параметров вроде веса и клички, это уже не свойства объекта, а его функции. В классе эти функции обозначают как методы.
Метод класса — это блок кода, состоящий из ряда инструкций, который можно вызывать по его имени. Он обязательно содержит возвращаемый тип, название, аргументы и тело метода.
Синтаксис метода в Java:
Строка возвращаемыйТип показывает, какого типа данные вернёт метод. Например, если в качестве возвращаемого типа мы поставим тип String, то метод должен будет вернуть строку, а если int — целое число.
Чтобы вернуть значение из метода, используется специальное слово return. Если мы хотим, чтобы метод ничего не возвращал, то вместо возвращаемого типа нужно использовать специальное слово void.
Аргументы — то, что нужно передать в метод при его вызове. Мы можем указать сколько угодно параметров через запятую либо не указывать ни одного.
Для примера напишем простейший метод с именем sum (пока что не в нашем классе Pet), который складывает два переданных числа и возвращает их результат:
Возвращаемый тип метода int, он указан перед именем sum. Далее идут два аргумента a и b, у обоих также указан тип int
Важно помнить, что возвращаемый тип и тип переменных не обязательно должны совпадать
Аргументы метода работают как обычные переменные — за пределами метода к ним никак нельзя получить доступ. Внутри метода мы складываем значения из переменных a и b, записываем полученное значение в переменную c. После этого мы возвращаем значение переменной c — только оно доступно вне метода.
Вот пример:
Мы передали в метод sum два значения 1 и 2, а на выходе получили результат их сложения 3. Также можно создать метод, который принимает значение типа String, а возвращает длину этой строки:
В этом случае у нас возвращаемый типа int, а параметр str — типа String.
Попробуем использовать этот метод:
Также мы можем создать метод, который ничего не возвращает, а просто печатает переданное слово в консоль:
Либо метод, который ничего не принимает на вход, а просто печатает «Привет!»:
В методах, которые ничего не возвращают, слово return можно опустить.
Обратите внимание, что return полностью прекращает выполнение метода:
Теперь попробуем вызвать этот метод, передав в него число 3:
В этом случае мы ничего не увидим в консоли, так как 3 меньше 5, а значит, отработает блок if и произойдёт выход из метода с помощью слова return.
Но если передадим 6, увидим нашу надпись «Привет!»:
Определяем область действия
К чему применима аннотация
До этого шага мы использовали аннотации только для классов, но они применимы к интерфейсам, методам, параметрам класса, локальным переменным и не только. За область применимости аннотации отвечает другая аннотация — @Target. Полный список доступных значений для её единственного элемента value есть в официальной документации.
Если не задавать @Target, аннотацию можно использовать для любых программных элементов.
Мы же для примера напишем аннотацию, которая будет применима к методам и конструкторам классов. Предположим, она будет показывать, что после выполнения таких конструкторов и таких методов нужно будет отправить кому-то тревожное сообщение. Реализацию обработчика оставим за кадром, а аннотация будет выглядеть так:
Как использовать:
Если теперь попробуем написать @Alarm перед названием самого класса SecureChannel, получим ошибку компиляции, потому что в @Target не включено значение для типа элемента «класс».
Когда аннотация доступна
Если мы и правда хотим прямо во время выполнения программы искать какие-то помеченные аннотацией @Alarm методы, одним только указанием @Target не обойтись.
Есть ещё одна аннотация для описания аннотаций — это @Retention. Она определяет доступность в течение жизненного цикла программы. У её единственного элемента value всего три доступных значения:
Значение | Описание |
---|---|
RetentionPolicy.SOURCE | Аннотация останется только в файлах исходников, в .class-файлы сведения о ней не попадут |
RetentionPolicy.CLASS — значение по умолчанию | Аннотация будет сохранена в .class-файлах, но не будет доступна во время выполнения программы |
RetentionPolicy.RUNTIME | Аннотация будет сохранена в .class-файлах, доступна во время выполнения программы |
Воспользуемся новыми знаниями и допишем нашу тревожную аннотацию:
Теперь она будет доступна в рантайме, что и требовалось.
Осмотр Полей
Ранее мы проверяли только имена полей, в этом разделе мы покажем, как | получить и установить их значения во время выполнения .
Существует два основных метода, используемых для проверки полей класса во время выполнения: GetFields() и getField(fieldName) .
Метод GetFields() возвращает все доступные общедоступные поля рассматриваемого класса. Он вернет все открытые поля как в классе, так и во всех суперклассах.
Например, когда мы вызываем этот метод в классе Bird , мы получим только поле CATEGORY его суперкласса Animal , поскольку Bird сам по себе не объявляет никаких открытых полей:
@Test public void givenClass_whenGetsPublicFields_thenCorrect() { Class> birdClass = Class.forName("com.baeldung.reflection.Bird"); Field[] fields = birdClass.getFields(); assertEquals(1, fields.length); assertEquals("CATEGORY", fields.getName()); }
Этот метод также имеет вариант с именем getField , который возвращает только один объект Field , взяв имя поля:
@Test public void givenClass_whenGetsPublicFieldByName_thenCorrect() { Class> birdClass = Class.forName("com.baeldung.reflection.Bird"); Field field = birdClass.getField("CATEGORY"); assertEquals("CATEGORY", field.getName()); }
Мы не можем получить доступ к закрытым полям, объявленным в суперклассах и не объявленным в дочернем классе. Вот почему мы не можем получить доступ к полю name .
Однако мы можем проверить частные поля, объявленные в классе, с которым мы имеем дело, вызвав метод getDeclaredFields :
@Test public void givenClass_whenGetsDeclaredFields_thenCorrect(){ Class> birdClass = Class.forName("com.baeldung.reflection.Bird"); Field[] fields = birdClass.getDeclaredFields(); assertEquals(1, fields.length); assertEquals("walks", fields.getName()); }
Мы также можем использовать его другой вариант, если нам известно имя поля:
@Test public void givenClass_whenGetsFieldsByName_thenCorrect() { Class> birdClass = Class.forName("com.baeldung.reflection.Bird"); Field field = birdClass.getDeclaredField("walks"); assertEquals("walks", field.getName()); }
Если мы неправильно введем имя поля или введем несуществующее поле, мы получим исключение NoSuchFieldException .
Мы получаем тип поля следующим образом:
@Test public void givenClassField_whenGetsType_thenCorrect() { Field field = Class.forName("com.baeldung.reflection.Bird") .getDeclaredField("walks"); Class> fieldClass = field.getType(); assertEquals("boolean", fieldClass.getSimpleName()); }
Далее мы рассмотрим, как получить доступ к значениям полей и изменить их. Чтобы получить значение поля, не говоря уже о его настройке, мы должны сначала установить его доступность, вызвав метод setAccessible для объекта Field и передав ему логическое значение true :
@Test public void givenClassField_whenSetsAndGetsValue_thenCorrect() { Class> birdClass = Class.forName("com.baeldung.reflection.Bird"); Bird bird = (Bird) birdClass.getConstructor().newInstance(); Field field = birdClass.getDeclaredField("walks"); field.setAccessible(true); assertFalse(field.getBoolean(bird)); assertFalse(bird.walks()); field.set(bird, true); assertTrue(field.getBoolean(bird)); assertTrue(bird.walks()); }
В приведенном выше тесте мы удостоверяемся, что действительно значение поля walks является ложным, прежде чем установить его в значение true.
Обратите внимание, как мы используем объект Field для установки и получения значений, передавая ему экземпляр класса, с которым мы имеем дело, и, возможно, новое значение, которое мы хотим, чтобы поле имело в этом объекте. Одна важная вещь , которую следует отметить в отношении Field objects, заключается в том, что когда он объявлен как public static , нам не нужен экземпляр класса, содержащего их, мы можем просто передать null на его место и все равно получить значение поля по умолчанию, например:
Одна важная вещь , которую следует отметить в отношении Field objects, заключается в том, что когда он объявлен как public static , нам не нужен экземпляр класса, содержащего их, мы можем просто передать null на его место и все равно получить значение поля по умолчанию, например:
@Test public void givenClassField_whenGetsAndSetsWithNull_thenCorrect(){ Class> birdClass = Class.forName("com.baeldung.reflection.Bird"); Field field = birdClass.getField("CATEGORY"); field.setAccessible(true); assertEquals("domestic", field.get(null)); }
Методы в классах
Теперь, когда мы разобрались, что такое методы, давайте создадим два метода — eat и run — в классе Pet.
Пусть первый из них принимает на вход параметр типа int и увеличивает на это значение поле weight (сколько скушал питомец, на столько и потолстел). А после этого печатает в консоль «Я поел» и возвращает новый вес.
Второй из методов run пусть уменьшает вес на 1, но только если он больше 5, и печатает в консоль: «Я бегу». Иначе, если вес меньше или равен 5: «Я не могу бежать».
Теперь мы можем вызвать эти методы у объектов класса Pet. Чтобы это сделать, нужно обратиться к объекту, поставить точку и таким способом вызвать необходимый метод.
Иногда в каком-то методе требуется создать параметр, у которого имя совпадает с именем поля класса. В таких случаях, чтобы обратиться внутри метода именно к полю класса, а не к параметру нашего метода, используется ключевое слово this.
Для иллюстрации этого создадим метод, setName, который будет устанавливать переданное значение в поле name, а затем сообщать в консоль, что нашего питомца теперь зовут по-другому.
В результате с помощью this.name мы обращаемся к полю name и заносим в него значение из параметра метода name.
Также мы можем вызывать один метод вслед за другим. Давайте сделаем так, чтобы метод eat возвращал текущее животное с помощью this.
Теперь мы можем написать так:
Область видимости переменных внутри класса
Внутри класса область видимости зависит от типа переменной:
- Статические переменные принадлежат самому классу. Они доступны без создания объекта, область их видимости содержит все методы класса — как статические, так и нет.
- Нестатические переменные класса принадлежат экземпляру класса (объекта). В их область видимости попадают нестатические методы класса.
Пример:
Разберём область видимости каждой из переменных:
- private static int totalFuelConsumption — статическая переменная используется в статическом методе getTotalFuelConsumption () и в нестатическом методе drive (int km). Область видимости здесь — весь класс.
- private int fuel = 86 — переменная нестатическая. А значит, она может использоваться только в конструкторах класса и его нестатических методах.
- private int consumption — переменная нестатическая. Значит, она может использоваться только в конструкторах класса и его нестатических методах.
Ключевое слово this
Если посмотреть на конструктор класса Car, то переменная, которую передают в аргументе конструктора, названа таким же именем, как и параметр класса. В этом случае области видимости переменной consumption класса и аргумента конструктора consumption пересекаются. По умолчанию используется переменная с более узкой областью видимости, то есть аргумент метода. Чтобы использовать переменную класса, необходимо на неё явно указать с помощью ключевого слова this.
Создаём каркас аннотации
Аннотации похожи на интерфейсы. Если вы пока не очень ориентируетесь в интерфейсах, освежите свои знания о них в этой статье, перед тем как мы продолжим.
Они не просто похожи на интерфейсы: под капотом JVM (виртуальная машина Java) преобразует элементы аннотации в методы интерфейса, а саму аннотацию — в имплементацию (реализацию) этого интерфейса.
Вот как выглядит объявление простейшей аннотации для отметки особо интересных каналов:
Здесь, кроме заголовка, нет ничего полезного. Такие аннотации ещё называют маркерными — они действительно просто маркируют, обозначают какой-то признак.
Как использовать:
Или так:
При использовании маркерных аннотаций можно опускать круглые скобки после названия.
В аннотациях могут быть:
- обязательные элементы,
- необязательные элементы,
- константы.
Привет, Мир JNI
Далее, давайте посмотрим, как JNI работает на практике.
В этом уроке мы будем использовать C++ в качестве родного языка и G++ в качестве компилятора и компоновщика.
Мы можем использовать любой другой компилятор по вашему выбору, но вот как установить G++ на Ubuntu, Windows и Mac OS:
- Ubuntu Linux – выполнить команду “sudo apt-get install build-essential” в терминале
- Windows – Установка MinGW
- macOS – запустите команду “g++” в терминале, и если ее еще нет, она установит ее.
3.1. Создание класса Java
Давайте начнем создавать нашу первую программу JNI с реализации классического “Hello World”.
Для начала мы создадим следующий класс Java, который включает в себя собственный метод, который будет выполнять эту работу:
package com.baeldung.jni; public class HelloWorldJNI { static { System.loadLibrary("native"); } public static void main(String[] args) { new HelloWorldJNI().sayHello(); } // Declare a native method sayHello() that receives no arguments and returns void private native void sayHello(); }
Как мы видим, мы загружаем общую библиотеку в статический блок . Это гарантирует, что он будет готов, когда он нам понадобится и откуда бы он нам ни понадобился.
В качестве альтернативы, в этой тривиальной программе мы могли бы вместо этого загрузить библиотеку непосредственно перед вызовом нашего собственного метода, потому что мы больше нигде не используем собственную библиотеку.
3.2. Реализация метода в C++
Теперь нам нужно создать реализацию нашего собственного метода в C++.
В C++ определение и реализация обычно хранятся в файлах .h и .cpp соответственно.
Во-первых, для создания определения метода мы должны использовать флаг -h компилятора Java :
javac -h . HelloWorldJNI.java
Это приведет к созданию файла com_baeldung_jni_HelloWorldJNI.h со всеми собственными методами, включенными в класс, переданными в качестве параметра, в данном случае только один:
JNIEXPORT void JNICALL Java_com_baeldung_jni_HelloWorldJNI_sayHello (JNIEnv *, jobject);
Как мы видим, имя функции автоматически генерируется с использованием полного имени пакета, класса и метода.
Кроме того, кое-что интересное, что мы можем заметить, заключается в том, что мы получаем два параметра, передаваемых нашей функции; указатель на текущий JNIEnv; , а также объект Java, к которому прикреплен метод, экземпляр нашего класса HelloWorldJNI .
Теперь нам нужно создать новый файл .cpp для реализации функции sayHello . Здесь мы будем выполнять действия, которые выводят “Hello World” на консоль.
Мы назовем наш файл .cpp тем же именем, что и файл .h, содержащий заголовок, и добавим этот код для реализации собственной функции:
JNIEXPORT void JNICALL Java_com_baeldung_jni_HelloWorldJNI_sayHello (JNIEnv* env, jobject thisObject) { std::cout << "Hello from C++ !!" << std::endl; }
3.3. Компиляция И Связывание
На данный момент у нас есть все части, которые нам нужны, и есть связь между ними.
Нам нужно создать нашу общую библиотеку из кода C++ и запустить ее!
Для этого мы должны использовать компилятор G++, не забывая включать заголовки JNI из нашей установки Java JDK .
Версия Ubuntu:
g++ -c -fPIC -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux com_baeldung_jni_HelloWorldJNI.cpp -o com_baeldung_jni_HelloWorldJNI.o
Версия для Windows:
g++ -c -I%JAVA_HOME%\include -I%JAVA_HOME%\include\win32 com_baeldung_jni_HelloWorldJNI.cpp -o com_baeldung_jni_HelloWorldJNI.o
Версия для macOS;
g++ -c -fPIC -I${JAVA_HOME}/include -I${JAVA_HOME}/include/darwin com_baeldung_jni_HelloWorldJNI.cpp -o com_baeldung_jni_HelloWorldJNI.o
Как только мы скомпилируем код для нашей платформы в файл com_baeldung_jni_HelloWorldJNI.o , мы должны включить его в новую общую библиотеку. Что бы мы ни решили назвать, это аргумент, переданный в метод System.LoadLibrary .
Мы назвали наш “родной”, и мы загрузим его при запуске нашего Java-кода.
Затем компоновщик G++ связывает объектные файлы C++ в нашу мостовую библиотеку.
Версия Ubuntu:
g++ -shared -fPIC -o libnative.so com_baeldung_jni_HelloWorldJNI.o -lc
Версия для Windows:
g++ -shared -o native.dll com_baeldung_jni_HelloWorldJNI.o -Wl,--add-stdcall-alias
Версия для macOS:
g++ -dynamiclib -o libnative.dylib com_baeldung_jni_HelloWorldJNI.o -lc
И это все!
Теперь мы можем запустить нашу программу из командной строки.
Однако нам нужно добавить полный путь к каталогу, содержащему только что созданную библиотеку. Таким образом, Java будет знать, где искать наши родные библиотеки:
java -cp . -Djava.library.path=/NATIVE_SHARED_LIB_FOLDER com.baeldung.jni.HelloWorldJNI
Вывод на консоль:
Hello from C++ !!
Code Style на практике
Теперь разберём основные правила, которые есть в Code Style, и как их использовать в ваших рабочих проектах.
1. Название файла должно совпадать с именем главного класса в нём
Классы в java располагаются в отдельных файлах расширения.java, и название файла строго должно соответствовать имени класса, включая регистр.
️ Неправильно: файл main.java
Правильно: файл называется как класс — Main.java
2. Названия классов и интерфейсов
Классы и интерфейсы именуются с большой буквы. Если имя состоит из нескольких слов, то каждое слово также начинается с большой буквы. Подчёркивания, тире не используются. Такой стиль нейминга называется UpperCamelCase.
️ Неправильно: phoneBook, full_name, main, result-list
Правильно: PhoneBook, FullName, Main, ResultList
Класс принято называть существительным: Car, Bird, ArrayList, Book. Возможно добавлять уточняющее прилагательное ImmutableList.
Интерфейс, как и класс, может быть существительным: List, Set, Map. А также прилагательным, если указывает на свойство: Readable, Сomparable, Closable.
Отдельно стоить отметить классы тестов, к ним принято добавлять слово Test в окончании: HashTest, HashIntegrationTest
3. Названия методов
Методы именуются со строчной (маленькой) буквы; если имя состоит из нескольких слов, то каждое последующее слово начинается с большой буквы. Подчёркивания, тире не используются. Такой стиль нейминга называется lowerCamelCase.
️ Неправильно: Print (), get-Size (), Main (), is_hidden ()
Правильно: print (), getSize (), main (), isHidden ()
Методы — это действия, поэтому их принято называть глаголами: sendMessage (), stop (), parse (), add ()
Отдельно можно отменить геттеры и сеттеры. Название метода геттера формируется из названия переменной и префикса get. Если переменная length, то геттер: getLength (). Если переменная типа boolean и названа isAlive, то геттер будет также называться isAlive (), являясь ответом на вопрос в названии метода.
Сеттеры формируются по такому же принципу, но с префиксом set. Продолжая примеры выше: для length сеттер будет setLength (). Для переменной типа boolean isAlive, сеттер будет без приставки setAlive ().
4. Названия переменных
Переменные именуются со строчной (маленькой) буквы; если имя состоит из нескольких слов, то каждое последующее слово начинается с большой буквы. Подчёркивания, тире не используются. Такой стиль нейминга называется lowerCamelCase.
Имена переменных в общем случае — это существительные либо составные существительные с добавлением прилагательных.
Данные правила относятся к локальным переменным (внутри методов), параметрам класса, а также к не статическим константам.
5. Названия констант
Константы именуются в стиле SNAKE_CASE, то есть слова разделены нижним подчёркиванием и все буквы прописные (большие)
Константами считаются static final поля, но не все. Поле должно быть примитивом, String или иммутабельным классом. То есть параметры класса константы не должны иметь возможности изменяться, а его методы — иметь побочные эффекты, влияющие на другие классы. Элементы Enum тоже являются константами.
Константы:
️ Не именуются как константы:
Параметры класса
Мы можем создавать поля класса, каждое из которых имеет свой тип.
Поле класса — это переменная, которая описывает какое-либо из свойств данного класса.
Для наших домашних питомцев и полями класса будут вес, кличка и принадлежность к определённому типу (коровы, гуси, собаки и так далее). Очевидно, что здесь вес — это числовая переменная, а кличка и тип — строки символов. Тогда мы можем написать:
Переменные weight, name и type — поля нашего класса Pet, то есть свойства, которые описывают объект этого класса. Таких полей может быть сколько угодно, каждое имеет свой тип, как обычная переменная.
Мы уже пару раз упомянули словосочетание «объект класса». Так говорят, потому что любой объект является экземпляром какого-либо класса. Здесь действует простая аналогия: класс — это как бы чертёж, который описывает объект, его устройство, а объект — реализация чертежа, его материальное воплощение.
Давайте запрограммируем первый объект класса Pet. Пусть это будет кот (type) с кличкой (name) Барсик и весом (weight) 10 (измерение в килограммах).
Сперва необходимо создать переменную типа Pet:
Наш объект pet выглядит как обычная переменная, но в качестве типа указан класс Pet, и в данный момент в нём ничего нет. Инициализируем объект — воспользуемся такой синтаксической конструкцией:
Мы ставим знак равно, пишем ключевое слово new, имя нашего класса и круглые скобки. Принято говорить, что здесь мы вызываем конструктор класса Pet. Пока просто запомним это — о конструкторах и о том, как их использовать, будет рассказано в отдельной статье.
Теперь у нас появилась переменная pet типа Pet, в которой содержится объект класса Pet. Ранее в этом классе мы объявили поля, к которым можно обратиться и занести в них значения.
Чтобы получить доступ к какому-либо полю нашего класса Pet, нужно специальным образом обратиться к переменной pet — поставить точку и вызвать необходимое поле. Например, вот так:
Теперь во всех трёх полях есть по значению, а мы можем получить их из программы, если потребуется, — например, распечатать в консоль:
Изменить значение в любом из полей класса также несложно. Пусть наш кот Барсик слегка потолстеет — добавим к его весу 1 кг:
Как видим, мы просто изменили вес в поле weight, а при выводе получили уже другое значение.
Подключение правил в IDE
Соглашения по стилю кода Java различны, а, кроме того, разработчики могут использовать свои модификации. Чтобы у всей команды был один стиль кода, его можно задать в среде разработке.
Один из популярных наборов правил по оформлению кода для Java написала компания Google. Он называется Google Java Style Guide и доступен на GitHub. Вы можете без труда установить его в любимую IDE, с которой работаете. Для примера рассмотрим, как это сделать в среде разработки JetBrains IntelliJ IDEA:
- Скачайте файл intellij-java-google-style.xml c правилами форматирования с GitHub.
- Откройте IntelliJ IDEA, зайдите в настройки File → Settings… (Ctrl + Alt + S) и выберите загрузку файла в пункте Scheme, как на скриншоте:
В диалоговом меню выберите скачанный файл и в поле Scheme выберите GoogleStyle. Затем сохраните изменения, нажав Apply/OK.
Теперь при форматировании будут использоваться правила Java Google Style.
Что не так с дженерик-типами классов-наследников
В Java можно присвоить объект одного типа объекту другого типа, если типы совместимы: реализуют один и тот же интерфейс или лежат в одной цепочке наследования.
Например, PaperBox — наследник Box, и пример ниже успешно компилируется:
В терминах объектно-ориентированного программирования это называют отношением is a (является): бумажная коробка — это коробка (является коробкой). Или говорят, что PaperBox — это подтип (subtype) Box. При этом Box — супертип PaperBox.
Теперь возьмём не простую коробку, а её дженерик-вариант (Box<T>), в которую будем класть разные типы мусора: Paper, Glass и тому подобные типы — наследники Garbage:
В этом случае в качестве аргумента типа можно выбрать как Garbage, так и его подтип:
Но что, если Box<Garbage> станет типом параметра метода? Сможем ли мы в этом случае передать другой дженерик-тип? Напишем простой пример:
И убедимся, что замена тут не пройдёт. Несмотря на то что Paper — подтип Garbage, Box<Paper> — не подтип Box<Garbage>.
Дженерики инвариантны. Это означает, что, даже если A — подтип B, дженерик от A не является подтипом дженерика от B.
Для сравнения, массивы в Java ковариантны: если A — подтип B, A[] — подтип B[].
Когда следует использовать абстрактные классы
Теперь, давайте проанализируем несколько типичных сценариев, в которых мы должны предпочесть абстрактные классы интерфейсам и конкретным классам:
- Мы хотим инкапсулировать некоторые общие функции в одном месте (повторное использование кода), которые будут совместно использоваться несколькими связанными подклассами
- Нам нужно частично определить API, который наши подклассы могут легко расширить и усовершенствовать
- Подклассы должны наследовать один или несколько общих методов или полей с модификаторами защищенного доступа
Давайте иметь в виду, что все эти сценарии являются хорошими примерами полного, основанного на наследовании соблюдения принципа Open/Closed .
Более того, поскольку использование абстрактных классов неявно имеет дело с базовыми типами и подтипами, мы также используем преимущества полиморфизма .
Обратите внимание, что повторное использование кода является очень веской причиной для использования абстрактных классов, пока сохраняется связь “is-a” в иерархии классов. И Java 8 добавляет еще одну морщину с методами по умолчанию , что иногда может заменить необходимость создания абстрактного класса в целом
И Java 8 добавляет еще одну морщину с методами по умолчанию , что иногда может заменить необходимость создания абстрактного класса в целом.