Свежие записи из блогов Widowmaker1984

Widowmaker1984, блог «Java для собеседований»

Вопросы 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.

Widowmaker1984, блог «Java для собеседований»

Вопросы 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 ИмяКласса"

Widowmaker1984, блог «Java для собеседований»

Вопрос 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. Красота полиморфизма в том, что код, работая с различными классами, не должен знать, какой класс он использует, так как все они работают по одному принципу. Процесс, применяемый объектно-ориентированными языками программирования для реализации динамического полиморфизма, называется динамическим связыванием.

Widowmaker1984, блог «Java для собеседований»

Вопрос 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. Каждый объект содержит собственные копии переменных экземпляра. Вы можете создать несколько объектов на основе класса и присваивать разные значения их полям. При этом изменения переменных экземпляра одного объекта никак не влияют на переменные экземпляра другого объекта.

Widowmaker1984, блог «Java для собеседований»

Вопросы 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.
- Ухудшение производительности. Автоупаковка или распаковка ухудшают производительность приложения, поскольку это создает нежелательный объект, из-за которого сборщику мусора приходится работать чаще.

Widowmaker1984, блог «Java для собеседований»

Вопрос 23 (многомерные массивы)

"Какие виды массивов вы знаете?"

1. В Java два вида массивов: одномерные и многомерные. Последние в Java фактически представляют из себя массивы массивов.

2. Для создания многомерных массивов используются дополнительные скобки:

int[][] a = {
{ 1, 2, 3 },
{ 4, 5, 6 }
}

2.1. Также массив может создаваться ключевым словом new:

// трехмерный массив фиксированной длины
int[][][] b = new int[2][4][4];

скрытый текст3. Размер двумерного массива измеряется интересным способом. Длина массива определяется по его первой размерности, то есть вычисляется количество рядов. Чтобы узнать количество столбцов в ряду, надо указать ряд, а затем вычислить у него количество столбцов.

// число колонок у третьего ряда
System.out.println(matrix[2].length);

4. Допустим, вы хотите представить таблицу умножения в виде многомерного массива:
int[][] multiplicationTable = new int[10][10];

В некоторых языках такой массив создается в виде единого блока из 100 значений int. Java поступает иначе. Эта строка кода выполняет три действия:

- Объявляет переменную с именем multiplicationTable, которая содержит ссылку на массив ссылок, которые в свою очередь будут указывать на массивы int.
- Создает массив из 10 элементов (первый индекс), который будет содержать ссылки на 10 одномерных массивов int. Отсюда собственно и понятие – массив массивов.
- На этой стадии создания массив ссылок заполняется значениями по умолчанию, то есть значениями null.
- Создает еще 10 массивов, каждый из которых в свою очередь является массивом из 10 элементов int.
- Присваивает ссылки на каждый из этих 10 новых массивов элементам массива, созданного на втором шаге. По умолчанию каждый элемент int каждого из этих 10 новых массивов получает значение 0.

4.1. Другими словами, представленную выше строку кода, можно записать так:

int[][] multiplicationTable = new int[10][]; // первый индекс содержит ссылки на массивы int
for (int i = 0; i < 10; i++)
multiplicationTable[i] = new int[10]; // создаем 10 массивов int

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

4.2.1. Возьмем к примеру вот такие два определения двумерных массивов:

int[][] a = new int[10][1000];
int[][] b = new int[1000][10];

В случае массива a, количество порождаемых в памяти объектов равно 11, а в случае массива b1001. Создание и обслуживание каждого объекта в памяти виртуальной машины имеет свои накладные расходы, так как виртуальная машина считает ссылки для каждого объекта, хранит его атрибуты и т.д. и т.п. Таким образом массив b может занимать в памяти в полтора, а то и в два раза больше места чем массив a.

5. При работе с многомерными массивами в Java очень важно понять, что первые индексы многомерных массивов содержат только массивы ссылок, и только последний (самый правый) индекс содержит непосредственно элемент данных типа объявленного для массива.

5.1. То есть в Java можно объявить и более чем двумерные массивы. Например:

int[][][] dim3D;

И тут важно понимать, что первый индекс данного массива, содержит массив ссылок, на второй индекс данного массива, который в свою очередь тоже содержит массив ссылок на массивы значений int. То есть если в данном случае вывести на консоль значение int[1][1], получим адрес ссылки. И только по полному индексу int[1][1][1] сможем получить значение элемента массива типа int.

