在我的一些项目和一些书中被称为不使用内部类(匿名与否,静态或非静态) - 除了在某些限制条件下,如EventListener
s或Runnable
s - 是最佳做法。他们甚至在我的第一个行业项目中都是“禁止”。
这真的是最佳做法吗?为什么呢?
(我不得不说我经常使用它们......)
- 编辑---
我不能在所有这些答案中选择一个正确的答案:大部分都是正确的部分:我仍然会使用内部类,但我会尽量少用它们!
答案 0 :(得分:26)
在我看来,Java代码中90%的内部类是与单个类关联的实体,因此被“推入”作为内部类,或者因为Java不支持Lambdas而存在的匿名内部类。
我个人不喜欢看复杂的内部课程。它们增加了源文件的复杂性,它们使它更大,在调试和分析等方面处理它们很难。我喜欢将我的项目分成许多包,在这种情况下我可以使大多数实体成为顶级类仅限于包裹。
这给我留下了必要的内部类 - 例如动作听众,虚假的“功能”编程等等。这些通常是匿名的,虽然我不是粉丝(在许多情况下会更喜欢Lambda),但我住在一起他们但不喜欢他们。
我多年没有做过任何C#,但我想知道在引入Lambdas时内部类的流行程度或C#等价物的流行是否会被删除。
答案 1 :(得分:17)
清洁度。如果代码被分解为逻辑部分,而不是所有代码都存储在同一个文件中,则更容易理解代码。
那就是说,我不认为明智地使用内部类是不合适的。有时这些内部类只存在于一个目的,因此我可以将它们存在于使用它们的唯一文件中。但是,根据我的经验,这种情况并没有那么多。
答案 2 :(得分:6)
在进行基于事件的编程时,特别是在摇摆中,匿名类很好用。
答案 3 :(得分:4)
是的,禁止内部课程是一种有用的做法,因为找到一个禁止他们的地方是警告我在那里工作的好方法,因此保留了我未来的理智。 :)
正如gicappa所指出的,匿名内部类是最接近闭包的Java,并且非常适合在将行为传递到方法中的情况下使用,如果没有别的话。
答案 4 :(得分:4)
正如其他人所说,很多时候,当你使用匿名内部类时,它也会在其他地方使用...
因此,您可以轻松地将内部类代码复制到很多地方...... 当您使用非常简单的内部类来过滤/排序集合,使用谓词,比较器或类似的东西时,这似乎不是问题......
但是你必须知道,当你使用3次匿名内部类完成同样的事情时(例如删除Collection的“”),你实际上是在java PermGen上创建了3个新类。
因此,如果每个人都在任何地方使用内部类,这可能会导致应用程序具有更大的permgen。根据应用程序,这可能是一个问题...如果您正在从事该行业,您可以编写具有有限内存的嵌入式应用程序,应该进行优化...
请注意,这也是双花括号语法(带有非静态初始化块的匿名内部类)有时被视为反模式的原因:
new ArrayList<String>() {{
add("java");
add("jsp");
add("servlets");
}}
你应该问那些禁止你使用它们的人...... 恕我直言,这完全取决于背景......
答案 5 :(得分:3)
匿名内部类可以看到“new”语句周围的字段和变量。这可以使一些非常干净的设计成为一种非常好(但有点罗嗦)的方法,“我们如何制作简单版本的lambda语句”。
命名的内部类具有一个名称,有希望告诉的好处,可以用通常的方式记录,但是它与周围的类绑在一起。一个非常好的例子是Builder模式,其中内部类负责为初始化过程提供状态,而不是拥有众多构造函数。这样的构建器不能在类之间重用,因此将Builder与父类紧密结合是完全合理的。
答案 6 :(得分:3)
如果需要方法参数,我建议在使用它时要谨慎。我刚刚发现了与此相关的内存泄漏。它涉及使用GrizzlyContinuation的HttpServlet 总之,这里是错误的代码:
public void doGet(HttpServletRequest request, final HttpServletResponse response){
createSubscription(..., new SubscriptionListener(){
public void subscriptionCreated(final CallController controller) {
response.setStatus(200);
...
controller.resume();
}
public void subscriptionFailed(){
...
}
public void subscriptionTimeout(){
...
}});
}
因此,由于侦听器保留了侦听器,因此HttpServletResponse也会被保留,以防侦听器需要它(不明显)。然后只有在删除订阅时才会释放HttpServletResponse实例。如果你使用一个在它的构造函数中得到响应的内部类,一旦调用恢复释放内存,它就可以设置为null。
使用它们但要小心!
马丁
答案 7 :(得分:2)
某些框架,如Wicket,确实需要匿名内部类。
说永远不会是愚蠢的。永远不要把话说绝了!良好使用的一个例子可能是你有一些遗留代码是由许多类直接在Collection字段上运行的人编写的,无论出于何种原因,你不能改变其他类,但需要有条件地将操作镜像到另一个采集。最简单的方法是通过匿名内部类添加此行为。
bagOfStuff = new HashSet(){
@Override
public boolean add(Object o) {
boolean returnValue = super.add(o);
if(returnValue && o instanceof Job)
{
Job job = ((Job)o);
if(job.fooBar())
otherBagOfStuff.add(job);
}
return returnValue;
}
}
那就是说,他们绝对可以像穷人的关闭一样使用。
答案 8 :(得分:2)
这里提到的不的一个项是(非静态)内部类带有对它的封闭类的引用。更重要的是,内部类可以访问其封闭类的私有成员。它可能会破坏封装。
如果你有选择,不要使用内部类。
答案 9 :(得分:1)
在尝试模拟多重继承时,内部类是合适的。它类似于使用C ++在幕后发生的事情:当你在C ++中有多个继承时,内存中的对象布局实际上是几个对象实例的串联;然后编译器计算出在调用方法时如何调整“this”指针。在Java中,没有多重继承,但内部类可用于在另一种类型下提供给定实例的“视图”。
大多数情况下,可以坚持单继承,但偶尔使用多继承是正确的工具,这是使用内部类的时候。
这意味着内部类在某种程度上比通常的类更复杂,就像多继承比单继承更复杂一样:许多程序员在围绕这个概念时难以理解。因此,“最佳实践”:避免内部课程,因为它会让你的同事感到困惑。在我看来,这不是一个好的论据,在我的工作场所,我们很乐意在我们认为合适时使用内部课程。
(内部类的一个小缺点是它们在源代码中添加了一个额外级别的缩进。当有人希望将代码保留在79列中时,这有点令人讨厌。)
答案 10 :(得分:1)
当我们需要使用一个方法(如Runnable,ActionListener和其他方法)实现接口时,通常会使用匿名内部类。
匿名内部类的一个更好的工具是当你不想创建某个类的子类但是你需要覆盖其中一个(或两个)方法时。
当您希望在两个类之间实现紧密一致时,可以使用命名的内部类。它们不像匿名内部类那么有用,我不能确定它是一个好用的做法。
Java也有嵌套(或内部静态)类。当您想要提供某些特殊访问权限时,可以使用它们,而标准的公共或默认访问级别是不够的。
答案 11 :(得分:1)
内部类通常用于“传递行为”作为方法的参数。具有闭包的其他语言以优雅的方式支持此功能。 使用内部类会产生一些不优雅的代码(恕我直言),因为它有语言限制,但它很有用,并且广泛用于处理事件,而blocks通常用于内部类。
所以我会说内部类非常有用。
答案 12 :(得分:1)
没有内部类的代码更强可维护和可读。当您从内部类访问外部类的私有数据成员时,JDK编译器在外部类中创建package-access
成员函数,以便内部类访问private members
。这留下了安全漏洞。的在
一般我们应该避免使用内部类。
仅当内部类仅与内部类相关时才使用内部类 外部类和/或内部类的上下文可以是私有的,这样只有外部类才能访问它。内部类主要用于实现辅助类,如迭代器,比较器等,它们在 外类的背景。
答案 13 :(得分:0)
是的,使用它们是好的,当你试图保持一个类的内聚,并且这些类永远不应该从外部类的上下文之外实例化,使构造函数成为私有的,并且你有非常好的内聚封装。任何说你从不使用它们的人都不知道他们在说什么。对于事件处理程序和匿名内部类擅长的其他事情,它们比使用大量仅适用于特定类的事件处理程序来混淆包名称空间的方法更好。
答案 14 :(得分:0)
由于其他张贴者给出的原因,我倾向于避免使用非静态内部类。但是,我有一个特别喜欢的模式,其中非静态内部类非常有效:延迟加载有状态类。
典型的延迟加载状态类是使用实体ID构造的,然后根据需要可以延迟加载其他实体信息。通常,为了延迟加载其他信息,我们需要依赖项。但是依赖项+状态==反模式!
非静态内部类提供了一种避免这种反模式的方法。希望下面的简单示例比单词能够更好地说明这一点:
/*
* Stateless outer class holding dependencies
*/
public class DataAssembler {
private final LoadingService loadingService;
@Inject
DataAssembler(LoadingService loadingService) {
this.loadingService = loadingService;
}
public LazyData assemble(long id) {
return new LazyData(id);
}
/*
* Stateful non-static inner class that has access to the outer
* class' dependencies in order to lazily load data.
*/
public class LazyData {
private final long id;
private LazyData(long id) {
this.id = id;
}
public long id() {
return id;
}
public String expensiveData() {
return loadingService.buildExpensiveDate(id);
}
}
}
值得一提的是,除了上述示例外,还有许多其他模式可以使用内部类;内部类与任何其他Java功能一样-在适当的时候可以使用它们,而在不适当的时候使用!