Các phương thức trợ giúp riêng nên tĩnh nếu chúng có thể tĩnh


205

Giả sử tôi có một lớp được thiết kế để khởi tạo. Tôi có một số phương thức "người trợ giúp" riêng trong lớp không yêu cầu quyền truy cập vào bất kỳ thành viên nào trong lớp và chỉ hoạt động dựa trên các đối số của họ, trả về kết quả.

public class Example {
   private Something member;

   public double compute() {
       double total = 0;
       total += computeOne(member);
       total += computeMore(member);
       return total;         
   }

   private double computeOne(Something arg) { ... }
   private double computeMore(Something arg) {... } 
} 

Có bất kỳ lý do cụ thể để chỉ định computeOnecomputeMorenhư các phương thức tĩnh - hoặc bất kỳ lý do cụ thể nào không?

Chắc chắn dễ dàng nhất để chúng là không tĩnh, mặc dù chúng chắc chắn có thể tĩnh mà không gây ra bất kỳ vấn đề nào.


3
Xem thêm khi không sử dụng từ khóa tĩnh trong Java: stackoverflow.com/questions/1766715/
Mark

Câu trả lời:


174

Tôi thích các phương pháp trợ giúp như vậy được private static; Điều này sẽ cho người đọc thấy rõ rằng họ sẽ không sửa đổi trạng thái của đối tượng. IDE của tôi cũng sẽ hiển thị các lệnh gọi đến các phương thức tĩnh in nghiêng, vì vậy tôi sẽ biết phương thức này là tĩnh mà không cần tìm chữ ký.


2
Một trong những chủ đề yêu thích của tôi. Tôi đang sử dụng NetBeans và nó hiển thị các cuộc gọi phương thức tĩnh với phông chữ in nghiêng.
skiabox

2
Tôi thường khai báo các phương thức của trình trợ giúp vì protected staticchúng làm cho chúng có thể kiểm tra rất dễ dàng trong một lớp kiểm tra trong cùng một gói.
James

2
@James hoặc đơn giản là static (nếu chúng tôi chỉ muốn thử nghiệm).
mrod

3
"Sẽ không sửa đổi trạng thái của đối tượng" - giải thích đầy đủ về cách phân biệt nó với phương thức riêng tư.
HopeKing

110

Nó có thể dẫn đến mã byte nhỏ hơn một chút, vì các phương thức tĩnh sẽ không được truy cập this. Tôi không nghĩ nó tạo ra bất kỳ sự khác biệt nào về tốc độ (và nếu có, nó có thể quá nhỏ để tạo ra sự khác biệt về tổng thể).

Tôi sẽ làm cho chúng tĩnh, vì tôi thường làm như vậy nếu có thể. Nhưng đó chỉ là tôi.


EDIT: Câu trả lời này tiếp tục bị hạ thấp, có thể là do sự khẳng định không có căn cứ về kích thước mã byte. Vì vậy, tôi thực sự sẽ chạy thử nghiệm.

class TestBytecodeSize {
    private void doSomething(int arg) { }
    private static void doSomethingStatic(int arg) { }
    public static void main(String[] args) {
        // do it twice both ways
        doSomethingStatic(0);
        doSomethingStatic(0);
        TestBytecodeSize t = new TestBytecodeSize();
        t.doSomething(0);
        t.doSomething(0);
    }
}

Mã byte (lấy với javap -c -private TestBytecodeSize):

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

private void doSomething(int);
  Code:
   0:   return

private static void doSomethingStatic(int);
  Code:
   0:   return

public static void main(java.lang.String[]);
  Code:
   0:   iconst_0
   1:   invokestatic    #2; //Method doSomethingStatic:(I)V
   4:   iconst_0
   5:   invokestatic    #2; //Method doSomethingStatic:(I)V
   8:   new     #3; //class TestBytecodeSize
   11:  dup
   12:  invokespecial   #4; //Method "<init>":()V
   15:  astore_1
   16:  aload_1
   17:  iconst_0
   18:  invokespecial   #5; //Method doSomething:(I)V
   21:  aload_1
   22:  iconst_0
   23:  invokespecial   #5; //Method doSomething:(I)V
   26:  return

}

