Синтаксические конструкции языка Java

1 Теоретическая часть

1.1 Элементы исходного текста

В Java различаются заглавные и строчные буквы.

Исходный текст на Java состоит из лексем. Лексема (token) – это последовательность символов, имеющих определенное совокупное значение. Между отдельными лексемами располагают разделители – пробел, табуляция, новая строка и т. д. Лексемы делятся на следующие группы:

  • ключевые слова
  • идентификаторы
  • литералы
  • знаки операций.

Набор ключевых слов ограничен. Ключевые слова нельзя использовать в качестве идентификаторов, т.е. они являются зарезервированными. В тексте занятий зарезервированные слова выделяются жирным шрифтом. Некоторые слова зарезервированы (const и goto), но не являются ключевыми. Эти слова нельзя использовать ни в каком контексте.

Идентификаторы используются для именования переменных, функций и других программных объектов. Первым символом должна быть буква или символ "_" (underscore character). Далее могут использоваться также цифры. Использование символа подчеркивания в начале имени нежелательно.

Хороший стиль программирования предполагает следующие правила именования:

  • имя переменной или функции начинается со строчной буквы
  • имя класса или интерфейса начинается с заглавной буквы
  • имя пакета состоит из одних строчных букв

Чем больше область видимости имени, тем это имя должно быть длиннее и содержательнее. Классы крайне нежелательно именовать одной буквой. Однобуквенные переменные обычно определяются внутри небольшого программного блока – короткой функции, цикла и т.д.

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

thisIsMyVariable

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

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

Язык Java поддерживает также так называемые комментарии Javadoc (/**  */).

Специальное средство, входящее в JDK – программа javadoc.exe – использует комментарии третьего вида для автоматической генерации документации. Для этого такие комментарии должны быть отформатированы по стандартам Javadoc. Можно добавлять комментарии для уровня классов и интерфейсов, а также для уровня методов и полей. Комментарии Javadoc должны быть размещены перед соответствующими фрагментами кода. Каждый комментарий состоит из описания и тегов. В комментарии javadoc можно вставлять теги форматирования HTML. Не рекомендуется вставлять теги, обеспечивающие вывод жирным шрифтом, курсивом, смысловое выделение и т. д. Имеется специальный набор тегов Javadoc, таких как @author, @param, @return и т. д. Эти теги обеспечивают стандартное представление документации, касающейся классов и методов. Например, следующие комментарии перед методом

/**
* Возвращает имя файла по соответствующему индексу.<br>
* Имена файлов хранятся в массиве строк.
* @param i индекс
* @return имя файла.
* */
public String getFileName(int i) {
    return names[i];
}

обеспечивают генерацию следующего фрагмента HTML-файла документации:

Method Detail

getFileName

public String getFileName(int i)
Возвращает имя файла по соответствующему индексу.
Имена файлов хранятся в массиве строк.
Parameters:
i - индекс
Returns:
file имя файла.

1.2 Определение локальных переменных. Примитивные типы

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

Локальные переменные определяются внутри методов. Описание локальных переменных начинается с имени типа, после которого следует список имен переменных этого типа. Переменные могут быть проинициализированы начальными значениями. Например:

int i = 11;
double d = 0, x;
float f; 
int j, k;

Локальные переменные могут быть определены в любом месте внутри тела функции.

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

int i;
int k = i + 1; // Ошибка! Неопределенное значение

Неинициализированные переменные можно использовать для присваивания им значений:

int i;
int k = 10;
i = k + 12;

Ключевое слово final применительно к именам переменных означает, что переменные не могут быть изменены.

final int h = 0;

Слово const зарезервировано, но не используется.

Примитивные, или встроенные типы делятся на целые типы, типы с плавающей точкой, символьные и Булевы. Примитивные типы приведены в следующей таблице.

Примитивные типы данных

Ключевое слово Описание Размеры
Целые типы
byte маленькое целое (от -128 до 127) 8 битов
short короткое целое (от -32768 до 32767) 16 битов
int целое (-2147483648 до 2147483647) 32 бита
long длинное целое (от -9223372036854775808 до 9223372036854775807) 64 бита
Типы с плавающей точкой
float вещественное число обычной точности 32 бита
double вещественное число повышенной точности 64 бита
Прочие типы
char символ кодировки Unicode 16 битов
boolean Булево значение (true или false)  

Во многих других языках (например, в С и в С++) формат и размеры примитивных типов данных зависят от платформы (DOS, Win 32 и т. д.). В Java размеры и формат стандартизированы и не зависят от платформы.

Константы целого типа записываются в виде последовательности десятичных цифр. Тип константы по умолчанию – int. Он может быть уточнен добавлением в конце константы букв L или l (тип long). Целые константы могут записываться в восьмеричной системе счисления, в этом случае первой цифрой должна быть цифра 0, число может содержать только цифры 0 ... 7. Целые константы можно записывать и в шестнадцатеричной системе счисления, в этом случае запись константы начинается с символов 0x или 0X. Для обозначения цифр более 9 используются латинские буквы a, b, c, d, e и f (большие или маленькие). Например:

int octal = 023; // 19
int hex = 0xEF;  // 239

В Java 7 появилась возможность задавать также двоичные константы (с использованием префиксов 0B или 0b):

int m = 0b110011; // 51

Кроме того, группы разрядов в константах можно разделять знаком подчеркивания, например: int n = 1_048_576;