5.2. Нижеприведенный код не вызовет ошибки компиляции, но вызовет ошибку во время исполнения: NullPointerException.

int[][] multiplicationTable = new int[10][];
multiplicationTable[0][0] = 10; // ошибка во время исполнения

Это происходит потому, что не был создан объект, в данном случае массив int-ов. То есть создан массив хранящий ссылки на массивы int-ов, но сами эти массивы еще не создны, поэтому обращение к несуществующему объекту вызывает ошибку. Это можно исправить следующим кодом:

int[][] multiplicationTable = new int[10][];
multiplicationTable[0] = new int [10];
multiplicationTable[0][0] = 10; // нет ошибки

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

float[][][] globalTemperatureData = new float[360][][];
float[][][] globalTemperatureData = new float[360][180][];

Но такие варианты ошибочны:

float[][][] globalTemperatureData = new float[360][][100]; // Ошибка!
float[][][] globalTemperatureData = new float[][180][100]; // Ошибка!

Widowmaker1984, блог «Java для собеседований»

Вопрос 22 (массивы)

"Что такое массив?"

1. Массив — это конечная последовательность упорядоченных элементов одного типа, доступ к каждому из которых осуществляется по его индексу. В Java массив используется для хранения коллекции данных, но бывает полезно думать о массиве как о совокупности переменных одного типа.

скрытый текст1.1. Размер или длина массива — это общее количество элементов в массиве. Размер массива задаётся при создании массива и не может быть изменён в дальнейшем, т. е. нельзя убрать элементы из массива или добавить их туда, но можно присвоить существующим элементам новые значения. Индекс начального элемента — 0, следующего за ним — 1 и т. д. Индекс последнего элемента в массиве — на единицу меньше, чем размер массива. Данное решение было навязано математиками, которым было удобно начинать отсчёт массивов с нуля.

1.2. В Java массивы являются объектами. Это значит, что имя, которое даётся каждому массиву, лишь указывает на адрес какого-то фрагмента данных в памяти. Кроме адреса в этой переменной ничего не хранится. Индекс массива, фактически, указывает насколько надо отступить от начального элемента массива в памяти, чтоб добраться до нужного элемента.

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

1.3. Если нужно изменять длину, то вместо стандартного массива следует использовать списочный массив ArrayList.

1.4. Разрешается массив с интерфейсным типом таким, как тип компонент . Элементы такого массива могут иметь значением пустую ссылку (null) или экземпляры любого классового типа, который реализует интерфейс. Разрешается массив с классовым типом abstract как тип компонент. Элементы такого массива могут иметь значением - пустую ссылку (null) или экземпляры любого подкласса класса abstract, который сам не abstract.

1.5. В Java (в отличие от Cи) массив типа char — не String, и ни строка. Объект String языка Java не меняется, то есть никогда не меняется его содержание, в то время как массив типа char имеет непостоянные элементы. Метод toCharArray в классе String, возвращает массив символов, содержащий ту же последовательность символов.

2. Возможны следующие варианты объявления массива:

тип[] имя;
тип имя[];

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

2.1. Примеры:

int[] a;
double[] ar1;
double ar2[];

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


3.1. Примеры:
a = new int[10]; // массив из 10 элементов типа int
int n = 5;
ar1 = new double[n]; // Массив из 5 элементов double
ar2 = {3.14, 2.71, 0, -2.5, 99.123}; // Массив из 6 элементов типа double

скрытый текст3.1.2. Можно смешать два способа. Например, если требуется задать явно значения только для некоторых элементов массива, а остальные должные иметь значения по умолчанию.

int[] ar1 = new int[6]; // массив из шести элементов с начальным значением 0 для каждого элемента
ar1[3] = 5; // четвертому элементу присвоено значение 5
ar1[5] = 7; // шестому элементу присвоено значение 7


3.2. Объявить имя для массива и создать сам массив можно на одной строке по следующей схеме:

тип[] имя = new тип[размер];
тип[] имя = {эл0, эл1, …, элN};

3.2.1. Примеры:

int[] mas1 = {10,20,30};
int[] mas2 = new int[3];

скрытый текст4. Чтобы обратиться к какому-то из элементов массива для того, чтобы прочитать или изменить его значение, нужно указать имя массива и за ним индекс элемента в квадратных скобках. Например, на первый элемент массива mas1 можно ссылаться как на mas1[0], на третий элемент как mas1[2]. Элемент массива с конкретным индексом ведёт себя также, как переменная.

