Khai báo các biến trong hoặc ngoài vòng lặp


236

Tại sao sau đây làm việc tốt?

String str;
while (condition) {
    str = calculateStr();
    .....
}

Nhưng điều này được cho là nguy hiểm / không chính xác:

while (condition) {
    String str = calculateStr();
    .....
}

Có cần phải khai báo các biến ngoài vòng lặp không?

Câu trả lời:


289

Phạm vi của các biến cục bộ phải luôn nhỏ nhất có thể.

Trong ví dụ của bạn tôi đoán strkhông sử dụng bên ngoài của whilevòng lặp, nếu không bạn sẽ không được đặt câu hỏi, vì tuyên bố nó bên trong whilevòng lặp sẽ không là một lựa chọn, vì nó sẽ không biên dịch.

Vì vậy, kể từ khi strđược không sử dụng bên ngoài vòng lặp, phạm vi nhỏ nhất có thể cho strtrong các vòng lặp while.

Vì vậy, câu trả lời rõ ràngstrhoàn toàn nên được khai báo trong vòng lặp while. Không ifs, không ands, không buts.

Trường hợp duy nhất mà quy tắc này có thể bị vi phạm là nếu vì một lý do nào đó, điều quan trọng quan trọng là mọi chu kỳ đồng hồ phải được vắt ra khỏi mã, trong trường hợp đó bạn có thể muốn xem xét việc khởi tạo một cái gì đó trong phạm vi bên ngoài và sử dụng lại thay vì tái hiện nó trên mỗi lần lặp của một phạm vi bên trong. Tuy nhiên, điều này không áp dụng cho ví dụ của bạn, do tính bất biến của các chuỗi trong java: một thể hiện mới của str sẽ luôn được tạo ở đầu vòng lặp của bạn và nó sẽ phải bị loại bỏ ở cuối vòng lặp, vì vậy sẽ có không có khả năng để tối ưu hóa ở đó.

EDIT: (đưa ra nhận xét của tôi dưới đây trong câu trả lời)

Trong mọi trường hợp, cách đúng đắn để làm mọi thứ là viết đúng mã của bạn, thiết lập yêu cầu hiệu suất cho sản phẩm của bạn, đo sản phẩm cuối cùng của bạn theo yêu cầu này và nếu nó không thỏa mãn nó, thì hãy tối ưu hóa mọi thứ. Và điều thường xảy ra là bạn tìm cách cung cấp một số tối ưu hóa thuật toán chính thức và tốt đẹp chỉ trong một vài nơi khiến chương trình của chúng tôi đáp ứng các yêu cầu về hiệu suất của nó thay vì phải đi khắp toàn bộ cơ sở mã của bạn và điều chỉnh và hack mọi thứ trong đó để bóp chu kỳ đồng hồ ở đây và ở đó.


2
Truy vấn trên đoạn cuối: Nếu nó là một chuỗi khác thì String không bất biến thì nó có ảnh hưởng không?
Harry Joy

1
@HarryJoy Vâng, tất nhiên, lấy ví dụ StringBuilder, có thể thay đổi. Nếu bạn sử dụng StringBuilder để xây dựng một chuỗi mới trong mỗi lần lặp của vòng lặp, thì bạn có thể tối ưu hóa mọi thứ bằng cách phân bổ StringBuilder bên ngoài vòng lặp. Tuy nhiên, đây không phải là một thực hành khuyến khích. Nếu bạn làm điều đó mà không có lý do chính đáng, đó là một tối ưu hóa sớm.
Mike Nakis

