Sử dụng dấu ngắt có gắn nhãn có phải là một phương pháp hay trong Java không?


81

Tôi đang nhìn chằm chằm vào một số mã cũ từ năm 2001 và bắt gặp tuyên bố này:

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

Tôi chưa bao giờ thấy điều này trước đây và đã phát hiện ra các dấu ngắt được gắn nhãn ở đây:

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

Điều này về cơ bản không hoạt động như thế gotonào? Nó thậm chí có thực hành tốt để sử dụng nó? Nó làm tôi không yên.


2
được label913định nghĩa ở đâu?
Nikolay Kuznetsov

1
Trong trường hợp này, khoảng 200 dòng sau đó ở cuối một câu lệnh switch khá lớn.
avgvstvs

tiếp tục và tên nhãn sẽ làm cho đoạn code để quá trình đó một phần của mã nơi khối được đánh dấu với một nhãn
user_CC

5
Trong trường hợp này, vấn đề là kích thước của câu lệnh switch!
Cyrille Ka

8
Điều này trông giống như mã được tạo (thực tế là một trình phân tích cú pháp). Có rất nhiều thủ thuật được sử dụng với mã được tạo mà sẽ không được sử dụng trong mã viết tay (chủ yếu vì chúng giúp tạo dễ dàng hơn).
parsifal

Câu trả lời:


119

Không, nó không giống như goto, vì bạn không thể "đi" đến một phần khác của luồng điều khiển.

Từ trang bạn đã liên kết:

Câu lệnh break kết thúc câu lệnh được gắn nhãn; nó không chuyển luồng điều khiển đến nhãn. Luồng điều khiển được chuyển đến câu lệnh ngay sau câu lệnh được gắn nhãn (kết thúc).

Điều này có nghĩa là bạn chỉ có thể phá vỡ các vòng lặp hiện đang được thực thi.

Hãy xem xét ví dụ này:

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++) {

}

Bạn có thể thay thế xxxbằng firsthoặc second(để phá vỡ vòng lặp bên ngoài hoặc bên trong), vì cả hai vòng đều đang được thực thi, khi bạn nhấn breakcâu lệnh, nhưng thay thế xxxbằng thirdsẽ không biên dịch.


1
@user_CC Không thực sự, bạn vẫn không thể nhảy ra khỏi luồng điều khiển. Một tiếp tục được gắn nhãn sẽ chỉ bắt đầu lặp lại tiếp theo của vòng lặp được gắn nhãn và sẽ chỉ hoạt động trong khi vòng lặp đó đã được thực thi.
Thomas

3
vậy Nếu tôi đã sử dụng break first;, trình biên dịch có quay trở lại dòng ngay sau đó first:và thực hiện lại các vòng lặp đó không?
Ungeheuer

15
@JohnnyCoder không, break first;sẽ phá vỡ (kết thúc) vòng lặp được gắn nhãn first, tức là nó sẽ tiếp tục với third. Mặt khác, continue first;sẽ kết thúc sự lặp lại hiện tại của firstvà ngắt secondcùng với điều đó và sẽ tiếp tục với giá trị tiếp theo của iin first.
Thomas

1
@TheTechExpertGuy tốt, không hẳn vậy. Nó chỉ gần giống với gotonghĩa của một if-elsetuyên bố. Goto "linh hoạt" hơn nhiều như trong "đi bất cứ đâu", ví dụ: đến một vị trí trước vòng lặp (trên thực tế, đó là cách các ngôn ngữ cũ được sử dụng để triển khai vòng lặp: "nếu điều kiện không được đáp ứng, hãy chuyển đến đầu của phần thân vòng lặp (lại)") . Tôi đã phải đối mặt với chúng gần đây và hãy tin tôi: đó gotolà một cơn đau thực sự ở mông trong khi những khoảng nghỉ được dán nhãn thì không.
Thomas

1
@TheTechExpertGuy, bạn được chào đón :) - Vì bạn là người mới bắt đầu, tôi cảm thấy có xu hướng đưa ra lời khuyên một lần nữa: không sử dụng gotochút nào, ngay cả khi C ++ hỗ trợ nó. Hầu như luôn có một cách tốt hơn để giải quyết mọi việc sẽ giúp bạn đỡ phải đau đầu hơn sau này. Sự cám dỗ có thể đến ... hãy chống lại!
Thomas

16