Константы типа char берут в одиночные кавычки (апострофы), значение константы задается либо знаком из текущего набора символов, или целой константой, которой предшествует обратная косая черта (символ с заданным кодом). Есть ряд специальных символов, которые могут использоваться как значение константы типа char (такие двойные символы называются управляющими последовательностями, или escape-последовательностями):

'\n' - новая строка,
'\t' - горизонтальная табуляция,
'\r' - перевод на начало строки, 
'\'' - одиночные кавычки (апостроф),
'\"' - двойные кавычки,
'\\' - обратная косая черта (backslash).

Для хранения данных символьного типа в памяти используется таблица Unicode.

Константы вещественных типов могут записываться в форме с точкой или в экспоненциальном формате и по умолчанию имеют тип double. При необходимости тип константы можно уточнить, записав конце суффикс f или F для типа float, суффикс d или D для типа double. Например:

1.5f    // 1.5   типа float
2.4E-2d // 0.024 типа double

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

int   i  = 10;
float f  = i;        // Такое преобразование допускается
long  l  = f;        // Ошибка! Сужающее преобразование
long  l1 = (long) f; // Явное приведение типов

Числа без десятичной точки интерпретируются как целые (типа int). Числа с плавающей точкой имеют тип double. Для приведения их к более узким типам используется явное приведение типов:

float f  = 10.5;         // Ошибка! Сужающее преобразование
float f1 = (float) 10.5; // Явное приведение типов

За исключением редких случаев, связанных с критической нехваткой памяти, вместо float целесообразно использовать double. Тем более, что стандартные математические функции для работы с вещественными числами получают параметры и возвращают результат типа double.

В Java нет беззнаковых целых типов. Это связано с тем, что беззнаковые целые считаются небезопасными, так как не вполне корректно ведут себя при переходе через 0. Обычно такие ошибки не отслеживаются при выполнении. Например, при использовании беззнаковых целых в C++ мы можем получить такие странные результаты:

// C++:
unsigned int a = 1;
unsigned int b = 2;
unsigned int c = a - b; // c = 4294967295

Единственным оправданным применением беззнаковых целых могли бы быть побитовые операции (они будут рассмотрены ниже). Однако и в этом случае в Java используются целые со знаком.

Булевым переменным можно присваивать только константы true и false. Переменные типа boolean нельзя неявно или явно приводить к другим типам и наоборот. Отсутствие неявного приведения целого к типу boolean позволяет избежать ошибок, связанных с неправильным использованием присваивания.

Константа-строка состоит из символов, которые берут в двойные кавычки. Например:

"Это строка"

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

int k = 1;
double d = 2.5;
System.out.println(k + " " + d); // 1 2.5

1.3 Выражения и операции

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

Хороший стиль программирования предполагает выделение знаков бинарных операций пробелами с двух сторон.

К арифметическим операциям относятся +, – (бинарные и унарные), *, / а также операция взятия остатка % (применима только к целым). Если / применяется к целым (оба операнда – целые), то результатом деления будет тоже целое, а остаток отбрасывается. Если хотя бы один операнд имеет тип с плавающей точкой (вещественный), мы получаем вещественный результат.

System.out.println(1 / 2);   // 0
System.out.println(1.0 / 2); // 0.5

Побитовые операции используются для операндов целочисленного типа.

Унарная операция НЕ (~) применяется для инвертирования разрядов операнда.

byte bits = 23;           // 0 0 0 1 0 1 1 1
bits = (byte)~bits;       // 1 1 1 0 1 0 0 0 или 232
System.out.println(bits); // -24

Последний результат кажется несколько странным. Дело в том, что для представления отрицательных чисел используется их двоичное дополнение до 2 в степени количества бит, в данном случае – до 256.

232 – 256 = -24

При побитовом отрицании и других подобных операциях необходимо явное приведение к типу byte, так как по умолчанию побитовые операции возвращают целое значение.

Бинарные операции сдвига влево (<<) и вправо (>>) предназначены для сдвига разрядов первого операнда на число разрядов, определяемое вторым операндом. При этом разряды, которые выходят за границу, отбрасываются, а вместо недостающих вставляются нули.

byte bits = 1;               // 0 0 0 0 0 0 0 1
bits = (byte) (bits << 3);   // 0 0 0 0 1 0 0 0
System.out.println(bits);    // 8
bits = (byte) (bits >> 2);   // 0 0 0 0 0 0 1 0
System.out.println(bits);    // 2

При работе с целыми величинами операция >> сохраняет знак величины. Специальная операция сдвига >>> (отсутствующая в С++) сдвигает все биты, в том числе и старший.

byte bits = -24;          // 1 1 1 0 1 0 0 0
int i = bits >> 1;
System.out.println(i);    // -12
i = bits >>> 1;
System.out.println(i);    // 2147483636

Бинарная операция И (&) выполняет логическую операцию И поразрядно для своих операндов. Аналогично, операция ИЛИ (|) поразрядно выполняет логическую операцию ИЛИ. Бинарная операция "исключающее ИЛИ" (^) выполняет операцию "исключающее ИЛИ" для каждого разряда: для одинаковых разрядов результат 0, а для отличающихся -1.

byte result;
byte b1 = 101;              //  0 1 1 0 0 1 0 1
byte b2 = 47;               //  0 0 1 0 1 1 1 1
result = (byte) (b1 & b2);  //  0 0 1 0 0 1 0 1
System.out.println(result); //  37     
result = (byte) (b1 | b2);  //  0 1 1 0 1 1 1 1
System.out.println(result); //  111
result = (byte) (b1 ^ b2);  //  0 1 0 0 1 0 1 0
System.out.println(result); //  74

