为什么在Java中使用静态嵌套接口?


235

我刚刚在我们的代码库中找到了一个静态嵌套接口。

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

我以前从未见过。原始开发人员无法获得。因此,我不得不问:

静态接口背后的语义是什么?如果我删除,会发生什么变化static?为什么有人会这样做?


2
这不是“内部接口”:它是一个嵌套接口。内部在Java中具有特定含义。
罗恩侯爵

Answers:


293

上例中的static关键字是多余的(嵌套接口自动为“ static”),可以删除而不会影响语义;我建议将其删除。接口方法上的“公共”和接口字段上的“公共最终”也是如此-修饰符是多余的,只会给源代码增加混乱。

无论哪种方式,开发人员都只是声明一个名为Foo.Bar的接口。与封闭类没有进一步的关联,除了无法访问Foo的代码也将无法访问Foo.Bar。(从源代码开始,即使Foo是程序包私有的,字节码或反射也可以访问Foo.Bar!)

如果希望只在外部类中使用嵌套接口,则可以采用这种方式创建嵌套接口,这样您就不必创建新的顶级名称。例如:

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

1
在Jesse Glick的答案中,这是什么意思:(即使Foo是程序包私有的,也可以从源代码-字节码或反射方式访问Foo.Bar!)。
Vasu

Kaillash的私有方法可以通过relfection(在反射包中)和直接访问生成的.class文件的字节码来访问。
gmoore

2
通过“字节码...可以访问Foo.Bar”,我的意思是即使没有引用Foo,也可以加载并运行引用Foo.Bar的已编译类。如果该类是在Foo公开之前的较早时间编译的,或者该类是用非Java语言手工汇编或编译的,则可能发生这种情况。但是,Java编译器会检查封闭类的access修饰符即使结果字节码不会引用该封闭类。
Jesse Glick

@Jesse是否可以通过反射来访问私有顶级类中的私有静态类?
Pacerier

1
@Pacerier:不。更准确地说,您可以加载类并检查其成员,但是如果不使用setAccessible(true),则无法实例化它或在其上调用方法。换句话说,它通过反射是可见的,但不可访问。但是,如果嵌套的静态类是公共的,则默认情况下可以通过反射来访问它,即使它不是静态(在编译期间)也可以访问。
Jesse Glick 2012年

72

已经回答了这个问题,但是使用嵌套接口的一个很好的理由是,其嵌套函数是否与其所在的类直接相关。一个很好的例子是Listener。如果您有一个类,Foo并且希望其他类能够侦听其上的事件,则可以声明一个名为的接口FooListener,这没关系,但是声明一个嵌套的接口并让其他类实现Foo.Listener(嵌套类Foo.Event也不错)。


11
一个典型的示例是java.util.Map.Entry(嵌套在另一个接口中的一个接口)。
圣保罗Ebermann

4
我知道这是一个古老的话题,但是我希望外部类位于其自己的包中,并且任何补充接口(例如Map.Entry)或类也都应包含在该包中。我之所以这样说,是因为我喜欢保持课堂简短。读者还可以通过查看包中的类来查看与该类相关的其他实体。我可能会有一个java.collections.map地图包。这与面向对象和模块化有关。java.util里面太多了。util就像common-有气味的IMO
David Kerr

1
@Shaggy:java.util当然里面有太多东西。就是说,我也不认为将其分解为您建议的如此细粒度的软件包也是理想的。
ColinD 2012年

1
@DavidKerr假设您java.util.MapEntry在自己的程序包之外调用此接口。第一反应:与此类相关的接口是什么?我看着课。除非javadoc链接到此接口,否则我不知道它们之间的关系。而且人们不会看进口包装。每个人都有自己的意见。没有人是对的,没有人是错的
雷蒙德·谢农

我说过的@RaymondChenon的一部分是将Map及其相关类(例如Map.Entry)放入单独的包(例如java.collections.map)中。这样,所有与地图相关的代码都位于一个位置(程序包)中,并且顶级Map类不会负担。我可能会将相关的实现(HashMap等)放在同一个程序包中。
大卫·克尔

14

成员接口是隐式静态的。您可以删除示例中的static修饰符,而无需更改代码的语义。另请参见Java语言规范8.5.1。静态成员类型声明


这不是“内部接口”:它是一个嵌套接口。内部在Java中具有特定含义。
罗恩侯爵

@EJP,我可以互换使用这些术语来阅读各种博客。内部接口是否存在?它们与嵌套接口有何不同?
Number945 '18

@BreakingBenjamin,嵌套接口表示静态。存在内部类和嵌套类,但没有内部接口。即使是这样,也应该称为嵌套接口。内部-非静态且嵌套为静态。
Sundar Rajan '18年

