Как вырваться из вложенных циклов в Java?


1819

У меня есть конструкция вложенного цикла, как это:

for (Type type : types) {
    for (Type t : types2) {
         if (some condition) {
             // Do something and break...
             break; // Breaks out of the inner loop
         }
    }
}

Теперь, как я могу вырваться из обеих петель? Я смотрел на похожие вопросы, но ни один из них не касается конкретно Java. Я не мог применить эти решения, потому что большинство использовали gotos.

Я не хочу помещать внутренний цикл в другой метод.

Я не хочу перезапускать циклы. При разрыве я заканчиваю выполнение цикла цикла.

Ответы:


2427

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

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

public class Test {
    public static void main(String[] args) {
        outerloop:
        for (int i=0; i < 5; i++) {
            for (int j=0; j < 5; j++) {
                if (i * j > 6) {
                    System.out.println("Breaking");
                    break outerloop;
                }
                System.out.println(i + " " + j);
            }
        }
        System.out.println("Done");
    }
}

Это печатает:

0 0
0 1
0 2
0 3
0 4
1 0
1 1
1 2
1 3
1 4
2 0
2 1
2 2
2 3
Breaking
Done

287
Это действительно сразу после цикла. Попробуй это! Да, метка идет перед циклом, но это потому, что она маркирует цикл, а не место, в которое вы хотите выйти. (Вы также можете продолжить с лейблом.)
Джон Скит

2
Perl также разрешает это своей собственной системой меток. Я думаю, что многие языки это делают - меня вряд ли удивит, что это на Java.
Эван Кэрролл

9
@Evan - это утверждение явно верно - на языках, которые обещают, что это правда. Но Java не дает этого обещания. Если язык противоречит вашим предположениям, возможно, виноваты именно ваши предположения. В этом случае, я думаю, вы все еще отчасти правы - принцип наименьшего удивления для многих тех, кто никогда не слышал (или не забывал) об этой форме break. Даже тогда исключения являются еще одним известным исключением (извините). Но я все еще был бы недоволен этим, если бы это не было очевидно (небольшие циклы, предупреждающий комментарий, если метка / разрыв все еще недостаточно видны).
Steve314

6
@MuhammadBabar: outerloop это лейбл. Я не знаю точно, какой код вы пробовали, но код в моем ответе компилируется и работает просто отлично.
Джон Скит

4
@NisargPatil Только потому, что он находится в sonarLint, не делает его запахом кода. Это просто означает, что был разработчик, который добавил это в sonarLint с личным зудом, чтобы поцарапать, и он полон таких правил, которые имеют смысл только при злоупотреблении или потому, что у какого-то разработчика есть личный крестовый поход против них. Помеченные разрывы и продолжения - это очень элегантный способ описать, что вы хотите сделать.
john16384

403

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

Это окупится за удобочитаемость.

Код станет примерно таким:

private static String search(...) 
{
    for (Type type : types) {
        for (Type t : types2) {
            if (some condition) {
                // Do something and break...
                return search;
            }
        }
    }
    return null; 
}

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

 public class Test {
    public static void main(String[] args) {
        loop();
        System.out.println("Done");
    }

    public static void loop() {
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 5; j++) {
                if (i * j > 6) {
                    System.out.println("Breaking");
                    return;
                }
                System.out.println(i + " " + j);
            }
        }
    }
}

30
Иногда вы используете несколько локальных переменных, которые находятся вне внутреннего цикла, передавая их все может показаться неуклюжим.
Haoest

1
Так как же это решение должно печатать «Готово», как в принятом ответе?
JohnDoe

1
@John. Вы называете это, а затем печатаете System.out.println («готово»); try {} finally {} в методе поиска также является опцией.
Zo72

2
Я полагаю, что это лучшая практика, но что произойдет, если вы захотите продолжить, а не сломаться? Метки поддерживают одинаково хорошо (или плохо!), Но я не уверен, как преобразовать эту логику для продолжения.
Роб Грант

@RobertGrant Если вы хотите продолжить вместо прерывания, переместите внешний цикл за пределы loopметода и вернитесь из метода для продолжения.
Muhd

215

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

search: {
    for (Type type : types) {
        for (Type t : types2) {
            if (some condition) {
                // Do something and break...
                break search;
            }
        }
    }
}

40
Вам не нужно создавать новый блок, чтобы использовать метку.
Джон Скит

81
Нет, но это проясняет намерение. Смотрите первый комментарий о принятом ответе.
Бомба

2
это фактически не именованный блок, после метки вы можете написать любое выражение Java как без метки, name: if(...){...}делает ли названное условие? :)
La VloZ Merrill

