为什么我们要使用内部类?

10

我想问一下,为什么我们需要内部类以及为什么要使用它们?
我知道如何使用内部类,但不知道为什么要使用它们。

7个回答

12

一些内部类是公开的(例如Java中的Map.Entry),但这只是例外而不是规律。

内部类基本上是实现细节。

例如,Swing在事件监听器中广泛使用内部类。如果没有它们,你会用一堆你不需要看到的类来污染全局命名空间(这可能会使它们的用途更难确定)。

实际上,内部类是一种作用域形式。包访问从包外隐藏类。私有内部类将该类从该类外部隐藏。

在Java中,内部类还是函数指针或方法委托(C#中的委托)或闭包缺失的替代品。它们是传递函数给另一个函数的手段。例如,在 Executor 类中,您有:

void execute(Runnable r);

所以你可以传递一个方法。在C/C++中可以通过以下方式实现:

void execute(void (*run)());

指向函数的指针。


5
这篇来自wikipedia的文章可能会帮助您理解为什么我们需要内部类:
一个普通或顶级类的实例可以独立存在。相比之下,内部类的实例不能在没有绑定到顶级类的情况下被实例化。
让我们以一个拥有四个轮子的汽车的抽象概念为例。我们的轮子具有依赖于成为汽车一部分的特定功能。这种概念并不表示轮子作为一般形式的轮子,可以是车辆的一部分。相反,它将它们表示为特定于这个轮子的。我们可以使用内部类来建模这个概念:
我们有顶级类Car。类Car的实例由四个类Wheel的实例组成。Wheel的这个特定实现是特定于汽车的,因此代码并没有对轮子的一般概念进行建模,而这种概念最好表示为顶级类。因此,它在语义上与类Car相关联,并且Wheel的代码在某种程度上与其外部类耦合。
内部类为我们提供了一种准确建模这种关系的机制。我们说我们的轮子类是Car.Wheel,Car是顶级类,Wheel是内部类。
因此,内部类允许对程序的某些部分进行面向对象封装,否则这些部分将无法封装到一个类中。

4
Java中的匿名内部类是使用适配器模式的一种方式。
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中内部类的概念,只有嵌套类。

另请参阅内部类示例


喜欢你的例子! - Stats_Lover

3

我大多数时候使用内部类是因为内部类是其他语言中最接近闭包概念的东西。这使得创建和操作具有访问其外部作用域变量的内部嵌套作用域对象成为可能。在创建回调(例如在Swing中定义各种Listener)等方面,这通常非常有用。


2
我使用它们来进行作用域限定,例如,如果我有一个类名为ebook,并且我有ebookPrice,我将ebookPrice放置在ebook类之间,因为它与ebook相关,并且只能在其中使用(至少在概念上是这样的)。
ebookPrice可能继承自更高级别范围中的Price,与每个其他类相关。
(仅代表个人观点)。

2
有一些语言将内部类带到了一个相当不同的层次,比如 Beta 和 Newspeak。在这些语言中,嵌套类被用作包装(即没有包)。
要了解这种观点的全面内容,请参阅 Object Teams 博客上的"我们需要多少关于模块的概念?"。也请参考 Gilad Bracha 在他的博客上的工作......

1

面向对象的优势

在我看来,内部类最重要的特点是它可以将通常不会转化为对象的事物转化为对象。这使得你的代码比没有内部类时更加面向对象。

让我们来看看成员类。由于它的实例是其父实例的成员,因此它可以访问父类中的每个成员和方法。乍一看,这似乎并不重要;我们已经可以从父类的方法中访问那些内容了。然而,成员类允许我们将逻辑从父类中提取出来并将其对象化。例如,一个树类可能有一个方法和许多辅助方法来执行树的搜索或遍历。从面向对象的角度来看,树是一棵树,而不是一个搜索算法。但是,你需要对树的数据结构有深入的了解才能完成搜索。

一个内部类允许我们将逻辑移出当前类并放到自己的类中。从面向对象的角度看,我们已经把功能从不应该存在的位置移出,并放到了自己的类中。通过使用内部类,我们成功地将搜索算法与树解耦。现在,为了改变搜索算法,我们只需简单地交换一个新类就可以了。我可以继续下去,但这会开启我们的代码对面向对象技术提供的许多优势。 组织上的优势 面向对象设计并非人人都能理解,但幸运的是,内部类提供了更多的好处。从组织的角度看,通过命名空间,内部类允许我们进一步组织我们的包结构。类可以嵌套在类中,而不是把所有东西都倒入一个扁平的包中。明确地说,如果没有内部类,我们将被限制在以下层次结构中:
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
        }
    }
}

每当你看到开关或大的if/if else块时,就应该在脑海中听到响亮的警钟。一般来说,这样的结构是不良的面向对象设计,因为代码中一个部分的变化可能需要相应更改switch语句。内部成员类和匿名类允许我们避免使用switched actionPerformed()方法。
相反,我们可以为要监听的每个组件定义实现ActionListener的内部类。这可能会导致许多内部类。但是,我们可以避免大型switch语句并获得封装我们的操作逻辑的附加奖励。此外,该方法可能提高性能。在具有n个比较的switch中,在平均情况下我们可以期望n/2个比较。内部类允许我们设置动作执行者和动作侦听器之间的1:1对应关系。在大GUI中,这种优化可以对性能产生重大影响。匿名方法可能如下所示:
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的许多代码完成辅助功能,因此输入也很困难。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接