Освоюємо Java/Керування порядком виконання
Java, як і інші мови програмування, підтримує умовні інструкції та цикли, що визначають порядок виконання інструкцій у програмі. В англійській мові для цього поняття застосовують термін control flow — керування течією.
Блоки
ред.Перед тим як знайомитись з керувальними структурами, спочатку необхідно ознайомитися з блоками. Блок або складена інструкція – це будь-яка кількість простих інструкцій, які оточені парою фігурних дужок. Блок визначає область видимості ваших змінних. Блоки можуть бути вкладені в середину інших блоків. Ви вже зустрічалися з блоками при створенні найпростіших програм у методі main(). Наступний приклад демонструє вкладення блоку у блок методу main:
public static void main(String[] args)
{
int n;
. . .
{
int k;
. . .
} // змінна k визначена лише до цього місця
}
Проте не можна визначати однакові змінні в двох вкладених блоках (на відміну від С++, де це можливо).
public static void main(String[] args)
{
int n;
. . .
{
int k;
int n; // помилка! – не можна перевизначити n у внутрішньому блоці
. . .
}
}
Умовні інструкції
ред.Умовна інструкція в Java має форму:
if (умова) інструкція;
Умова повинна бути оточена дужками і, якщо, умова вірна (true) буде виконана інструкція за умовою, інакше вона не буде виконана, а буде виконана наступна інструкція після умовної інструкції.
Приклад:
int a = 5;
if (a < 100) System.out.println("Число менше ста");
Зазвичай, необхідно виконати не одну інструкцію, в такому разі інструкції розміщають у блоці:
if (умова){ iнструкція 1; ….. iнструкція n; }
В такому разі при істинності умови, виконуються усі інструкції у блоці, якщо умова невірна, то виконується наступна інструкція після закриваючої дужки блоку. Якщо ж необхідно здійснити певну дію в разі не виконання умови, то в такому разі застосовують умовну інструкцію наступного виду:
if (умова) інструкція1; else інструкція2;
if (yourSales >= target)
{
performance = "Satisfactory";
bonus = 100 + 0.01 * (yourSales - target);
}
else
{
performance = "Unsatisfactory";
bonus = 0;
}
Інструкції if можуть іти одна за одною без використання else:
if (x <= 0) if (x == 0) sign = 0; else sign = -1;
Для того, щоб програма була більш читабельна бажано застосовувати фігурні дужки:
if (x <= 0) { if (x == 0) sign = 0; else sign = -1; }
Вони нічого не змінюють, але вираз стає більш зрозумілим. Інструкція чи блок інструкцій виконується лише в разі виконання усіх умов.
Щоправда дану інструкцію можна також переписати ускладнивши умову використавши булевий оператор і (&&):
If (x <= 0 && x==0) sign = 0; else sign=-1;
Можна також використовувати повторюваність інструкцій if….else.
if (yourSales >= 2 * target)
{
performance = "Excellent";
bonus = 1000;
}
else if (yourSales >= 1.5 * target)
{
performance = "Fine";
bonus = 500;
}
else if (yourSales >= target)
{
performance = "Satisfactory";
bonus = 100;
}
else
{
System.out.println("You're fired");
}
Це дає можливість перевірити ряд умов, якщо попередні умови не виконуються.
Цикли
ред.Цикли – це послідовність інструкцій, які можуть повторно виконуватись певну кількість раз в залежності від заданої в програмі умови. Розрізняють цикли з передумовою, з післяумовою та з лічильником.
Цикл while
ред.Цикл while (перекладається як «доки») – це цикл з передумовою, тіло якого (тобто інструкція або блок інструкцій) виконується, якщо умова істинна. Якщо умова з самого початку хибна, то цикл не виконається жодного разу.
Загальний вигляд:
while (умова) інструкція;
Якщо немає фігурних дужок, то перша інструкція, яка йде після оголошення циклу, вважається тілом циклу. Всі інші інструкції знаходяться поза циклом. Якщо в циклі повинні виконуватись кілька інструкцій, то необхідно використати фігурні дужки. Вони також можуть використовуватись при одній інструкції заради кращого візуального розуміння коду.
while (умова){ інструкція 1; ... інструкція N; }
В наступному прикладі демонструється мінігра із вгадуванням числа від 0 до 10, яка створена з використанням циклу while:
import java.util.*;
public class Game {
public static void main(String[] args) {
Scanner in = new Scanner(System.in); // створюємо сканер для введення даних з консолі
Random generator = new Random(); //створюємо генератор випадкових чисел
System.out.println("Спробуйте відгадати число від 0 до 10");
int gn;
String more = "Y";
while (more.equals("Y") || more.equals("y")) { // поки змінна more рівна “Y” або “y”
gn = generator.nextInt(10); // генерація випадкового числа від 0 до 10;
System.out.print("Введіть число від 0 до 10: ");
int number = in.nextInt(); // зчитуємо число з клавіатури
if (gn == number)
System.out.print("Вгадали!!! Спробуєте ще раз? (Y/N)");
else
System.out.print("Не вгадали. Спробуєте ще раз? (Y/N)");
more = in.next(); // отримати відповідь
}
}
}
Результат виконання:
Спробуйте відгадати число від 0 до 10 Введіть число від 0 до 10: 6 Не вгадали. Спробуєте ще раз? (Y/N)y Введіть число від 0 до 10: 7 Вгадали!!! Спробуєте ще раз? (Y/N) n
Програма генерує випадкове число при кожному повторі циклу і пропонує вгадати його. Після вводу користувачем числа - виводить відповідне повідомлення вгадано чи ні. Після цього пропонується здійснити нову спробу. Якщо користувач вводить з клавіатури “Y" або “y”, то гра продовжується, якщо введе щось інше, то завершується.
Для генерації випадкових чисел використано клас Random, що містить методи для генерації випадкових чисел. Зокрема, у нашій програмі використано метод nextInt(), який дозволяє генерувати випадкові числа.
Для зчитування з клавіатури використано клас Scanner, який був доданий в java 5.0, для зручного вводу з клавіатури. Метод nextInt() – читає ціле число, next() – читає цілий рядок з клавіатури.
Для того, щоб переконатися, що користувач хоче продовжити гру використано метод equals() з класу String - Strint1.equals (String2), що перевіряє чи один рядок (String1) тексту рівний іншому (String2).
У вищенаведеній програмі пояснення потребують три рядка. Для початку Вам необхідно лише в загальному зрозуміти їхнє призначення.
import java.util.*;
Random generator = new Random();
gn=generator.nextInt(10);
Нам необхідний генератор цілих чисел, який реалізовує класс Random у пакеті java.util. Для цього ми імпортуємо відповідний клас (в С/С++ аналогом є підключення бібліотеки). Далі в програмі створюємо змінну generator, яка вказуватиме на екземпляр класу random і дозволятиме звернення до методів даного класу. new Random() - створює відповідний об'єкт. Для генерації випадкових чисел ми використовуємо метод nextInt() класу Random.
Цикл do/while
ред.Якщо необхідно, щоб умова виконувалася хоча б один раз можна скористатися циклом з післяумовою do/while:
do інструкція while (умова);
Зокрема, в програмі з вгадуванням чисел, більш логічніше було б застосувати саме даний цикл, оскільки необхідне хоча б одне виконання тіла циклу.
import java.util.*;
public class Tmp {
public static void main(String[] args) {
Scanner in = new Scanner(System.in); // створюємо Сканер для введення даних з консолі
Random generator = new Random(); // створюємо генератор випадкових чисел
System.out.println("Спробуйте відгадати число від 0 до 10");
int gn;
String more;
do {
gn = generator.nextInt(10); //генерація випадкового числа від 0 до 10;
System.out.print("Введіть число від 0 до 10: ");
int number = in.nextInt();
if (gn == number)
System.out.print("Вгадали!!! Спробуєте ще раз? (Y/N)");
else
System.out.print("Не вгадали. Спробуєте ще раз? (Y/N)");
more = in.next();
} while (more.equals("Y") || more.equals("y"));
}
}
Цикл з лічильником for
ред.Цикл for – доволі часто вживаний цикл. Він застосовується при необхідності виконати інструкції певну кількість раз з одночасним збільшенням або зменшенням певної змінної. Часто використовується для здійснення перебору певних масивів даних, зокрема, також для сортування масивів. Приклад використання:
for (int i = 1; i <= 10; i++) {
System.out.println(i);
}
Наведений вище приклад виведе на консолі в стовпчик числа від 1 до 10. Як бачимо в умові циклу перший слот відводиться для ініціалізації змінної, причому оголосити змінну можна і в іншому місці. Другий слот – для умови, яка перевіряється перед виконанням ітерації, третій слот – вказує як модифікувати змінну-лічильник. Тобто в наведеному прикладі при кожному виконанні ітерації, лічильник "і
" буде збільшуватися на одиницю поки не стане рівним десяти.
Найчастіше даний цикл використовується для перебору елементів масиву. Масив – це впорядкований набір даних одного типу. Найпростіший масив можна оголосити та ініціалізувати таким чином: int a[]={1, 5, 6, 1, 3};
. Для того, щоб звернутися до певного елементу масиву використовуються квадратні дужки з відповідним індексом елементу. Наприклад а[3] – звернення до четвертого елементу масиву (номери елементів відраховуються з нуля). В наступному прикладі створюється масив і послідовно виводяться його елементи:
public class MyArray {
public static void main(String[] args) {
int a[] = {1, 5, 6, 1, 3}; // створюємо масив і заповнюємо його числами
int size = a.length;
System.out.println("Елементи масиву:");
for (int j = 0; j < size; j++) {
System.out.println("а[" + j + "]=" + a[j]);
}
}
}
Результат виконання:
Елементи масиву: а[0]=1 а[1]=5 а[2]=6 а[3]=1 а[4]=3
В дужках циклу for, при потребі, одночасно можна проходити по кількох змінних:
for (int i=0,j=20; i<=j+2;i++,j--) {
//code
}
Цикл «for each»
ред.Починаючи з java SE 5.0 в мові з’явився новий цикл, призначення якого є перебір елементів масиву або подібних до масиву типів даних (колекції).
Загальний вигляд циклу наступний:
for (type var : arr) { //тіло циклу }
Наприклад, вивести елементи масиву a, можна таким чином:
for (int element : a)
System.out.println(element);
Використання даного циклу, дозволяє уникнути проблем пов’язаних з помилками при заданні умови в класичному циклі for. В інших мовах програмування цикл такого виду так і називається foreach, проте, щоб уникнути необхідності значних змін в пакетах, в java пішли простішим шляхом і перевантажили цикл for.
Інструкції, що порушують порядок виконання
ред.В java відсутня інструкція goto
, яка дозволяла переходити в будь-яке місце в програмі. Її використання давно вважається поганим стилем програмування, оскільки робить текст програми заплутаним. Про це, зокрема, писав ще Дональд Кнут і він же зазначав, що інколи все таки корисно її застосовувати, щоб припинити виконання певного методу, циклу чи блоку і вийти за їхні межі. Для цієї мети в java існують спеціальні інструкції. Зокрема, інструкцію break
можна використати для передчасного виходу з циклу.
while (years <= 100)
{
balance += payment;
double interest = balance * interestRate / 100;
balance += interest;
if (balance >= goal) break;
years++;
}
Таким чином додано дві умови для виходу з циклу. Хоча даний фрагмент можна також переписати наступним чином:
while (years <= 100 && balance < goal)
{
balance += payment;
double interest = balance * interestRate / 100;
balance += interest;
if (balance < goal)
years++;
}
Якщо ми використовуємо вкладені цикли (один цикл в іншому), то інструкція break
припинить цикл, в якому вона знаходиться. Якщо вона знаходиться у внутрішньому циклі, то він припинить своє виконання, а зовнішній же цикл буде виконуватись і далі. Якщо потрібно повністю припинити виконання і внутрішнього і зовнішнього, то використовується інструкція break
з міткою.
Scanner in = new Scanner(System.in);
int n;
read_data:
while (. . .) // this loop statement is tagged with the label
{
. . .
for (. . .) // this inner loop is not labeled
{
System.out.print("Enter a number >= 0: ");
n = in.nextInt();
if (n < 0) // should never happen—can't go on
break read_data;
// break out of read_data loop
. . .
}
}
// наступна інструкція буде виконана зразу ж після інструкції break з міткою
if (n < 0) // check for bad situation
{
// deal with bad situation
}
else
{
// carry out normal processing
}
Після використання break
з міткою, часто виникає необхідність перевірити чому саме цикл припинив виконання, що і робиться в кінці наведеного вище фрагменті коду програми.
Якщо ж нам потрібно виходити з циклу, а лише припинити певну його ітерацію, то для цієї використовується інструкція continue
, яка переносить порядок виконання до заголовку інструкції.
Scanner in = new Scanner(System.in);
while (sum < goal)
{
System.out.print("Enter a number: ");
n = in.nextInt();
if (n < 0) continue;
sum += n; // not executed if n < 0
}
Щоправда завжди можна обійтися без даних інструкцій змінивши логіку програми. Деякі з програмістів уникають використовувати інструкції break
та continue
.
Множинний вибір (switch)
ред.Інструкція if/else може бути доволі громіздкою, якщо необхідно здійснити множинний вибір з багатьох альтернатив. Тож як і в С/C++ в java існує інструкція switch
, яка здійснити вибір з багатьох варіантів. Щоправда вона дещо незграбна і деякі програмісти вважають за краще уникати її використання.
Наприклад, якщо Ви організовуєте певне меню і пропонуєте користувачу вибрати, номер конкретного пункту, то можна використати наступний код:
Scanner in = new Scanner(System.in);
System.out.print("Select an option (1, 2, 3, 4) ");
int choice = in.nextInt();
switch (choice) {
case 1:
. . .
break;
case 2:
. . .
break;
case 3:
. . .
break;
case 4:
. . .
break;
default:
// bad input
. . .
break;
Якщо пропустити інструкцію break, то всі інші інструкції будуть також виконані. Тобто якщо справдиться умова першого варіанту, то будуть здійснені ще й дії вказані для виконання у всіх інших варіантах до наступної інструкції break.
int k=1;
switch (k) {
case 1:
System.out.print(1);
case 2:
System.out.print(2);
case 3:
System.out.print(3);
break;
case 4:
System.out.print(4);
break;
default:
// викинути виняток (виведе помилку при виконанні програми)
throw new IllegalStateException("Unexpected value: " + k);
}
Як результат буде виведено: 123
До версії Java 7, яка вийшла у 2011 році, case мітка мала бути лише цілим числом або нумерованою константою. Починаючи із Java 7 можна перевіряти таким чином на рівність також рядки:
String input = . . .;
switch (input)
{
case "A":
. . .
break;
. . .
}
В Java 12 з'явилася можливість повертати значення з інструкції switch за допомогою ключового слова break, проте в Java 13 для цього ввели додаткове слово yield
, а використовувати для цього break заборонили.
int result = switch (mode) {
case "A":
yield 1;
case "B":
yield 2;
. . .
В Java 12 також для switch з'явився синтаксис подібний до лямбда виразів з підтримкою повернення значення із switch:
String str="A";
var result = switch (str) {
case "A" -> 1;
case "B" -> 2;
case "C" -> 3;
case "D" -> 4;
default -> throw new IllegalStateException("Unexpected value: " + str);
};
System.out.println(result); //result=1
Аналогічний результат можна отримати так:
String str="A";
int result;
switch (str) {
case "A" -> result=1;
case "B" -> result=2;
case "C" -> result=3;
default -> throw new IllegalStateException("Unexpected value: " + str);
};
System.out.println("result="+result);
Запримітьте, що інструкція break
вже не потрібна. Якщо потрібно, щоб виконалось декілька випадків, то використовуємо кому між значеннями case (раніше для цього спеціально пропускалась інструкція break):
String str="A";
var result = switch (str) {
case "A","B","C" -> 1;
case "D" -> 4;
default -> throw new IllegalStateException("Unexpected value: " + str);
};
System.out.println(result); //result=1
Якщо потрібно виконати певні інструкції в блоці, то для повернення результату використовується інструкція yield (починаючи з Java 13):
String str="C";
var result = switch (str) {
case "A" -> 1;
case "B" -> 2;
case "C" -> {
System.out.println("3!");
yield 3; // return 3
}
default -> throw new IllegalStateException("Unexpected value: " + str);
};
System.out.println("result="+result); //result=3
Замість цифри, при поверненні, може використовуватися змінна або вираз.
int x=2;
int y=3;
String str="C";
var result = switch (str) {
case "A" -> x+y;
. . .
Додатковий матеріал
ред.