Tại sao một giao diện lồng nhau tĩnh được sử dụng trong Java?


235

Tôi vừa tìm thấy một giao diện lồng nhau tĩnh trong cơ sở mã của chúng tôi.

class Foo {
    public static interface Bar {
        /* snip */
    }
    /* snip */
}

Tôi chưa bao giờ nhìn thấy điều này trước đây. Các nhà phát triển ban đầu là ngoài tầm với. Vì vậy tôi phải hỏi SO:

Các ngữ nghĩa đằng sau một giao diện tĩnh là gì? Điều gì sẽ thay đổi, nếu tôi loại bỏ static? Tại sao mọi người sẽ làm điều này?


2
Đây không phải là một giao diện bên trong: nó là một giao diện lồng nhau . Nội tại có một ý nghĩa cụ thể trong Java.
Hầu tước Lorne

Câu trả lời:


293

Từ khóa tĩnh trong ví dụ trên là dự phòng (giao diện lồng nhau sẽ tự động "tĩnh") và có thể bị xóa mà không ảnh hưởng đến ngữ nghĩa; Tôi muốn giới thiệu nó được gỡ bỏ. Điều tương tự cũng xảy ra đối với "công khai" đối với các phương thức giao diện và "chung kết chung" trên các trường giao diện - các công cụ sửa đổi là dự phòng và chỉ cần thêm lộn xộn vào mã nguồn.

Dù bằng cách nào, nhà phát triển chỉ đơn giản là khai báo một giao diện có tên Foo.Bar. Không có liên kết nào nữa với lớp kèm theo, ngoại trừ mã không thể truy cập Foo cũng sẽ không thể truy cập Foo.Bar. (Từ mã nguồn - mã byte hoặc phản chiếu có thể truy cập Foo.Bar ngay cả khi Foo là gói riêng tư!)

Kiểu này có thể chấp nhận được khi tạo giao diện lồng nhau theo cách này nếu bạn cho rằng nó chỉ được sử dụng từ lớp bên ngoài, do đó bạn không tạo tên cấp cao nhất mới. Ví dụ:

public class Foo {
    public interface Bar {
        void callback();
    }
    public static void registerCallback(Bar bar) {...}
}
// ...elsewhere...
Foo.registerCallback(new Foo.Bar() {
    public void callback() {...}
});

1
Trong câu trả lời của Jesse Glick, điều này có nghĩa là gì: (Từ mã nguồn - mã byte hoặc phản chiếu có thể truy cập Foo.Bar ngay cả khi Foo là gói riêng tư!).
Vasu

Kaillash, các phương thức riêng tư có thể được truy cập thông qua việc định dạng lại (trong gói phản chiếu) và thông qua truy cập trực tiếp vào mã byte của các tệp. Class được tạo.
gmoore

2
Bởi "bytecode ... có thể truy cập Foo.Bar" Ý tôi là một lớp được biên dịch tham chiếu Foo.Bar có thể được tải và chạy ngay cả khi nó không thể tham chiếu Foo. Điều này có thể xảy ra nếu lớp được biên dịch vào thời điểm sớm hơn khi Foo ở chế độ công khai hoặc nếu lớp được lắp ráp bằng tay hoặc được biên dịch từ một số ngôn ngữ không phải Java, v.v. Tuy nhiên, trình biên dịch Java kiểm tra trình sửa đổi truy cập trên lớp kèm theo ngay cả khi mã byte kết quả sẽ không tham chiếu đến lớp kèm theo đó.
Jesse Glick

@Jlie Có thể truy cập một lớp tĩnh riêng trong lớp cấp cao nhất riêng tư thông qua phản xạ không?
Pacerier

1
@Pacerier: không. Chính xác hơn, bạn có thể tải lớp và kiểm tra các thành viên của nó, nhưng không thể khởi tạo nó hoặc gọi các phương thức trên nó mà không sử dụng setAccessible (true). Nói cách khác, nó có thể nhìn thấy, nhưng không thể truy cập được, thông qua sự phản chiếu. Nhưng nếu lớp tĩnh lồng nhau là công khai, nó có thể truy cập theo mặc định thông qua sự phản chiếu mặc dù nó không thể truy cập tĩnh (trong quá trình biên dịch).
Jesse Glick

72