К операциям отношения относятся проверка на равенство == и на неравенство != , а также проверки > >= < <=.

К логическим операциям относятся логическое И (&&) и ИЛИ (||). Операции отношения и логические возвращают значение типа boolean (true – если условие истинно, и false – если условие ложно). Логическое отрицание ! преобразует свой операнд в истину (true) если он равен false и в ложь (false) если он равен true.

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

К операциям присваивания относятся операции простого и составного присваивания. Результатом операции простого присваивания является значение того выражения, которое присваивается левому операнду.

Составное присваивание можно представить в общем виде следующим образом:

a op= b 

В данном случае op – арифметическая или побитовая операция: + - * / % | & ^ << >>. Каждая составная операция эквивалентна следующему присваиванию:

a = (a) op (b);

Например,

x += 5;

эквивалентно

x = x + 5;

Операция инкремента ++ обеспечивает увеличение целой переменной на единицу. Она имеет две формы: префиксную и постфиксную. Префиксная форма обеспечивает увеличение переменной до того, как значение операции будет использовано, а постфиксная – после. Операция декремента (--) обеспечивает уменьшение переменной на единицу и правила ее использования аналогичны.

Условная операция (тернарная) имеет следующий вид:

условие ? выражение1 : выражение2 

Вначале вычисляется значение условия. Если оно истинно, то вычисляется выражение1 и его значение возвращается условной операцией. Если значение условия ложно, то вычисляется выражение2 и возвращается его значение.

Порядок применения унарных операций и операций присваивания "справа налево", а всех остальных операций – "слева направо".

В Java есть операция "запятая", которая разрешается только в заголовках циклов, например:

int i, j;
for (i = 0, j = 0; i < 10; i++, j += 2) {
    System.out.println(i + " " + j);
}

В Java не допускается непосредственная работа с указателями, следовательно, нет операций разыменования, выбора элемента по указателю и взятия адреса (*, -> и &).

В Java нет операции sizeof() поскольку ее главное предназначение в С++ – выяснять размеры тех или иных данных при переносе исходного текста на другую платформу. В Java размеры всех типов стандартизированы.

Операции имеют приоритет, который можно изменить, используя скобки. Далее в таблице приведены операции Java в порядке убывания приоритета.

Операции Обозначение
постфиксные ++ --
унарные (префиксные) ++ -- + - ~ !
умножение и деление * / %
сложение и вычитание + -
сдвиг << >> >>>
операции отношения < > <= >= instanceof
операции проверки равенства == !=
побитовое И &
побитовое исключающее ИЛИ ^
побитовое ИЛИ |
логическое И &&
логическое ИЛИ ||
тернарная операция ? :
присваивание = += -= *= /= %= &= ^= |= <<= >>= >>>=

1.4 Инструкции (операторы). Управление выполнением программы

Инструкция (утверждение, оператор, англ. statement) – наименьшая автономная часть языка программирования, определяющие действия, выполняемые программой. Программа представляет собой последовательность инструкций. Большинство инструкций языка Java аналогично C++.

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

Инструкция-выражение представляет собой полное выражение, заканчивающееся точкой с запятой. Например:

k = i + j + 1;

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

В Java нельзя во внутреннем блоке определять локальные имена, уже описанные в наружном блоке:

{
    int i = 0; {
        int j = 1; // Переменная j определяется во внутреннем блоке
        int i = 2; // Ошибка! Переменная i определена в наружном блоке
    }
}

Инструкции выбора – условная инструкция и переключатель. Условная инструкция применяется в двух видах:

if (условное_выражение)
    инструкция_1
else
    инструкция_2

или

if (условное_выражение)
    инструкция_1

При выполнении данной инструкции вычисляется условное выражение и если оно истинно (true), то выполняется инструкция_1, а иначе – инструкция_2. Условное выражение должно быть типа boolean.

Переключатель позволяет выбрать одну из нескольких возможных ветвей вычислений и строится по схеме:

switch (целое_или_строковое_выражение) блок

Блок в этом случае представляет собой тело переключателя и имеет такой вид:

{
case константа_1: 
    инструкции
case константа_2: 
    инструкции
    ...
default: 
    инструкции
}

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

Инструкции цикла представлены в трех вариантах: цикл с предусловием, цикл с постусловием и цикл с параметром.

Цикл с предусловием строится по схеме

while (выражение-условие)
    инструкция (тело цикла)

При каждом повторении цикла вычисляется выражение-условие и если значение этого выражения true, выполняется инструкция – тело цикла. Если условие дает значение false, инструкция (тело цикла) не выполняется и осуществляется переход к последующим инструкциям.

Цикл с постусловием строится по схеме:

do 
    инструкция (тело цикла)
while (выражение-условие); 

Выражение-условие вычисляется и проверяется после каждого повторения инструкции – тела цикла, цикл повторяется, пока условие выполняется. Тело цикла в цикле с постусловием выполняется хотя бы один раз.

Цикл с параметром строится по схеме:

for (E1; E2; E3)
    инструкция (тело цикла)

где E1, E2 и E3 – выражения скалярного типа. Цикл с параметром реализуется по следующему алгоритму:

  • вычисляется выражение E1 (обычно это выражение выполняет подготовку к началу цикла);
  • вычисляется выражение E2 и если оно равно false выполняется переход к следующей инструкции программы (выход из цикла);
  • если E2 не равно false, выполняется инструкция – тело цикла;
  • вычисляется выражение E3 – выполняется подготовка к повторению цикла, после чего снова выполняется выражение E2.

