Lợi ích từ việc khai báo một phương thức là tĩnh là gì


94

Gần đây tôi đã xem qua các cảnh báo của mình trong Eclipse và bắt gặp cảnh báo này:

cảnh báo tĩnh

Nó sẽ đưa ra cảnh báo trình biên dịch nếu phương thức có thể được khai báo là tĩnh.

[sửa] Trích dẫn chính xác trong phần trợ giúp Eclipse, với sự căng thẳng về vấn đề riêng tư và cuối cùng:

Khi được kích hoạt, trình biên dịch sẽ đưa ra lỗi hoặc cảnh báo cho các phương thức là riêng tư hoặc cuối cùng và chỉ tham chiếu đến các thành viên tĩnh.

Có, tôi biết tôi có thể tắt tính năng này, nhưng tôi muốn biết lý do bật tính năng này?

Tại sao sẽ là một điều tốt khi khai báo mọi phương thức có thể là tĩnh?

Điều này sẽ mang lại bất kỳ lợi ích hiệu suất nào? (trong miền di động)

Chỉ ra một phương thức là static, tôi cho rằng bạn không sử dụng bất kỳ biến phiên bản nào, do đó có thể được chuyển sang một lớp kiểu utils?

Vào cuối ngày, tôi chỉ nên tắt 'bỏ qua' này hay tôi nên sửa hơn 100 cảnh báo mà nó đã đưa ra cho tôi?

Bạn có nghĩ rằng đây chỉ là những từ khóa phụ làm bẩn mã, vì dù sao thì trình biên dịch cũng sẽ chỉ đưa ra những phương thức này? (kiểu như bạn không khai báo mọi biến mà bạn có thể cuối cùng nhưng bạn có thể ).


Không chắc chắn nhưng điều này đơn giản có thể được xem như một trợ giúp lập trình. Cảnh báo chỉ đơn giản là những chỉ dẫn về những thứ cần xem xét.
James P. Ngày

1
Tôi tò mò về loại chức năng mà các phương pháp này thực hiện. Có lẽ điều gì đó đã không được thực hiện đúng nếu có quá nhiều điều này.
ArjunShankar

Câu trả lời:


132

Bất cứ khi nào bạn viết một phương thức, bạn hoàn thành một hợp đồng trong một phạm vi nhất định. Phạm vi càng hẹp, khả năng bạn viết lỗi càng nhỏ.

Khi một phương thức là tĩnh, bạn không thể truy cập các thành viên không tĩnh; do đó, phạm vi của bạn hẹp hơn. Vì vậy, nếu bạn không cần và sẽ không bao giờ cần (ngay cả trong các lớp con) các thành viên không tĩnh để thực hiện hợp đồng của mình, tại sao lại cấp quyền truy cập vào các trường này cho phương thức của bạn? Khai báo phương thức statictrong trường hợp này sẽ cho phép trình biên dịch kiểm tra rằng bạn không sử dụng các thành viên mà bạn không có ý định sử dụng.

Và hơn thế nữa, nó sẽ giúp những người đọc mã của bạn hiểu được bản chất của hợp đồng.

Đó là lý do tại sao việc khai báo một phương thức được coi là tốt statickhi nó thực sự triển khai một hợp đồng tĩnh.

Trong một số trường hợp, phương thức của bạn chỉ có nghĩa là một cái gì đó liên quan đến một thể hiện của lớp của bạn và nó sẽ xảy ra rằng việc triển khai nó không thực sự sử dụng bất kỳ trường hoặc thể hiện không tĩnh nào. Trong những trường hợp như vậy, bạn sẽ không đánh dấu phương pháp static.

Ví dụ về nơi bạn sẽ không sử dụng statictừ khóa:

  • Một móc nối mở rộng không làm gì cả (nhưng có thể làm điều gì đó với dữ liệu cá thể trong một lớp con)
  • Một hành vi mặc định rất đơn giản có nghĩa là có thể tùy chỉnh trong một lớp con.
  • Triển khai trình xử lý sự kiện: việc triển khai sẽ thay đổi theo lớp của trình xử lý sự kiện nhưng sẽ không sử dụng bất kỳ thuộc tính nào của thể hiện trình xử lý sự kiện.