7
@HarryJoy Cách đúng đắn để làm mọi thứ là viết đúng mã của bạn , thiết lập yêu cầu hiệu suất cho sản phẩm của bạn, đo sản phẩm cuối cùng của bạn theo yêu cầu này và nếu nó không thỏa mãn nó, thì hãy tối ưu hóa mọi thứ. Và bạn biết những gì? Bạn thường sẽ có thể cung cấp một số tối ưu hóa thuật toán chính thức và tốt đẹp chỉ trong một vài nơi sẽ thực hiện thủ thuật thay vì phải đi khắp toàn bộ cơ sở mã của bạn và điều chỉnh và hack mọi thứ để siết chặt chu kỳ đồng hồ ở đây và đó.
Mike Nakis

2
@MikeNakis tôi nghĩ bạn đang nghĩ trong phạm vi rất hẹp.
Siten

5
Bạn thấy đấy, CPU bộ nhớ cache đa lõi, đa lõi, đa tuyến, bộ nhớ cache đa cấp cho phép chúng ta tập trung vào làm theo các thực tiễn tốt nhất mà không phải lo lắng về chu kỳ xung nhịp. Hơn nữa, tối ưu hóa chỉ được khuyến khích nếu và chỉ khi nó được xác định là cần thiết và khi cần thiết, một vài điều chỉnh cục bộ cao thường sẽ đạt được hiệu suất mong muốn, do đó không cần phải xả rác tất cả mã của chúng tôi với ít hack trong tên của hiệu suất.
Mike Nakis

293

Tôi đã so sánh mã byte của hai ví dụ (tương tự) đó:

Hãy xem 1. ví dụ :

package inside;

public class Test {
    public static void main(String[] args) {
        while(true){
            String str = String.valueOf(System.currentTimeMillis());
            System.out.println(str);
        }
    }
}

sau đó javac Test.java, javap -c Testbạn sẽ nhận được:

public class inside.Test extends java.lang.Object{
public inside.Test();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   invokestatic    #2; //Method java/lang/System.currentTimeMillis:()J
   3:   invokestatic    #3; //Method java/lang/String.valueOf:(J)Ljava/lang/String;
   6:   astore_1
   7:   getstatic       #4; //Field java/lang/System.out:Ljava/io/PrintStream;
   10:  aload_1
   11:  invokevirtual   #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   14:  goto    0

}

Hãy xem 2. ví dụ :

package outside;

public class Test {
    public static void main(String[] args) {
        String str;
        while(true){
            str =  String.valueOf(System.currentTimeMillis());
            System.out.println(str);
        }
    }
}

sau đó javac Test.java, javap -c Testbạn sẽ nhận được:

public class outside.Test extends java.lang.Object{
public outside.Test();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   invokestatic    #2; //Method java/lang/System.currentTimeMillis:()J
   3:   invokestatic    #3; //Method java/lang/String.valueOf:(J)Ljava/lang/String;
   6:   astore_1
   7:   getstatic       #4; //Field java/lang/System.out:Ljava/io/PrintStream;
   10:  aload_1
   11:  invokevirtual   #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   14:  goto    0

}

Các quan sát cho thấy rằng không có sự khác biệt giữa hai ví dụ này. Đó là kết quả của thông số kỹ thuật JVM ...

Nhưng trong tên của thực hành mã hóa tốt nhất, nên khai báo biến trong phạm vi nhỏ nhất có thể (trong ví dụ này là bên trong vòng lặp, vì đây là nơi duy nhất sử dụng biến).


3
Nó là kết quả của Thông báo JVM, không phải là 'tối ưu hóa trình biên dịch'. Các vị trí ngăn xếp được yêu cầu bởi một phương thức đều được phân bổ khi nhập vào phương thức. Đó là cách mã byte được chỉ định.
Hầu tước Lorne

2
@Arhimed có thêm một lý do để đặt nó vào trong vòng lặp (hoặc chỉ là khối '{}'): trình biên dịch sẽ sử dụng lại bộ nhớ được phân bổ trong khung ngăn xếp cho biến trong phạm vi khác nếu bạn khai báo trong phạm vi khác một số biến .
Serge

