Классы
Именование
Правилами языка, именование ограничено следующим образом:
- Только буквы и цифры, “_” и “$”;
- Первым символом не может быть цифра;
- Не может быть ключевое слово (включая
var
); - Не может состоять только из “_“.
Модификаторы доступа
private
- закрытый доступ;default
- не указывается. Доступ будет ограничен только для классов в рамках пакета;protected
- default
+ доступен для дочерних классов;public
- публичный класс.
Модификатор strictfp
используется для обозначения ограничения точности вычислений с float
и double
по стандарту IEEE, для обеспечения переносимости (портативности) приложения. Используется для научных целей - когда требуется высокая точность вычислений на любой архитектуре или ОС. В современной JVM указывается по умолчанию для всех методов.
Модификатор protected
Данный модификатор очень коварен и работает не всегда очевидно.
Главное, что для него следует запомнить - доступ предоставляется только в рамках тех классов и сущностей, которые наследуются или являются классами, объявившими метод.
Для внешних пакетов следует рассматривать protected как private (т.е. даже для наследников родительский метод виден не будет если сущность, к которой обращаются, является родительской):
1 | package x; |
Пример выше актуален только если класс B
будет в ином пакете, нежели A
. Если в том же пакете, все print
будут видны.
static
Доступ к static
переменной есть даже у null
объекта - Java обращается не к переменной объекта, а к классу:
1 | Integer x = 1; |
Перегрузка метода
Метод считается перегруженным, если есть другой метод с отличной сигнатурой, но с тем же именем.
Перегрузке может быть подвержено все что угодно: список аргументов, результат, список исключений, область видимости. Ограничением, правда, служит результат (return
), который отличается от оригинала, но при этом, не отличается список атрибутов:
1 | public void meth(int a) {} |
И даже при изменении на static
:
1 | public static String meth(int a) {} // ошибка |
В случае передачи varargs
компилятор так же найдет ошибку перегрузки:
1 | public void meth(int[] x); |
varargs
компилируется как массив соответствующего типа. Т.е. в первом случае будет дубликат метода, а во втором - ошибка перегрузки (отличный return
).
В случае наличия двух методов с классом-оберткой и примитивом - Java пытается использовать примитив если он был передан:
1 | public int meth(int x); |
Наследование
Основные правила наследования:
- Доступы к методам и свойствам только на расширение (
private
->protected
,protected
->public
); - Типизация только на детализацию типов (
CharSequence
->String
,Number
->Float
,IOException
->FileNotFoundException
).
Наследование с generic сложнее - хотя Java на этапе компиляции отбрасывает типизацию generic и добавляет ее в виде преобразования типов в runtime - в определении методов, для @Override
необходимо указывать родительский тип generic. Т.е. нельзя сделать так:
1 | class A { |
При этом, получается что на этапе компиляции это 2 одинаковых метода с аргументом List
, что не должно выдавать ошибку если бы отсутствовал generic. Так, например:
1 | public void x(CharSequence x) {} |
В данном случае, сигнатура будет рассматриваться как перегрузка метода.
Для generic допускается уточнение аргумента, т.е.:
1 | public void x(List<String> x) {} |
Допускается, но методы будут рассматриваться как перегруженные.
На возвращаемые типы действуют аналогичные правила: уточнение типа допускается, а уточнение generic - нет:
1 | public List<CharSequence> x() {} |
Private наследование
Поскольку методы private
видны только в рамках класса, наследование таких методов запрещено - они просто не видны. При этом, создание дочерними классами таких же методов разрешено (даже с той же сигнатурой) - это будут абсолютно независимые методы!
Static наследование
Для static
существует псевдо-наследование, при котором вызов метода допускает вызов статического метода с тем же именем и сигнатурой:
1 | class A { |
При вызове метода с четким указанием класса:
1 | A.x(); |
Но если у класса B
убрать определение static
метода x
, то:
1 | b.x(); // вызов A.x() |
Final наследование
Ключевое слово final запрещает @Override для методов, в т.ч. и static:
1 | class A { |
Наследование и вызов static
В коде может быть неочевидная уловка:
1 | class X { |
Необходимо следить за маркером static
и источником вызова. Для static
- то что его вызывает (какой тип), тот метод и будет вызван!
Конвертация между типами
Компилятор проверяет возможность конвертации и в случае, если никакой связи между классами нет (т.е. не обнаружено наследование), то конвертация выдаст ошибку на этапе компиляции:
1 | class X {} |
При этом, прямого наследования в классовой иерархии нет - это и влечет ошибку. Однако использование промежуточных типов решает проблему (как в примере выше).
1 | interface Z {} |
Inner классы
- может быть
public
,protected
,package-private
,private
; - может быть наследуемым или сам расширять другие типы;
- может быть
abstract
илиfinal
; - имеет доступ ко всем методам и свойствам объекта, в класс которых вписан (в том числе, и к
private
).
Создание может быть выполнено и вне класса:
1 | My.InnerClass inner = new My.InnerClass(); |
При этом, класс InnerClass
должен быть объявлен как static
.
Так же допускается создание с использованием объекта:
1 | My my = new My(); |
При этом, классу достаточно быть видимым из места, где выполняется его создание. В данном случае, подобная запись равноценна созданию сущности в методе в рамках класса My
и обращение к этому методу.
Вложенный класс и родительский, могут содержать переменную с одинаковым именем. В этом случае доступ к ним можно осуществить так:
1 | public class A{ |
Для инициализации сущности потребуется объект родительского класса.
Static-inner класс
Позволяет создавать сущности вложенных классов без родительских. Поддерживает импорт посредством static
:
1 | import static my.pack.My.InnerClass; |
С последующим обращением к классу по имени - InnerClass
.
Local классы
- не имеют модификаторов;
- не могут содержать
static
полей и методов, кромеstatic final
; - доступны все поля и методы родительского класса;
- доступно использование внешних переменных (в т.ч. так же локальных), но только если они имею модификатор
final
или являются effective final:
1 | class A { |
Anonymous классы
Почти то же самое, что и local классы, но не имеют имени и используются как разовое решение:
1 | class A { |
И local и anonymous объекты могут использоваться для возврата из метода (return
). Хотя, если класс не будет виден родительскому классу для метода, типом возврата может стать только Object
.
1 | class A { |
Anonymous классы могут быть реализацией интерфейса, но только одного. Плюсом таких классов является использование в любом месте кода, без необходимости дополнительных определений.
И для local и для anonymous справедливо то, что они могут создаваться и вне метода, например, как часть определения переменной класса:
1 | public class X { |
Enum
Все конструкторы объектов должны быть private
(либо не указаны вовсе). Модификаторы доступа public
и protected
запрещены для enum
.
Инициализация enum
выполняется только в случае обращения к ним и только 1 раз. Причем, инициализация проводится сразу для всех значений. А не только для того, к которому обратились:
1 | enum Test { |
Вывод:
1 | ----------1 |
Допускается регистрация абстрактных методов:
1 | enum Test { |