4.1. В качестве индекса можно использовать числа или выражения, которые возвращают положительное значение типа int. Поэтому при вычислении выражения с типом long, следует преобразовать результат в int, иначе при компиляции возникнет ошибка. С типами short и byte проблем не будет, так как они полностью укладываются в диапазон int.

4.2. Длину любого созданного массива не обязательно запоминать, потому что имеется свойство, которое его хранит. Обратиться к этому свойству можно дописав .length к имени массива. Это свойство нельзя изменять (т. е. ему нельзя ничего присваивать), можно только читать. Используя это свойство можно писать программный код для обработки массива даже не зная его конкретного размера. Например, последний элемент массива mice всегда mice[mice.length - 1].

4.3. Нужно быть осторожным с копированием массивов. Массив — это не числа, а специальный объект, который по особому хранится в памяти. Скажем, в следующем случае:

int[] luckyNumbers = anyNumbers;

Массив остается прежним, и вторая переменная обращается к нему же, а не создаёт вторую копию.

4.3.1. Если же нужна именно копия массива, следует использовать метод Arrays.copyOf().

5. Методы для массива. Класс java.util.Arrays содержит различные статические методы для поиска, сортировки, сравнения и заполнения элементов массива. Методы перегружаются для всех примитивных типов.

- copyOf() − предназначен для копирования массива
- copyOfRange() − копирует часть массива
- toString() − позволяет получить все элементы в виде одной строки
- sort() — сортирует массив методом quick sort
- binarySearch() − ищет элемент методом бинарного поиска
- fill() − заполняет массив переданным значением (удобно использовать, если нам необходимо значение по умолчанию для массива)
- equals() − проверяет на идентичность массивы
- deepEquals() − проверяет на идентичность массивы массивов
- asList() − возвращает массив как коллекцию

5.1. Класс Arrays содержит метод equals() для проверки на равенство целых массивов. Чтобы два массива считались равными, они должны содержать одинаковое количество элементов, и каждый элемент должен быть эквивалентен соответствующему элементу другого массива.

5.2. Сортировка (упорядочение по значениям) массива a производится методами Arrays.sort(a) и Arrays.sort(a, index1, index2). Первый метод упорядочивает в порядке возрастания весь массив, второй — часть элементов (от индекса index1 до индекса index2). Имеются и более сложные методы сортировки.

5.2. Метод Arrays.copyOf(оригинальный_массив, новая_длина) — возвращает массив-копию новой длины. Если новая длина меньше оригинальной, то массив усекается до этой длины, а если больше, дополняется нулями.

5.3. Если использовать вызов метода toString() непосредственно у массива, получится что-то не читаемое. Метод Arrays.toString(массив) возвращает строковое представление массива со строковым представлением элементов, заключенных в квадратные скобки. Метод deepToString() удобен для вывода многомерных массивов. Этот метод мы также уже использовали выше.

5.4. Метод Arrays.fill() позволяет быстро заполнить массив одинаковыми значениями. У метода есть восемнадцать перегруженных версий для разных типов и объектов. Метод fill() просто дублирует одно заданное значение в каждом элементе массива (в случае объектов копирует одну ссылку в каждый элемент).

Widowmaker1984, блог «Java для собеседований»

Вопросы 20, 21 (операторы break и continue)

"Какой оператор используется для немедленной остановки цикла?"
"Какой оператор используется для перехода к следующей итерации цикла?"


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

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

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

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

2.3. В-третьих, break может применяться как «цивилизованная» форма оператора безусловного перехода goto. Java не содержит оператора goto, потому что он выполняет переход произвольным и неструктурированным способом. Код, интенсивно использующий goto, обычно трудно понять и поддерживать. Он также отменяет некоторые оптимизации компилятора. Существует, однако, несколько мест в программе, где goto — ценная и законная конструкция управления потоком выполнения. Например, он может быть полезен, когда вы выходите из глубоко вложенного набора циклов. Чтобы обрабатывать такие ситуации, Java определяет расширенную форму оператора break. Используя ее, вы можете выйти из одного или большего количества блоков кода. Этим блокам не нужно быть частью цикла или оператора switch. Далее, вы можете определить точно, где выполнение будет продолжено, потому что данная форма break работает с меткой и обеспечивает преимущества goto, минуя его проблемы.

2.3.1. Оператор break с меткой имеет следующую общую форму:
break label;