В следующем примере сумма

y = 12 + 22 + 32 + ... + n2

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

С помощью цикла while:

int y = 0;
int i = 1;
while (i <= n) {
    y += i * i;
    i++;
}

С помощью цикла do ... while:

int y = 0;
int i = 1;
do {
    y += i * i;
    i++;
}
while (i <= n);

С помощью цикла for:

int y = 0;
for (int i = 1; i <= n; i++) {
    y += i * i;
}

Начиная с версии JDK 1.5 (Java 5) синтаксис цикла for расширен для более удобной работы с массивами. Это расширение будет рассмотрено позже.

В сочетании с инструкциями цикла используются инструкции перехода – инструкция break, позволяющая прервать выполнение самой внутренней из объемлющих циклических конструкций, инструкция continue, прерывающая текущую итерацию самого внутреннего из объемлющих его циклов while, do или for. Обычно break используется в конструкции:

if (условие_досрочного_завершения_цикла)
    break;

В Java после ключевых слов break и continue можно разместить метку, которая предшествует одному из вложенных циклов. В этом случае утверждения относятся не к самому внутреннему, а к помеченному циклу. Например:

    int a;
    . . .
    double b = 0;
label:
    for (int i = 0; i < 10; i++) {
        for (int j = 0; j < 10; j++) {
            if (i + j + a == 0) {
                break label;
            }
            b += 1 / (i + j + a);
        }
    }

Утверждение goto не допускается в языке Java, но goto является зарезервированным словом. Это слово никак не может быть использовано.

1.5 Использование стандартных средств ввода-вывода

Для обеспечения консольного ввода Java предоставляет несколько вариантов:

  • непосредственное использование потока ввода System.in, в частности его функции read();
  • использование класса java.io.Console;
  • использование класса java.util.Scanner.

Недостатком непосредственного использования функции System.in.read() является обилие ручной работы. Так, чтение данных с помощью потока System.in осуществляется в массив элементов типа byte. Далее, требуется определение реального количества введенных байтов. Кроме того, необходима обработка возможных исключений. С появлением классов java.io.Console и java.util.Scanner ввод данных с клавиатуры существенно упростился.

Класс java.io.Console, появившийся в JDK 1.6, предоставляет упрощенные средства консольного ввода-вывода. Например, так с клавиатуры можно прочитать строку:

java.io.Console console = System.console(); // Получение системной консоли
String s = console.readLine();              // Чтение строки

К недостаткам использования данного класса можно отнести следующие:

  • класс предоставляет простые функции только для чтения строки и вывода в стиле языка C, что связано с необходимостью дополнительной "ручной" работы;
  • консольное окно большинства интегрированных сред не поддерживает консольного ввода, осуществляемого средствами класса java.io.Console, следовательно, отладка таких программ затруднена.

Наиболее удобным средством ввода данных является класс java.util.Scanner. Класс Scanner предоставляет функции для чтения данных из различных источников, например, из файлов. В нашем случае мы указываем на необходимость чтения с клавиатуры (стандартный поток ввода System.in). Для чтения данных используются функции nextDouble() (чтение вещественного числа, которое может быть как целым, так и дробным), nextInt() (чтение заведомо целого числа) и т. д.

Для возможности работы с классом Scanner перед описанием класса необходимо добавить:

import java.util.Scanner;

Это позволит использовать имя Scanner без дополнительного префикса.

Для организации чтения данных с помощью класса Scanner необходимо создать объект этого класса, проинициализировав его стандартным потоком System.in. Далее можно читать данные. Например:

package ua.inf.iwanoff.third;

import java.util.Scanner;

public class ScannerTest {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String s = scanner.next();       // чтение строки
        double d = scanner.nextDouble(); // чтение вещественного числа
        int i = scanner.nextInt();       // чтение целого числа
        // ... использование введенных данных
    }

}

Большим удобством класса является возможность произвольного размещения данных во входном потоке: отдельные данные можно разделять пробелами, табуляцией, либо переводом строки. Единственное исключение – использование функции nextLine(), которая читает данные до перевода строки.

Для вывода в консольное окно используются функции print(), println() и printf() потока out класса System. Функции print() и println() реализованы для всех стандартных типов данных, но чаще всего их используют с параметрами строкового типа. Данные выводятся "как есть", с использованием форматирования по умолчанию. В отличие от print(), println() функция осуществляет перевод курсора на новую строку после вывода.

Функция printf() во многом аналогична подобной функции языка C. Первый параметр – так называемая строка форматирования. Далее следует произвольное количество параметров, значения которых следует вывести на консоль. Например:

    double d = 3.5;
    int i = 12;
    System.out.printf("%f %d%n", d, i);

При форматировании используются следующие спецификаторы формата:

Спецификатор формата
Выполняемое форматирование
%a

Шестнадцатеричное значение с плавающей точкой

%b

Логическое (булево) значение аргумента

%c

Символьное представление аргумента

%d

Десятичное целое значение аргумента

%h

Хэш-код аргумента

%e

Экспоненциальное представление аргумента

%f

Десятичное значение с плавающей точкой

%g

Выбирает более короткое представление из двух: %е или %f

%o

Восьмеричное целое значение аргумента

%n

Вставка символа новой строки

%t

Время и дата