1
Nếu vòng lặp của nó thông qua một danh sách các đối tượng dữ liệu, thì nó sẽ tạo ra sự khác biệt nào cho phần lớn dữ liệu? Có lẽ là 40 ngàn.
Mithun Khatri

7
Đối với bất kỳ ai trong số các bạn finalyêu thích: khai báo strnhư finaltrong insidetrường hợp gói cũng không có gì khác biệt =)
skia.heliou

27

Khai báo các đối tượng trong phạm vi nhỏ nhất cải thiện khả năng đọc .

Hiệu suất không thành vấn đề đối với các trình biên dịch ngày nay. (Trong kịch bản này)
Từ góc độ bảo trì, tùy chọn thứ 2 là tốt hơn.
Khai báo và khởi tạo các biến ở cùng một vị trí, trong phạm vi hẹp nhất có thể.

Như Donald Ervin Knuth đã nói:

"Chúng ta nên quên đi những hiệu quả nhỏ, nói về 97% thời gian: tối ưu hóa sớm là gốc rễ của mọi tội lỗi"

tức là) tình huống trong đó một lập trình viên cho phép xem xét hiệu suất ảnh hưởng đến việc thiết kế một đoạn mã. Điều này có thể dẫn đến một thiết kế không sạch như nó có thể hoặc mã không chính xác, bởi vì mã phức tạp bởi tối ưu hóa và lập trình viên bị phân tâm khi tối ưu hóa .


1
"Tùy chọn thứ 2 có hiệu suất nhanh hơn một chút" => bạn đã đo chưa? Theo một trong những câu trả lời, mã byte là như nhau nên tôi không thấy hiệu suất có thể khác nhau như thế nào.
assylias

Tôi xin lỗi nhưng đó thực sự không phải là cách đúng đắn để kiểm tra hiệu năng của chương trình java (và làm thế nào bạn có thể kiểm tra hiệu suất của một vòng lặp vô hạn?)
assylias

Tôi đồng ý với những điểm khác của bạn - chỉ là tôi tin rằng không có sự khác biệt về hiệu suất.
assylias

11

nếu bạn muốn sử dụng strbên ngoài looop cũng; khai báo bên ngoài mặt khác, phiên bản 2 là tốt


11

Hãy bỏ qua câu trả lời cập nhật ...

Đối với những người quan tâm đến hiệu suất, hãy loại bỏ System.out và giới hạn vòng lặp ở mức 1 byte. Sử dụng gấp đôi (kiểm tra 1/2) và sử dụng Chuỗi (3/4) thời gian đã trôi qua tính bằng mili giây được đưa ra dưới đây với Windows 7 Professional 64 bit và JDK-1.7.0_21. Các mã byte (cũng được đưa ra dưới đây cho test1 và test2) không giống nhau. Tôi đã quá lười biếng để kiểm tra với các đối tượng có thể thay đổi và tương đối phức tạp.

gấp đôi

Test1 mất: 2710 ms

Test2 lấy: 2790 ms

Chuỗi (chỉ cần thay thế gấp đôi bằng chuỗi trong các bài kiểm tra)

Test3 mất: 1200 ms

Test4 mất: 3000 ms

Biên dịch và nhận mã byte

javac.exe LocalTest1.java

javap.exe -c LocalTest1 > LocalTest1.bc


public class LocalTest1 {

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        double test;
        for (double i = 0; i < 1000000000; i++) {
            test = i;
        }
        long finish = System.currentTimeMillis();
        System.out.println("Test1 Took: " + (finish - start) + " msecs");
    }

}

public class LocalTest2 {

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        for (double i = 0; i < 1000000000; i++) {
            double test = i;
        }
        long finish = System.currentTimeMillis();
        System.out.println("Test1 Took: " + (finish - start) + " msecs");
    }
}