Gọi phương thức tĩnh mất hai mã byte (byteops?): iconst_0(Đối với đối số) và invokestatic.
Gọi phương thức không tĩnh mất ba: aload_1(đối vớiTestBytecodeSize đối tượng, tôi cho là), iconst_0(đối với đối số) và invokespecial. (Lưu ý rằng nếu đây không phải là các phương thức riêng tư, thì nó sẽ invokevirtualthay thế invokespecial; xem Phương thức gọi JLS §7.7 .)

Bây giờ, như tôi đã nói, tôi không hy vọng sẽ có bất kỳ sự khác biệt lớn nào về hiệu suất giữa hai điều này, ngoài thực tế invokestaticđòi hỏi một mã byte ít hơn.invokestaticinvokespecialcả hai nên nhanh hơn một chút invokevirtual, vì cả hai đều sử dụng liên kết tĩnh thay vì động, nhưng tôi không biết liệu cái này có nhanh hơn cái kia không. Tôi cũng không thể tìm thấy bất kỳ tài liệu tham khảo tốt. Gần nhất tôi có thể tìm thấy là bài viết JavaWorld 1997 này , về cơ bản khôi phục lại những gì tôi vừa nói:

Các hướng dẫn nhanh nhất rất có thể sẽ là invokespecialinvokestatic, bởi vì các phương thức được gọi bởi các hướng dẫn này bị ràng buộc tĩnh. Khi JVM giải quyết tham chiếu tượng trưng cho các hướng dẫn này và thay thế nó bằng tham chiếu trực tiếp, tham chiếu trực tiếp đó có thể sẽ bao gồm một con trỏ tới mã byte thực tế.

Nhưng nhiều thứ đã thay đổi kể từ năm 1997.

Vì vậy, kết luận ... tôi đoán tôi vẫn còn gắn bó với những gì tôi đã nói trước đó. Tốc độ không phải là lý do để chọn cái khác, vì nó sẽ là tối ưu hóa vi mô tốt nhất.


tất cả những phiếu bầu tiêu cực cho những gì có vẻ như là một câu trả lời rất đơn giản. +1 về lợi ích của sự thật, công lý, ....
Steve B.

Cảm ơn. (Mặc dù tôi có thể đã xóa nó và nhận được một huy hiệu khi nó ở -3 .: D)
Michael Myers

2
Trình biên dịch JIT sẽ chuyển sang nội tuyến và tối ưu hóa chúng, do đó, số lượng các lệnh bytecode ít liên quan đến tốc độ của nó.
Esko Luontola

3
Đó là lý do tại sao tôi nói "Tôi không nghĩ nó tạo ra bất kỳ sự khác biệt nào về tốc độ (và nếu có, nó có thể quá nhỏ để tạo ra sự khác biệt về tổng thể)."
Michael Myers

7
Trước khi tham gia vào bất kỳ tối ưu hóa vi mô nào của loại đó, bạn nên xem xét ảnh hưởng của trình biên dịch điểm nóng. Dòng dưới cùng: viết mã của bạn để con người dễ đọc và để lại tối ưu hóa cho trình biên dịch
Ichthyo

18

Sở thích cá nhân của tôi sẽ là tuyên bố chúng tĩnh, vì đó là một lá cờ rõ ràng mà chúng không trạng thái.


18

Câu trả lơi con phụ thuộc vao nhiêu thư.

Nếu thành viên là một biến đối tượng cụ thể cho đối tượng bạn đang xử lý, thì tại sao lại chuyển nó thành một tham số?

Ví dụ:

public class Example {
   private Something member;

   public double compute() {
       double total = 0;
       total += computeOne();
       total += computeMore();
       return total;         
   }

   private double computeOne() { /* Process member here */ }
   private double computeMore() { /* Process member here */ } 
}