1
+1 Nó về việc giảm thiểu cơ hội tự bắn vào chân và giảm mức độ bạn cần biết để hiểu một phương pháp.
Peter Lawrey

7
Thêm vào đó, bạn không cần phải loay hoay tìm kiếm một thể hiện chỉ để gọi một hàm độc lập, khép kín.
Marko Topolnik

1
Vì vậy, trong các thế giới khác, bất kỳ phương thức nào không sử dụng biến cá thể sẽ được khai báo là tĩnh? Tôi luôn nghĩ rằng các phương thức chỉ nên tĩnh nếu nó mong muốn (như phương thức utitility).
Petr Mensik

18
@PetrMensik Nếu một privatephương thức có thể được khai báo là static, thì hầu như nó sẽ luôn như vậy . Đối với bất kỳ cấp độ truy cập nào khác, có các yếu tố khác cần xem xét, chẳng hạn như điều động động.
Marko Topolnik

1
@JamesPoulson Ý tôi là điều phối phương thức động, điều xảy ra trong khi exetucing object.method()chọn phương thức để gọi.
Marko Topolnik

15

Không có khái niệm về tối ưu hóa ở đây.

Một staticphương thức là staticdo bạn khai báo rõ ràng rằng phương thức đó không dựa vào bất kỳ trường hợp nào của lớp bao quanh chỉ vì nó không cần. Vì vậy, cảnh báo Eclipse đó, như đã nêu trong tài liệu:

Khi được kích hoạt, trình biên dịch sẽ đưa ra lỗi hoặc cảnh báo cho các phương thức là riêng tư hoặc cuối cùng và chỉ tham chiếu đến các thành viên tĩnh.

Nếu bạn không cần bất kỳ biến phiên bản nào và phương thức của bạn là private (không thể gọi từ bên ngoài) hoặc cuối cùng (không thể bị ghi đè) thì không có lý do gì để đặt nó là một phương thức bình thường thay vì một phương thức tĩnh. Phương thức tĩnh vốn đã an toàn hơn ngay cả khi bạn được phép làm ít việc hơn với nó (nó không cần bất kỳ trường hợp nào, bạn không có bất kỳ thisđối tượng ngầm nào ).


7

Tôi không có thông tin về hiệu suất, tôi cho rằng nó tốt hơn một chút, vì mã không cần thực hiện điều phối động dựa trên loại.

Tuy nhiên, một lập luận mạnh mẽ hơn nhiều chống lại việc tái cấu trúc thành các phương thức tĩnh là hiện nay việc sử dụng static được coi là một phương pháp không tốt. Các phương thức / biến static không tích hợp tốt vào một ngôn ngữ hướng đối tượng và cũng khó để kiểm tra đúng cách. Đây là lý do tại sao một số ngôn ngữ mới hơn loại bỏ hoàn toàn khái niệm về phương thức / biến tĩnh hoặc cố gắng đưa nó vào ngôn ngữ theo cách phù hợp hơn với OO (ví dụ: Đối tượng trong Scala).

Hầu hết thời gian, bạn cần các phương thức tĩnh để triển khai các hàm chỉ sử dụng tham số làm đầu vào và tạo ra đầu ra bằng cách sử dụng đó (ví dụ: các hàm tiện ích / trợ giúp) Trong các ngôn ngữ hiện đại, có một khái niệm hàm lớp đầu tiên cho phép điều đó, vì vậy tĩnh là không cần thiết. Java 8 sẽ được tích hợp các biểu thức lambda, vì vậy chúng tôi đang chuyển sang hướng này.


