2010-08-31 23 views
13

Khi tôi thấy chính mình gọi cùng một phương thức getter nhiều lần, đây có phải là vấn đề không? Tốt hơn là [luôn] gán cho một biến cục bộ và chỉ gọi một lần?Tối ưu hóa sớm trong Java: khi nào sử dụng "x = foo.getX()" vs đơn giản "foo.getX()"

Tôi chắc chắn câu trả lời tất nhiên là "nó phụ thuộc".

Tôi quan tâm hơn đến trường hợp đơn giản hơn, nơi getter chỉ đơn giản là phương thức kiểu "pass-along-the-value-of-a-private-variable". tức là không có tính toán đắt tiền nào liên quan, không có kết nối cơ sở dữ liệu nào được tiêu thụ, v.v.

Câu hỏi của tôi "tốt hơn" liên quan đến cả khả năng đọc mã (kiểu) và hiệu suất. ví dụ là nó rằng có rất nhiều hit hiệu suất để có:

SomeMethod1(a, b, foo.getX(), c); 
SomeMethod2(b, foo.getX(), c); 
SomeMethod3(foo.getX()); 

vs:

X x = foo.getX(); 
SomeMethod1(a, b, x, c); 
SomeMethod2(b, x, c); 
SomeMethod3(x); 

Tôi nhận ra câu hỏi này là một chút nit-kén cá chọn canh và màu xám. Nhưng tôi mới nhận ra, tôi không có cách nào nhất quán để đánh giá những sự cân bằng này. Đang đánh bắt cá cho một số tiêu chí có nhiều hơn là chỉ hoàn toàn hay thay đổi.

Cảm ơn.

+2

Đây là trường hợp chỉ có một chuỗi cập nhật foo mà tôi đoán? – extraneon

+2

+1 cho việc sử dụng câu hay thay đổi trong câu hỏi –

+3

Có phải foo không thay đổi (ít nhất là trong suốt thời gian tồn tại của x) không? Nếu không, thì chúng không giống nhau. Nó là không đủ rằng getX không có tác dụng phụ. Nếu SomeMethod1 gọi foo.setX thì chúng không giống nhau. Nếu chúng không giống nhau, thì thay vì lo lắng về khả năng đọc hoặc hiệu suất, hãy chọn đúng. – emory

Trả lời

14

Lựa chọn không thực sự là về hiệu suất truy cập nhưng về khả năng đọc mã.

Khi bạn tạo một biến, bạn có thể đặt cho nó tên mà nó xứng đáng trong ngữ cảnh hiện tại. Khi bạn sử dụng cùng một giá trị nhiều lần, nó chắc chắn có ý nghĩa thực, nhiều hơn một tên phương thức (hoặc tệ hơn là một chuỗi các phương thức).
Và nó thực sự tốt hơn để đọc:

String username = user.getName(); 
SomeMethod1(a, b, username, c); 
SomeMethod2(b, username, c); 
SomeMethod3(username); 

hơn

SomeMethod1(a, b, user.getName(), c); 
SomeMethod2(b, user.getName(), c); 
SomeMethod3(user.getName()); 
+2

IMO, 'user.getName()' đôi khi dễ đọc hơn, bởi vì bạn không phải tìm kiếm (và cuộn lên) đến vị trí đã được đặt. Bạn có thể đặt tên biến 'username' quá, nếu nó được đặt thông qua' request.getUsername() '- vậy cái nào là nó? –

+0

+1 cho khả năng đọc mã – extraneon

+0

@chris_l như tôi đã nói mỗi tên biến có ý nghĩa riêng trong ngữ cảnh được sử dụng. Và có, tôi có lẽ sẽ đặt tên 'request.getUsername()' tên người dùng quá. Và nếu tôi phải sử dụng cả hai, sẽ có một lý do, như tên người dùng mới và tên người dùng cũ. newUsername, oldUsername có thể phù hợp. Đó là tất cả về bối cảnh. –

2

Hầu hết thời gian tôi sẽ sử dụng getX nếu chỉ một lần và tạo var cho nó cho tất cả các trường hợp khác. Thường chỉ để lưu gõ.

Liên quan đến hiệu suất, trình biên dịch có thể tối ưu hóa phần lớn chi phí, nhưng khả năng tác dụng phụ có thể buộc trình biên dịch hoạt động nhiều hơn khi thực hiện nhiều cuộc gọi phương thức.

8

Đối với các bản ghi đơn giản - những thứ chỉ trả về một giá trị - HotSpot inline nó trong mã gọi, vì vậy nó sẽ nhanh như nó có thể.

Tôi, tuy nhiên, có nguyên tắc về việc giữ một tuyên bố trên một dòng, điều này thường dẫn đến các biểu thức như "foo.getBar()" quá dài để vừa. Sau đó, nó dễ đọc hơn - với tôi - để giải nén nó thành một biến cục bộ ("Bar bar = foo.getBar()").

6

Chúng có thể là 2 thứ khác nhau.