5
+1: Mặc dù ví dụ này là xấu, nhưng có quá nhiều phương thức mà COULD tĩnh là mùi mã xấu. Nhân tiện, các phương thức tĩnh thực sự là funcitons, chúng hoàn toàn không phải là OO. Chúng có thể thuộc về một phương thức trong một lớp khác hoặc bạn có thể thiếu một lớp mà hàm này có thể thuộc về một phương thức.
Bill K

11

Một lý do bạn có thể muốn khai báo các phương thức của trình trợ giúp tĩnh là nếu bạn cần gọi chúng trong hàm tạo của lớp "trước" thishoặc super. Ví dụ:

public class MyClass extends SomeOtherClass { 
    public MyClass(String arg) {
       super(recoverInt(arg));
    }

    private static int recoverInt(String arg) {
       return Integer.parseInt(arg.substring(arg.length() - 1));
    }
}

Đây là một chút của một ví dụ giả định nhưng rõ ràng recoverIntkhông thể là một phương thức ví dụ trong trường hợp này.


bạn có thể gọi một phương thức cá thể trong hàm tạo, nhưng nếu bạn ủy quyền cho một hàm tạo khác với super (...) hoặc this (...), bạn không thể gọi một phương thức cá thể cho đến sau khi ủy nhiệm (nhưng thống kê là ok như bạn chỉ ra)
Kip

10

Tôi thực sự không thể nghĩ ra những lợi thế rõ ràng cho phương pháp tĩnh riêng. Điều đó đang được nói, không có lợi thế cụ thể để làm cho chúng không tĩnh. Đây chủ yếu là vấn đề trình bày: bạn có thể muốn làm cho chúng tĩnh để nhấn mạnh rõ ràng thực tế rằng chúng không làm thay đổi một đối tượng.

Đối với phương thức có các đặc quyền truy cập khác nhau, tôi nghĩ có hai đối số chính:

  • các phương thức tĩnh có thể được gọi mà không tạo một thể hiện của một đối tượng, có thể hữu ích
  • phương thức tĩnh không thể được kế thừa, đây có thể là một vấn đề nếu bạn cần đa hình (nhưng không liên quan đến phương thức riêng).

Bên cạnh đó, sự khác biệt là khá nhỏ và tôi hoàn toàn nghi ngờ rằng con trỏ thêm này được truyền cho phương thức cá thể tạo ra sự khác biệt đáng kể.


4
Ưu điểm của việc không làm cho nó tĩnh là ai đó có thể phân lớp và ghi đè hành vi của phương thức.
Clint Miller

7
Clint nó là một phương thức riêng tư để bạn không thể ghi đè lên phương thức.
Bhushan Bhangale

Chính xác, đối với phương pháp riêng tư tôi không nghĩ nó tạo ra nhiều sự khác biệt. Tuy nhiên, đối với các phương thức công khai, tôi đồng ý với những gì bạn đang nói
Axelle Ziegler

8

Câu trả lời đúng là:

bất kỳ phương thức nào không lấy bất kỳ thông tin nào từ một trường và không đưa bất kỳ thông tin nào vào một trường, không phải là một phương thức thể hiện. Bất kỳ phương thức nào không sử dụng hoặc thay đổi bất kỳ trường nào trong lớp hoặc đối tượng của nó cũng có thể là một phương thức tĩnh.


5

hoặc bất kỳ lý do cụ thể nào để không [khai báo chúng là tĩnh]?

Đúng.

Bằng cách giữ chúng làm phương thức ví dụ, bạn cho phép bản thân cung cấp một triển khai khác sau này.

Nghe có vẻ ngớ ngẩn (và thực tế sẽ là nếu các phương thức đó chỉ được bạn sử dụng trong chương trình 50 dòng) nhưng trong các ứng dụng lớn hơn hoặc trong các thư viện được người khác sử dụng, bạn có thể quyết định chọn triển khai tốt hơn, nhưng không muốn phá mã hiện có.

