Java для собеседований5 читателей тэги

Автор: Widowmaker1984

Тематический блог, где я хочу пройтись по стандартным вопросам, которые задают при собеседованиях на вакансию Java-разработчика. В большинстве своем это пересказ и/или компиляция нескольких общедоступных источников. Пишется главным образом для себя и поэтому в объеме который кажется интересным лично мне. С другой стороны, если вам данный блог пригодится, буду только рад. Вопросы и уточнения в комментариях только приветствуются.

Вопросы 45, 47, 48, 53 (инициализация, ее блоки и порядок)

"Где можно инициаилизировать статические/нестатические поля?"
"Зачем нужны и какие бывают блоки инициализации?"
"Каков порядок вызова конструкторов и блоков инициализации двух классов: потомка и его предка?"
"Что будет, если в static блоке кода возникнет исключительная ситуация?"


1. Блок инициализации (initialization block) — последовательность команд, выполняемых при создании (загрузке) классов и объектов. Существуют два типа: статический блок инициализации, обычно называемый для краткости статический блок (static block), и динамический блок инициализации (instance block).

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

2. Статический блок — это, в сущности, конструктор для всего класса. Его синтаксис:

static {
// Static block code
}

скрытый текст2.1. Команды в статическом блоке будут выполняется при первой загрузке класса. То есть в одном из двух случаев (том, что наступит раньше):
- При создании первого объекта класса в процессе работы программы, перед запуском конструктора.
- При первом вызове статической функции, перед выполнением.

2.2. Например, нужно создать класс, моделирующий автомобили, произведенные конкретной компанией. Каждый объект — это автомобиль, а в классе есть инициализируемое в статическом блоке статическое поле, которое содержит базу данных всех автомобилей.

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


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

3. Динамический блок представляет собой дополнение к конструкторам. Его синтаксис:
{
// Instance block code
}

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

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

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

4. При разработке языка Java был установлен постоянный порядок действий при загрузке.

4.1. Во время загрузки класса порядок выглядит следующим образом:

- Определения статических полей родительских классов.
- Инициализация статических полей и выполнение статических блоков родительских классов.
- Определения статических полей класса.
- Инициализация статических полей и выполнение статических блоков класса.

4.2. Затем, при создании объекта, порядок выглядит следующим образом:

- Определения полей объекта из родительских классов.
- Инициализация полей и выполнение динамических блоков из родительских классов.
- Выполнение конструкторов из родительских классов.
- Определения полей объекта из его класса.
- Инициализация полей и выполнение динамических блоков из его класса.
- Выполнение конструктора из его класса.

4.3. Итого порядок инициализации таков:

- Статические элементы родителя.
- Статические элементы наследника.
- Поля родителя.
- Конструктор родителя.
- Поля наследника.
- Конструктор наследника.

5. Статические поля можно инициализировать:
- при объявлении;
- в статическом блоке инициализации.

6. Нестатические поля можно инициализировать:
- при объявлении;
- в динамическом блоке инициализации;
- в конструкторе.

Вопросы 43, 46 (преобразования ссылочных типов)

"Какие преобразования называются нисходящими и восходящими?"
"Зачем нужен оператор instanceof?"

1. Приведение типов (преобразование типов) — преобразование значения переменной одного типа в значение другого типа.

1.1. Преобразование от от подкласса внизу к суперклассу вверху иерархии называется восходящим (upcasting). Восходящее преобразование всегда безопасно, так как это переход от конкретного типа к более общему типу. Такое преобразование осуществляется автоматически (потому что объект подкласса также представляет собою объект суперкласса).

Object tom = new Person("Tom");

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

// нисходящее преобразование от Object к типу Employee
Employee emp = (Employee)sam;

1.2.1. Но при попытке преобразования к типу Employee мы можем получить ошибку при выполнении, если переменная sam — это не ссылка не объект Employee . Нередко данные приходят извне, и мы можем точно не знать, какой именно объект они представляют. Соответственно, возникает большая вероятная столкнуться с ошибкой. И перед тем, как провести преобразование типов, мы можем проверить, возможно ли приведение с помощью оператора instanceof.

2. Выражение sam instanceof Employee проверяет, является ли переменная sam объектом типа Employee. Общая форма: [CсылкаНаОбъект] instanceof [Тип].