4
Эта конструкция имеет большое преимущество по сравнению с forпрямой маркировкой . Вы можете добавить код перед последним, }который будет выполнен, только если условие никогда не выполнялось.
Florian F

2
Это именованный блок, а не именованный цикл. Вы не могли бы внутри этого цикла "продолжить поиск"; что является полностью допустимым синтаксисом, если цикл называется поиском. Вы можете сломать это, но вы не можете продолжать это.
Tatarize

132

Я никогда не использую ярлыки. Кажется, это плохая практика. Вот что я бы сделал:

boolean finished = false;
for (int i = 0; i < 5 && !finished; i++) {
    for (int j = 0; j < 5; j++) {
        if (i * j > 6) {
            finished = true;
            break;
        }
    }
}

4
Не должно ли это быть && !finishedвместо || !finished? А зачем тогда breakвообще использовать и не использовать && !finishedдля внутреннего цикла тоже?
Гэндальф

4
Я использую, breakчтобы иметь возможность произвольно выйти из цикла. Если после этого ifблока есть код , вы можете breakвыполнить его до выполнения. Но ты прав &&. Починил это.
Эль Манди

2
отличное решение! это именно то, как я бы сделал это на практике, если по какой-то причине использование дополнительной функции нежелательно.
Бенрот

6
Существует потенциальная проблема, если после внутреннего цикла есть какая-то логика ... которая будет продолжать выполняться, а внешний цикл прерывается только тогда, когда начинается новая итерация ...
Картик Каруппаннан

7
Я не понимаю, почему нужно так делать. Люди, работающие с кодом, должны иметь возможность использовать все возможности языка. Я понимаю, что важно писать код, понятный другим, но не ограничивая использование официальных инструментов, предоставляемых языком, и находить обходные пути для той же функциональности. Или вы имели в виду что-то еще под "плохой практикой"?
codepleb

107

Вы можете использовать метки:

label1: 
for (int i = 0;;) {
    for (int g = 0;;) {
      break label1;
    }
}

4
+1 от меня. Просто, к сути, отвечает на вопрос. Не может быть обвинен в повторении существующего ответа, потому что вы ответили одновременно.
Heimdall

40

Используйте функцию:

public void doSomething(List<Type> types, List<Type> types2){
  for(Type t1 : types){
    for (Type t : types2) {
      if (some condition) {
         // Do something and return...
         return;
      }
    }
  }
}

20

Вы можете использовать временную переменную:

boolean outerBreak = false;
for (Type type : types) {
   if(outerBreak) break;
    for (Type t : types2) {
         if (some condition) {
             // Do something and break...
             outerBreak = true;
             break; // Breaks out of the inner loop
         }
    }
}

В зависимости от вашей функции вы также можете выйти / вернуться из внутреннего цикла:

for (Type type : types) {
    for (Type t : types2) {
         if (some condition) {
             // Do something and break...
             return;
         }
    }
}

7
Я нахожу этот путь беспорядочным.
Бута

11
Дополнительная проверка состояния каждый раз через цикл? Нет, спасибо.
рянденки

15

Если вам не нравятся breaks и gotos, вы можете использовать «традиционный» цикл for вместо for-in с дополнительным условием прерывания:

int a, b;
bool abort = false;
for (a = 0; a < 10 && !abort; a++) {
    for (b = 0; b < 10 && !abort; b++) {
        if (condition) {
            doSomeThing();
            abort = true;
        }
    }
}

2
Не подходит для петель foreach.
Джон Макклейн

1
@JohnMcClane И вы спамите это на многих ответах на вопрос 9+ лет, потому что ...?
Quintec

12

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

int s = type.size();
for (int i = 0; i < s; i++) {
    for (int j = 0; j < t.size(); j++) {
        if (condition) {
            // do stuff after which you want 
            // to completely break out of both loops
            s = 0; // enables the _main_ loop to terminate
            break;
        }
    }
}

Я не нахожу крутым повторять все элементы после того, как условие нарушено. Таким образом, я бы добавил перерыв в другом случае.
Бут

@boutta Я не уверен, как вы пришли к такому выводу. Как только условие выполнено, оба цикла завершаются.
Swifty McSwifterton

Хорошо, я не получил часть с манипуляциями с 's' var. Но я нахожу такой плохой стиль, так как s представляет размер. Тогда я предпочитаю ответ от ddyer с явными переменными: stackoverflow.com/a/25124317/15108
Бут