Compiled from "LocalTest1.java"
public class LocalTest1 {
  public LocalTest1();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
       3: lstore_1
       4: dconst_0
       5: dstore        5
       7: dload         5
       9: ldc2_w        #3                  // double 1.0E9d
      12: dcmpg
      13: ifge          28
      16: dload         5
      18: dstore_3
      19: dload         5
      21: dconst_1
      22: dadd
      23: dstore        5
      25: goto          7
      28: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
      31: lstore        5
      33: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      36: new           #6                  // class java/lang/StringBuilder
      39: dup
      40: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
      43: ldc           #8                  // String Test1 Took:
      45: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      48: lload         5
      50: lload_1
      51: lsub
      52: invokevirtual #10                 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
      55: ldc           #11                 // String  msecs
      57: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      60: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      63: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      66: return
}


Compiled from "LocalTest2.java"
public class LocalTest2 {
  public LocalTest2();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
       3: lstore_1
       4: dconst_0
       5: dstore_3
       6: dload_3
       7: ldc2_w        #3                  // double 1.0E9d
      10: dcmpg
      11: ifge          24
      14: dload_3
      15: dstore        5
      17: dload_3
      18: dconst_1
      19: dadd
      20: dstore_3
      21: goto          6
      24: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
      27: lstore_3
      28: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      31: new           #6                  // class java/lang/StringBuilder
      34: dup
      35: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
      38: ldc           #8                  // String Test1 Took:
      40: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      43: lload_3
      44: lload_1
      45: lsub
      46: invokevirtual #10                 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
      49: ldc           #11                 // String  msecs
      51: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      54: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      57: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      60: return
}

CẬP NHẬT TRẢ LỜI

Thật sự không dễ để so sánh hiệu năng với tất cả các tối ưu hóa JVM. Tuy nhiên, nó là một phần có thể. Thử nghiệm tốt hơn và kết quả chi tiết trong Google Caliper

  1. Một số chi tiết trên blog: Bạn nên khai báo một biến trong vòng lặp hay trước vòng lặp?
  2. Kho lưu trữ GitHub: https://github.com/gunduru/jvdt
  3. Kết quả kiểm tra cho trường hợp kép và vòng lặp 100M (và có tất cả các chi tiết JVM): https://microbenchmark.appspot.com/runs/b1cef8d1-0e2c-4120-be61-a99faff625b4

Đã khai báo Trước 1.759.209 Đã khai báo Bên cạnh 2.242.308

  • Đã khai báo Trước 1.759.209 ns
  • Khai báo bên trong 2.242.308 ns

Mã kiểm tra từng phần cho Tuyên bố kép

Điều này không giống với mã ở trên. Nếu bạn chỉ mã hóa một vòng lặp giả JVM sẽ bỏ qua nó, vì vậy ít nhất bạn cần gán và trả về một cái gì đó. Điều này cũng được khuyến nghị trong tài liệu Caliper.

@Param int size; // Set automatically by framework, provided in the Main
/**
* Variable is declared inside the loop.
*
* @param reps
* @return
*/
public double timeDeclaredInside(int reps) {
    /* Dummy variable needed to workaround smart JVM */
    double dummy = 0;

    /* Test loop */
    for (double i = 0; i <= size; i++) {

        /* Declaration and assignment */
        double test = i;

        /* Dummy assignment to fake JVM */
        if(i == size) {
            dummy = test;
        }
    }
    return dummy;
}

/**
* Variable is declared before the loop.
*
* @param reps
* @return
*/
public double timeDeclaredBefore(int reps) {

    /* Dummy variable needed to workaround smart JVM */
    double dummy = 0;

    /* Actual test variable */
    double test = 0;

    /* Test loop */
    for (double i = 0; i <= size; i++) {

        /* Assignment */
        test = i;

        /* Not actually needed here, but we need consistent performance results */
        if(i == size) {
            dummy = test;
        }
    }
    return dummy;
}

