为啥我们使用内部类?

Posted

技术标签:

【中文标题】为啥我们使用内部类?【英文标题】:Why we use inner classes?为什么我们使用内部类? 【发布时间】:2010-04-25 09:07:33 【问题描述】:

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

【问题讨论】:

【参考方案1】:

一些内部类是公开的(例如 Java 中的 Map.Entry),但这是迄今为止的例外而不是常态。

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

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

本质上,内部类是范围的一种形式。包访问隐藏包外部的类。私有内部类在该类之外隐藏该类。

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

void execute(Runnable r);

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

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

作为一个函数的指针。

【讨论】:

【参考方案2】:

wikipedia 的这篇文章可能会帮助你理解为什么我们需要一个内部类:

普通或***类的实例可以自己存在。相比之下,如果不绑定到***类,就无法实例化内部类的实例。

让我们采用具有四个***的汽车的抽象概念。我们的车轮有一个特定的功能,它依赖于我们汽车的一部分。这个概念并不将***表示为可以成为车辆一部分的更一般形式的***。相反,它将它们表示为特定于这个。我们可以使用内部类对这个概念进行建模,如下所示:

我们有***汽车。 Car 类的实例由 Wheel 类的四个实例组成。 Wheel 的这种特定实现是特定于汽车的,因此代码没有对 Wheel 的一般概念进行建模,后者可以更好地表示为***类。因此,它在语义上与 Car 类相连,Wheel 的代码以某种方式与其外部类耦合。

内部类为我们提供了一种准确建模这种连接的机制。我们说我们的***类是 Car.Wheel,Car 是***类,Wheel 是内部类。

因此,内部类允许程序的某些部分面向对象,否则这些部分不会被封装到一个类中。

【讨论】:

【参考方案3】:

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 中,请确保您了解 difference between inner classs and nested class:

一个内部类与一个 其封闭类的实例和 可以直接访问该对象的 方法和字段

C# 没有 Java 意义上的内部类,只有嵌套类。

另见Inner Class Example。

【讨论】:

喜欢你的榜样!【参考方案4】:

大多数时候我使用内部类是因为内部类最接近其他语言中可用的closure 的概念。这允许创建和使用内部嵌套范围的对象,该对象可以访问其外部范围的变量。这在创建回调(例如,在 Swing 中定义各种 Listeners)时通常很有用。

【讨论】:

【参考方案5】:

我使用它们来确定范围,例如,如果我有 ebook 类并且我有 ebookPrice,我将 ebookPrice 包含在 ebook 类之间,因为它与它相关并且只能在其中使用(至少在概念上)。

ebookPrice 可以从 Price 继承,后者的范围更大,并且与其他所有类相关。

(只是我的两分钱)。

【讨论】:

【参考方案6】:

有些语言将内部类提升到完全不同的水平,例如 Beta 和新语。在这些语言中,类的嵌套用作包装(即没有包)。

要详细了解这一愿景,请参阅对象团队博客上的"How many concepts for modules do we need?"。另请参阅 Gilad Bracha 在his blog...上的工作...

【讨论】:

【参考方案7】:

面向对象的优势

以我的拙见,内部类最重要的特性是它允许您将通常不会变成对象的事物变成对象。与没有内部类的情况相比,这使您的代码更加面向对象。

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

内部类允许我们删除该逻辑并将其放入自己的类中。因此,从面向对象的角度来看,我们将功能从不属于它的地方取出,并将其放入自己的类中。通过使用内部类,我们成功地将搜索算法与树解耦。现在,要更改搜索算法,我们可以简单地换入一个新类。我可以继续,但这会使我们的代码获得面向对象技术提供的许多优势。

组织优势

并不是每个人都喜欢面向对象的设计,但幸运的是,内部类提供了更多功能。从组织的角度来看,内部类允许我们通过使用命名空间来进一步组织我们的包结构。类可以进一步嵌套在类中,而不是将所有内容都转储到平面包中。明确地说,没有内部类,我们仅限于以下层次结构:

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 语句中进行相应的更改。内部成员类和匿名类允许我们摆脱切换的 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 的代码完成辅助

【讨论】:

以上是关于为啥我们使用内部类?的主要内容,如果未能解决你的问题,请参考以下文章

java 内部类为啥不能static

为啥静态内部类可以减轻内存泄漏?

为啥扩展内部类的本地类不能访问内部类封闭实例?

为啥内部类的扩展会得到重复的外部类引用?

为啥将内部类注入同一个外部类中的另一个内部类不起作用?

为啥内部类的private变量可被外部类直接访问