@boutta Вы можете изменить sна значение, которое меньше iили изменить iна значение, большее или равное s, оба должны делать свое дело. Вы правы насчет изменения s, потому что позже его можно будет использовать в другом месте, но изменение iне повредит, просто убедится, что первое forне продолжит цикл.
Жолти

9

Я предпочитаю добавить явный «выход» к тестам цикла. Любой случайный читатель дает понять, что цикл может закончиться рано.

boolean earlyExit = false;
for(int i = 0 ; i < 10 && !earlyExit; i++) {
     for(int j = 0 ; i < 10 && !earlyExit; j++) { earlyExit = true; }
}

Не подходит для петель foreach.
Джон Макклейн

8

StreamРешение Java 8 :

List<Type> types1 = ...
List<Type> types2 = ...

types1.stream()
      .flatMap(type1 -> types2.stream().map(type2 -> new Type[]{type1, type2}))
      .filter(types -> /**some condition**/)
      .findFirst()
      .ifPresent(types -> /**do something**/);

1
@ Tvde1 и все еще полезен для других пользователей, поэтому всегда приветствуются новые методы и решения
Андрей Суворков

Вау, это безобразно
DS.

7

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

public Object searching(Object[] types) { // Or manipulating
    List<Object> typesReferences = new ArrayList<Object>();
    List<Object> typesReferences2 = new ArrayList<Object>();

    for (Object type : typesReferences) {
        Object o = getByCriterion(typesReferences2, type);
        if(o != null) return o;
    }
    return null;
}

private Object getByCriterion(List<Object> typesReferences2, Object criterion) {
    for (Object typeReference : typesReferences2) {
        if(typeReference.equals(criterion)) {
             // here comes other complex or specific logic || typeReference.equals(new Object())
             return typeReference;
        }
    }
    return null;
}

Основные минусы:

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

Плюсы:

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

Так что это просто обработка дела с помощью другого подхода.

В основном вопрос к автору этого вопроса: что вы думаете об этом подходе?


6

Вы можете выйти из всех циклов, не используя метки: и флаги.

Это просто хитрое решение.

Здесь условие 1 - это условие, которое используется для разрыва цикла K и J. И условие 2 - это условие, которое используется для разрыва цикла K, J и I.

Например:

public class BreakTesting {
    public static void main(String[] args) {
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                for (int k = 0; k < 9; k++) {
                    if (condition1) {
                        System.out.println("Breaking from Loop K and J");
                        k = 9;
                        j = 9;
                    }
                    if (condition2) {
                        System.out.println("Breaking from Loop K, J and I");
                        k = 9;
                        j = 9;
                        i = 9;
                    }
                }
            }
        }
        System.out.println("End of I , J , K");
    }
}

2
Как бы я использовал это для циклов for-each? ;)
Вилли Ментцель

5
Это не работает, если у вас более сложное условие цикла, например list.size ()> 5. И это действительно просто взломать. Трудно читать и плохая практика!
Нейрон

Это подвержено ошибкам. Представьте, что вы изменили внутренний цикл на (int k = 0; k < 10; k++)и вы не исправили все k = 9на k = 10. Вы можете попасть в бесконечный цикл.
Флориан Ф

5

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

loop1:
 for(int i= 0; i<6; i++){
    for(int j=0; j<5; j++){
          if(i==3)
            break loop1;
        }
    }

предположим, что есть 3 цикла, и вы хотите завершить цикл 3: Пример 2:

loop3: 
for(int i= 0; i<6; i++){
loop2:
  for(int k= 0; k<6; k++){
loop1:
    for(int j=0; j<5; j++){
          if(i==3)
            break loop3;
        }
    }
}

4
boolean broken = false; // declared outside of the loop for efficiency
for (Type type : types) {
    for (Type t : types2) {
        if (some condition) {
            broken = true;
            break;
        }
    }

    if (broken) {
        break;
    }
}

4

Если это внутри какой-то функции, почему бы вам просто не вернуть ее:

for (Type type : types) {
    for (Type t : types2) {
         if (some condition) {
            return value;
         }
    }
}

Я предпочитаю эту модель. Это часто заставляло меня разбивать петли на отдельные функции. Мой код всегда был лучше после этого, поэтому мне очень нравится этот ответ.
Билл К

4

Лучший и простой метод ..

outerloop:
for(int i=0; i<10; i++){
    // here we can break Outer loop by 
    break outerloop;

    innerloop:
    for(int i=0; i<10; i++){
        // here we can break innerloop by 
        break innerloop;
     }
}

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

Я собирался напечатать то же самое. Этикетки в этом случае несколько бесполезны.
Таслим Осени