7
Các phương thức tĩnh rất khó kiểm tra (miễn là chúng độc lập --- đặc biệt là các hàm thuần túy, là mục tiêu chính của một phương thức tĩnh). Khái niệm OO không có gì để cung cấp trong trường hợp đó. Ngoài ra, khái niệm về một hàm hạng nhất có rất ít liên quan đến khái niệm về một phương thức tĩnh. Nếu bạn cần một hàm hạng nhất thực hiện công việc của một phương thức tĩnh hiện có, thì việc triển khai nó chỉ là một số ít ký tự.
Marko Topolnik

5
Nhận xét về khả năng kiểm thử có lẽ không hướng trực tiếp đến phương thức tĩnh, nhưng các phương thức tĩnh khó bắt chước hơn nhiều, vì vậy chúng làm phức tạp việc thử nghiệm các phương thức gọi là phương thức tĩnh.
Buhb

2
Các phương thức tĩnh rất dễ bị bắt chước nếu bạn sử dụng một khuôn khổ mô phỏng mạnh mẽ như JMockit , PowerMock hoặc Groovy .
Jeff Olson

Đây là những private staticphương pháp, do đó bạn sẽ không cần để chế nhạo họ
Blundell

3

1. Phương thức khai báostaticmang lại lợi ích hiệu suất nhỏ, nhưng điều hữu ích hơn, nó cho phép sử dụng nó mà không cần có một cá thể đối tượng trong tay (ví dụ như về phương thức factory hoặc nhận một singleton). Nó cũng phục vụ mục đích tài liệu là nói lên bản chất của phương pháp. Không nên bỏ qua mục đích tài liệu này, vì nó cung cấp gợi ý ngay lập tức về bản chất của phương pháp cho người đọc mã và người dùng API, đồng thời cũng là công cụ tư duy cho lập trình viên ban đầu - rõ ràng về ý nghĩa dự định sẽ giúp bạn cũng suy nghĩ thẳng thắn và tạo ra mã chất lượng tốt hơn (tôi nghĩ dựa trên kinh nghiệm cá nhân của tôi, nhưng mọi người thì khác). Ví dụ, nó là hợp lý và do đó mong muốn phân biệt giữa các phương thức hoạt động trên một kiểu và các phương thức hoạt động trên một thể hiện của kiểu (như được chỉ ra bởiJon Skeet trong bình luận của mình cho một câu hỏi C # ).

Tuy nhiên, một trường hợp sử dụng khác cho staticcác phương thức là bắt chước giao diện lập trình thủ tục. Hãy nghĩ về java.lang.System.println()lớp và các phương thức và thuộc tính trong đó. Lớp java.lang.Systemđược sử dụng giống như một không gian tên nhóm hơn là một đối tượng có thể khởi tạo.

2. Làm thế nào để Eclipse (hoặc bất kỳ loại thực thể nào khác được lập trình hoặc bất kỳ loại thực thể nào khác - biocomposable hoặc không biocomposable -) có thể biết chắc chắn phương thức nào có thể được khai báo là static? Ngay cả khi một lớp cơ sở không truy cập các biến cá thể hoặc gọi các phương thức không tĩnh, theo cơ chế kế thừa, mọi thứ có thể thay đổi. Chỉ khi phương thức không thể được ghi đè bằng cách kế thừa lớp con, chúng ta mới có thể khẳng định chắc chắn 100% rằng phương thức thực sự có thể được khai báo static. Ghi đè một phương thức là không thể chính xác trong hai trường hợp

  1. private (không có lớp con nào có thể sử dụng nó trực tiếp và về nguyên tắc thậm chí không biết về nó), hoặc
  2. final (ngay cả khi lớp con có thể truy cập được, không có cách nào thay đổi phương thức để tham chiếu đến dữ liệu cá thể hoặc các hàm).

Do đó, logic của tùy chọn Eclipse.

3. Người đăng ban đầu cũng hỏi: " Chỉ ra một phương thức là tĩnh, tôi cho rằng đang cho thấy rằng bạn không sử dụng bất kỳ biến cá thể nào, do đó có thể được chuyển sang lớp kiểu utils? " Đây là một điểm rất tốt. Đôi khi loại thay đổi thiết kế này được chỉ ra bằng cảnh báo.

Đó là một tùy chọn rất hữu ích, mà cá nhân tôi chắc chắn sẽ bật, tôi có sử dụng Eclipse và tôi có lập trình bằng Java không.


1

Hãy xem câu trả lời của Samuel về cách phạm vi của phương pháp thay đổi. Tôi đoán, đây là khía cạnh chính của việc làm cho một phương thức tĩnh.

Bạn cũng đã hỏi về hiệu suất:

Có thể có một mức tăng hiệu suất nhỏ, bởi vì một cuộc gọi đến một phương thức tĩnh không cần tham chiếu ngầm "this" làm tham số.

Tuy nhiên, tác động hiệu suất này thực sự rất nhỏ. Do đó, đó là tất cả về phạm vi.


1

Từ nguyên tắc về Hiệu suất của Android:

Ưu tiên Tĩnh hơn Ảo Nếu bạn không cần truy cập các trường của một đối tượng, hãy đặt phương thức của bạn ở trạng thái tĩnh. Lời mời sẽ nhanh hơn khoảng 15% -20%. Nó cũng là một thực tiễn tốt, vì bạn có thể biết từ ký hiệu phương thức rằng việc gọi phương thức không thể thay đổi trạng thái của đối tượng.

http://developer.android.com/training/articles/perf-tips.html#PreferStatic


"rằng việc gọi phương thức không thể thay đổi trạng thái của đối tượng" - là cực kỳ sai lầm. Tôi không biết bạn thế nào, nhưng tôi chắc chắn coi các thuộc tính tĩnh của một lớp là một phần của trạng thái đối tượng.
Adam Parkin

0

Vâng, tài liệu Eclipse nói về cảnh báo được đề cập:

Phương thức có thể là tĩnh

Khi được kích hoạt, trình biên dịch sẽ đưa ra lỗi hoặc cảnh báo cho các phương thức là riêng tư hoặc cuối cùng và chỉ tham chiếu đến các thành viên tĩnh

Tôi nghĩ nó nói lên tất cả. Nếu phương thức là riêng tư và cuối cùng và chỉ tham chiếu đến các thành viên tĩnh, phương thức được đề cập cũng có thể được khai báo là tĩnh và bằng cách này, hiển nhiên rằng chúng ta chỉ có ý định truy cập nội dung tĩnh từ nó.

Tôi thành thật không nghĩ rằng có bất kỳ lý do bí ẩn nào khác đằng sau nó.


0

Tôi đã thiếu một số con số cho sự khác biệt về tốc độ. Vì vậy, tôi đã cố gắng chuẩn hóa chúng, điều này hóa ra không dễ dàng như vậy: Vòng lặp Java trở nên chậm hơn sau một số lần chạy / lỗi của JIT?

Cuối cùng tôi đã sử dụng Calibre và kết quả giống như chạy các bài kiểm tra của tôi bằng tay:

Không có sự khác biệt có thể đo lường được cho các cuộc gọi tĩnh / động.Ít nhất là không dành cho Linux / AMD64 / Java7.

Kết quả Caliper ở đây: https://microbenchmarks.appspot.com/runs/1426eac9-36ca-48f0-980f-0106af064e8f#r:scenario.benchmarkSpec.methodName,scenario.vmSpec.options.CMSLargeCoalSurplusPercent,scenario.vm. CMSLargeSplitSurplusPercent, kịch bản.vmSpec.options.CMSSmallCoalSurplusPercent, kịch bản.vmSpec.options.CMSSmallSplitSurplusPercent, kịch bản.vmSpec.options.FLSLargestBlockCoalSurplusPercent, kịch bản.vmSpec.options.CMSSmallSplitSurplusPercent, kịch bản.vmSpec.options.FLSLargestBlockCoalesceProximity, kịch bản.vmSpispec.opcuration

và kết quả của riêng tôi là:

Static: 352 ms
Dynamic: 353 ms
Static: 348 ms
Dynamic: 349 ms
Static: 349 ms
Dynamic: 348 ms
Static: 349 ms
Dynamic: 344 ms

Lớp Kiểm tra Caliper là:

public class TestPerfomanceOfStaticMethodsCaliper extends Benchmark {

    public static void main( String [] args ){

        CaliperMain.main( TestPerfomanceOfStaticMethodsCaliper.class, args );
    }

    public int timeAddDynamic( long reps ){
        int r=0;
        for( int i = 0; i < reps; i++ ) {
            r |= addDynamic( 1, i );
        }
        return r;
    }

    public int timeAddStatic( long reps ){
        int r=0;
        for( int i = 0; i < reps; i++ ) {
            r |= addStatic( 1, i );
        }
        return r;
    }

    public int addDynamic( int a, int b ){

        return a+b;
    }

    private static int addStatic( int a, int b ){

        return a+b;
    }

}

Và lớp Kiểm tra của riêng tôi là:

public class TestPerformanceOfStaticVsDynamicCalls {

    private static final int RUNS = 1_000_000_000;

    public static void main( String [] args ) throws Exception{

        new TestPerformanceOfStaticVsDynamicCalls().run();
    }

    private void run(){

        int r=0;
        long start, end;

        for( int loop = 0; loop<10; loop++ ){

            // Benchmark

            start = System.currentTimeMillis();
            for( int i = 0; i < RUNS; i++ ) {
                r += addStatic( 1, i );
            }
            end = System.currentTimeMillis();
            System.out.println( "Static: " + ( end - start ) + " ms" );

            start = System.currentTimeMillis();
            for( int i = 0; i < RUNS; i++ ) {
                r += addDynamic( 1, i );
            }
            end = System.currentTimeMillis();
            System.out.println( "Dynamic: " + ( end - start ) + " ms" );

            // Do something with r to keep compiler happy
            System.out.println( r );

        }

    }

    private int addDynamic( int a, int b ){

        return a+b;
    }

    private static int addStatic( int a, int b ){

        return a+b;
    }

}

Sẽ rất thú vị để xem kết quả trên các thiết bị Android khác nhau và các phiên bản
Blundell

Đúng vậy. Tôi nghĩ rằng bạn sẽ quan tâm :) Nhưng vì bạn đang xây dựng phần mềm Android và có thể có một thiết bị Android được kết nối với trạm phát triển của bạn, tôi khuyên bạn chỉ cần chọn mã, chạy nó và chia sẻ kết quả?
Scheintod

