[java] 레이블이 지정된 휴식을 사용하는 것이 Java에서 좋은 방법입니까?

나는 2001 년의 오래된 코드를보고 있는데 다음과 같은 진술을 발견했다.

   else {
     do {
       int c = XMLDocumentFragmentScannerImpl.this.scanContent();
       if (c == 60) {
         XMLDocumentFragmentScannerImpl.this.fEntityScanner.scanChar();
         XMLDocumentFragmentScannerImpl.this.setScannerState(1);
         break label913;
       }

나는 전에 이것을 본 적이 없으며 여기에서 레이블이 지정된 휴식을 발견했습니다.

http://docs.oracle.com/javase/tutorial/java/nutsandbolts/branch.html

이것은 본질적으로 다음과 같이 작동하지 goto않습니까? 그것을 사용하는 것이 좋은 습관입니까? 그것은 나를 불안하게 만든다.



답변

아니요, 제어 흐름의 다른 부분으로 “이동”할 수 없기 때문에 이동과는 다릅니다.

링크 한 페이지에서 :

break 문은 레이블이 지정된 문을 종료합니다. 제어 흐름을 레이블로 전달하지 않습니다. 제어 흐름은 레이블이 지정된 (종료 된) 명령문 바로 다음 명령문으로 전송됩니다.

이는 현재 실행중인 루프 만 중단 할 수 있음을 의미합니다.

이 예를 고려하십시오.

first:
for( int i = 0; i < 10; i++) {
  second:
  for(int j = 0; j < 5; j ++ )
  {
    break xxx;
  }
}

third:
for( int a = 0; a < 10; a++) {

}

당신은 대체 할 수 xxxfirstsecond모두 루프는 당신이 명중 할 때, 실행되고 있기 때문에, (외부 또는 내부 루프를 중단하기 위해) break문을하지만, 교체 xxx로하는 third컴파일되지 않습니다.


답변

goto레이블이 지정된 명령문 (일반적으로 루프 구성)의 끝까지 만 제어를 보내기 때문에 그렇게 끔찍하지는 않습니다 . 만드는 것은 gotounlikeable이 어디에 임의의 지점이라는 것이다 포함한 라벨은 당신이 집에서 재배 반복 동작을 할 수 있습니다 그래서, 방법 원에서 최대 높은 발견했다. Java의 레이블 분리 기능은 제어가 앞으로 만 진행되기 때문에 그런 종류의 광기를 허용하지 않습니다.

약 12 년 전에 한 번만 사용했습니다. 중첩 루프에서 벗어나야하는 상황에서 루프에서 더 복잡한 테스트를 위해 더 구조화 된 대안을 만들었을 것입니다. 나는 그것을 많이 사용하는 것을 권하지 않을 것이지만 자동적 인 잘못된 코드 냄새로 플래그를 지정하지는 않을 것입니다.


답변

항상 break새로운 방법 으로 대체 할 수 있습니다 .

두 목록의 공통 요소에 대한 코드 검사를 고려하십시오.

List list1, list2;

boolean foundCommonElement = false;
for (Object el : list1) {
    if (list2.contains(el)) {
        foundCommonElement = true;
        break;
    }
}

다음과 같이 다시 작성할 수 있습니다.

boolean haveCommonElement(List list1, List list2) {
    for (Object el : list1) {
        if (list2.contains(el)) {
            return true;
        }
    }
    return false;
}

물론 두 목록 사이의 공통 요소를 확인하려면 list1.retainAll(new HashSet<>(list2))메서드를 사용 O(n)하여 O(n)추가 메모리를 사용하거나 두 목록을 모두에서 정렬 O(n * log n)한 다음에서 공통 요소를 찾는 것이 O(n)좋습니다.


답변

이 답변의 나머지 부분을 읽기 전에 유해한 것으로 간주되는 문으로 이동을 읽어 보십시오 . 전체를 읽고 싶지 않다면 여기에 핵심 포인트가 있습니다.

go to 문을 자유롭게 사용하면 프로세스 진행 상황을 설명하는 의미있는 좌표 집합을 찾기가 매우 어려워지는 즉각적인 결과가 발생합니다.

또는 다시 goto말해 , 문제 는 프로그래머가 그 시점에서 프로그램 상태를 이해하지 않고도 프로그램이 코드 블록의 중간에 도착할 수 있다는 것입니다. 표준 블록 지향 구조는 상태 전이를 명확하게 묘사하도록 설계되었으며, 레이블 break은 프로그램을 특정 알려진 상태 (포함 레이블이있는 블록 외부)로 전환 하기위한 것입니다.

실제 명령형 프로그램에서 상태는 블록 경계에 의해 명확하게 묘사되지 않으므로 레이블 break이 좋은 아이디어 인지 여부는 의문 입니다. 블록이 블록 외부에서 보이는 상태를 변경하고 블록을 종료 할 여러 지점이있는 경우 레이블 break이있는은 기본 요소와 동일합니다 goto. 유일한 차이점은 불확정 상태의 블록 중간에 착륙 할 가능성이 아니라 불확정 상태로 새 블록을 시작한다는 것입니다.

따라서 일반적으로 레이블 break이 위험한 것으로 간주합니다 . 제 생각에는 블록을 둘러싸는 범위에 대한 제한된 액세스와 함께 함수로 변환해야한다는 신호입니다.

그러나이 예제 코드는 분명히 파서 생성기의 산물이었습니다 (OP는 이것이 Xerces 소스 코드라고 언급했습니다). 그리고 파서 생성기 (또는 일반적으로 코드 생성기)는 상태에 대한 완벽한 지식을 가지고 있고 인간이 그것을 이해할 필요가 없기 때문에 생성하는 코드에 자유를 가져다줍니다.


답변

이것은 goto흐름 제어를 뒤로 건너 뛰는 문장 과는 다릅니다. 레이블은 단순히 중단이 발생한 위치를 보여줍니다 (프로그래머). 또한 흐름 제어는 break.

그것을 사용하는 것에 관해서는 개인적으로 수천 줄의 코드를 작성하기 시작하면 그다지 중요하지 않기 때문에 개인적으로 그다지 유용하지 않다고 생각합니다. 그러나 다시 말하지만 사용 사례에 따라 다릅니다.


답변

의도를 표현하는 더 깨끗한 방법은 적어도 내 생각에 루프를 포함하는 코드 조각을 별도의 메서드에 간단히 넣는 return것입니다.

예를 들어 다음을 변경하십시오.

someLabel:
for (int j = 0; j < 5; j++)
{
    // do something
    if ...
        break someLabel;
}

이것으로 :

private void Foo() {
    for (int j = 0; j < 5; j++)
    {
        // do something
        if ...
            return;
    }
}

이것은 또한 미래에 (또는 미래에 당신의 ) 코드로 작업 할 수있는 다른 언어에 능숙한 개발자들에게 더 관용적입니다 .


답변