2.1. Здесь CсылкаНаОбъект обозначает ссылку на экземпляр класса, а Тип- конкретный тип этого класса. Если ссылка_на_объект относится к указанному типу или может быть приведена к нему (по сути является ли объект экземпляром указанного класса или его потомком), то вычисление оператора instanceof дает в итоге логическое значение true, иначе - логическое значение false.

2.2. Большинство программ не нуждается в операторе instanceof, поскольку типы объектов обычно известны заранее. Но этот оператор может пригодиться при разработке обобщенных процедур, оперирующих объектами из сложной иерархии классов. Хотя обычно там, где код должен работать с параметрами различного типа, принято использовать дженерики.

Вопрос 40 (аргументы переменной длины)

"Может ли метод принимать разное количество параметров (аргументы переменной длины)?"

1. Начиная с Java 5 и выше можно передавать методу переменное количество аргументов одного типа. Параметр в методе объявляется следующим образом:
[ИмяТипа]... [ИмяПараметра]

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

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

3. При перегрузке метода, принимающего аргумент переменной длины, могут случаться непредвиденные ошибки. Они связаны с неопределенностью, которая может возникать при вызове перегруженного метода с аргументом переменной длины.

3.1. Например, следующие перегруженные версии метода vaTest () изначально неоднозначны, несмотря на то, что одна из них принимает обычный параметр (т.е. отличается по сигнатуре):

