Tại sao chúng ta không nên sử dụng bảo vệ tĩnh trong java


122

Tôi đã trả lời câu hỏi này Có cách nào để ghi đè các biến lớp trong Java không? Bình luận đầu tiên với 36 phiếu tán thành là:

Nếu bạn nhìn thấy một protected static, hãy chạy.

Bất cứ ai có thể giải thích tại sao một protected staticcau mày?


6
Không có gì sai với một trường tĩnh được bảo vệ, miễn là nó final. Một trường tĩnh có thể thay đổi được chia sẻ giữa các lớp chắc chắn là nguyên nhân gây lo lắng. Nhiều lớp cập nhật một trường tĩnh không có khả năng đáng tin cậy hoặc dễ theo dõi, đặc biệt vì sự hiện diện của bất kỳ trường hoặc phương thức được bảo vệ nào ngụ ý rằng lớp đó được mở rộng bởi các lớp trong các gói khác, có thể là các lớp không nằm dưới sự kiểm soát của tác giả của lớp chứa trường được bảo vệ.
VGR

6
@VGR, finalkhông có nghĩa là trường này là bất biến. Bạn luôn có thể sửa đổi objecttham chiếu được tham chiếu bởi một finalbiến tham chiếu.
Zeeshan

@VGR Tôi không đồng ý. Lý do DUY NHẤT bạn tạo ra một biến tĩnh là chỉ có quyền truy cập vào nó từ bên trong một gói khác bằng cách kế thừa và quyền truy cập vào một trường duy nhất KHÔNG phải là lý do cho sự kế thừa. Đó là một thiết kế thiếu sót, IMO, và nếu bạn dùng đến điều đó, có lẽ bạn nên suy nghĩ lại cấu trúc ứng dụng của mình. Đó chỉ là ý kiến ​​của tôi.
Dioxin

@LoneRider Bạn nói đúng. Tôi đã nghĩ rằng không thể thay đổi, và cuối cùng chắc chắn không đảm bảo điều đó.
VGR

Ngay cả tôi đã đến đây từ cùng một câu hỏi.
Raj Rajeshwar Singh Rathore

Câu trả lời:


86

Đó là một vấn đề mang tính phong cách hơn là một vấn đề trực tiếp. Nó cho thấy rằng bạn đã không suy nghĩ đúng đắn về những gì đang diễn ra với lớp học.

Hãy nghĩ về những gì staticcó nghĩa là:

Biến này tồn tại ở cấp độ lớp, nó không tồn tại riêng biệt cho từng cá thể và nó không tồn tại độc lập trong các lớp mở rộng tôi .

Hãy nghĩ về những gì protectedcó nghĩa là:

Biến này có thể được nhìn thấy bởi lớp này, các lớp trong cùng một gói và các lớp mở rộng tôi .

Hai ý nghĩa không hoàn toàn loại trừ nhau nhưng nó khá gần nhau.

Trường hợp duy nhất tôi có thể thấy nơi bạn có thể sử dụng cả hai cùng nhau là nếu bạn có một lớp trừu tượng được thiết kế để mở rộng và lớp mở rộng sau đó có thể sửa đổi hành vi bằng cách sử dụng các hằng số được xác định trong bản gốc. Mặc dù vậy, kiểu sắp xếp đó rất có thể sẽ rất lộn xộn và chỉ ra điểm yếu trong thiết kế của các lớp.

Trong hầu hết các trường hợp, sẽ tốt hơn nếu để các hằng số là công khai vì điều đó chỉ làm cho mọi thứ sạch sẽ hơn và cho phép mọi người phân loại phụ linh hoạt hơn. Hoàn toàn khác với bất kỳ thứ gì khác trong nhiều trường hợp, thành phần được ưu tiên hơn là kế thừa, trong khi các lớp trừu tượng buộc kế thừa.

Để xem một ví dụ về cách điều này có thể phá vỡ mọi thứ và để minh họa ý của tôi khi biến không tồn tại độc lập, hãy thử mã ví dụ sau:

public class Program {
    public static void main (String[] args) throws java.lang.Exception {
        System.out.println(new Test2().getTest());
        Test.test = "changed";
        System.out.println(new Test2().getTest());
    }
}