4

Довольно необычный подход, но с точки зрения длины кода ( не производительности ) это самое простое, что вы можете сделать:

for(int i = 0; i++; i < j) {
    if(wanna exit) {
        i = i + j; // if more nested, also add the 
                   // maximum value for the other loops
    }
}


4

Другое решение, упомянутое без примера (на самом деле оно работает в коде prod).

try {
    for (Type type : types) {
        for (Type t : types2) {
            if (some condition #1) {
                // Do something and break the loop.
                throw new BreakLoopException();
            }
        }
    }
}
catch (BreakLoopException e) {
    // Do something on look breaking.
}

Конечно, он BreakLoopExceptionдолжен быть внутренним, частным и ускоренным без отслеживания стека:

private static class BreakLoopException extends Exception {
    @Override
    public StackTraceElement[] getStackTrace() {
        return new StackTraceElement[0];
    }
}

1
Это было упомянуто на самом деле, в ответ набрав -23 голоса ... stackoverflow.com/a/886980/2516301 . Это сделает работу, но это очень плохая практика программирования ...
Vefthym

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

1
Это было бы еще более читабельным, если бы он был разбит на отдельный вызов функции с возвратом по центру. Часто делается даже лучше с помощью небольшого рефакторинга, поэтому он имеет смысл как самостоятельная (часто многократно используемая) функция.
Билл К

2

for (int j = 0; j < 5; j++) //inner loopследует заменить на for (int j = 0; j < 5 && !exitloops; j++).

Здесь, в этом случае завершенные вложенные циклы должны быть завершены, если условие выполнено True. Но если мы используем exitloopsтолько верхнийloop

 for (int i = 0; i < 5 && !exitloops; i++) //upper loop

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

Пример: если i = 3и j=2тогда условие false. Но в следующей итерации внутреннего цикла , j=3 то условие (i*j)стало 9что , trueно внутренний цикл будет продолжаться до jстать 5.

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

boolean exitloops = false;
for (int i = 0; i < 5 && !exitloops; i++) { //here should exitloops as a Conditional Statement to get out from the loops if exitloops become true. 
    for (int j = 0; j < 5 && !exitloops; j++) { //here should also use exitloops as a Conditional Statement. 
        if (i * j > 6) {
            exitloops = true;
            System.out.println("Inner loop still Continues For i * j is => "+i*j);
            break;
        }
        System.out.println(i*j);
    }
}

2

Как и предложение @ 1800 INFORMATION, используйте условие, которое разрывает внутренний цикл, как условие на внешний цикл:

boolean hasAccess = false;
for (int i = 0; i < x && hasAccess == false; i++){
    for (int j = 0; j < y; j++){
        if (condition == true){
            hasAccess = true;
            break;
        }
    }
}

2

Если это новая реализация, вы можете попробовать переписать логику в виде операторов if-else_if-else.

while(keep_going) {

    if(keep_going && condition_one_holds) {
        // Code
    }
    if(keep_going && condition_two_holds) {
        // Code
    }
    if(keep_going && condition_three_holds) {
        // Code
    }
    if(keep_going && something_goes_really_bad) {
        keep_going=false;
    }
    if(keep_going && condition_four_holds) {
        // Code
    }
    if(keep_going && condition_five_holds) {
        // Code
    }
}

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

something_bad_has_happened = false;
while(something is true && !something_bad_has_happened){
    // Code, things happen
    while(something else && !something_bad_has_happened){
        // Lots of code, things happens
        if(something happened){
            -> Then control should be returned ->
            something_bad_has_happened=true;
            continue;
        }
    }
    if(something_bad_has_happened) { // The things below will not be executed
        continue;
    }

    // Other things may happen here as well, but they will not be executed
    //  once control is returned from the inner cycle.
}

ВОТ! Таким образом, хотя простой перерыв не сработает, его можно использовать для работы continue.

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


2

Демо для break, continueи label:

Ключевые слова Java breakиcontinue имеют значение по умолчанию. Это «ближайший цикл», и сегодня, после нескольких лет использования Java, я только что понял!

Кажется, используется редко, но полезно.

import org.junit.Test;

/**
 * Created by cui on 17-5-4.
 */

public class BranchLabel {
    @Test
    public void test() {
        System.out.println("testBreak");
        testBreak();

        System.out.println("testBreakLabel");
        testBreakLabel();

        System.out.println("testContinue");
        testContinue();
        System.out.println("testContinueLabel");
        testContinueLabel();
    }