Vì vậy, bạn tạo một lớp con và trả về trong các phiên bản mới và vì các phương thức được khai báo là phương thức cá thể, bạn chỉ cần để đa hình thực hiện công việc của nó.

Ngoài ra, bạn có thể hưởng lợi từ việc đặt hàm tạo riêng tư và cung cấp phương thức tĩnh cho cùng một lý do.

Vì vậy, khuyến nghị của tôi là giữ chúng như các phương thức cá thể và tránh tĩnh nếu có thể.
Tận dụng sự năng động mà ngôn ngữ cung cấp.

Xem ở đây để xem một video có liên quan: Cách thiết kế API tốt và lý do tại sao nó quan trọng

Mặc dù nó không liên quan trực tiếp đến cuộc thảo luận về phương pháp "tĩnh so với cá thể", nhưng nó chạm vào một số điểm thú vị trong thiết kế API.


1
Hoàn toàn đồng ý. Tôi thường cố gắng tránh các thống kê và tư nhân hoàn toàn vì lý do này. Tôi nghĩ rằng hầu hết các câu trả lời khác đã bỏ lỡ điểm.
Clint Miller

22
Chúng ta đang nói về những người trợ giúp riêng tư , điều đó có nghĩa là họ không phải là một phần của API công khai của lớp. Điều đó có nghĩa là không có gì ngăn cản bạn cung cấp một triển khai khác sau này, khiến chúng không tĩnh hoặc thực hiện bất kỳ thay đổi nào khác.
Jonik

Aaaahmm ... đó là một điểm tốt Jonik. Tuy nhiên, tạo thói quen tốt là đủ lý do (tất nhiên là nói một cách chủ quan)
OscarRyz

2
Hoàn toàn không đồng ý. Không có lý do chính đáng nào bạn muốn thay đổi một phương thức riêng tư mà không thay đổi lớp mà nó được định nghĩa. Cách duy nhất để làm những gì bạn đề xuất là thao tác mã byte.
Peter Lawrey

2
Những gì bạn đề xuất có thể có ý nghĩa nếu phương thức này không riêng tư, nhưng ngay cả khi đó tôi sẽ đề nghị bạn thiết kế dựa trên nhu cầu hiện tại thay vì thiết kế dựa trên nhu cầu tưởng tượng.
Peter Lawrey

5

Một vấn đề về việc có các phương thức tĩnh là nó có thể làm cho đối tượng khó sử dụng hơn trong các thử nghiệm đơn vị . Mockito không thể tạo giả cho các phương thức tĩnh và bạn không thể tạo triển khai lớp con của phương thức.


2
Bạn không nên viết bài kiểm tra cho các phương pháp riêng tư. Xem tại đây . Không cần phải kiểm tra một phương thức riêng tư cho dù đó là tĩnh hay không.
BW

@BW Đó là một điều rất chủ quan. Có nhiều lý do hợp lệ để kiểm tra một phương pháp riêng tư.
ruohola

4

Nếu phương thức về cơ bản chỉ là một chương trình con sẽ không bao giờ sử dụng trước thông tin trạng thái, hãy khai báo nó là tĩnh.

Điều này cho phép nó được sử dụng trong các phương thức tĩnh khác hoặc trong khởi tạo lớp, nghĩa là:

public class Example {
   //...

   //Only possible if computeOne is static
   public final static double COMPUTED_ONE = computeOne(new Something("1"));

   //...
}

3

Sở thích của tôi trong những trường hợp như thế này là để làm computeOnecomputeMorephương pháp tĩnh. Lý do: đóng gói. Càng ít mã có quyền truy cập vào việc triển khai lớp của bạn thì càng tốt.

Trong ví dụ bạn đưa ra, bạn nói rằng computeOnecomputeMorekhông cần phải truy cập vào các phần bên trong của lớp, vậy tại sao lại tạo cơ hội cho những người duy trì lớp học hòa giải với các bên trong.


3

Tôi muốn làm rõ một vài điều mà các áp phích khác đã nói là nó cung cấp thông tin sai.