Nó không quá khủng khiếp gotovì nó chỉ gửi điều khiển đến cuối câu lệnh được gắn nhãn (thường là một cấu trúc vòng lặp). Điều khiến gotokhông thể chấp nhận được là nó là một nhánh tùy ý cho bất kỳ đâu, bao gồm cả các nhãn được tìm thấy cao hơn trong nguồn phương thức, để bạn có thể có hành vi lặp tự phát triển. Chức năng ngắt nhãn trong Java không cho phép kiểu điên rồ đó bởi vì quyền kiểm soát chỉ tiến lên phía trước.

Tôi chỉ mới sử dụng nó một lần vào khoảng 12 năm trước, trong tình huống tôi cần thoát ra khỏi các vòng lồng nhau, giải pháp thay thế có cấu trúc hơn sẽ được thực hiện cho các thử nghiệm phức tạp hơn trong các vòng lặp. Tôi sẽ không khuyên bạn nên sử dụng nó nhiều nhưng tôi sẽ không gắn cờ nó là mùi mã xấu tự động.


6
@Boann: Tôi không có ý cho rằng goto hoàn toàn xấu xa trong mọi bối cảnh. đó chỉ là một trong những thứ như số học con trỏ, nạp chồng toán tử và quản lý bộ nhớ rõ ràng mà những người tạo ra Java đã quyết định không đáng gặp rắc rối. Và sau khi đọc các chương trình COBOL dường như hơn 90% là gotos, tôi không buồn vì họ đã loại bỏ nó khỏi Java.
Nathan Hughes

1
Ồ, bạn đã đúng trong lần đầu tiên @NathanHughes, goto khá là ác. Tất nhiên không phải trong mọi tình huống, nhưng vâng, tôi không bỏ lỡ nó.
Perception

2
goto có thể "hữu ích" hơn các nhãn, @Boann, nhưng điều đó hoàn toàn vượt trội so với chi phí bảo trì của chúng. nhãn không phải là cây đũa thần như gotos, và đó là một điều tốt.
DavidS

9

Luôn có thể thay thế breakbằng phương pháp mới.

Xem xét kiểm tra mã cho bất kỳ phần tử phổ biến nào trong hai danh sách:

List list1, list2;

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

Bạn có thể viết lại nó như thế này:

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

Tất nhiên, để kiểm tra các phần tử chung giữa hai danh sách, tốt hơn nên sử dụng list1.retainAll(new HashSet<>(list2))phương pháp để thực hiện điều đó O(n)với O(n)bộ nhớ bổ sung hoặc sắp xếp cả hai danh sách tại O(n * log n)và sau đó tìm các phần tử chung tại O(n).


Đây hầu như luôn là cách tiếp cận đúng theo quan điểm của tôi. gotolà (có khả năng) có hại vì nó có thể đưa bạn đến rất nhiều nơi - hoàn toàn không rõ ràng những gì đang được hoàn thành; việc tạo một chức năng trợ giúp mang lại giới hạn rõ ràng cho người tiếp theo về những gì bạn đang cố gắng làm và nơi bạn sẽ làm điều đó, nó cho bạn cơ hội đặt tên cho phần chức năng đó. Cách này rõ ràng hơn.
ethanbustad

@ethanbustad Nhưng đây là về nhãn break, không phải goto. Chắc chắn, việc tái cấu trúc hiển thị ở đây là một ý tưởng hay trong cả hai trường hợp. Nhưng được dán nhãn breakgần như không được kiểm duyệt và dễ bị mã hóa như vậy goto.
underscore_d

Ví dụ này thật khủng khiếp, break không hoạt động nếu, nhãn ở đây chỉ là vô dụng.
Diracnote

8

Trước khi đọc phần còn lại của câu trả lời này, vui lòng đọc Đi đến Tuyên bố Được coi là Có hại . Nếu bạn không muốn đọc toàn bộ, đây là điều tôi coi là điểm mấu chốt:

Việc sử dụng không hạn chế câu lệnh go to có hậu quả ngay lập tức là rất khó tìm được một tập hợp tọa độ có ý nghĩa để mô tả tiến trình của quá trình.

Hoặc để diễn đạt lại, vấn đề gotolà chương trình có thể đến giữa một khối mã, mà lập trình viên không hiểu trạng thái chương trình tại thời điểm đó. Các cấu trúc hướng khối tiêu chuẩn được thiết kế để phân định rõ ràng các chuyển đổi trạng thái, một cấu trúc được gắn nhãn breaknhằm mục đích đưa chương trình đến một trạng thái đã biết cụ thể (bên ngoài khối có nhãn chứa).

