我想问一下,为什么我们需要内部类以及为什么要使用它们?
我知道如何使用内部类,但不知道为什么要使用它们。
我想问一下,为什么我们需要内部类以及为什么要使用它们?
我知道如何使用内部类,但不知道为什么要使用它们。
一些内部类是公开的(例如Java中的Map.Entry
),但这只是例外而不是规律。
内部类基本上是实现细节。
例如,Swing在事件监听器中广泛使用内部类。如果没有它们,你会用一堆你不需要看到的类来污染全局命名空间(这可能会使它们的用途更难确定)。
实际上,内部类是一种作用域形式。包访问从包外隐藏类。私有内部类将该类从该类外部隐藏。
在Java中,内部类还是函数指针或方法委托(C#中的委托)或闭包缺失的替代品。它们是传递函数给另一个函数的手段。例如,在 Executor
类中,您有:
void execute(Runnable r);
所以你可以传递一个方法。在C/C++中可以通过以下方式实现:
void execute(void (*run)());
指向函数的指针。
interface Bar
{
public void bar();
}
class Foo
{
public void foo()
{
// do something relevant
}
// it happens that foo() defines the same contract (or a compatible one) as
// Bar.bar(); with an anonymous inner class we can adapt Foo to the Bar
// interface
public Bar asBar()
{
// return an instance of an anonymous inner class that implements
// the Bar inteface
return new Bar()
{
public void bar()
{
// from an inner class, we can access the enclosing class methods
// as the "this pointers" are "linked"
foo();
}
};
}
}
在Java中,请确保您理解内部类和嵌套类之间的区别:
内部类与其封闭类的实例相关联,并直接访问该对象的方法和字段
C#没有Java中内部类的概念,只有嵌套类。
另请参阅内部类示例。
我大多数时候使用内部类是因为内部类是其他语言中最接近闭包概念的东西。这使得创建和操作具有访问其外部作用域变量的内部嵌套作用域对象成为可能。在创建回调(例如在Swing中定义各种Listener
)等方面,这通常非常有用。
面向对象的优势
在我看来,内部类最重要的特点是它可以将通常不会转化为对象的事物转化为对象。这使得你的代码比没有内部类时更加面向对象。
让我们来看看成员类。由于它的实例是其父实例的成员,因此它可以访问父类中的每个成员和方法。乍一看,这似乎并不重要;我们已经可以从父类的方法中访问那些内容了。然而,成员类允许我们将逻辑从父类中提取出来并将其对象化。例如,一个树类可能有一个方法和许多辅助方法来执行树的搜索或遍历。从面向对象的角度来看,树是一棵树,而不是一个搜索算法。但是,你需要对树的数据结构有深入的了解才能完成搜索。
一个内部类允许我们将逻辑移出当前类并放到自己的类中。从面向对象的角度看,我们已经把功能从不应该存在的位置移出,并放到了自己的类中。通过使用内部类,我们成功地将搜索算法与树解耦。现在,为了改变搜索算法,我们只需简单地交换一个新类就可以了。我可以继续下去,但这会开启我们的代码对面向对象技术提供的许多优势。 组织上的优势 面向对象设计并非人人都能理解,但幸运的是,内部类提供了更多的好处。从组织的角度看,通过命名空间,内部类允许我们进一步组织我们的包结构。类可以嵌套在类中,而不是把所有东西都倒入一个扁平的包中。明确地说,如果没有内部类,我们将被限制在以下层次结构中:package1
class 1
class 2
...
class n
...
package n
package 1
class 1
class 2
class 1
class 2
...
class n
小心使用,内部类可以提供更自然适合您的类的结构层次。
回调优势
成员内部类和匿名类都提供了一种方便的方法来定义回调。最明显的例子与GUI代码相关。但是,回调的应用可以扩展到许多领域。
大多数Java GUI都有某种组件来引发actionPerformed()方法调用。不幸的是,大多数开发人员只是让他们的主窗口实现ActionListener。因此,所有组件共享相同的actionPerformed()方法。为了找出哪个组件执行了操作,通常在actionPerformed()方法中有一个巨大且丑陋的开关。
这是一个整体实现的示例:
public class SomeGUI extends JFrame implements ActionListener {
protected JButton button1;
protected JButton button2;
//...
protected JButton buttonN;
public void actionPerformed(ActionEvent e) {
if (e.getSource() == button1) {
// do something
} else if (e.getSource() == button2) {
//... you get the picture
}
}
}
public class SomeGUI extends JFrame {
// ... button member declarations ...
protected void buildGUI() {
button1 = new JButton();
button2 = new JButton();
//...
button1.addActionListener(
new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent e) {
// do something
}
});
// .. repeat for each button
}
}
public class SomeGUI extends JFrame
{
... button member declarations ...
protected void buildGUI()
{
button1 = new JButton();
button2 = new JButton();
...
button1.addActionListener(
new java.awt.event.ActionListener()
{
public void actionPerformed(java.awt.event.ActionEvent e)
{
// do something
}
}
);
.. repeat for each button
由于内部类可以访问父级中的所有内容,因此我们可以将任何原本出现在单个actionPerformed()实现中的逻辑移动到内部类中。
我更喜欢使用成员类作为回调。但是,这是个人偏好的问题。我只是觉得匿名类太多会使代码混乱。我也觉得如果匿名类超过一两行,它们可能变得笨重。
缺点?
与其他任何事物一样,你必须将好处和坏处相结合。内部类有其缺点。从维护的角度来看,经验不足的Java开发人员可能会发现内部类难以理解。使用内部类还将增加代码中的总类数。此外,从开发的角度来看,大多数Java工具对内部类的支持都有所欠缺。例如,我日常编码使用IBM的VisualAge for Java。虽然内部类将在VisualAge中编译,但没有内部类浏览器或模板。相反,您必须直接在类定义中输入内部类。这使得浏览内部类变得困难。当您在类定义中输入或使用内部类时,由于失去了VisualAge的许多代码完成辅助功能,因此输入也很困难。