Thứ nhất vì các phương thức là riêng tư ngay cả khi bạn khai báo chúng tĩnh, bạn sẽ không thể truy cập chúng bên ngoài lớp này. Thứ hai, chúng là riêng tư, do đó bạn thậm chí không thể ghi đè trong lớp con nên tĩnh hoặc không tĩnh không tạo ra bất kỳ sự khác biệt nào. Thứ ba, một phương thức riêng không tĩnh có thể được gọi từ một hàm tạo của lớp, nó không cần phải tĩnh.

Bây giờ đến câu hỏi của bạn nếu một phương thức trợ giúp riêng nên được định nghĩa là tĩnh hoặc không tĩnh. Tôi sẽ đi với câu trả lời của Steve khi đánh dấu một phương thức tĩnh cho thấy phương thức này không trạng thái vì tôi cũng tuân theo quy tắc này khi tôi viết mã.


nhưng có những nơi chỉ có thể gọi một phương thức tĩnh, như trong ví dụ của tôi và trong ví dụ của Chris Marshall
Kip

Có Kip câu trả lời của bạn và Chris là chính xác. Tôi đã không nhận xét về những người như họ là câu trả lời chính xác. Tôi chỉ nói về câu trả lời sai.
Bhushan Bhangale

3

Từ kinh nghiệm tôi sẽ nói rằng các phương pháp riêng tư như vậy có xu hướng khá phổ biến và có thể tái sử dụng.

Tôi nghĩ rằng điều đầu tiên cần làm là đặt câu hỏi liệu phương thức này có thể hữu ích bên ngoài bối cảnh lớp hiện tại hay không. Nếu vậy, tôi sẽ làm chính xác những gì Mọi người đề xuất và trích xuất phương thức này thành tĩnh cho một số lớp utils nơi ai đó hy vọng kiểm tra trước khi thực hiện phương thức mới thực hiện chính xác điều tương tự.

Các phương thức riêng được sử dụng chung như vậy là nguồn gốc của phần lớn sao chép mã trong dự án vì mỗi nhà phát triển độc lập tái tạo chúng ngay tại nơi cô ấy cần sử dụng. Vì vậy, tập trung các phương pháp như vậy là một cách để đi.


2

Câu hỏi tĩnh / không tĩnh xuất hiện "tôi có thực sự cần sử dụng một đối tượng của lớp này không"?

Vì vậy, bạn đang truyền đối tượng giữa các phương thức khác nhau? Đối tượng có chứa thông tin hữu ích bên ngoài ngữ cảnh của các phương thức tĩnh không? Có lý do nào để không xác định phương thức theo cả hai cách nếu bạn sử dụng cả hai cách không?

Nếu bạn rơi vào tình huống khó xử này, đối với tôi, dường như bạn có tất cả dữ liệu cần thiết cho phương thức trôi nổi trong mã của bạn bên ngoài đối tượng. Đây có phải là những gì bạn muốn? Mỗi lần thu thập dữ liệu đó vào một đối tượng sẽ dễ dàng hơn? Bạn có thể chỉ mơ hồ về việc cam kết với một mô hình duy nhất. Nếu bạn có thể làm tất cả bằng một cách, sau đó chọn tĩnh hoặc không tĩnh và đi với nó.


2

Cụ thể hơn với ví dụ bạn đã đưa ra, dường như mục đích của việc xác định các phương thức này là nhằm mục đích rõ ràng hơn khi bạn đọc nó hơn là cho chức năng (chúng được định nghĩa là riêng tư). Trong trường hợp đó, đi với tĩnh thực sự không có gì cho bạn, vì mục đích của tĩnh là để lộ chức năng lớp.


Câu trả lời nhỏ này, bị chôn vùi dưới một loạt các câu trả lời khác, thực sự chạm đến bản chất của vấn đề: chức năng cấp lớp so với chức năng cấp đối tượng.
eljenso

Cảm ơn, el, tôi thực sự đánh giá cao nó. Tôi cũng không ngại một chút bỏ phiếu :)
chú ý