%x

Шестнадцатеричное целое значение аргумента

%%

Вставка знака %

 

Более подробно форматированный вывод будет рассмотрен в следующем модуле при изучении работы с текстовыми данными.

Имеется также возможность вывода сообщений в поток System.err (стандартный поток сообщений об ошибках). Вывод в этот поток имеет более высокий приоритет, чем вывод в System.out, поэтому иногда сообщения об ошибках опережают "нормальный" вывод.

1.6 Использование аргументов командной строки

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

public static void main(String[] args) {
    System.out.println(args[0]);
}

Аргументы командной строки в программе представлены в виде массива строк. Количество аргументов, которые были введены в командной строке, можно получить с помощью выражения args.length. В следующем примере программа осуществляет чтение из командной строки имени пользователя и выводит приветствие:

package ua.inf.iwanoff.third;

public class HelloUser {

    public static void main(String[] args) {
        if (args.length > 0) {
            System.out.println("Привет, " + args[0] + "!");
        }
        else {
            System.out.println("Привет, незнакомец!");
        }
    }

}

Из приведенного примера видно, что строки можно сшивать с помощью операции "+".

Допустим, необходимые пакеты были созданы в папке c:\Examples. Тогда после компиляции файла HelloUser.java запустить приложение можно такой командой:

java -cp c:\Examples ua.inf.iwanoff.third.HelloUser Вася

Получим сообщение на консоли:

Привет, Вася!

1.7 Статические поля и методы

1.7.1 Определение статических методов и полей

В Java термины "метод" и "функция" являются синонимами. Все инструкции могут располагаться только внутри функций.

В языках программирования функция (function, процедура, подпрограмма) представляет собой часть программного кода, на которую передается управление при вызове, далее выполняются инструкции, определяющие код этой функции, после чего управление передается обратно в точку вызова функции. Функции могут быть переданы некоторые значения в виде так называемых параметров (или аргументов, arguments). На основании значений этих параметров вычисляется так называемое возвращаемое значение. Оно передается в точку вызова функции, где может быть использовано в различных целях, в соответствии со своим типом.

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

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

спецификаторы static тип_результата имя_функции(список_формальных_параметров) тело

Перед телом функции может располагаться список исключений, генерируемых внутри функции. Тело функции представляет собой составное утверждение (блок).

В каждой Java-программе присутствует, по крайней мере, статическая функция main(), с которой начинается выполнение программы. Из метода main() могут быть вызваны другие функции. Функции могут вызываться из выражений (если эти функции возвращают результат) или отдельной инструкцией (если тип результата - void). Например:

static void printHello() {
    System.out.println("Hello!");
}

public static void main(String[] args) {
    printHello();
}    

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

Статические поля (static fields) в Java - это данные, описанные в области видимости класса и не являющиеся частью объекта. Статические поля во многом аналогичны глобальным переменным, используемым во многих языках программирования. Как и глобальные переменные, статические поля доступны для чтения и записи в различных функциях. Например:

package ua.inf.iwanoff.third;

public class TestStatic {

    static boolean printed;

    static void printHello() {
        System.out.println("Hello!");
        printed = true;
    }

    public static void main(String[] args) {
        printed = false;
        printHello();
        System.out.println(printed); // true
    }

}

Статическое поле может быть проинициализировано начальным значением:

static boolean printed = false;

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

1.7.2 Параметры функции

Параметры (аргументы) функции, указываемые в списке при определении функции, называются формальными (formal arguments). Параметры, указываемые при вызове функции, называются фактическими (actual arguments).

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

Список описаний формальных параметров:

тип_параметра имя_параметра, тип_параметра имя_параметра, ...

Пример определения функции, вычисляющей сумму двух целых чисел:

static int sum(int a, int b) {
    int c = a + b;
    return c;   // Можно обойтись без переменной с: return a + b;
}

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

int x = 4;
int y = 5;
int z = sum(x, y);
int t = sum(1, 3);

Функция может не иметь параметров:

static int zero() {
    return 0;
}

При вызове такой функции также нужно использовать скобки:

System.out.println(zero());

Инструкция return обеспечивает механизм завершения работы функции. Если утверждение return сопровождается некоторым выражением, значение этого выражения становится возвращаемым значением функции.

Функция может не возвращать никакого результата. Для обозначения этого используется тип void. В этом случае в теле функции return может отсутствовать. Например:

static void hello() {
    System.out.println("Hello!");
}

Такую функцию можно вызвать только отдельным утверждением:

hello();

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

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

public class Test;

    static void f(int k) {
        k++;       // k = 2;
    }

    public static void main(String[] args) {
        int k = 1;
        f(k);
        System.out.println(k); // k = 1;
    }
}

При передаче в функции параметров-ссылок ссылка копируется, а объект, на которую она ссылается - тот же самый что и в вызывающей функции. Действия, произведенные в функции над этим объектом, проводят к изменению его в вызывающей функции. В отличие от C++ и C#, Java не предоставляет возможности передачи параметров типов-значений по ссылке.

1.7.3 Перегрузка имен функций

Перегрузкой имени (function name overload) называется его использование для обозначения разных операций над разными типами.

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

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

// Выбор максимального из двух целых чисел:
static int max(int x, int y) { }
// Выбор максимального из трех вещественных чисел:
static double max(double x, double y, double z) { }
// Выбор максимального элемента массива:
static int max(int[] a) { }

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

Примечание: В отличие от C++ и C#, в Java не поддерживается механизм параметров по умолчанию.