Tóm tắt: kê khai Trước khi biểu thị hiệu suất tốt hơn - thực sự nhỏ bé - và nó trái với nguyên tắc phạm vi nhỏ nhất. JVM thực sự nên làm điều này cho bạn


Phương pháp thử nghiệm không hợp lệ và bạn không cung cấp bất kỳ lời giải thích nào về kết quả của mình.
Hầu tước Lorne

1
@EJP Điều này sẽ khá rõ ràng cho những người quan tâm đến chủ đề này. Phương pháp được lấy từ câu trả lời của PrimosK để cung cấp thêm thông tin hữu ích. Thành thật mà nói tôi không biết làm thế nào để cải thiện câu trả lời này, có lẽ bạn có thể nhấp vào chỉnh sửa và chỉ cho chúng tôi cách thực hiện đúng?
Onur Günduru

2
1) Mã byte Java được tối ưu hóa (sắp xếp lại, thu gọn, v.v.) trong thời gian chạy, vì vậy đừng quan tâm đến những gì được ghi trong các tệp. Class quá nhiều. 2) có 1.000.000.000 lượt chạy để đạt được hiệu suất chiến thắng là 2,8 giây, do đó, khoảng 2,8ns mỗi lần chạy so với phong cách lập trình phù hợp và an toàn. Một người chiến thắng rõ ràng cho tôi. 3) Vì bạn không cung cấp thông tin về khởi động, thời gian của bạn khá vô dụng.
hardcoded

@Hardcoding kiểm tra tốt hơn / điểm chuẩn vi mô với caliper chỉ cho các vòng lặp đôi và 100M. Kết quả trực tuyến, nếu bạn muốn các trường hợp khác hãy chỉnh sửa.
Onur Günduru

Cảm ơn, điều này loại bỏ điểm 1) và 3). Nhưng ngay cả khi thời gian đã tăng lên ~ 5ns mỗi chu kỳ, đây vẫn là thời gian bị bỏ qua. Về lý thuyết có một tiềm năng tối ưu hóa nhỏ, trong thực tế, những thứ bạn đang làm trong mỗi chu kỳ thường đắt hơn rất nhiều. Vì vậy, tiềm năng sẽ là một vài giây tối đa trong một vài phút hoặc thậm chí vài giờ. Có các tùy chọn khác có tiềm năng cao hơn (ví dụ: Ngã ba / Tham gia, Luồng song song) mà tôi sẽ kiểm tra trước khi dành thời gian cho loại tối ưu hóa cấp thấp này.
hardcoded

7

Một giải pháp cho vấn đề này có thể là cung cấp một phạm vi biến đổi gói gọn vòng lặp while:

{
  // all tmp loop variables here ....
  // ....
  String str;
  while(condition){
      str = calculateStr();
      .....
  }
}

Chúng sẽ tự động hủy tham chiếu khi phạm vi bên ngoài kết thúc.


6

Bên trong, phạm vi càng ít biến được nhìn thấy càng tốt.


5

Nếu bạn không cần sử dụng strvòng lặp while (phạm vi liên quan) thì điều kiện thứ hai tức là

  while(condition){
        String str = calculateStr();
        .....
    }

sẽ tốt hơn vì nếu bạn xác định một đối tượng trên ngăn xếp chỉ khi điều đó conditionlà đúng. Tức là sử dụng nó nếu bạn cần


2
Lưu ý rằng ngay cả trong biến thể đầu tiên, không có đối tượng nào được xây dựng nếu điều kiện sai.
Philipp Wendler

@ Phillip: Có bạn đúng. Lỗi của tôi. Tôi đã suy nghĩ như bây giờ. Bạn nghĩ gì?
Cratylus

1
Chà "định nghĩa một đối tượng trên ngăn xếp" là một thuật ngữ hơi kỳ lạ trong thế giới Java. Ngoài ra, việc phân bổ một biến trên ngăn xếp thường là không có thời gian chạy, vậy tại sao phải bận tâm? Phạm vi để giúp các lập trình viên là vấn đề thực sự.
Philipp Wendler