Câu hỏi đã được trả lời, nhưng một lý do chính đáng để sử dụng giao diện lồng nhau là nếu chức năng của nó có liên quan trực tiếp đến lớp mà nó đang ở. Một ví dụ hay về điều này là a Listener. Nếu bạn có một lớp Foovà bạn muốn các lớp khác có thể nghe các sự kiện trên đó, bạn có thể khai báo một giao diện có tên FooListener, nhưng có thể rõ ràng hơn khi khai báo giao diện lồng nhau và các lớp khác đó sẽ thực hiện Foo.Listener( một lớp lồng nhau Foo.Eventkhông tệ cùng với điều này).


11
Một ví dụ điển hình là java.util.Map.Entry(đó là một giao diện được lồng trong một giao diện khác).
Paŭlo Ebermann

4
Tôi biết đây là một chủ đề cũ, nhưng tôi thích lớp bên ngoài nằm trong gói riêng của nó và bất kỳ giao diện bổ sung nào (ví dụ Map.Entry) hoặc các lớp cũng nằm trong gói đó. Tôi nói điều này bởi vì tôi thích giữ cho lớp học của tôi ngắn và đi vào điểm chính. Ngoài ra, một người đọc có thể thấy những gì các thực thể khác có liên quan đến một lớp bằng cách nhìn vào các lớp trong gói. Tôi có thể có một gói java.collections.mapcho bản đồ. Đây là về OO và mô-đun. java.utilcó quá nhiều trong đó. utilgiống như common- một mùi IMO
David Kerr

1
@Shaggy: java.utilchắc chắn có quá nhiều trong đó. Điều đó nói rằng, tôi không nghĩ rằng việc chia nó thành các gói nhỏ như bạn đề nghị là lý tưởng.
ColinD

1
@DavidKerr Giả sử bạn gọi giao diện này java.util.MapEntrybên ngoài gói riêng của nó. Phản xạ đầu tiên: giao diện liên quan đến lớp này là gì? Tôi nhìn vào lớp. Trừ khi javadoc liên kết đến giao diện này, tôi không biết gì về mối quan hệ của họ. Thêm vào đó mọi người đừng nhìn vào gói nhập khẩu. Mọi người đều có ý kiến ​​riêng. Không ai đúng, không ai sai
Raymond Chenon

@RaymondChenon một phần của những gì tôi đã nói là đặt Map và các lớp liên quan của nó, ví dụ Map.Entry vào một gói riêng, ví dụ java.collections.map. Bằng cách đó, tất cả các mã liên quan đến bản đồ nằm ở một vị trí (gói) và lớp Bản đồ cấp cao nhất không bị gánh nặng. Tôi có thể đặt các triển khai liên quan (HashMap, v.v.) trong cùng một gói.
David Kerr

14

Giao diện thành viên là hoàn toàn tĩnh. Công cụ sửa đổi tĩnh trong ví dụ của bạn có thể được gỡ bỏ mà không thay đổi ngữ nghĩa của mã. Xem thêm Đặc tả ngôn ngữ Java 8.5.1. Tuyên bố loại thành viên tĩnh


Đây không phải là một 'giao diện bên trong': nó là một giao diện lồng nhau. Nội tại có một ý nghĩa cụ thể trong Java.
Hầu tước Lorne

@EJP, tôi đọc các blog khác nhau bằng cách sử dụng các thuật ngữ thay thế cho nhau. Giao diện bên trong có tồn tại không? Làm thế nào chúng khác với giao diện lồng nhau?
Số945

@BreakingBenjamin, Giao diện lồng nhau có nghĩa là tĩnh. Có tồn tại lớp bên trong và lớp lồng nhau, nhưng không có giao diện bên trong. Ngay cả nếu vậy, nó nên được gọi là giao diện lồng nhau. Bên trong - không tĩnh và lồng nhau là tĩnh.
Sundar Rajan

9

Một giao diện bên trong phải tĩnh để được truy cập. Giao diện không được liên kết với các thể hiện của lớp, nhưng với chính lớp đó, vì vậy nó sẽ được truy cập cùng Foo.Bar, như vậy:

public class Baz implements Foo.Bar {
   ...
}

Trong hầu hết các cách, điều này không khác với một lớp bên trong tĩnh.


35
Một giao diện lồng nhau được tự động tĩnh, cho dù người ta có viết từ khóa hay không.
Paŭlo Ebermann

3
Chúng tôi thực sự cần một cách để cộng đồng bỏ phiếu chấp nhận một câu trả lời khác: stackoverflow.com/a/74400/632951
Pacerier


@ ClintonN.Dreisbach Bạn có thể giải thích điều gì có ý nghĩa The interface isn't associated with instances of the class, but with the class itselfhơn nữa không, tôi đã không hiểu được
Kasun Siyambalapitiya