1.7.4 Рекурсия

Функция может вызывать саму себя. Такой прием называется рекурсией (recursion). Рекурсия может быть прямой и косвенной. При использовании прямой рекурсии вызов функции находится в ней самой. Косвенная рекурсия осуществляется через другую функцию. Иногда рекурсию можно использовать вместо циклов. В следующем примере вычисляется сумма квадратов натуральных чисел:

package ua.inf.iwanoff.third;

public class Sum {

    public static double sum(int n) {
        if (n <= 1) {
            return 1;
        }
        else {
            return n * n + sum(n - 1);
        }
    }

    public static void main(String[] args) {
        System.out.println(sum(5));
    }

}

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

Однако с точки зрения эффективности использование рекурсии дает всегда менее эффективное решение, чем использование циклов. Кроме того, неправильное использование рекурсии может привести к переполнению программного стека.

1.8 Вызов статических функций из других классов. Стандартные математические функции

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

Функция
Содержание
Пример вызова
double pow(double a, double b)
Вычисление ab
Math.pow(x, y)
double sqrt(double a)
Вычисление квадратного корня
Math.sqrt(x)
double sin(double a)
Вычисление синуса
Math.sin(x)
double cos(double a)
Вычисление косинуса
Math.cos(x)
double tan(double a)
Вычисление тангенса
Math.tan(x)
double asin(double a)
Вычисление арксинуса
Math.asin(x)
double acos(double a)
Вычисление арккосинуса
Math.acos(x)
double atan(double a)
Вычисление арктангенса
Math.atan(x)
double exp(double a)
Вычисление ex
Math.exp(x)
double log(double a)
Вычисление натурального логарифма
Math.log(x)
double abs(double a)
int abs(int a)
Определение модуля числа
Math.abs(x)
long round(double a)
Округление до ближайшего целого
Math.round(x)

Кроме математических функций, класс Math предоставляет такие полезные константы, как Math.PI, Math.E.

Если классы находятся в других пакетах (не в java.lang), для вызова этих функций следует применять префикс - имя_пакета.имя_классса. Если необходимы классы вложенных пакетов, префикс будет еще более сложным. Для того, чтобы обойтись без использования полных имен, применяется конструкция import.

Не следует включать в программу утверждение import java.lang.*. Этот пакет импортируется автоматически.

В ранних версиях Java (до JDK 1.4 включительно) обращение к отдельным функциям классов требовало явного указания класса (или объекта для нестатических функций). Например:

double d = Math.sin(1); // sin() - статическая функция класса java.lang.Math

Дополнительная форма импорта, появившаяся в Java 5, позволяет импортировать только статические элементы указанного класса:

import static java.lang.Math.*;

Теперь статические элементы можно использовать без дополнительного квалификатора, например:

double d = sin(1);

2 Примеры программ

2.1 Сумма цифр

Допустим, необходимо определить сумму цифр числа. Можно использовать нахождение остатка от деления.

package ua.inf.iwanoff.third;

import java.util.Scanner;

public class SumOfDigits {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int sum = 0;
        while (n > 0) {
            sum += n % 10;
            n /= 10;
        }
        System.out.println(sum);
    }

}

2.2 Вычисление экспоненты

Предположим, что мы хотим написать программу, которая считывает x и вычисляет ex:

y = ex = 1 + x + x2/2! + x3/3! + ...

Цикл прекращается, если новое слагаемое меньше 0.00001.

Первый вариант использует цикл while:

package ua.inf.iwanoff.third;

import java.util.Scanner;

public class Exponent {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        double x = scanner.nextDouble();
        double z = 1; // слагаемое
        int i = 1;
        double y = 0;
        while (z > 0.00001) {
            y += z;
            z *= x / i;
            i++;
        }
        System.out.println("y = " + y);
    }

}

Альтернативное решение (цикл for):

package ua.inf.iwanoff.third;

import java.util.Scanner;

public class Exponent {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        double x = scanner.nextDouble();
        double z = 1; // слагаемое
        double y = 0;
        for (int i = 1; z > 0.00001; i++) {
            y += z;
            z *= x / i;
        }
        System.out.println("y = " + y);
    }

}

2.3 Степени числа 2

Допустим, нам необходимо получить значения степеней 2 до заданного n (от 0 до 30) включительно. Можно реализовать два варианта – с использованием арифметических действий и побитовых операций.

2.3.1 Использование арифметических операций

В созданный ранее пакет добавляем новый класс с именем Powers. Для выполнения однотипных действий целесообразно создать цикл, в теле которого вычисляется очередная степень 2 и выводится на экран.

На Java алгоритм вычисления можно представить в следующем виде:

int n = ... // ввод n
final int k = 2;  // основание
int power = 1;
for (int i = 0; i <= n; i++) {
    System.out.printf("2 ^ %2d = %d\n", i, power);
    power *= k;
}

В данном примере для описания переменных используется тип int (целый), так как и основание, и показатель, и степень – целые числа. Начальное значение результата

int power = 1;

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

power = power * k;    

писать

power *= k;    

Программа будет иметь вид:

package ua.inf.iwanoff.third;

import java.util.Scanner;

public class Powers {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("Введите n в диапазоне от 0 до 30");
        int n = scanner.nextInt();
        int power = 1;
        final int k = 2;
        if (n < 0 || n > 30) {
            System.err.println("Неправильное значение n");
        }
        else {
            for (int i = 0; i <= n; i++) {
                System.out.printf("2 ^ %2d = %d\n", i, power);
                power *= k;
            }
        }
    }

}