Tôi đã cho nó +1 vào thời điểm tôi viết bình luận.
eljenso

1
Tôi không đồng ý. Mã rõ ràng vẫn còn quan trọng đối với các phương pháp riêng tư. Nó có thể ít quan trọng hơn nhưng nó vẫn là một cái gì đó để phấn đấu.
Truyền thuyết Bước sóng

1

Một lý do là, tất cả những thứ khác đều bằng nhau, các cuộc gọi phương thức tĩnh sẽ nhanh hơn. Các phương thức tĩnh không thể là ảo và không lấy tham chiếu ẩn này.


Xem câu trả lời của tôi dưới đây (tôi đã thêm phân tích sau khi nó đã ở mức -3, vì vậy nó không hiển thị lắm).
Michael Myers

1

Như nhiều người nói, làm cho nó là một tĩnh ! Đây là quy tắc ngón tay cái mà tôi tuân theo: Nếu bạn nghĩ phương thức này chỉ là một hàm toán học nghĩa là nó không trạng thái, không liên quan đến bất kỳ biến đối tượng nào (=> không có vars màu xanh lam [trong nhật thực] trong phương thức) và kết quả của phương thức sẽ giống nhau cho số lượng cuộc gọi 'n' (có cùng tham số, tất nhiên), sau đó đánh dấu phương thức đó là STATIC.

Và nếu bạn nghĩ rằng phương thức này sẽ hữu ích cho lớp khác thì hãy chuyển nó sang lớp Util nếu không, hãy đặt phương thức này ở chế độ riêng tư trong mẫu. (giảm thiểu khả năng tiếp cận)


1

Off-Topic: Tôi sẽ giữ các phương thức của trình trợ giúp trong một lớp trình trợ giúp / tiện ích độc lập chỉ có các phương thức tĩnh trong đó.

Rắc rối với việc có các phương thức của trình trợ giúp tại điểm sử dụng (đọc 'cùng một lớp') là ai đó ở dưới dòng có thể chỉ chọn đăng các phương thức trợ giúp không liên quan của họ ở cùng một nơi


-1: SO không phải là một diễn đàn nhắn tin. Bạn sẽ làm tốt hơn bằng cách thực sự hỏi một câu hỏi thích hợp.
Stu Thompson

1
Tôi muốn có các phương thức của trình trợ giúp cùng với lớp mà chúng bị ràng buộc / liên quan đến như các phương thức tĩnh. Thậm chí, tôi có thể tiết lộ một số là công khai nếu chúng nên được sử dụng bên ngoài. Bằng cách đó, việc duy trì API cho lớp đó sẽ dễ dàng hơn, nếu nó được cho là được phơi bày công khai và được sử dụng nhiều.
Ivaylo Slavov

1
class Whatever {

    public static varType myVar = initializeClassVariable();

    private static varType initializeClassVariable() {

        //initialization code goes here
    }
}

Ưu điểm của các phương thức tĩnh riêng là chúng có thể được sử dụng lại sau này nếu bạn cần khởi tạo lại biến lớp.


1
  1. Nếu không có công cụ sửa đổi tĩnh, bạn không thể hiểu rằng phương thức này không trạng thái mà không cần phân tích bổ sung, có thể dễ dàng thực hiện khi bạn (viết lại) phương thức.

  2. Sau đó, công cụ sửa đổi "tĩnh" có thể cung cấp cho bạn ý tưởng về tái cấu trúc bên cạnh những thứ khác mà người khác có thể thấy không đáng tin. Ví dụ: di chuyển phương thức sang một số lớp Utility hoặc chuyển đổi nó thành một phương thức thành viên ..


0

Tôi sẽ tuyên bố chúng là tĩnh để gắn cờ chúng là không trạng thái.

Java không có cơ chế tốt hơn cho các hoạt động nhỏ không được xuất khẩu, vì vậy tôi nghĩ rằng tĩnh riêng là chấp nhận được.

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.