3

Tôi nghĩ rằng tài nguyên tốt nhất để trả lời câu hỏi của bạn sẽ là bài viết sau:

Sự khác nhau giữa khai báo biến trước hoặc trong vòng lặp?

Theo sự hiểu biết của tôi điều này sẽ phụ thuộc vào ngôn ngữ. IIRC Java tối ưu hóa điều này, do đó không có sự khác biệt nào, nhưng JavaScript (ví dụ) sẽ thực hiện phân bổ toàn bộ bộ nhớ mỗi lần trong vòng lặp. Trong Java đặc biệt tôi nghĩ rằng lần thứ hai sẽ chạy nhanh hơn khi thực hiện hồ sơ.


3

Như nhiều người đã chỉ ra,

String str;
while(condition){
    str = calculateStr();
    .....
}

KHÔNG tốt hơn thế này:

while(condition){
    String str = calculateStr();
    .....
}

Vì vậy, đừng khai báo các biến ngoài phạm vi của chúng nếu bạn không sử dụng lại ...


1
ngoại trừ có lẽ theo cách này: liên kết
Dainius Kreivys

2

Khai báo chuỗi str bên ngoài vòng lặp wile cho phép nó được tham chiếu bên trong & bên ngoài vòng lặp while. Khai báo chuỗi str bên trong vòng lặp while cho phép nó chỉ được tham chiếu bên trong vòng lặp while.


1

Các biến nên được khai báo càng gần nơi chúng được sử dụng càng tốt.

Nó làm cho RAII (Thu nhận tài nguyên là khởi tạo) dễ dàng hơn.

Nó giữ phạm vi của các biến chặt chẽ. Điều này cho phép trình tối ưu hóa hoạt động tốt hơn.



1

Các strbiến sẽ có mặt và dè dặt một số không gian trong bộ nhớ ngay cả sau khi thực hiện dưới mã.

 String str;
    while(condition){
        str = calculateStr();
        .....
    }

Các strbiến sẽ không có sẵn và cũng là bộ nhớ sẽ được phát hành được phân bổ cho strbiến ở bên dưới mã.

while(condition){
    String str = calculateStr();
    .....
}

Nếu chúng tôi theo dõi cái thứ hai chắc chắn điều này sẽ làm giảm bộ nhớ hệ thống của chúng tôi và tăng hiệu suất.


0

Khai báo bên trong vòng lặp giới hạn phạm vi của biến tương ứng. Tất cả phụ thuộc vào yêu cầu của dự án về phạm vi của biến.


0

Thực sự, câu hỏi nêu trên là một vấn đề lập trình. Bạn muốn lập trình mã của bạn như thế nào? Nơi nào bạn cần 'STR' để được truy cập? Không có việc sử dụng khai báo một biến được sử dụng cục bộ như một biến toàn cục. Cơ bản về lập trình tôi tin.


-1

Hai ví dụ này dẫn đến cùng một điều. Tuy nhiên, cái đầu tiên cung cấp cho bạn sử dụng strbiến ngoài vòng lặp while; thứ hai thì không.


-1

Cảnh báo cho hầu hết mọi người trong câu hỏi này: Đây là mã mẫu trong đó bên trong vòng lặp có thể dễ dàng chậm hơn 200 lần trên máy tính của tôi với Java 7 (và mức tiêu thụ bộ nhớ cũng hơi khác nhau). Nhưng đó là về phân bổ và không chỉ phạm vi.

public class Test
{
    private final static int STUFF_SIZE = 512;
    private final static long LOOP = 10000000l;

    private static class Foo
    {
        private long[] bigStuff = new long[STUFF_SIZE];

        public Foo(long value)
        {
            setValue(value);
        }

        public void setValue(long value)
        {
            // Putting value in a random place.
            bigStuff[(int) (value % STUFF_SIZE)] = value;
        }