2.3.1.1. Здесь label — имя метки, которая идентифицирует некоторый блок кода. Для именования блока поместите метку в его начале (перед открывающей блок фигурной скобкой). Метка — это любой допустимый идентификатор Java, за которым следует двоеточие. После маркировки блока, его метку можно использовать как аргумент оператора break. Это приведет к тому, что выполнение будет продолжено с конца помеченного блока.

2.3.1.2. Когда эта форма break выполняется, управление передается из именованного блока кода (чья метка указана в операторе break) на следующий за этим блоком оператор. Помеченный блок кода обязательно должен включать данный оператор break, но не требуется, чтобы это включение было непосредственным (т. е. break может включаться не прямо в блок со своей меткой, а во вложенный в него блок, возможно, тоже помеченный). Это означает, что вы можете использовать помеченный оператор break, чтобы выйти из набора вложенных блоков.

3. Оператор continue заставляет цикл пропустить оставшуюся часть его тела, сразу перейти к его следующей итерации. В циклах while или do...while, контроль сразу же переходит в логическое выражение. В цикле for ключевое слово continue распоряжается процессом так, чтобы сразу же перейти к оператору обновления, а затем к проверке условия. По сути это goto-переход мимо следующих операций тела в конец блока цикла.

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

3.2. Как и в операторе break, в continue можно определить метку, указывающую, какой включающий цикл следует продолжить.

4. Кроме break и continue в Java имеется третий оператор управления: return. Он используется для явного возврата из метода, т. е. передает программное управление обратно в вызывающую программу. Оператор return относят к категории операторов перехода. Его можно использовать в любом месте метода для выполнения перехода обратно в вызывающую метод программу. При этом он немедленно заканчивает выполнение метода, в котором он находится.

Widowmaker1984, блог «Java для собеседований»

Вопросы 17, 18, 19 (циклы)

"Какие циклы вы знаете, в чем их отличия?"
"Что такое «итерация цикла»?"
"Какие параметры имеет цикл for, можно ли их не задать?"


1. Цикл — это многократно повторяющийся фрагмент программы. В java существует два типа циклов: типа «пока» и типа «n-раз». Первый тип «пока» предназначен для повторения какого-то действия до тех пор, пока выполняется некоторое условие. Пример: увеличивать число на 5 до тех пор, пока оно не станет трёхзначным. Второй тип «n-раз» предназначен для повторения каких-то действий заранее известное количество раз. Пример: умножить число само на себя 4 раза.

скрытый текст1.1. Однократное выполнение кода, размещенного в повторяющемся фрагменте ("теле цикла"), называется его итерацией.

2. Цикл типа «пока» (операторы while и do…while). Оператор while повторяет указанные действия до тех пор, пока его параметр имеет истинное значение. Общая форма оператора:


while (УсловиеВыполнения) {
ТелоЦикла;
}

скрытый текст2.1. Пока УсловиеВыполнения выполняется (результат его вычисления равен true), будет выполняться и ТелоЦикла, где может быть один оператор или их группа. Когда условие становится ложным, программа передаёт управление на строчку сразу после цикла. Ключевым моментом цикла while является то, что его тело может ни разу не выполниться.

2.2. Бывает цикл типа «пока» с постпроверкой условия. Для его записи используется конструкция из операторов do…while. Общая форма оператора:


do {
ТелоЦикла;
} while (УсловиеВыполнения);

скрытый текст2.2.1. Отличие данного оператора от while только в том, что он является оператором постусловия (сначала выполнит, потом проверит). То есть, даже если условие не выполняется никогда, всё равно действие будет выполнено один раз. Если логическое выражение истинно, контроль переходит обратно, чтобы выполнить операторы, и они в цикле выполняются снова. Этот процесс повторяется до тех пор, пока логическое выражение не станет ложным.

3. Цикл типа «n-раз» (оператор for). Он позволяет эффективно написать цикл, который должен выполниться определенное количество раз. Цикл for полезен, когда известно, сколько раз должна быть повторена задача. Общая форма оператора:


for (Инициализация; УсловиеВыполнения; Обновление) {
ТелоЦикла;
}