abstract class Test {
    protected static String test = "test";
}

class Test2 extends Test {
    public String getTest() {
        return test;
    }
}

Bạn sẽ thấy kết quả:

test
changed

Hãy tự mình thử tại: https://ideone.com/KM8u8O

Lớp Test2có thể truy cập thành viên tĩnh testtừ đó Testmà không cần đặt tên đủ điều kiện - nhưng nó không kế thừa hoặc lấy bản sao của chính nó. Nó đang nhìn vào cùng một đối tượng trong bộ nhớ.


4
Các bạn bị treo trên quyền thừa kế. Trường hợp điển hình mà điều này được nhìn thấy là ai đó muốn truy cập gói mà không công khai (ví dụ: một bài kiểm tra đơn vị).
spudone

3
@spudone nhưng các bài kiểm tra đơn vị thường được đặt trong cùng một gói. Để cấp cho họ quyền truy cập, bạn chỉ cần sử dụng cấp truy cập (gói) mặc định. Được bảo vệ cung cấp quyền truy cập vào các lớp con không bắt buộc hoặc không liên quan đến kiểm thử đơn vị.
Tim B

1
@Kamafeather Protected có nghĩa là trẻ em có thể nhìn thấy bạn từ các lớp khác nhau và bất kỳ lớp nào trong cùng một gói có thể nhìn thấy bạn. Vì vậy, có Chương trình nằm trong cùng một gói và do đó có thể nhìn thấy thành viên được bảo vệ.
Tim B

2
" lớp mở rộng sau đó có thể sửa đổi hành vi " Điều này sẽ vi phạm nguyên tắc Dự phòng Liskov. Một lớp con không nên sửa đổi hành vi của siêu kiểu của nó. Điều này nằm trong toàn bộ đối số "hình vuông không phải là hình chữ nhật": điều chỉnh lớp cha ( Rectangle) để điều chỉnh chiều rộng / chiều cao để đảm bảo bằng nhau sẽ (cho Square) sẽ tạo ra kết quả không mong muốn nếu bạn thay thế mọi siêu kiểu bằng một thể hiện của kiểu con của nó.
Dioxin

1
" Trường hợp duy nhất tôi có thể thấy nơi bạn có thể sử dụng cả hai cùng nhau là nếu bạn có một lớp trừu tượng được thiết kế để mở rộng và lớp mở rộng sau đó có thể sửa đổi hành vi bằng cách sử dụng các hằng số được xác định trong bản gốc " Một chút ẩn, nhưng nó trong ở đó. Ví dụ mã cũng thể hiện tuyên bố
Dioxin

32

Nó bị cau có vì nó mâu thuẫn.

Tạo một biến có protectednghĩa là nó sẽ được sử dụng trong gói hoặc nó sẽ được kế thừa trong một lớp con .

Việc đặt biến staticlàm cho nó trở thành một thành viên của lớp, loại bỏ ý định kế thừa nó . Điều này chỉ có ý định sử dụng trong một gói và chúng tôi cópackage-private cho điều đó (không có bổ sung).