        public long getValue()
        {
            // Retrieving whatever value.
            return bigStuff[STUFF_SIZE / 2];
        }
    }

    public static long test1()
    {
        long total = 0;

        for (long i = 0; i < LOOP; i++)
        {
            Foo foo = new Foo(i);
            total += foo.getValue();
        }

        return total;
    }

    public static long test2()
    {
        long total = 0;

        Foo foo = new Foo(0);
        for (long i = 0; i < LOOP; i++)
        {
            foo.setValue(i);
            total += foo.getValue();
        }

        return total;
    }

    public static void main(String[] args)
    {
        long start;

        start = System.currentTimeMillis();
        test1();
        System.out.println(System.currentTimeMillis() - start);

        start = System.currentTimeMillis();
        test2();
        System.out.println(System.currentTimeMillis() - start);
    }
}

Kết luận: Tùy thuộc vào kích thước của biến cục bộ, sự khác biệt có thể rất lớn, ngay cả với các biến không quá lớn.

Chỉ cần nói rằng đôi khi, bên ngoài hoặc bên trong vòng lặp KHÔNG quan trọng.


1
Chắc chắn, thứ hai là nhanh hơn, nhưng bạn đang làm những thứ khác nhau: test1 đang tạo ra rất nhiều Foo-Object với mảng lớn, test2 thì không. test2 đang sử dụng lại cùng một đối tượng Foo, điều này có thể nguy hiểm trong môi trường đa luồng.
hardcoded

Nguy hiểm trong môi trường đa luồng ??? Hãy giải thích tại sao. Chúng ta đang nói về một biến cục bộ. Nó được tạo ra tại mỗi cuộc gọi của phương thức.
rt15

Nếu bạn chuyển Foo-Object cho một hoạt động đang xử lý dữ liệu không đồng bộ, thì hoạt động đó vẫn có thể hoạt động trên thể hiện Foo trong khi bạn đang thay đổi dữ liệu trong đó. Nó thậm chí không phải là đa luồng để có tác dụng phụ. Vì vậy, việc tái sử dụng cá thể khá nguy hiểm, khi bạn không biết ai vẫn đang sử dụng cá thể
Hardcoding

Ps: Phương pháp setValue của bạn nên được bigStuff[(int) (value % STUFF_SIZE)] = value;(Hãy thử một giá trị của 2147483649L)
hardcoded

Nói về tác dụng phụ: Bạn đã so sánh kết quả của các phương pháp của mình chưa?
hardcoded

-1

Tôi nghĩ rằng kích thước của các đối tượng là tốt. Trong một trong những dự án của tôi, chúng tôi đã khai báo và khởi tạo một mảng hai chiều lớn khiến ứng dụng trở thành ngoại lệ ngoài bộ nhớ. Thay vào đó, chúng tôi đã di chuyển khai báo ra khỏi vòng lặp và xóa mảng khi bắt đầu mỗi lần lặp.


-2

Bạn có nguy cơ NullPointerExceptionnếu calculateStr()phương thức của bạn trả về null và sau đó bạn cố gắng gọi một phương thức trên str.

Tổng quát hơn, tránh có các biến với một giá trị null . Bằng cách này, nó mạnh hơn cho các thuộc tính lớp.


2
Đây không phải là cách liên quan đến câu hỏi. Xác suất của NullPulumException (trên các lệnh gọi hàm trong tương lai) sẽ không phụ thuộc vào cách khai báo một biến.
Sa mạc băng

1
Tôi không nghĩ vậy, bởi vì câu hỏi là "cách tốt nhất để làm điều đó là gì?". IMHO Tôi muốn một mã an toàn hơn.
Rémi Doolaeghe

1
Không có rủi ro nào về việc NullPointerException.Nếu mã này cố gắng return str;sẽ gặp phải lỗi biên dịch.
Hầu tước Lorne
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.