static void vaTest(int ... v) {
// ...
static void vaTest(int n, int ... v) {
//...
}

Вопросы 35-39, 41, 42, 44 (методы, их переопределение и перегрузка)

"Дайте определение понятию «метод»."
"Что такое сигнатура метода?"
"Какие методы называются перегруженными?"
"Могут ли нестатические методы перегрузить статические?"
"Расскажите о переопределение методов."
"Можно ли сузить уровень доступа/тип возвращаемого значения при переопределении метода?"
"Как получить доступ к переопределенным методам родительского класса?"
"Чем отличается переопределение от перегрузки?"


1. Метод в Java — это законченная последовательность действий (инструкций), направленных на решение отдельной задачи, которая вызывается по определенному имени. По сути, это аналог функций и процедур из более ранних (не ООП) языков. Только эти функции являются членами классов и для различия с обычными функциями, согласно терминологии объектно-ориентированного программирования, называются методами.

1.1. Методы определяются внутри классов.

1.2. Если тип возвращаемого значения не void, в теле метода должен быть хотя бы один оператор return [выражение], где тип выражения должен совпадать с типом возвращаемого значения. Этот оператор возвращает результат вычисления выражения в точку вызова метода.

1.2.1. Если тип возвращаемого значения – void, возврат из метода выполняется либо после выполнения последнего оператора тела метода, либо в результате выполнения оператора return без указания выражения (таких операторов в теле метода может быть несколько).

скрытый текст2. Сигнатура метода — это имя метода плюс его параметры (причем порядок параметров имеет значение). В сигнатуру метода не входит возвращаемое значение, бросаемые им исключения, а также модификаторы (в т.ч. public, protected, private, abstract, static, final, synchronized, native, strictfp).

3. В языке Java в пределах одного класса можно определить два или более метода, которые совместно используют одно и то же имя, но имеют разные сигнатуры. Когда это имеет место, методы называют перегру­женными, а о процессе говорят как о перегрузке метода (method overloading).

3.1. Когда метод вызывается, то по количеству фактических параметров и/или их типам среда выполнения Java определяет, какую именно версию перегруженного метода надо вызывать (тип возвращаемого значения во внимание не принимается, хотя, в принципе, он тоже может отличаться у разных версий перегруженных методов).

4. Кроме перегрузки существует также замещение, или переопределение методов (англ. overriding). Замещение происходит, когда класс потомок (подкласс) определяет некоторый метод, который уже есть в родительском классе, таким образом новый метод заменяет метод суперкласса. У нового метода подкласса должна быть та же сигнатура и тип возвращаемого результата, что и у метода суперкласса.

4.1. Переопределяемые методы лучше предварять аннотацией @Override. В этом случае компилятор получает возможность проверить, что вы переопределили метод, а не написали новый. Таким образом можно избежать некоторых ошибок из-за невнимательности.

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

4.2.1. В отличие от вызова конструктора суперкласса вызов метода предка можно осуществлять в любом месте переопределенного метода потомка (не обязательно в первой строчке).

4.3. При переопределении метода нельзя сузить модификатор доступа к методу (например с public в MainClass до private в Class extends MainClass). Также обычно нельзя изменить тип возвращаемого значения (будет ошибка "attempting to use incompatible return type)". Но можно сузить возвращаемое значение, если они совместимы.

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

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

6. Модификатор static означает что метод статический, он принадлежит классу, а не конкретному его экземпляру. Из другого класса мы можем вызывать его так: [имя класса].[имя метода]().

6.1. Статические методы можно перегрузить. В том числе можно перегрузить его нестатическим методом.

6.1.1. Нельзя перегружать два метода в Java, если они отличаются только статическим ключевым словом (количество параметров и типов параметров одинаково). Т.к. их сигнатура при этом одинакова.

6.2. Статические методы не могут быть переопределены в точном смысле слова, при попытке это сделать просто будет создан другой статический метод с тем же именем. На практике это означает, что компилятор решит, какой метод будет выполняться во время компиляции, а не во время выполнения, как это происходит с переопределенными методами экземпляра (не будет полиморфизма).

Вопрос 34 (this и super)

"О чем говорят ключевые слова «this», «super», где и как их можно использовать?"

1. Слова this и super — это два специальных ключевых слова в Java, которые представляют соответственно текущий экземпляр класса и его суперкласса.

1.1. Один из примеров использования this и super — это вызовы конструкторов. Внутри класса для вызова своего конструктора без аргументов используется this(), тогда как super() используется для вызова конструктора без аргументов ("конструктора по умолчанию") суперкласса. Таким способом вызывать можно не только конструктор без аргументов, но и вообще любой другой конструктор, передав ему соответствующие параметры.

1.2. Также this и super в Java используются для обращения к переменным экземпляра класса и его суперкласса. К ним можно обращаться и без префиксов super и this, но только если в текущем блоке такие переменные не перекрываются другими переменными, т.е. если в нем нет локальных переменных с такими же именами, в противном же случае использовать имена с префиксами придется обязательно.

скрытый текст1.2.1. Классическим примером такого подхода является использование this внутри конструктора, который принимает параметр с таким же именем, как и у переменной экземпляра.

1.3. Во внутренних классах с помощью этих слов очень удобно ссылаться на внешний класс, используя форму записи Outer.this для его текущего экземпляра и Outer.super — для его родителя, где Outer имя внешнего класса.

2. Чем this и super похожи:
- И this, и super нестатические переменные, соответственно их нельзя использовать в статическом контексте ( в том числе нельзя использовать в методе main).
- И this, и super могут использоваться внутри конструкторов для вызова других конструкторов по цепочке.
- Внутри конструктора this и super должны стоять выше всех других выражений, в самом начале, иначе компилятор выдаст сообщение об ошибке. Из чего следует, что в одном конструкторе не может быть одновременно и this(), и super().

3. Чем this и super различаются:
- Переменная this ссылается на текущий экземпляр класса, в котором она используется, тогда как super — на текущий экземпляр родительского класса.
- Каждый конструктор при отсутствии явных вызовов других конструкторов неявно вызывает с помощью super() конструктор без аргументов родительского класса, при этом всегда остается возможность явно вызвать любой другой конструктор с помощью либо this(), либо super().

3.1. Имея дело с перегруженными конструкторами, удобно один конструктор вызывать из другого через ключевое слово this. При выполнении конструктора this() сначала выполняется перегруженный конструктор, который соответствует указанному списку параметров. Затем выполняются операторы, находящиеся внутри исходного конструктора, если таковые существуют.

3.1.1. Использование перегруженных конструкторов через конструктор this() позволяет исключить дублирование кода, уменьшая время загрузки классов. Но следует быть осторожным, так как конструкторы, которые вызывают конструктор this(), выполняются немного медленнее.

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

Вопросы 29, 32 (модификаторы прав доступа)

"Как правильно организовать доступ к полям класса?"
"Какие модификации уровня доступа вы знаете, расскажите про каждый из них."


1. Модификаторы — ключевые слова, которые добавляются при инициализации для изменения значений. Язык Java имеет широкий спектр модификаторов, основные из них:
- модификаторы доступа;
- модификаторы класса, метода, переменной и потока, используемые не для доступа.

2. В Java существуют следующие модификаторы доступа:
- private: члены класса доступны только внутри класса;
- default (package-private) (модификатор, по-умолчанию): члены класса видны внутри пакета (если класс будет так объявлен он будет доступен только внутри пакета);
- protected: члены класса доступны внутри пакета и в наследниках;
- public: члены класс доступны всем;

скрытый текст2.1. Последовательность модификаторов по убыванию уровня закрытости: private, default, protected, public.

2.2. Во время наследования возможно изменения модификаторов доступа в сторону большей видимости. Так сделано для того, чтобы не нарушался принцип LSP («подкласс не должен требовать от вызывающего кода больше, чем базовый класс, и не должен предоставлять вызывающему коду меньше, чем базовый класс») для наследуемого класса.

2.2.1. Поэтому методы, объявленные как public в суперклассе, также должны быть public во всех подклассах. Методы, объявленные как protected в суперклассе, должны либо быть либо protected, либо public в подклассах; они не могут быть private. Методы, объявленные как private для всех не наследуются, так что нет никакого правила для них.

3. Переменная или метод, объявленные без модификатора контроля доступа доступны для любого другого класса в том же пакете.

3.1. При этом поля в интерфейсе неявно являются public, static, final, а методы в интерфейсе по умолчанию являются public.

4. Модификатор private — методы, переменные и конструкторы, которые объявлены как private в Java могут быть доступны только в пределах самого объявленного класса. Модификатор доступа private является наиболее ограничивающим уровенем доступа. Использование этого модификатора в Java является основным способом, чтобы скрыть данные.

4.1.Классы (исключая внутренние) и интерфейсы не могут быть private.

4.2. Переменные, объявленные как private, могут быть доступны вне класса, если получающие их открытые (public) методы присутствуют в классе. Например, пусть переменная format класса Logger является private, так что нет никакого способа для других классов, чтобы получить и установить её значение напрямую. Но можно определить два открытых (public) метода: getFormat(), который возвращает значение format, и setFormat(String), который устанавливает её значение.

5. Модификатор public — класс, метод, конструктор, интерфейс и т.д. объявленные как public могут быть доступны из любого другого класса. Поэтому поля, методы, блоки, объявленные внутри public класса могут быть доступны из любого класса. Благодаря наследованию классов, в Java все публичные (public) методы и переменные класса наследуются его подклассами.

5.1.Тем не менее, чтобы получить доступ к public классу в другом пакете, этот класс придется импортировать.

6. Модификатор protected — переменные, методы и конструкторы, которые объявляются как protected в суперклассе, могут быть доступны только для подклассов в другом пакете или для любого класса в пакете класса protected. Доступ protected дает подклассу возможность использовать вспомогательный метод или переменную, при этом не позволяя их использовать неродственным классам.

6.1. Модификатор доступа protected в Java не может быть применен к классу и интерфейсам. Методы и поля могут быть объявлены как protected, однако методы и поля в интерфейсе не могут быть объявлены как protected.

7. Почему бы не объявить все переменные и методы с модификатором public, чтобы они были доступны в любой точке программы вне зависимости от пакета или класса? Возьмем, например, поле age, которое представляет возраст. Если другой класс имеет прямой доступ к этому полю, то есть вероятность, что в процессе работы программы ему будет передано некорректное значение, например, отрицательное число. Подобное изменение данных не является желательным.

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

7.2. В теории часто рекомендуется использовать для полей модификатор доступа private, а доступ осуществлять через специальные методы с заголовками, начинающимися с get/set.

Вопросы 30, 31, 33 (конструкторы)

"Дайте определение понятию «конструктор»."
"Чем отличаются конструкторы по-умолчанию, копирования и конструктор с параметрами?"
"Расскажите об особенностях класса с единственным закрытым (private) конструктором"


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

скрытый текст1.1. Конструктор похож на метод, но не является методом, он даже не считается членом класса. Поэтому его нельзя наследовать или переопределить в подклассе.

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

1.3. В отличие от метода, конструктор никогда ничего не возвращает (даже void).
private int Cat(); // так выглядит метод по имени Cat
Cat(); // так выглядит конструктор класса Cat

1.3.1. Если возвращается тип void, это уже не конструктор а метод, несмотря на совпадение с именем класса.

1.3.2. В конструкторе допускается оператор return, но только пустой, без всякого возвращаемого значения;

1.3.3. В конструкторе допускается применение модификаторов доступа, можно задать один из модификаторов: public, protected, private или без модификатора. Конструктор не может иметь модификаторов abstract, final, native, static или synchronized;

2. Существуют два вида конструкторов - явные и неявные.

2.1. Конструктор имеется в любом классе. Если он не указан явно, компилятор Java сам создаст конструктор по умолчанию (default constructor), который будет без параметров, пустым и не делает ничего, кроме вызова конструктора суперкласса. Если вы сами создали конструктор, то конструктор по умолчанию использоваться не будет.


2.1.1. То есть public class Example {} эквивалентно написанию:

public class Example
{
Example()
{
super;
}
}

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

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

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

2.2.1.1.Тем не менее, в Java, начиная с версии 1.5, можно использовать конструкторы с аргументами переменной длины. И если есть конструктор, имеющий аргумент переменной длины, то вызов конструктора по умолчанию (т.е. без параметров) ошибкой не будет. Не будет потому, что аргумент переменной длины может быть пустым.

2.3.Конструкторов может быть несколько в классе. В этом случае конструкторы называют перегруженными. Java различает перегруженные методы по числу, типам и последовательности типов входных параметров (но не по возвращаемому типу).

2.3.1. В случае, если в параметрах перегруженного конструктора используется примитив, который может быть сужен (например int < — double), то вызов метода со "суженным" значением возможен, несмотря на то, что метода, перегруженного с таким параметром нет.

2.4. Конструктор копирования — это конструктор, который принимает в качестве параметра объект класса с целью его клонирования.

3. При создании объекта последовательно выполняются следующие действия:
— Ищется класс объекта среди уже используемых в программе классов, во всех доступных программе каталогах и библиотеках.
— После обнаружения класса выполняется создание и инициализация его статических полей. Для каждого класса они инициализируются только один раз.
— Выделяется память под объект.
— Выполняется инициализация полей класса.
— Отрабатывает конструктор класса.
— Формируется ссылка на созданный и инициализированный объект. Эта ссылка и является значением выражения, создающего объект.

3.1. Объект может быть создан и с помощью вызова метода newInstance() класса java.lang.Class. В этом случае используется конструктор без списка параметров.

4. Конструкторы могут быть частыми (private). Есть три варианта их использования:
- чтобы предотвратить создание экземпляра за пределами класса (класс констант, класс статических методов);
- чтобы предотвратить расширение (если вы создаете только частный конструктор, ни один класс не может расширить ваш класс, потому что он не может вызвать конструктор super(). Это своего рода синоним для final);
- перегруженные конструкторы (В результате методов перегрузки и конструкторов некоторые могут быть частными и некоторыми публичными. Например, если в ваших конструкторах есть непубличный класс, вы можете создать публичный конструктор, который создает экземпляр данного класса, а затем передает его частному конструктору).

4.1. Например, иногда класс создаётся только для хранения каких-то статических полей и статических методов. Таким классам принято давать имена Utils, но это не обязательно. Такому классу не нужен конструктор, но если автор класса его не создал, система сама создаст конструктор по умолчанию. Такой конструктор не имеет смысла и может послужить источником ошибок. Чтобы предохраниться от подобной проблемы, нужно явно создать пустой конструктор и сделать его закрытым.

4.2. Если у класса есть только частный конструктор, невозможно создать объект класса за его пределами. Поэтому нельзя унаследоваться от такого класса. При попытке будет выдаваться ошибка: "There is no default constructor available in имяКласса". А при попытке создать объект этого класса: "ИмяКласса() has private access in ИмяКласса"

Вопрос 26 (основные принципы ООП)

"Назовите принципы ООП и расскажите о каждом."

1. Есть четыре основные характеристики ООП:
- Абстракция
- Инкапсуляция
- Наследование
- Полиморфизм

2. Абстракция — это разработка классов, исходя из предоставляемой ими функциональности; а не из деталей их реализации. Такое разделение может быть выражено через специальный «интерфейс», сосредотачивающий описание всех возможных применений программы. Абстракция упрощает код, скрывая несущественные детали.

2.1. С точки зрения программирования абстракция — это правильное разделение программы на объекты. Это четкое определение концептуальных границ объекта в виде отличающих его от других объектов характеристик.

скрытый текст2.2. Такой подход является основой ООП. Он позволяет работать с объектами, не вдаваясь в особенности их реализации. В каждом конкретном случае применяется тот или иной более частный подход: инкапсуляция, полиморфизм или наследование. Например, при необходимости обратиться к скрытым данным объекта, следует воспользоваться инкапсуляцией, создав, так называемую, функцию доступа или свойство.

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

3.1. Пример. Для блондинки, управление машиной представляется как руль, педали, и коробка передач - Рулить(), Газовать(), Тормозить(), ПереключатьСкорость(). Для нее не доступны тонкости работы этих методов - она не знает что происходит под капотом, когда она нажимает на педаль, или как руль воздействует на колеса. В этом случае Рулить(), Газовать(), Тормозить(), ПереключатьСкорость() - спецификация интерфейсов, все что под капотом - реализация.

3.2. Инкапсуляция выступает договором для объекта, что он должен скрыть, а что открыть для доступа другими объектами. При этом пользователю для взаимодействия с объектом предоставляется только спецификация (интерфейс) объекта. Реализуется с помощью ключевого слова: public. Пользователь не может использовать закрытые данные и методы. Реализуется с помощью ключевых слов: private, protected, internal.

4. Наследование — это включение поведения (т.е. методов) и состояния (т.е. переменных) базового класса в производный класс, таким образом они становятся доступны в этом производном классе. Главное преимущество наследования в том, что оно обеспечивает формальный механизм повторного использования кода и избегает дублирования.

4.1. Наследование обычно возникает, когда все объекты одного класса одновременно являются объектами другого класса (отношение общее/частное). Например, все объекты класса Студент являются объектами класса Человек. В этом случае говорят, что класс Студент наследует от класса Человек. Аналогично класс Собака может наследовать от класса Животное, а класс Далматинец от класса Собака. Класс, который наследует, называется подклассом или потомком, а класс, от которого наследуют, называется суперклассом или предком.

4.1.1. Если класс №2 является потомком класса №1, а класс №3 является потомком класса №2, то класс №3 является также потомком класса №1.

4.2. Наследование избавляет программиста от лишней работы. Например, если в программе необходимо ввести новый класс Далматинец, его можно создать на основе уже существующего класса Собака, не программируя заново все поля и методы, а лишь добавив те, которых не хватало в суперклассе.

4.2.1. Однако наследование делает код сильно связанным. При желании изменить суперкласс, придется знать все детали подклассов, чтобы не разрушить код.


4.3. Для того, чтобы один класс был потомком другого, необходимо при его объявлении после имени класса указать ключевое слово extends и название суперкласса. Например:

class Dalmatian extends Dog {
// дополнительные поля и методы
}

скрытый текст4.3.1. Если ключевое слово extends не указано, считается, что класс унаследован от универсального класса Object.

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

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

5.1.1. Пусть, к примеру, мы хотим расширить класс Dog классом BigDog, для того, чтобы наша программа особым образом моделировала поведение больших злых собак. В частности, большие собаки лают по-другому. Во-первых, громче, а во-вторых, они не умеют считать. Поэтому мы переопределим метод voice(). Теперь создадим в методе main() двух разных собак: обычную и большую и заставим их лаять.

Dog dog = new Dog("Тузик", 2);
dog.voice();
dog = new BigDog();
dog.voice();

Т.е. переменная dog имеет тип Dog, но в третьей строке она начинает указывать на объект класса BigDog, то есть БОЛЬШУЮ собаку, которая при вызове метода voice() будет лаять как БОЛЬШАЯ собака.

5.2. Красота полиморфизма в том, что код, работая с различными классами, не должен знать, какой класс он использует, так как все они работают по одному принципу. Процесс, применяемый объектно-ориентированными языками программирования для реализации динамического полиморфизма, называется динамическим связыванием.

Вопрос 27, 28 (классы и объекты)

"Дайте определение понятию «класс»."
"Что такое поле/атрибут класса?"

1. Java является объектно-ориентированным языком, поэтому такие понятия как "класс" и "объект" играют в нем ключевую роль. Любую программу на Java можно представить как набор взаимодействующих между собой объектов.

скрытый текст1.1. Шаблоном или описанием объекта является класс, а объект представляет экземпляр этого класса. Можно провести следующую аналогию. У нас у всех есть некоторое представление о человеке: наличие двух рук, двух ног, головы, туловища и т.д. Есть некоторый шаблон — этот шаблон можно назвать классом. Реально же существующий человек (фактически экземпляр данного класса) является объектом этого класса.

2. Класс представляет новый тип, поэтому мы можем определять переменные, которые представляют данный тип.В Java принято начинать имена класса с большой буквы. Класс определяется с помощью ключевого слова сlass:


class Person{
}

скрытый текст2.1. В данном случае класс называется Person. После названия класса идут фигурные скобки, между которыми помещается тело класса - то есть его поля и методы. Вся функциональность класса представлена его членами: полями (полями или переменными экземпляра называются переменные, определенные внутри класса), которые хранят состояние объекта, и методами, которые определяют поведение объекта. Методы и переменные внутри класса являются членами класса.

2.2. В классе могут быть несколько переменных и методов. Например, класс Person, который представляет человека, мог бы иметь следующее определение:


class Person{
String name; // имя
int age; // возраст
void displayInfo(){
System.out.printf("Name: %s Age: %d", name, age);
}
}

скрытый текст2.3. В классе Person определены два поля: name представляет имя человека, а age - его возраст. И также определен метод displayInfo, который ничего не возвращает и просто выводит эти данные на консоль.

2.3.1. Java автоматически присвоит полям значения по умолчанию. Например, для int это будет значение 0. Но не всегда значения по умолчанию подойдут в вашем классе. Если вы создали переменную для описания количества лап у кота, логично сразу присвоить значение 4. Считается хорошей практикой сразу присваивать нужные значения полям класса, не полагаясь на систему.

3. Новый объект (или экземпляр) создаётся из существующего класса при помощи ключевого слова new:

Cat barsik = new Cat(); // создали кота из класса Cat

3.1. Слово Cat используется дважды, но оно имеет разный смысл. Слева от оператора присваивания определяется имя переменной и его тип Cat. В правой части выражения происходит выделение памяти для нового экземпляра класса Cat и инициализируется экземпляр. Оператор присваивания присваивает переменной ссылку на только что созданный объект. Имена объектов не нужно начинать с большой буквы, как у класса. Если имя экземпляра класса состоит из нескольких слов, то используется "верблюжья" нотация, когда все слова, кроме первого, пишутся с большой буквы — superBlackCat.

3.2. Каждый объект содержит собственные копии переменных экземпляра. Вы можете создать несколько объектов на основе класса и присваивать разные значения их полям. При этом изменения переменных экземпляра одного объекта никак не влияют на переменные экземпляра другого объекта.

Вопросы 24, 25 (оболочки и упаковка)

"Что вы знаете о классах оболочках?"
"Что такое автоупаковка (boxing/unboxing)?"


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

1.1. Ниже показаны примитивные типы и их классы-обертки в Java:

byte <-> Byte (аргументы: byte или String)
short <-> Short (аргументы: short или String)
int <-> Integer (аргументы: int или String)
long <-> Long (аргументы: long или String)
float <-> Float (аргументы: float, double или String)
double <-> Double (аргументы: double или String)
char <-> Character (аргументы: char)
boolean <-> Boolean (аргументы: boolean или String)

скрытый текст1.1.1. Числовые классы имеют общего предка — абстрактный класс Number, в котором описаны шесть методов, возвращающих числовое значение, содержащееся в классе, приведенное к соответствующему примитивному типу: byteValue(), doubleValue(), floatValue(), intValue(), longValue(), shortValue(). Эти методы переопределены в каждом из шести числовых классов-оболочек.

1.2. Зачем нужны классы-оболочки в Java? Разработчиками языка Java было принято решение отделить примитивные типы и классы-оболочки, указав при этом следующее:

- Используйте классы-обертки, когда работаете с коллекциями.
- Используйте примитивные типы для того, чтобы ваши программы были максимально просты (также они занимают меньше места).
- Еще одним важным моментом является то, что примитивные типы не могут быть null, а классы-оболочки — могут.
- С помощью класса-оболочки можно выполнять специальные операции: например с помощью Integer можно перевести текст в число (с помощью метода .parseInt()). Если попробовать сделать это с помощью кода самому придется изрядно повозиться.
- Также классы-оболочки могут быть использованы для достижения полиморфизма.

1.2.1. Каждый класс — обертка умеет получать свой тип данных из строки, для этого используется метод pasre:

ИмяТипа.parseИмяТипа(string s);
Integer i = Integer.parseInt("123123");
Boolean b = Boolean.parseBoolean("true");

2. Создание объекта-оболочки из переменной примитивного типа называется упаковкой (boxing), а получение значения примитивного типа из объекта-оболочки — распаковкой (unboxing).

2.1. Для понимания смысла упаковки-распаковки нужно понимать, как работает исполняемая среда и программа на уровне процессора, а также понимать, что ООП — это всего лишь надстройка над классическим структурным программированием. Промежуточный байт-код во время выполнения программы преобразуется исполняемой средой в команды конкретного микропроцессора.

Есть 3 основных способа хранения, обработки данных и их взаимодействия с программой: процессор-регистр, процессор-стек, и процессор-память. Команды работы с регистрами самые короткие и быстрые, со стеком — короткие, но выполняются на большее число тактов процессора, а команды работы с памятью — самые длинные и медленные. При этом в большинстве случаев простые типы данных хранятся и обрабатываются в регистрах процессора и в стеке, классы и более сложные типы данных хранятся в так называемой "куче" - динамической памяти, которая контролируется исполняемой средой. Механизм кучи достаточно сложен и работа с кучей по времени всегда дольше.

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

Упаковку-распаковку придумали для того, чтобы избежать потери производительности программного обеспечения: если критически важна скорость — распаковываем свои данные в простой компактный формат, если скорость не критична — упаковываем в объектный тип и упрощаем программу. Кроме скорости возможна ситуация со значительными затратами ресурсов: к примеру, если необходимо создать массив из миллиона однобайтовых элементов.

2.2. Начиная с Java 5 автоупаковка и распаковка позволяет легко конвертировать примитивные типы в их соответствующие классы-оболочки и наоборот. В тех случаях, когда по контексту требуются объекты (присваивание, вызов метода с передачей параметров), а мы используем значения примитивных типов (переменные или выражения типа 2 * 3), всегда происходит автоупаковка. Объектам-оболочкам можно присваивать значения примитивных типов, а переменным примитивных типов - значения переменных-оболочек, при этом при необходимости автоматически создаются объекты-оболочки с соответствующими значениями (автоупаковка) или наоборот, примитивные значения извлекаются из оболочек (автораспаковка):

int a = 5;
Integer b = 10;
a = b; // OK, атораспаковка
b = a * 123; // OK, автоупаковка

2.2.1. Автоупаковка применяется компилятором Java в следующих условиях:

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

2.2.2. Распаковка применяется компилятором Java в следующих условиях:

- Когда объект передается в качестве параметра методу, который ожидает соответствующий примитивный тип.
- Когда объект присваивается переменной соответствующего примитивного типа.
- В выражениях, в которых один или оба аргумента являются экземплярами классов-обёрток (кроме операции == и !=).

2.2.3. Автоупаковка и распаковка могут использоваться с операторами сравнения. Следующий фрагмент кода иллюстрирует, как это происходит:

Integer in = new Integer(25);
if (in < 35)

2.2.4. Автоупаковка и распаковка при перегрузке метода. Автоупаковка и распаковка выполняется при перегрузке метода на основании следующих правил:

- Расширение «побеждает» упаковку (В ситуации, когда становится выбор между расширением и упаковкой, расширение предпочтительней).
- Расширение «побеждает» переменное количество аргументов.
- Упаковка «побеждает» переменное количество аргументов.

2.3. Что нужно учитывать:
- При сравнении оператором ‘==’ может возникнуть путаница, так как он может применяться и к примитивным типам, и к объектам. Когда этот оператор применяется к объектам, он фактически сравнивает ссылки на объекты а не сами объекты.
- Смешение объектов и примитивных типов в операторах равенства и отношения. Если мы сравниваем примитивный тип с объектом, происходит распаковка объекта, который может бросить NullPointerException если объект null.
- Ухудшение производительности. Автоупаковка или распаковка ухудшают производительность приложения, поскольку это создает нежелательный объект, из-за которого сборщику мусора приходится работать чаще.

Лучшее   Правила сайта   Вход   Регистрация   Восстановление пароля

Материалы сайта предназначены для лиц старше 16 лет (16+)