Блок Примитивные типы
Скачать 6.67 Mb.
|
часть автомобиля. Программируя эту модель, Вы выразите это в том, что объект класса Engine станет членом класса Auto. Композиции обычно противопоставляют агрегацию и ассоциацию. Агрегация похожа на композицию в том, что это тоже отношение вида часть-целое, однако между объектами нет отношения владения. Например, "группа студентов" - студент часть группы, но студент может существовать и вне группы. То есть деструктор группы не должен "убивать" объектов-студентов. Ассоциация выражает любое отношение между объектами, которые могут вызывать методы друг друга. То есть делегирование можно устроить имея как отношение ассоциации, так и отношение композиции между объектами. Еще композиции противопоставляют наследование. Но в этом случае композиция и делегирование - синонимы. В случае с наследованием мы выносим общие методы в базовый класс Base, а различные реализации в классы-наследники Derived1, Derived2. Создавая экземпляр одного из Derived-классов, мы получаем необходимую функциональность объекта. Но то же самое можно сделать и по-другому. В классе Base мы по-прежнему оставляем общие методы, и добавляем ссылку (указатель) (то есть аггрегируем указатель на реализацию) на интерфейс вспомогательных методов IRealization. Вспомогательные методы по-разному реализуются в классах Realization1, Realization2, которые наследуют* интерфейс IRealization. Теперь в зависимости от того, на объект какого из классов Realization будет указывать объект Base, мы будем получать разную функциональность. Base делегирует свои обязаности в IRealization. Достоинства контроль видимости; реализация может быть заменена во время выполнения (run-time); слабая связанность, так как класс-интерфейс не зависит от реализации. Что такое агрегация? Агрегация (aggregation; «has-a» — есть, имеет, содержит) применяется когда один класс должен быть контейнером для других классов. Причем время существования содержащихся в нем классов (полей) никак не зависит от времени существования класса контейнера. Агрегация — отношение «часть-целое» между двумя равноправными объектами, когда один объект (контейнер) имеет ссылку на другой объект. Оба объекта могут существовать независимо: если контейнер будет уничтожен, то его содержимое — нет. Композиция — еще более жесткое отношение, когда объект не только является частью другого объекта, но и вообще не может принадлежать еще кому-то. Когда объект уничтожается, объекты, составляющие его, также уничтожаются. Данный вид связи на UML-диаграмме обозначается в виде линии с закрашенным ромбиком (ромбик всегда находится со стороны целого, а простая линия со стороны составной части). Разница между агрегацией и композицией разница между композицией и агрегацией заключается в том, что в случае композиции целое явно контролирует время жизни своей составной части (часть не существует без целого), а в случае агрегации целое хоть и содержит свою составную часть, время их жизни не связано. различие между этими двумя видами ассоциации состоит в том, что композиция может быть частью одного и только одного целого, в то время как агрегация может быть частью нескольких объектов. Простыми словами: Если один объект состоит из других объектов (поля класса имеют ссылочный тип), которые создаются внутри этого объекта-контейнера и при этом время жизни "частей" зависит от времени жизни целого (умирает контейнер — погибают и его составляющий), то это называется композицией. Но если после смерти объекта-контейнера его части остаются жить (не удаляются GC, тк на них ссылаются в другом месте программы), то это уже агрегация. Если еще проще, то вся разница между агрегацией и композицией состоит в том, каким образом инициализируются поля в классе-контейнере и как эта инициализация влияет на время их жизни после смерти основного класса. С точки зрения программирования - это выглядит так: если в классе-контейнере в теле конструктора (без передачи аргумента) или прямо в поле взять и присвоить ссылку на объект, то логично предположить, что со смертью основного класса умрут и все его внутренние объекты т.е. тут жесткая взаимозависимость — это композиция. А, если создать объект, а потом передал его в качестве аргумента в сеттер или в конструктор, а потом присвоить его полю, то со смертью класса — все созданные таким образом объекты будут жить и дальше — это агрегация. Что такое коллизия? Как известно, ситуация, когда у разных объектов одинаковые хеш-коды называется — коллизией. Вероятность возникновения коллизии зависит от используемого алгоритма генерации хеш-кода. Ковариантность типа возвращаемого значения Ковариантный тип возвращаемого значения – это то, что позволяет типу возвращаемого значения быть подтипом типа переопределенного метода . Чтобы применить это на практике, давайте возьмем простой класс Producer с методом product () . По умолчанию он возвращает Строку в качестве Объекта , чтобы обеспечить гибкость для дочерних классов В результате Object в качестве возвращаемого типа мы можем иметь более конкретный тип возвращаемого значения в дочернем классе. Это будет ковариантный тип возврата и будет производить числа из последовательностей символов Класс Object Класс Object является неявным классом-предком для всех классов в Java. Таким образом, все классы наследуют от Object определенный набор методов, среди которых: equals(), который производит сравнение текущего экземпляра с другим объектом; hashCode(), который возвращает хэш объекта - то есть некоторое число, которое зависит от типа объекта и его внутреннего состояния; toString(), который возвращает текстовое представление объекта; getClass(), который возвращает описание того класса, к которому принадлежит экземпляр; clone(), который создает и возвращает копию объекта; notify(), notifyAll(), wait(), которые связаны с потоками. Класс Class Для всех классов во время компиляции создается специальный класс Class. Получить экземпляр типа Class можно двумя способами: вызвав метод getClass() у любого объекта или обратившись к классу через конструкцию ИмяКласса.class. Описание класса содержит в себе всю информацию о классе: его имя, набор переменных, набор методов, внутренних классов и тд. Описание класса можно использовать в технологии Reflection, которая открывает большие возможности по динамическому изменению программы прямо во время работы. Reflection - это механизм манипулирования объектами в обход стандартных механизмов в runtime. С помощью Reflection можно получить полную информацию о внутреннем устройстве класса; узнать информацию, хранящуюся в объекте и изменить ее (даже приватную и защищенную); создать объект неизвестного на момент компиляции класса. Основными минусами технологии являются низкая скорость и грубое нарушение принципа инкапсуляции. InstanceOf vs getClass instanceof проверяет, является ли ссылка на объект с левой стороны (LHS) экземпляром типа с правой стороны (RHS) или некоторым подтипом. getClass() == ... проверяет, идентичны ли типы. Таким образом, метод instanceof будет работать медленнее, так как проделывает больший объем работы. Модификаторы доступа Существует 4 типа модификатора доступа. В порядке уменьшения закрытости: private (область видимости - только внутри класса); default (без модификатора) (область видимости - только внутри своего пакета); protected (область видимости - внутри своего пакета и внутри классов наследников); public (полная видимость). Ключевое слово static В языке программирования Java ключевым словом static помечают члены (поля или методы), которые принадлежат классу, а не экземпляру этого класса. Это означает, что какое бы количество объектов вы не создали, всегда будет создан только один член, доступный для использования всеми экземплярами класса. Ключевое слово static применимо к переменным, методам, блокам инициализации, импорту и вложенным классам (nested classes). В языке Java, если поле объявляется статическим (путем добавления модификатора static), то в независимости от количества созданных объектов класса — всегда будет существовать только один экземпляр статического поля. Значение такого поля будет единым и общим для всех объектов класса, содержащих это поле. С точки зрения используемой памяти, статические переменные размещаются в специальном пуле в памяти JVM, называемом Metaspace (до Java 8 он назывался Permanent Generation или PermGen, который был полностью удален и заменен на Metaspace). Причины использования статических полей: Когда значение поля должно быть общим для всех объектов класса, в котором оно определено Когда значение поля не зависит от наличия объектов класса, в котором оно определено Когда значение поля может быть изменено из любого объекта класса, в котором оно определено Ключевые моменты: Статические переменные могут быть созданы только, как переменные класса. Они не могут быть локальными переменными (IDEA выдаст ошибку Modifier 'static' not allowed here или java: illegal start of expression) К статическим полям класса можно получить доступ без создания объекта, используя имя класса (ссылка на объект не нужна) Несмотря на то, что получить доступ к статическим полям можно с помощью ссылки на объект (например, car1.numberOfCars), мы должны воздерживаться от её применения, поскольку в этом случае становится не совсем понятно, является ли эта переменная переменной экземпляра или же переменной класса. Вместо этого всегда необходимо ссылаться на статические переменные, используя имя класса (например, Car.numberOfCars) Подобно статическим полям, статические методы также принадлежат классу, а не объекту, поэтому их можно вызывать без создания экземпляра класса, в котором они находятся. При этом следует помнить, что из статического метода можно получить доступ только к статическим переменным или к другим статическим методам. В версии Java 8 появилась возможность определения статических методов в интерфейсах. Их поведение напоминает поведение методов по умолчанию (default methods), но есть существенные отличия – они не могут быть переопределены методами в реализующих интерфейс классах. Такая особенность позволяет избежать нежелательных результатов, которые возможны в случае некорректной реализации метода в классе. Обратите внимание, что для использования статических методов интерфейса не нужно использовать ключевое слово «implements». Достаточно (и это необходимо) использовать имя интерфейса вместе с именем статического метода. В других случаях, статические методы интерфейса видны только методам этого интерфейса. Причины использования статических методов: Для доступа / управления статическими переменными и другими статическими методами, которые не зависят от объектов Для служебных, вспомогательных классов и интерфейсов, поскольку не требуют создания объектов и соответственно, обеспечивают большую производительность Когда методу требуется доступ лишь к статическим полям класса Ключевые моменты: Статические методы в Java вызываются во время компиляции. Поскольку переопределение метода является частью полиморфизма во время выполнения (Runtime Polymorphism), статические методы не могут быть переопределены. Это справедливо также для статических методов интерфейса Абстрактные методы не могут быть статическими Статические методы не могут использовать ключевые слова this или super Методы экземпляра могут обращаться непосредственно как к методам экземпляра, так и к переменным экземпляра Методы экземпляра также могут непосредственно обращаться к статическим переменным и статическим методам Статические методы могут обращаться ко всем статическим переменным и другим статическим методам Статические методы не могут напрямую обращаться к переменным экземпляра и методам экземпляра. Для этого им нужна ссылка на объект Статические поля и методы не являются потокобезопасными. Т.к. каждый экземпляр класса имеет одну и ту же копию статической переменной, такая переменная нуждается в защите от одновременного обращения к ней нескольких потоков. Для этого статическая переменная должна быть синхронизирована Статические методы связываются во время компиляции, в отличие от не статических методов, которые связываются во время исполнения. Из-за этого статические методы не могут быть переопределены, т.к. полиморфизм во время выполнения не распространяется на них. Если объявить в классе-наследнике метод с таким же именем и сигнатурой, то он лишь перекроет (hiding methods) метод из суперкласса вместо его переопределения. При обращении к статическому методу, который объявлен как в родительском, так и в дочернем классе, во время компиляции всегда будет вызываться метод исходя из типа переменной. В этом есть смысл, только тогда, когда нет возможности или необходимости переопределения такого метода классами-наследниками. В Java 1.5 появилась реализация импорта статических переменных и статических методов класса, что позволяет обращаться к статическим членам класса непосредственно по имени члена, без дополнительного указания имени класса и пакета. Основная цель статического импорта — улучшение читабельности кода программы благодаря устранению постоянного повторения имени класса. Реализуется статический импорт на основе декларации static import, с последующим указанием импортируемого класса. Например так: import static java.util.Arrays.asList; Обратите внимание, что ключевые слова import и static в данном контексте не относятся к модификаторам доступа, поэтому, правило, что «модификаторы доступа могут следовать в любом порядке» здесь не работает. Язык программирования Java позволяет создавать классы внутри другого класса. Такой класс называется вложенным (nested). Вложенный класс группирует элементы, которые будут использоваться в одном месте, сделав тем сам код более организованным и читабельным. Вложенные классы бывают двух видов: вложенные классы, объявленные статическими, называются статическими вложенными классами (static nested classes) вложенные классы, объявленные без static, называются внутренними классами (inner classes) Основное различие между этими понятиями состоит в том, что внутренние классы имеют доступ ко всем членам включающего их класса (включая приватные) верхнего уровня, тогда как статические вложенные классы имеют доступ только к статическим членам внешнего класса. Причины использования статического внутреннего класса: Если какой-то класс используются только в одном другом классе, то их можно сгруппировать, поместив в один общий класс. Это усиливает инкапсуляцию Если вложенный класс не требует какого-либо доступа к членам экземпляра его класса, то лучше объявить его как статический, потому, что таким образом он не будет связан с внешним классом и, следовательно, будет более оптимальным, поскольку ему не потребуется память в куче или в стеке Ключевые моменты: Статические вложенные классы не имеют доступа к какому-либо члену экземпляра внешнего класса — он может получить к ним доступ только через ссылку на объект Статические вложенные классы могут получить доступ ко всем статическим членам внешнего класса, включая приватные Спецификация Java не позволяет объявлять класс верхнего уровня статическим. Только классы внутри других классов могут быть статическими Опять же, этот класс привязан к внешнему классу и если внешний наследуется другим классом, то этот не будет унаследован. При этом данный класс можно наследовать, как и он может наследоваться от любого другого класса и имплементировать интерфейс По сути статический вложенный класс ничем не отличается от любого другого внутреннего класса за исключением того, что его объект не содержит ссылку на создавший его объект внешнего класса Для использования статических методов/переменных/классов нам не нужно создавать объект данного класса Яркий пример вложенного статического класса — HashMap.Entry, который предоставляет структуру данных внутри HashMap. Стоит заметить, также как и любой другой внутренний класс, вложенные классы находятся в отдельном файле .class. Таким образом, если вы объявили пять вложенных классов в вашем главном классе, у вас будет 6 файлов с расширением .class Говоря о ключевом слове static, нельзя не упомянуть о его применении в определении констант — переменных, которые никогда не изменяются. В языке Java существует зарезервированное слово «const», но оно не используется, и Java не поддерживает константы на уровне языка. Выход из ситуации имеется: для определения константы необходимо добавить модификаторы «static final» к полю класса. Константы — это статические финальные поля, содержимое которых неизменно. Это относится к примитивам, String, неизменяемым типам и неизменяемым коллекциям неизменяемых типов. Если состояние объекта может измениться, он не является константой. Модификатор static делает переменную доступной без создания экземпляра класса, а final делает ее неизменяемой. При этом нужно помнить, что если мы сделаем переменную только static, то ее легко можно будет изменить, обратившись к ней через имя класса. Если переменная будет иметь только модификатор final, то при создании каждого экземпляра класса она может быть проинициализирована своим значением. Соответственно, используя совместно модификаторы static и final, переменная остается статической и может быть проинициализирована только один раз. В Java константой считается не та переменная, которую нельзя изменить в рамках одного объекта, а та, которую не могут изменить ни один экземпляр класса в котором она находится (такая переменная создается и инициализируется один раз для всех экземпляров, сколько бы их не было). Методы экземпляра также могут непосредственно обращаться к статическим переменным и статическим методам. Но статические методы не могут напрямую обращаться к переменным экземпляра и методам экземпляра. Для этого им нужна ссылка на объект. Это связано с тем, что обращаясь напрямую к нестатическому полю или методу класса, мы обращаемся к полю или методу, которое инициализируется лишь при создании объекта. Поэтому нельзя обращаться к неинициализированной переменной или к методу, который реализуется лишь при вызове его объектом. Сигнатура метода - это имя метода плюс параметры (причем порядок параметров имеет значение). Переопределение метода (overload). Когда подкласс предоставляет реализацию метода, который уже находится в суперклассе, это называется переопределением. Перегрузка метода (override). Перегрузка - это возможность создавать несколько методов с одним и тем же именем с разными реализациями. Сходство: Оба являются типами полиморфизма. При перегрузке и переопределении методы имеют одно и то же имя. Различия:
При переопределении метода сужать модификатор доступа не разрешается, т.к. это приведёт к нарушению принципа подстановки Барбары Лисков. Расширение уровня доступа возможно. Можно изменять все, что не мешает компилятору понять какой метод родительского класса имеется в виду: Изменять тип возвращаемого значения при переопределении метода разрешено только в сторону сужения типа (вместо родительского класса - наследника). При изменении типа, количества, порядка следования аргументов вместо переопределения будет происходить overloading (перегрузка) метода. Секцию throws метода можно не указывать, но стоит помнить, что она остаётся действительной, если уже определена у метода родительского класса. Так же, возможно добавлять новые исключения, являющиеся наследниками от уже объявленных или исключения RuntimeException. Порядок следования таких элементов при переопределении значения не имеет. Аннотация Override На самом деле аннотация @Override указывает, что далее мы собираемся переопределять метод базового класса. При этом, если в базовом классе не окажется метода с аналогичной сигнатурой, то мы получим предупреждение компилятора о том, что хотя мы и собирались что-то переопределить, по факту этого не произошло. Может ли статический метод быть перегружен? Перегружен - да. Всё работает точно так же, как и с обычными методами - 2 статических метода могут иметь одинаковое имя, если количество их параметров или типов различается. Переопределён - нет. Выбор вызываемого статического метода происходит при раннем связывании (на этапе компиляции, а не выполнения) и выполняться всегда будет родительский метод, хотя синтаксически переопределение статического метода - это вполне корректная языковая конструкция. В целом, к статическим полям и методам рекомендуется обращаться через имя класса, а не объект. Ключевое слово final final при использовании с методом он ограничивает наследующий класс от переопределения определения метода/ final при использовании для класса в качестве модификатора он ограничивает расширение или наследование класса любым другим классом. Метод equals() Метод equals() является методом класса Object и его реализация выглядит следующим образом:
Несмотря на простоту реализации этого метода, он выполняет свой контракт, который гарантирует выполнения следующих условий: Рефлексивность. x.equals(x) всегда возвращает true Симметричность. x.equals(y) обязан вернуть true, если y.equals(x) возвращает true Транзитивность. Если x.equals(y) возвращает true, y.equals(z) возвращает true, тогда x.equals(z) должен вернуть true Консистентность. x.equals(y) при множественном вызове метода всегда возвращает неизменно true или false, если объекты между вызовами не изменялись. Условие NULL. Вызов x.equals(null) всегда должен возвращать false. Дополнительным условием для объектов x,y и z является их неравенство null, потому что если осуществить вызов null.equals(x) мы получим исключение NullPointerException. Реализация equals() класса Object гарантирует выполнения контракта. Лучшим способом его не сломать является отказ от переопределения метода equals() в вашем классе. Но все таки, время от времени нам необходимо это делать. |