Nếu GetX là không xác định thì một 1st sẽ cho kết quả khác nhau hơn so với 2

Cá nhân, tôi muốn sử dụng một trong 2. Nó rõ ràng hơn và ít tốn kém hơn một cách không cần thiết.

+3

Từ bạn đang tìm kiếm là "idempotent". 'x + = 2; return x; 'là hoàn toàn xác định, nhưng sẽ cho kết quả khác nhau khi được gọi nhiều lần. –

+0

@Michael Madsen: đúng. suy nghĩ cẩu thả về phần của tôi – gbn

3

tôi sử dụng phong cách thứ hai nếu nó làm cho mã của tôi dễ đọc hơn hoặc nếu tôi phải sử dụng giá trị giao một lần nữa. Tôi không bao giờ xem xét hiệu suất (trên những điều tầm thường) trừ khi tôi phải làm.

+0

Plus có thể dễ dàng hơn để đặt điểm ngắt thành X x = foo.getX(); để kiểm tra giá trị được trả về. – RealHowTo

3

Điều đó tùy thuộc vào những gì getX() thực sự thực hiện. Xem xét lớp này:

public class Foo { 
    private X x; 

    public X getX() { return x; } 
} 

Trong trường hợp này, khi bạn thực hiện một cuộc gọi đến foo.getX(), JVM sẽ tối ưu hóa tất cả các con đường xuống foo.x(như trong tài liệu tham khảo trực tiếp đến foo 's lĩnh vực tư nhân, cơ bản là một con trỏ bộ nhớ). Tuy nhiên, nếu lớp trông như thế này:

public class Foo { 
    private X x; 

    public X getX() { return cleanUpValue(x); } 

    private X cleanUpValue(X x) { 
     /* some modifications/sanitization to x such as null safety checks */ 
    } 
} 

JVM không thể thực inline nó một cách hiệu quả nữa kể từ bởi Foo của hợp đồng xây dựng, nó có để khử trùng x trước khi trao nó ra.

Để tóm tắt, nếu getX() không thực sự làm gì ngoài việc trả lại trường, thì không có sự khác biệt sau khi tối ưu hóa ban đầu chạy sang bytecode cho dù bạn gọi phương thức chỉ một lần hoặc nhiều lần.

+0

Nếu tôi đã hiểu nó một cách chính xác, HotSpot có thể nội tuyến getX() ngay cả khi nó gọi nhiều mã hơn. Quyết định được dựa trên kích thước của phương pháp - không phải là những gì nó thực sự làm. –

+0

Tôi dường như nhớ đọc một số nội dung trên jruby rằng Hotspot hiện đang inline lên đến 9 cuộc gọi lồng nhau –

+0

JVM không có vấn đề gì với nội tuyến getX và cleanUpValue. –

1

Hai điều phải được xem xét:

  1. Liệu các cuộc gọi đến GetX() có bất kỳ tác dụng phụ? Sau các mẫu mã hóa đã được thiết lập, một getter không nên thay đổi đối tượng mà nó được gọi, trong hầu hết các trường hợp, không có tác dụng phụ. Do đó, nó tương đương về mặt ngữ nghĩa để gọi getter một lần và lưu trữ giá trị cục bộ so với gọi hàm getter nhiều lần. (Khái niệm này được gọi là idempotency - nó không quan trọng cho dù bạn gọi một phương pháp một lần hoặc nhiều lần, ảnh hưởng trên dữ liệu là chính xác như nhau.)

  2. Nếu getter không có tác dụng phụ, trình biên dịch có thể loại bỏ một cách an toàn các cuộc gọi tiếp theo đến getter và tạo lưu trữ cục bộ tạm thời - do đó, mã vẫn cực kỳ dễ đọc và bạn có tất cả lợi thế về tốc độ từ việc gọi getter chỉ một lần. Điều này quan trọng hơn nếu getter không đơn giản trả về một giá trị nhưng phải lấy/tính giá trị hoặc chạy một số xác nhận hợp lệ.

Giả sử getter của bạn không thay đổi đối tượng mà nó hoạt động nó có lẽ là dễ đọc hơn để có nhiều cuộc gọi đến GetX() - và nhờ vào trình biên dịch bạn không cần phải kinh doanh hiệu quả để có thể đọc và bảo trì.

2

tôi thường lưu nó tại địa phương nếu:

  1. Tôi sẽ sử dụng nó trong một vòng lặp và tôi không muốn hoặc mong đợi giá trị thay đổi trong vòng lặp.

  2. Tôi sắp sử dụng nó trong một dòng mã dài hoặc hàm tham số & rất dài.

  3. Tôi muốn đổi tên biến để tương ứng tốt hơn với nhiệm vụ trong tầm tay.

  4. Thử nghiệm cho thấy tăng hiệu suất đáng kể.

Nếu không, tôi thích khả năng nhận giá trị hiện tại và mức độ trừu tượng thấp hơn của các cuộc gọi phương thức.

Các vấn đề liên quan