скрытый текст3.1. Процесс управления в цикле:
- Стадия инициализации выполняется первой, и только один раз. Этот шаг позволяет объявлять и инициализировать любые переменные для управления циклом, и он заканчивается точкой с запятой(;).
- Далее логическое выражение. Если оно истинно, тело цикла выполняется, если оно ложно, тело цикла не будет выполнено и контроль переходит к следующему оператору.
- После того как тело цикла for выполняется, контроль переходит обратно к оператору обновления. Он позволяет обновлять какие-либо переменные для управления циклом, и записывается без точки с запятой в конце.
- Логическое выражение затем оценивается снова. Если истинно, цикл выполняется и процесс повторяется. Если ложно, цикл for завершается.

3.1.1 Цикл можно записать следующим образом: for(;;); будет аналогично while(true).

3.2. Улучшенный цикл for — в основном используется для обхода коллекцией элементов, включая массивы. Был введен начиная с Java 5, позволяет достигнуть большей краткости кода. Наиболее фундаментальным отличием использования по сравнению со "старым for" является отсутствие необходимости использовать счетчик. Общая форма оператора:


for (Объявление: Выражение)
{
ТелоЦикла
}

скрытый текст3.2.1. Объявление — это переменная, например Object listElement. Эта переменная должна иметь тип, совместимый с каждым элементом списка, массива или коллекции, по которым производится итерация. Выражение — выражение, вычисляющее что-то, по чему можно делать итерацию (возвращает коллекцию или массив). Может быть переменной, вызовом функции или комплексным выражением. Тело Цикла — один или несколько операторов, применяемых ко всем элементам коллекции или массива.

Widowmaker1984, блог «Java для собеседований»

Вопрос 16 (операторы if и switch)

"Какова роль и правила написания оператора выбора?"

1. В Java есть так называемые конструкции ветвления:
- условный оператор if;
- оператор switch;

2. Общая форма условного оператора if в Java такая:

if (Условие1) {
//действие(-я), которые выполняются, если Условие1 истинно;
}
else if (Условие2) {
//действие(-я), которые выполняются, если Условие2 истинно;
}
(...)
else if (УсловиеN) {
//действие(-я), которые выполняются, если УсловиеNn истинно;
}
else {
//действие(-я), которые выполняются, если все условия ложные;
}

скрытый текст2.1. Оператор всегда начинается со слова if, за которым всегда идут круглые скобки с условием. После круглых скобок никогда не ставится точка с запятой. Для того, чтобы указать альтернативный вариант ("если не выполняется, тогда...") используется слово else. Если условий несколько, каждое из них будет записываться через комбинацию else if, после которых в круглых скобках записывается альтернативное условие. Последний вариант ("если не то, не то и не то, тогда...") записывается через else без условия.

3. Конструкции с операторами if else, предлагающими большое количество условных ветвлений, могут выглядеть очень громоздкими. Поэтому в тех случаях, когда необходимо повторять проверку значения одной и той же переменной, есть более элегантное решение с помощью оператора switch. Его часто называют оператором выбора. Оператор switch эффективнее набора вложенных операторов if. Выбор осуществляется в зависимости от целочисленного выражения. Общая форма оператора выглядит так:

switch (ВыражениеДляСравнения {
case Совпадение1:
команда;
break;
case Совпадение2:
команда;
break;
case Совпадение3:
команда;
break;
default:
оператор;
break;
}

скрытый текст3.1. Параметр ВыражениеДляСравнения - выражение, в результате вычисления которого получается как правило целое число. Команда switch сравнивает результат ВыражениеДляСравнения с каждым последующим Совпадением. Если обнаруживается совпадение, то исполняется команда или набор команд, которые прописаны за данным оператором. Если совпадений не будет, то исполняется команда после ключевого слова default. Однако оператор default не является обязательным. В случае без него при отсутствии совпадений программа не выполняет никаких действий.

3.2. Каждая секция case обычно заканчивается командой break, которая передаёт управление к концу команды switch. Если ее не использовать, выполнение кода продолжится. Хотя иногда это и используется. Если код в блоках case совпадает, их можно объединить.

case 4:
case 6:
case 9:
case 11:
numDays = 30;
break;

3.3. Оператор switch отличается от оператора if тем, что он может выполнять проверку только равенства, а оператор if может вычислять результат булева выражения любого типа. Две константы case в одном и том же операторе switch не могут иметь одинаковые значения. Тип каждого значения должен быть совместим с типом выражения. Можно использовать простые типы byte, short, char, int. Также можно использовать Enum и String (начиная с JDK7), и специальные классы, которые являются обёрткой для примитивных типов: Character, Byte, Short, Integer.

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

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