Tình hình duy nhất tôi có thể tìm thấy hữu ích này là nếu bạn đã khai báo một lớp nên được sử dụng để khởi động các ứng dụng (như JavaFX của Application#launch, và chỉ muốn để có thể khởi động từ một lớp con. Nếu làm như vậy, đảm bảo phương pháp này cũng là finalđể không cho phép trốn . Nhưng đây không phải là "chuẩn mực" và có lẽ đã được triển khai để ngăn việc tăng thêm độ phức tạp bằng cách thêm một cách mới để khởi chạy ứng dụng.

Để xem các cấp độ truy cập của từng công cụ sửa đổi, hãy xem phần này: Hướng dẫn Java - Kiểm soát quyền truy cập vào các thành viên của một lớp


4
Tôi không hiểu làm thế nào sẽ staticloại bỏ ý định kế thừa nó. Bởi vì lớp con của tôi trong một gói khác vẫn yêu cầu trường super protectedđể truy cập, mặc dù nó static. package-privatekhông thể giúp đỡ
Aobo Yang

@AoboYang Bạn nói đúng, đó là lý do tại sao một số người sử dụng protected static. Nhưng đó là mùi mã, do đó là phần " chạy ". Phần bổ trợ truy cập và phần thừa kế là hai chủ thể khác nhau. Có, bạn sẽ không thể truy cập một thành viên tĩnh từ một siêu lớp nếu có package-private. Nhưng bạn không nên dựa vào kế thừa cho staticcác trường tham chiếu ngay từ đầu; đó là một dấu hiệu của thiết kế kém. Bạn sẽ nhận thấy các nỗ lực ghi đè staticcác phương thức không mang lại kết quả nào, đó là một dấu hiệu rõ ràng rằng kế thừa không dựa trên lớp. Nếu bạn cần truy cập bên ngoài lớp học hoặc gói, thì phảipublic
Dioxin

3
Lúc đầu, tôi có một lớp học với một số private staticchức năng sử dụng, nhưng tôi nghĩ ai đó có thể muốn cải thiện hoặc tùy chỉnh từ lớp học của tôi và những chức năng sử dụng này cũng có thể mang lại sự tiện lợi cho họ. publiccó thể không thích hợp vì các phương thức sử dụng không dành cho người dùng các cá thể của lớp tôi. Bạn có thể giúp tôi tìm ra một thiết kế tốt thay vì protected? Cảm ơn
Aobo Yang

Trong liên kết đó có ghi 'Sử dụng cấp độ truy cập hạn chế nhất phù hợp với một thành viên cụ thể. Sử dụng chế độ riêng tư trừ khi bạn có lý do chính đáng để không. ' , điều mâu thuẫn với câu hỏi này, bất kỳ suy nghĩ nào?
Cecilia

13

Tôi không thấy lý do cụ thể tại sao điều này phải được cau mày. Có thể luôn có các lựa chọn thay thế để đạt được cùng một hành vi và nó sẽ phụ thuộc vào bảo vệ thực tế liệu những lựa chọn thay thế này có "tốt hơn" so với một phương pháp tĩnh được bảo vệ hay không. Nhưng một ví dụ trong đó ít nhất một phương thức static được bảo vệ sẽ hợp lý có thể là:

(Đã chỉnh sửa để chia thành các gói riêng biệt, để sử dụng protectedrõ ràng hơn)

package a;
import java.util.List;

public abstract class BaseClass
{
    public Integer compute(List<Integer> list)
    {
        return computeDefaultA(list)+computeDefaultB(list);
    }

    protected static Integer computeDefaultA(List<Integer> list)
    {
        return 12;
    }
    protected static Integer computeDefaultB(List<Integer> list)
    {
        return 34;
    }
}

Xuất phát từ đó:

package a.b;

import java.util.List;

import a.BaseClass;

abstract class ExtendingClassA extends BaseClass
{
    @Override
    public Integer compute(List<Integer> list)
    {
        return computeDefaultA(list)+computeOwnB(list);
    }

    private static Integer computeOwnB(List<Integer> list)
    {
        return 56;
    }
}

Một lớp dẫn xuất khác:

package a.b;

import java.util.List;

import a.BaseClass;

abstract class ExtendingClassB extends BaseClass
{
    @Override
    public Integer compute(List<Integer> list)
    {
        return computeOwnA(list)+computeDefaultB(list);
    }

    private static Integer computeOwnA(List<Integer> list)
    {
        return 78;
    }
}

Công cụ protected staticsửa đổi chắc chắn có thể được giải thích ở đây:

  • Các phương thức có thể như vậy static, bởi vì chúng không phụ thuộc vào các biến cá thể. Chúng không nhằm mục đích sử dụng trực tiếp như một phương thức đa hình, mà là các phương thức "tiện ích" cung cấp các triển khai mặc định là một phần của một phép tính phức tạp hơn và đóng vai trò là "khối xây dựng" của việc triển khai thực tế.
  • Các phương pháp không nên như vậy public, bởi vì chúng là một chi tiết triển khai. Và chúng không thể được privatevì chúng nên được gọi bởi các lớp mở rộng. Chúng cũng không thể có khả năng hiển thị "mặc định", vì sau đó chúng sẽ không thể truy cập được đối với các lớp mở rộng trong các gói khác.

(CHỈNH SỬA: Người ta có thể cho rằng nhận xét ban đầu chỉ tham chiếu đến các trường chứ không phải các phương thức - tuy nhiên, sau đó, nó quá chung chung)


Trong trường hợp này, bạn nên xác định các triển khai mặc định là protected final(vì bạn không muốn chúng bị ghi đè) chứ không phải static. Thực tế là một phương thức không sử dụng các biến cá thể không có nghĩa là nó nên như vậy static(mặc dù nó có thể ).
barfuin

2
@Thomas Chắc chắn, trong trường hợp này, điều này cũng có thể. Nói chung: Điều này chắc chắn là chủ quan một phần, nhưng quy tắc chung của tôi là: Khi một phương thức không được dự định sử dụng đa hình và nó có thể là tĩnh, thì tôi làm cho nó tĩnh. Ngược lại với việc làm rõ nó final, nó không chỉ làm rõ rằng phương thức không nhằm mục đích bị ghi đè, mà còn làm rõ cho người đọc rằng phương thức không sử dụng các biến cá thể. Thật ngắn gọn: Đơn giản là không có lý do gì để không làm cho nó tĩnh.
Marco 13

1
Khi một phương thức không có ý định được sử dụng đa hình và nó có thể là tĩnh, thì tôi làm cho nó tĩnh. - Điều này sẽ khiến bạn gặp rắc rối khi bắt đầu sử dụng các khuôn khổ giả để kiểm thử đơn vị. Nhưng điều này dẫn chúng ta đến một chủ đề khác ...
barfuin

@ Marco13 cũng có trường hợp khi bạn tạo ra một thứ gì đó protected, không phải để thể hiện tính kế thừa, mà là để giữ nó trong một gói duy nhất - không bị lộ. Tôi đoán các giao diện kín sẽ trở nên hữu ích theo cách này khi chúng xuất hiện trong java
Eugene

@Eugene Nhận xét đó làm tôi khó chịu một chút. Có thể đạt được khả năng hiển thị gói mà không cần bất kỳ công cụ sửa đổi nào, ngược lại protectedcó nghĩa là "Kế thừa các lớp (ngay cả trong các gói khác nhau)" ...
Marco13

7

Các thành viên tĩnh không được kế thừa và các thành viên được bảo vệ chỉ hiển thị với các lớp con (và tất nhiên là cả lớp chứa), do đó, a protected staticcó cùng khả năng hiển thị static, gợi ý cho người lập trình hiểu lầm.


1
Bạn có thể vui lòng cung cấp nguồn cho báo cáo đầu tiên của bạn không? Trong một thử nghiệm nhanh, một int tĩnh được bảo vệ đã được kế thừa và sử dụng lại trong một lớp con mà không có vấn đề gì.
hiergiltdiestfu

Tĩnh được bảo vệ có cùng khả năng hiển thị với tĩnh của gói-riêng, không phải tĩnh riêng. Để thêm vào này, nếu bạn đang sử dụng bảo vệ và tĩnh, hết sức mình để chỉ loại bỏ các sửa đổi lần truy cập để làm cho nó gói-tư nhân (nếu ý định của bạn là để làm cho nó thể truy cập trong gói)
Dioxin

2
Uhm ... không. gói riêng tư và protectedkhông giống nhau. Nếu bạn vừa nói static, trường này chỉ hiển thị cho các lớp con trong cùng một gói.
Aaron Digulla

1
@AaronDigulla protected staticcho phép truy cập trong gói hoặc từ một lớp con . Tạo một biến static sẽ loại bỏ ý định của phân lớp con, để lại ý định duy nhất là truy cập từ bên trong gói.
Dioxin

@VinceEmigh: Xin lỗi, tôi đang nói chuyện với Bohemian. Lẽ ra điều này phải rõ ràng hơn.
Aaron Digulla

5

Trên thực tế, không có gì sai về cơ bản protected static. Nếu bạn thực sự muốn một biến hoặc phương thức tĩnh hiển thị cho gói và tất cả các lớp con của lớp khai báo thì hãy tiếp tục và tạo nó protected static.

Một số người thường tránh sử dụng protectedvì nhiều lý do khác nhau và một số người nghĩ rằng các staticbiến không cuối cùng nên được tránh bằng mọi cách (cá nhân tôi thông cảm với biến sau ở một mức độ nào đó), vì vậy tôi đoán sự kết hợp của protectedstaticphải có vẻ xấu ^ 2 với những biến thuộc cả hai nhóm.


3

Protected được sử dụng để nó có thể được sử dụng trong các lớp con. Không có logic nào trong việc xác định một static được bảo vệ khi sử dụng trong ngữ cảnh của các lớp cụ thể vì bạn có thể truy cập vào cùng một biến là một cách tĩnh. Tuy nhiên, trình biên dịch sẽ đưa ra cảnh báo để truy cập vào biến static siêu lớp theo cách tĩnh.


1

Vâng, như hầu hết mọi người đã trả lời:

  • protectedcó nghĩa là - ' gói-riêng tư + khả năng hiển thị đối với các lớp con - thuộc tính / hành vi được HẠNH PHÚC '
  • staticcó nghĩa là - ' trường hợp ngược lại - nó là thuộc tính / hành vi CLASS, tức là nó KHÔNG ĐƯỢC HẠNH PHÚC '

Do đó chúng hơi mâu thuẫn và không tương thích.

Tuy nhiên, gần đây tôi đã đưa ra một trường hợp sử dụng trong đó có thể hợp lý khi sử dụng hai điều này cùng nhau. Hãy tưởng tượng rằng bạn muốn tạo một abstractlớp là lớp cha cho các kiểu bất biến và nó có một loạt các thuộc tính chung cho các kiểu con. Để triển khai tính bất biến đúng cách và duy trì tính dễ đọc, người ta có thể quyết định sử dụng mẫu Builder .

package X;
public abstract class AbstractType {
    protected Object field1;
    protected Object field2;
    ...
    protected Object fieldN;

    protected static abstract class BaseBuilder<T extends BaseBuilder<T>> {
        private Object field1; // = some default value here
        private Object field2; // = some default value here
        ...
        private Object fieldN; // = some default value here

        public T field1(Object value) { this.field1 = value; return self();}
        public T field2(Object value) { this.field2 = value; return self();}
        ...
        public T fieldN(Object value) { this.fieldN = value; return self();}
        protected abstract T self(); // should always return this;
        public abstract AbstractType build();
    }

    private AbstractType(BaseBuilder<?> b) {
        this.field1 = b.field1;
        this.field2 = b.field2;
        ...
        this.fieldN = b.fieldN;
    }
}

Và tại sao protected static? Bởi vì tôi muốn một kiểu con không trừu tượng trong AbstactTypeđó triển khai Bộ dựng không trừu tượng của riêng nó và nằm bên ngoài package Xđể có thể truy cập và sử dụng lại BaseBuilder.

package Y;
public MyType1 extends AbstractType {
    private Object filedN1;

    public static class Builder extends AbstractType.BaseBuilder<Builder> {
        private Object fieldN1; // = some default value here

        public Builder fieldN1(Object value) { this.fieldN1 = value; return self();}
        @Override protected Builder self() { return this; }
        @Override public MyType build() { return new MyType(this); }
    }

    private MyType(Builder b) {
        super(b);
        this.fieldN1 = b.fieldN1;
    }
}

Tất nhiên chúng ta có thể BaseBuildercông khai nhưng sau đó chúng ta lại đi đến một nhận định trái ngược khác:

  • Chúng ta có một lớp không thể tức thời (trừu tượng)
  • Chúng tôi cung cấp một trình tạo công khai cho nó

Vì vậy, trong cả hai trường hợp với protected staticpublicxây dựngabstract class chúng tôi kết hợp các câu lệnh trái ngược nhau. Đó là một vấn đề của sở thích cá nhân.

Tuy nhiên, tôi vẫn thích publicxây dựng mộtabstract class vì đối protected staticvới tôi cảm thấy không tự nhiên hơn trong một thế giới OOD và OOP!


0

Không có gì sai khi có protected static. Một điều mà nhiều người đang bỏ qua là bạn có thể muốn viết các trường hợp thử nghiệm cho các phương thức tĩnh mà bạn không muốn để lộ trong các trường hợp bình thường. Tôi nhận thấy điều này đặc biệt hữu ích để viết các bài kiểm tra cho phương thức tĩnh trong các lớp tiện ích.

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.