Освоюємо 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;
            . . .

Додатковий матеріал ред.


Вступ в класи та методи · Масиви