6

Câu trả lời của Jesse rất gần, nhưng tôi nghĩ rằng có một mã tốt hơn để chứng minh tại sao một giao diện bên trong có thể hữu ích. Nhìn vào mã dưới đây trước khi bạn đọc tiếp. Bạn có thể tìm thấy lý do tại sao giao diện bên trong là hữu ích? Câu trả lời là lớp DoS SomethingAl yet có thể được khởi tạo với bất kỳ lớp nào thực hiện A và C; Không chỉ là sở thú lớp học cụ thể. Tất nhiên, điều này có thể đạt được ngay cả khi AC không ở bên trong, nhưng hãy tưởng tượng ghép các tên dài hơn (không chỉ A và C) và thực hiện điều này cho các kết hợp khác (giả sử A và B, C và B, v.v.) và bạn dễ dàng xem mọi thứ vượt quá tầm kiểm soát Chưa kể rằng mọi người đánh giá cây nguồn của bạn sẽ bị choáng ngợp bởi các giao diện chỉ có ý nghĩa trong một lớp. Vì vậy, để tóm tắt,một giao diện bên trong cho phép xây dựng các loại tùy chỉnh và cải thiện việc đóng gói của chúng .

class ConcreteA implements A {
 :
}

class ConcreteB implements B {
 :
}

class ConcreteC implements C {
 :
}

class Zoo implements A, C {
 :
}

class DoSomethingAlready {
  interface AC extends A, C { }

  private final AC ac;

  DoSomethingAlready(AC ac) {
    this.ac = ac;
  }
}

2
Thật vô nghĩa. Lớp Zoonào không thực hiện giao diện AC, do đó, các trường hợp Zookhông thể được truyền cho constructor của DoSomethingAlreadymà hy vọng AC. Thực tế ACmở rộng cả hai, AC, không ngụ ý rằng các lớp thực hiện ACkỳ diệu cũng thực hiện AC.
Holger

3

Để trả lời câu hỏi của bạn rất trực tiếp, hãy nhìn vào Map.Entry.

Bản đồ

điều này cũng có thể hữu ích

Mục nhập blog inerfaces Nested


4
Đây là một ví dụ, nhưng không thực sự là một câu trả lời.
Paŭlo Ebermann

Tôi sử dụng Map.Entry để tạo các đối tượng "Ghép" rất nhiều. Nó được phơi bày. Việc thực hiện Pair có hai trường phái suy nghĩ, nhưng đó không phải là vấn đề ở đây. Map.Entry có thể là bên trong nhưng tôi sử dụng nó bên ngoài.
Ravindranath Akila

0

Thông thường tôi thấy các lớp bên trong tĩnh. Các lớp bên trong tĩnh không thể tham chiếu các lớp chứa trong đó các lớp không tĩnh có thể. Trừ khi bạn gặp phải một số va chạm gói (đã có giao diện có tên là Bar trong cùng gói với Foo) Tôi nghĩ tôi sẽ biến nó thành tệp riêng. Nó cũng có thể là một quyết định thiết kế để thực thi kết nối hợp lý giữa Foo và Bar. Có lẽ tác giả dự định Bar chỉ được sử dụng với Foo (mặc dù giao diện bên trong tĩnh sẽ không thực thi điều này, chỉ là một kết nối hợp lý)


"Nội tĩnh" là một mâu thuẫn trong các điều khoản. Lớp lồng nhau là một trong hai tĩnh hoặc bên trong.
Hầu tước Lorne

0

Nếu bạn sẽ thay đổi lớp Foo thành giao diện Foo, từ khóa "công khai" trong ví dụ trên cũng sẽ bị dư thừa vì

giao diện được xác định bên trong một giao diện khác sẽ hoàn toàn công khai tĩnh.


Không trả lời câu hỏi
Mưa

0

Năm 1998, Philip Wadler đã đề xuất một sự khác biệt giữa giao diện tĩnh và giao diện không tĩnh.

Theo như tôi có thể thấy, sự khác biệt duy nhất trong việc làm cho một giao diện không tĩnh là bây giờ nó có thể bao gồm các lớp bên trong không tĩnh; vì vậy thay đổi sẽ không hiển thị bất kỳ chương trình Java hiện có nào.