Trong một chương trình mệnh lệnh thế giới thực, trạng thái không được phân định rõ ràng bởi các ranh giới khối, vì vậy sẽ có câu hỏi liệu việc gắn nhãn có phải breaklà một ý tưởng hay hay không. Nếu khối thay đổi trạng thái có thể nhìn thấy từ bên ngoài khối và có nhiều điểm để thoát khỏi khối, thì một được gắn nhãn breaktương đương với khối nguyên thủy goto. Sự khác biệt duy nhất là, thay vì có cơ hội hạ cánh ở giữa một khối có trạng thái không xác định, bạn bắt đầu một khối mới với trạng thái không xác định.

Vì vậy, nói chung, tôi sẽ coi một nhãn hiệu breaklà nguy hiểm. Theo ý kiến ​​của tôi, đó là một dấu hiệu cho thấy khối nên được chuyển đổi thành một chức năng, với quyền truy cập hạn chế vào phạm vi bao quanh.

Tuy nhiên, mã ví dụ này rõ ràng là sản phẩm của trình tạo phân tích cú pháp (OP nhận xét rằng đó là mã nguồn Xerces). Và trình tạo phân tích cú pháp (hay trình tạo mã nói chung) thường có quyền tự do với mã do chúng tạo ra, bởi vì chúng có kiến ​​thức hoàn hảo về trạng thái và con người không cần phải hiểu nó.


Tôi không đồng ý. breakkhá hữu ích trong một số trường hợp. Ví dụ: khi bạn đang tìm kiếm một số giá trị có thể đến từ một số nguồn đã đặt hàng. Bạn đang thử từng cái một, và khi cái đầu tiên được tìm thấy, bạn break. Nó là mã thanh lịch hơn if / else if / else if / else if. (Ít nhất là ít dòng hơn.) Chuyển mọi thứ sang ngữ cảnh của một phương pháp có thể không phải lúc nào cũng thực tế. Một trường hợp khác là, nếu bạn đang phá vỡ có điều kiện từ một vài cấp độ lồng nhau. Tóm lại, nếu bạn không thích break, thì bạn không thích returnbất cứ nơi nào khác ngoại trừ phần cuối của một phương thức.
Ondra Žižka,

Đối với ngữ cảnh; "Go To Statement Được coi là Có hại" được viết khi những thứ thích dowhilekhông tồn tại và if(.. ) gotothay vào đó mọi người đang sử dụng ở mọi nơi. Nó nhằm khuyến khích các nhà thiết kế ngôn ngữ thêm hỗ trợ cho những thứ như dowhile. Đáng buồn thay, một khi ban nhạc bắt đầu lăn bánh, nó đã trở thành một đoàn tàu cường điệu được thúc đẩy bởi những người thiếu hiểu biết đưa những thứ như breakvào vòng lặp "chỉ được thực hiện một lần" và ném ngoại lệ cho những người biết điều gì, đối với tất cả các trường hợp (hiếm) gotolà sạch hơn và ít gây hại hơn.
Brendan

1

Điều này không giống như một gotocâu lệnh trong đó bạn nhảy ngược điều khiển luồng. Một nhãn chỉ hiển thị cho bạn (người lập trình) nơi xảy ra sự cố. Ngoài ra, điều khiển luồng được chuyển sang câu lệnh tiếp theo sau break.

Về việc sử dụng nó, cá nhân tôi nghĩ rằng nó không có tác dụng gì lớn vì một khi bạn bắt đầu viết mã trị giá hàng nghìn dòng thì nó trở nên không đáng kể. Nhưng một lần nữa, nó sẽ phụ thuộc vào trường hợp sử dụng.


for(int i = 0; i < 1; i++) { do_something(); if(I_want_to_go_backwards) { i = 0; continue; } }.
Brendan

1

Theo tôi, một cách rõ ràng hơn để diễn đạt ý định có thể là đặt đoạn mã chứa vòng lặp vào một phương thức riêng biệt và đơn giản returntừ nó.

Ví dụ: thay đổi điều này:

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

vào cái này:

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

Điều này cũng dễ hiểu hơn đối với các nhà phát triển thông thạo các ngôn ngữ khác có thể hoạt động với mã của bạn trong tương lai (hoặc tương lai bạn ).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.