9

内部接口必须是静态的才能被访问。该接口与该类的实例无关,但与该类本身相关联,因此可以通过进行访问Foo.Bar,如下所示:

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

在大多数情况下,这与静态内部类没有什么不同。


35
无论是否编写关键字,嵌套接口都是自动静态的。
圣保罗Ebermann

3
我们确实需要社区投票接受不同答案的方法:stackoverflow.com/a/74400/632951
Pacerier,2012年


@ ClintonN.Dreisbach能否The interface isn't associated with instances of the class, but with the class itself进一步解释什么,我没明白
Kasun Siyambalapitiya

6

Jesse的答案很接近,但是我认为有更好的代码来说明为什么内部接口可能有用。在继续阅读之前,请看下面的代码。您能找到为什么内部接口有用吗?答案是,可以使用实现A和C的任何类实例化DoSomethingAlready 类。不只是具体的动物园。当然,即使AC不是内部的,也可以实现,但是想像一下将更长的名称(不仅是A和C)串联起来,然后将其用于其他组合(例如A和B,C和B等),您就可以轻松实现看看事情如何失控。更不用说审查您的源代码树的人会被仅在一类中有意义的界面所淹没。因此,总而言之,内部接口可以构造自定义类型并改进其封装

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
这是无稽之谈。该类Zoo没有实现接口AC,因此,实例Zoo不能被传递到的构造DoSomethingAlready,其预期ACAC扩展A和的事实C并不意味着类也可以实现AC也可以魔术地实现AC
Holger


0

通常,我看到静态内部类。静态内部类不能引用包含类,而非静态类可以。除非您遇到一些程序包冲突(与Foo相同的程序包中已经有一个名为Bar的接口),否则我认为它应该是它自己的文件。强制执行Foo和Bar之间的逻辑连接也可能是一项设计决策。也许作者打算将Bar只与Foo一起使用(尽管静态内部接口不会强制执行此操作,而只是逻辑连接)


“静态内部”在术语上是矛盾的。嵌套类是任一静态的或内部。
罗恩侯爵


0

1998年,Philip Wadler提出了静态接口和非静态接口之间的区别。

据我所知,使接口成为非静态接口的唯一区别是它现在可以包括非静态内部类。因此更改不会使任何现有Java程序无效。

例如,他提出了表达问题的解决方案,即一方面表达为“您的语言可以表达多少”与另一方面表达为“您要用语言表达的术语”之间的不匹配。

静态和非静态嵌套接口之间的区别的示例可以在他的示例代码中看到:

// 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);
    }
}

他的建议从未在Java 1.5.0中实现。因此,所有其他答案都是正确的:静态和非静态嵌套接口没有区别。


应该注意的是,所有这些都涉及GJ,它是Java中泛型的非常早期的处理器。
罗恩侯爵

-1

在Java中,静态接口/类允许接口/类的使用像顶级类一样,也就是说,它可以由其他类声明。因此,您可以执行以下操作:

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

没有静态,以上内容将无法编译。这样做的好处是,您不需要新的源文件即可声明接口。由于必须编写Foo.Bar,因此它还可视化地将Bar接口与Foo类关联,并暗示Foo类对Foo.Bar实例进行了某些操作。

Java中类类型的描述


如果没有静态它不会编译。“静态”是多余的。
罗恩侯爵

@EJP:根据我链接的那篇文章,没有静态的内部类只能由拥有类使用,而不能由欠类使用。静态使其成为嵌套的顶级类,并且可以被拥有类范围之外的对象使用(至少,根据本文所述)。静态对象并非完全多余,并且会影响内部类的范围。这适用于类,接口可能始终像顶级对象一样工作(需要阅读语言规范以进行检查)。也许我应该将答案的接口和类部分分开(不好)。
Skizz 2015年

-6

静态意味着包(项目)的任何类部分都可以访问它而无需使用指针。根据情况,这可能有用或受阻。

Math类是“静态”方法有用的完美示例。Math中的所有方法都是静态的。这意味着您不必费力地创建新实例,声明变量并将它们存储在更多变量中,您只需输入数据即可获得结果。

静态并不总是那么有用。例如,如果要进行案例比较,则可能需要以几种不同的方式存储数据。您不能创建三个具有相同签名的静态方法。您需要3个不同的实例(非静态),然后可以进行比较,如果是静态的,则数据不会随输入而改变。

静态方法适合一次性返回和快速计算或轻松获得数据。


1
我知道静态的意思。我只是从来没有在界面上看到过它。因此,您的问题不合时宜-对不起
Mo.

1
我认为他的回答是题外话。
Koray Tugay 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.