    /**
     testBreak
     a=0,b=0
     a=0,b=1
     a=1,b=0
     a=1,b=1
     a=2,b=0
     a=2,b=1
     a=3,b=0
     a=3,b=1
     a=4,b=0
     a=4,b=1
     */
    public void testBreak() {
        for (int a = 0; a < 5; a++) {
            for (int b = 0; b < 5; b++) {
                if (b == 2) {
                    break;
                }
                System.out.println("a=" + a + ",b=" + b);
            }
        }
    }

    /**
     testContinue
     a=0,b=0
     a=0,b=1
     a=0,b=3
     a=0,b=4
     a=1,b=0
     a=1,b=1
     a=1,b=3
     a=1,b=4
     a=2,b=0
     a=2,b=1
     a=2,b=3
     a=2,b=4
     a=3,b=0
     a=3,b=1
     a=3,b=3
     a=3,b=4
     a=4,b=0
     a=4,b=1
     a=4,b=3
     a=4,b=4
     */
    public void testContinue() {
        for (int a = 0; a < 5; a++) {
            for (int b = 0; b < 5; b++) {
                if (b == 2) {
                    continue;
                }
                System.out.println("a=" + a + ",b=" + b);
            }
        }
    }

    /**
     testBreakLabel
     a=0,b=0,c=0
     a=0,b=0,c=1
     * */
    public void testBreakLabel() {
        anyName:
        for (int a = 0; a < 5; a++) {
            for (int b = 0; b < 5; b++) {
                for (int c = 0; c < 5; c++) {
                    if (c == 2) {
                        break anyName;
                    }
                    System.out.println("a=" + a + ",b=" + b + ",c=" + c);
                }
            }
        }
    }

    /**
     testContinueLabel
     a=0,b=0,c=0
     a=0,b=0,c=1
     a=1,b=0,c=0
     a=1,b=0,c=1
     a=2,b=0,c=0
     a=2,b=0,c=1
     a=3,b=0,c=0
     a=3,b=0,c=1
     a=4,b=0,c=0
     a=4,b=0,c=1
     */
    public void testContinueLabel() {
        anyName:
        for (int a = 0; a < 5; a++) {
            for (int b = 0; b < 5; b++) {
                for (int c = 0; c < 5; c++) {
                    if (c == 2) {
                        continue anyName;
                    }
                    System.out.println("a=" + a + ",b=" + b + ",c=" + c);
                }
            }
        }
    }
}

2

демонстрация

public static void main(String[] args) {
    outer:
    while (true) {
        while (true) {
            break outer;
        }
    }
}

1

Вы можете сделать следующее:

  1. установить локальную переменную в false

  2. установите эту переменную trueв первом цикле, когда вы хотите прервать

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

    boolean isBreakNeeded = false;
    for (int i = 0; i < some.length; i++) {
        for (int j = 0; j < some.lengthasWell; j++) {
            //want to set variable if (){
            isBreakNeeded = true;
            break;
        }
    
        if (isBreakNeeded) {
            break; //will make you break from the outer loop as well
        }
    }

1

В некоторых случаях мы можем whileэффективно использовать цикл здесь.

Random rand = new Random();
// Just an example
for (int k = 0; k < 10; ++k) {
    int count = 0;
    while (!(rand.nextInt(200) == 100)) {
       count++;
    }

    results[k] = count;
}

1

В Java нет функции перехода, как в C ++. Но все же, gotoэто зарезервированное ключевое слово в Java. Они могут реализовать это в будущем. На ваш вопрос ответ таков: в Java есть нечто, называемое меткой, к которой вы можете применить оператор continueand break. Найдите код ниже:

public static void main(String ...args) {
    outerLoop: for(int i=0;i<10;i++) {
    for(int j=10;j>0;j--) {
        System.out.println(i+" "+j);
        if(i==j) {
            System.out.println("Condition Fulfilled");
            break outerLoop;
        }
    }
    }
    System.out.println("Got out of the outer loop");
}

1

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

Нравится:

for (Type type : types) {
    boolean flag=false;
    for (Type t : types2) {
        if (some condition) {
            // Do something and break...
            flag=true;
            break; // Breaks out of the inner loop
        }
    }
    if(flag)
        break;
}

1
boolean condition = false;
for (Type type : types) {
    for (int i = 0; i < otherTypes.size && !condition; i ++) {
        condition = true; // If your condition is satisfied
    }
}

Используйте conditionв качестве флага, когда вы закончите обработку. Тогда внутренний цикл только продолжается, пока условие не было выполнено. В любом случае, внешний цикл будет продолжать chuggin '.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.