2.3.2 Использование побитовых операций

Наиболее эффективным будет подход, использующий побитовую операцию сдвига.

package ua.inf.iwanoff.third;

import java.util.Scanner;

public class PowersOfTwo {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("Введите n в диапазоне от 0 до 30");
        int n = scanner.nextInt();
        if (n < 0 || n > 30) {
            System.err.println("Неправильное значение n!");
        }
        else {
            for (int i = 0; i <= n; i++) {
                System.out.printf("2 ^ %2d = %d\n", i, 1 << i);
            }
        }
    }

}

2.4 Двоичное представление числа

Необходимо получить двоичное представление заданного числа.

package ua.inf.iwanoff.third;

import java.util.Scanner;

public class ToBinary {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        byte b = scanner.nextByte();
        for (int i = 7; i >= 0; i--) {
            System.out.print((byte)(1 & (b >> i)));
        }
    }

}

В приведенном выше цикле исходное число смещается на различное количество разрядов и младший бит результата выделяется путем выполнения операции И с единицей. Функция System.out.print() без параметров используется для вывода данных без перевода строки.

2.5 Наибольший общий делитель

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

package ua.inf.iwanoff.third;

import java.util.Scanner;

public class GreatestCommonDivisor {

    static int gcd(int m, int n) {
        while (m != n) {
            if (m > n) {
                m -= n;
            }
            else {
                n -= m;
            }
        }
        return m;
    }

    public static void main(String[] args) {
        Scanner s = new Scanner(System.in);
        int m = s.nextInt();
        int n = s.nextInt();
        System.out.println(gcd(m, n));
    }

}

2.6 Функция для вычисления целой степени

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

package ua.inf.iwanoff.third;

import java.util.Scanner;

public class PowerTest {

    static double power(double x, int n) {
        double p = 1;
        if (n >= 0) {
            for (int i = 1; i <= n; i++) {
                p *= x;
            }
            return p;
        }
        else {
            for (int i = 1; i <= -n; i++) {
                p *= x;
            }
            return 1 / p;
        }
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        double x = scanner.nextDouble();
        int n = scanner.nextInt();
        double y = power(x, n);
        System.out.println(y);
    }

}

2.7 Вычисления целой степени с использованием рекурсии

Программу предыдущего примера можно реализовать с помощью рекурсивной функции:

package ua.inf.iwanoff.third;

import java.util.Scanner;

public class RecursionTest {

    static double power(double x, int n) {
        if (n < 0) {
            return 1 / power(x, -n);
        }
        if (n == 0) {
            return 1;
        }
        return x * power(x, n - 1);
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        double x = scanner.nextDouble();
        int n = scanner.nextInt();
        double y = power(x, n);
        System.out.println(y);
    }

}

2.8 Вычисление π

Один из способов вычисления числа π, является подсчет суммы следующего ряда:

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

2.8.1 Использование цикла

Вначале реализуем вариант без рекурсии. Программа с функцией будет иметь следующий вид:

package ua.inf.iwanoff.third;

import java.util.Scanner;

public class PiTest {

    static double pi(double eps) {
        double sum = 0;
        double k = 1;
        double z = 4;
        double i = 1;
        while (Math.abs(z) > eps) {
            sum += z;
            i += 2;
            k = -k;
            z = (4 * k) / i;
        }
        return sum;
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        double eps = scanner.nextDouble();
        double y = pi(eps);
        System.out.println(y);
    }

}

В данном варианте можно вводить сколь угодно маленькое значение точности. Однако при слишком маленьких значениях процесс может затянуться.

2.8.2 Использование рекурсии

Второй вариант использует рекурсию. Основная рекурсивная функция - с двумя параметрами. Вначале ее нужно вызвать со значением параметра 1. Для удобства использования предлагается вариант с одним параметром. Программа с функцией будет иметь следующий вид:

package ua.inf.iwanoff.third;

import java.util.Scanner;

public class PiWithRecursion {

    static double pi(double eps, int n) {
        double z = 4.0 / n;
        if (z < eps) {
            return z;
        }
        return z - pi(eps, n + 2);
    }

    static double pi(double eps) {
        return pi(eps, 1);
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        double eps = scanner.nextDouble();
        double y = pi(eps);
        System.out.println(y);
    }

}

Недостатками реализации с рекурсией является большое время работы и переполнение программного стека. В нашем случае можно получить результат только для точности порядка одной миллиардной.

2.9 Разделение программы на независимые части

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

y = 12 + 22 + 32 + ... + n2

Первая реализация (без разделения на части):

package ua.inf.iwanoff.third;

import java.util.Scanner;

public class SplitDemo {

    public static void main(String[] args) {
        int n;
        System.out.println("Input n:");
        n = new Scanner(System.in).nextInt();
        int y = 0;
        for (int i = 1; i <= n; i++) {
            y += i * i;
        }
        System.out.println("y = " + y);
    }

}

Самое простое решение разделения на отдельные функции заключается в использовании статических полей, которые фактически заменяют собой отсутствующие в Java глобальные переменные:

package ua.inf.iwanoff.third;

import java.util.Scanner;

public class SplitDemo {
    static int n;
    static int y = 0;

    static void read() {
        System.out.println("Input n:");
        n = new Scanner(System.in).nextInt();
    }

    static void calc() {
        for (int i = 1; i <= n; i++) {
            y += i * i;
        }
    }