-2

Các phương thức bạn có thể khai báo là static là những phương thức không yêu cầu khởi tạo, chẳng hạn như

public class MyClass
{
    public static string InvertText(string text)
    {
        return text.Invert();
    }
}

Mà sau đó bạn có thể gọi ra trong bất kỳ lớp nào khác mà không cần cài đặt lớp đó.

public class MyClassTwo
{
    public void DoSomething()
    {
        var text = "hello world";
        Console.Write(MyClass.InvertText(text));
    }
}

... Nhưng đó là điều có thể bạn đã biết. Nó không mang lại cho bạn bất kỳ lợi ích thực sự nào, ngoài việc làm rõ hơn rằng phương pháp không sử dụng bất kỳ biến cá thể nào.

Nói cách khác, bạn có thể an toàn nhất chỉ cần tắt hoàn toàn. Nếu bạn biết rằng bạn sẽ không bao giờ sử dụng một phương thức trong các lớp khác (trong trường hợp đó nó chỉ nên là private), bạn không cần nó phải tĩnh.


1
Ngôn ngữ đó là gì? Ví dụ đó có biên dịch không? Không có gì là tĩnh ở đây.
Piotr Perak

Thật vậy, có vẻ như tôi đã quên 'static' từ phương thức InvertText. Và nó là một ví dụ dựa trên c #
NeroS
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.