Ví dụ, ông đã đề xuất một giải pháp cho Vấn đề Biểu hiện , mặt khác là sự không phù hợp giữa biểu thức là "ngôn ngữ của bạn có thể diễn tả được bao nhiêu" và mặt khác là "thuật ngữ bạn đang cố gắng thể hiện bằng ngôn ngữ của mình" .

Một ví dụ về sự khác biệt giữa các giao diện lồng nhau tĩnh và không tĩnh có thể được nhìn thấy trong mã mẫu của anh ấy :

// This code does NOT compile
class LangF<This extends LangF<This>> {
    interface Visitor<R> {
        public R forNum(int n);
    }

    interface Exp {
        // since Exp is non-static, it can refer to the type bound to This
        public <R> R visit(This.Visitor<R> v);
    }
}

Đề xuất của ông không bao giờ được thực hiện trong Java 1.5.0. Do đó, tất cả các câu trả lời khác đều đúng: không có sự khác biệt nào đối với các giao diện lồng nhau tĩnh và không tĩnh.


Cần lưu ý rằng tất cả những điều này đề cập đến GJ, một bộ xử lý rất sớm cho các tổng quát trong Java.
Hầu tước Lorne

-1

Trong Java, giao diện / lớp tĩnh cho phép giao diện / lớp được sử dụng như một lớp cấp cao nhất, nghĩa là nó có thể được khai báo bởi các lớp khác. Vì vậy, bạn có thể làm:

class Bob
{
  void FuncA ()
  {
    Foo.Bar foobar;
  }
}

Không có tĩnh, ở trên sẽ không biên dịch được. Ưu điểm của việc này là bạn không cần một tệp nguồn mới chỉ để khai báo giao diện. Nó cũng liên kết trực quan thanh giao diện với lớp Foo vì bạn phải viết Foo.Bar và ngụ ý rằng lớp Foo làm gì đó với các phiên bản của Foo.Bar.

Một mô tả về các loại lớp trong Java .


Không có tĩnh nó sẽ biên dịch. "Tĩnh" là dư thừa.
Hầu tước Lorne

@EJP: Theo bài báo mà tôi đã liên kết, không có lớp tĩnh, lớp bên trong chỉ có thể được sử dụng bởi lớp sở hữu chứ không phải bởi bất cứ thứ gì bên ngoài lớp nợ. Tĩnh làm cho nó trở thành một lớp cấp cao nhất lồng nhau và có thể được sử dụng bởi các đối tượng bên ngoài phạm vi của lớp sở hữu (ít nhất, theo bài viết). Tĩnh không hoàn toàn dư thừa và ảnh hưởng đến phạm vi của lớp bên trong. Điều này áp dụng cho các lớp, giao diện có thể luôn hoạt động như các đối tượng cấp cao nhất (cần đọc thông số ngôn ngữ để kiểm tra). Có lẽ tôi nên tách giao diện và các phần lớp trong câu trả lời của tôi (xấu của tôi).
Skizz

-6

Tĩnh có nghĩa là bất kỳ phần lớp nào của gói (dự án) có thể gia nhập nó mà không cần sử dụng con trỏ. Điều này có thể hữu ích hoặc cản trở tùy thuộc vào tình huống.

Ví dụ hoàn hảo về các phương pháp hữu ích của các phương thức "tĩnh" là lớp Math. Tất cả các phương thức trong Toán học là tĩnh. Điều này có nghĩa là bạn không cần phải tránh ra, tạo một thể hiện mới, khai báo các biến và lưu trữ chúng trong nhiều biến hơn nữa, bạn chỉ cần nhập dữ liệu của mình và nhận kết quả.

Tĩnh không phải lúc nào cũng hữu ích. Nếu bạn đang thực hiện so sánh trường hợp, bạn có thể muốn lưu trữ dữ liệu theo nhiều cách khác nhau. Bạn không thể tạo ba phương thức tĩnh có chữ ký giống hệt nhau. Bạn cần 3 trường hợp khác nhau, không tĩnh, sau đó bạn có thể và so sánh, nguyên nhân nếu nó tĩnh, dữ liệu sẽ không thay đổi cùng với đầu vào.

Phương pháp tĩnh tốt cho lợi nhuận một lần và tính toán nhanh hoặc dữ liệu dễ dàng thu được.


1
Tôi biết những gì tĩnh có nghĩa. Tôi chưa bao giờ nhìn thấy nó trên một giao diện trước đây. Vì vậy câu hỏi của bạn là off-topic - xin lỗi
Mo.

1
Tôi nghĩ rằng câu trả lời của anh ấy là lạc đề.
Koray Tugay
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.