    static void write()
    {
        System.out.println("y = " + y);
    }

    public static void main(String[] args) {
        read();
        calc();
        write();
    }

}

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

package ua.inf.iwanoff.third;

import java.util.Scanner;

public class SplitDemo {

    static int read() {
        System.out.println("Input n:");
        return new Scanner(System.in).nextInt();
    }

    static int calc(int n) {
        int y = 0;
        for (int i = 1; i <= n; i++)
            y += i * i;
        return y;
    }

    static void write(int y) {
        System.out.println("y = " + y);
    }

    public static void main(String[] args) {
        int n = read();
        int y = calc(n);
        write(y);
    }

}

Функция main() может быть реализована без каких-либо переменных.

  ...
  public static void main(String[] args) {
      write(calc(read()));
  }

3 Задания на самостоятельную работу

Примечание: здесь и далее звездочкой отмечены задания, обязательные к выполнению.

3.1 Решение квадратного уравнения

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

3.2 Сумма бесконечного ряда

Ввести значение eps и найти сумму ряда

y = 1 + 1/2 + 1/4 + 1/8 + 1/16 + ...

Добавлять слагаемые до тех пор, пока очередное слагаемое не станет меньше eps.

3.3 Степени числа 8*

Ввести значение n (от 0 до 10) и вывести значения степеней числа 8 до n включительно. Реализовать два подхода – с использованием арифметических и побитовых операций.

3.4 Двоичное представление числа*

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

3.6 Двоичное обратное представление числа*

Написать программу инвертированного двоичного представления числа так (в обратном порядке, слева – младшие биты, а справа – старшие).

3.5 Наибольший общий делитель

Реализовать функцию, которая вычисляет наибольший общий делитель двух чисел через использование рекурсии.

3.6 Поиск простых чисел

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

3.7 Определение чисел Фибоначчи*

Реализовать программу поиска чисел Фибоначчи:

F(1) = F(2) = 1; F(n) = F(n - 2) + F(n - 1)

Поиск n-го числа Фибоначчи осуществлять в отдельной функции. В функции main() осуществить ввод номера числа и вывод всех чисел Фибоначчи до введенного номера включительно. Реализовать два варианта - с использованием цикла и с использованием рекурсии.

3.8 Вычисление π*

Реализовать и протестировать функцию, которая вычисляет π с точностью до заданного ε по следующей формуле.

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

4 Контрольные вопросы

  1. Что такое лексема?
  2. Перечислите правила определения имен идентификаторов.
  3. Какая разница между ключевыми и зарезервированными словами?
  4. Можно ли в комментарий стиля C вложить другой комментарий стиля C?
  5. Чем отличаются комментарии Javadoc от других видов комментариев?
  6. Каким значением автоматически инициализируются вновь создаваемые локальные переменные?
  7. Можно ли использовать слово const для описания константы на Java?
  8. Размеры каких встроенных типов данных отличаются в различных реализациях Java?
  9. Как привести значение выражения типа boolean к целому типу?
  10. Как привести целое значение к значению типа boolean?
  11. На какие группы делятся операции по количеству операндов?
  12. Как получить дробное значение в результате деления двух целых чисел?
  13. Что происходит с младшими битами целого числа при побитовом сдвиге вправо?
  14. Для чего используется составное присваивание?
  15. Чем отличается префиксная и постфиксная форма инкремента и декремента?
  16. Можно ли применять операцию & вместо && для операндов типа boolean?
  17. Где может быть использована операция "запятая"?
  18. Для чего используется пустая инструкция?
  19. Как обратиться из внутреннего блока к локальной переменной наружного блока, если ее имя переопределено во внутреннем блоке?
  20. Можно ли использовать в переключателе выражение типа double для проверки условия ветвления?
  21. Какие существуют типы циклических конструкций и чем они отличаются по своему использованию?
  22. Тело какого из циклов всегда выполняется не зависимо от условия повторения?
  23. Всегда ли требуется метка при использовании break?
  24. Какие существуют средства поддержки ввода данных во время работы программы?
  25. Для чего используется класс Scanner?
  26. Как с помощью объекта класса Scanner прочитать целое значение?
  27. Как осуществляется форматированный вывод?
  28. Чем определяются типы параметров функции printf()?
  29. Что такое аргумент командной строки?
  30. Как в Java-программе использовать аргументы командной строки?
  31. Для чего используются статические поля?
  32. Что такое стек вызовов (программный стек)?
  33. Чем статические поля отличаются от глобальных и локальных переменных?
  34. С какой целью фрагменты кода выделяют в отдельные функции?
  35. В чем различие между формальными и фактическими параметрами?
  36. Как осуществляется сопоставление параметров при вызове функции?
  37. На что влияет тип возвращаемого значения функции?
  38. Для чего используются функции с результатом типа void?
  39. Обязательно ли использование утверждения return в теле функции?
  40. Можно ли использовать return в теле функции, возвращающей void?
  41. Можно ли передать параметры типов-значений по ссылке?
  42. В чем преимущества перегрузки имен функций?
  43. Можно ли в Java использовать параметры функций по умолчанию? С какой целью?
  44. Что такое рекурсия?
  45. В чем различие между прямой и косвенной рекурсией?
  46. Какие проблемы связаны с некорректным использованием рекурсии?
  47. В чем преимущества использования статического импорта?
  48. Можно ли обращаться к функциям из другого пакета, не используя инструкции import?

 

up