CodeGym /课程 /JAVA 25 SELF /内部类(非静态 inner)

内部类(非静态 inner)

JAVA 25 SELF
第 16 级 , 课程 0
可用

1. 什么是内部类(inner class)?

在 Java 中,类不仅可以在顶层(文件中)声明,也可以在另一个类的内部声明。这样的类称为嵌套类(nested class)。如果这样的类在没有 static 修饰符的情况下声明,它就被称为内部类(即 non-static inner class,或简称 inner class)。

内部类是定义在另一个类内部并与该外部类实例关联的类。它可以访问外部类的所有字段和方法,即使它们是 private。这就好比对象有一位“秘密助手”,它几乎什么都能做!

这大致就像房子与房间。房子是外部类,房间是内部类。房间不能脱离房子而存在,但可以使用它的资源:照明、供暖、家具。如果房子消失,房间也会消失。Java 中的内部类也是如此!

为什么需要内部类?

  • 逻辑关联:当一个类只用于配合另一个类且脱离它就没有意义时。
  • 封装:可以隐藏实现细节,不会污染包级命名空间。
  • 访问私有成员:内部类可以访问外部类的私有字段和方法。
  • 简洁:减少包级“噪声”类的数量。

内部类的声明语法

声明内部类很简单:在另一个类的类体中声明,且不带 static 修饰符。

class Outer {
    // 外部类的字段和方法

    class Inner {
        // 内部类的字段和方法
        void printHello() {
            System.out.println("Hello from Inner!");
        }
    }
}

这里 Inner 相对于 Outer 就是内部类。

示意图:可视化

Outer
│
├─ 字段/方法
│
└─ Inner(内部类)
      └─ 自己的字段/方法

2. 如何创建内部类实例

这里藏着新手最常踩的坑之一!内部类的实例总是与某个外部类对象绑定。

示例:

Outer outer = new Outer();             // 创建外部类对象
Outer.Inner inner = outer.new Inner(); // 通过外部对象创建内部类!
inner.printHello();                    // Hello from Inner!

如果仅仅写 new Inner() 会导致编译错误,因为 Java 不知道应该把该内部类绑定到哪个外部类对象上。

为什么会这样?

内部类可以访问外部类的字段和方法,因此它必须“知道”自己正在与哪个外部类对象配合工作。

3. 内部类的使用示例

我们来看一个内部类真正有用的例子。

示例 1:“背包与物品”的模型

假设我们有一个 Backpack 类,它可以存放物品(Item)。但我们希望 Item 只在 Backpack 内可用,因为离开背包我们并不关心这些物品。

public class Backpack {
    private String owner;

    public Backpack(String owner) {
        this.owner = owner;
    }

    // 内部类——物品只能存在于背包中!
    class Item {
        private String name;

        public Item(String name) {
            this.name = name;
        }

        public void printInfo() {
            // 魔法!能看到外部类的私有字段
            System.out.println(owner + " 拥有物品: " + name);
        }
    }
}

用法:

Backpack bp = new Backpack("小明");
Backpack.Item item = bp.new Item("Java 教材");
item.printInfo(); // 小明 拥有物品: Java 教材

注意:Item 即使字段 ownerprivate,也可以访问它!

示例 2:自定义集合的迭代器

在 Java 中,集合常常通过内部类实现迭代器。我们来实现一个带内部迭代器的简单集合。

public class IntList {
    private int[] data = new int[10];
    private int size = 0;

    public void add(int value) {
        data[size++] = value;
    }

    // 内部类——迭代器!
    class Iterator {
        private int index = 0;

        public boolean hasNext() {
            return index < size;
        }

        public int next() {
            return data[index++];
        }
    }
}

用法:

IntList list = new IntList();
list.add(10);
list.add(20);

IntList.Iterator it = list.new Iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

4. 内部类的特点

访问外部类成员

内部类可以访问任何(即使是 private)外部类的字段和方法。这很方便,但也需要谨慎:如果外部类发生变化,内部类可能会突然“崩”。

class Outer {
    private int secret = 42;

    class Inner {
        void showSecret() {
            System.out.println("秘密:" + secret);
        }
    }
}

内部类不能包含静态成员

内部(non-static inner)类不能包含静态字段或方法,除了 static final 常量(例如,public static final int MY_CONST = 123;)。这是因为内部类总是“绑定”到外部类的实例。

如果需要声明静态嵌套类——请使用 static 修饰符(这将在下一讲中介绍)。

内部类的可见性

内部类可以是 privateprotectedpublic,或包级可见(package-private)。

public class Outer {
    private class Inner { /* ... */ }
}

5. 有用的细节

何时使用内部类

内部类不是为了“好看”,而是为了代码的逻辑组织。请在以下场景使用:

  • 类只在一个地方需要(例如辅助迭代器、处理器、复杂结构的一部分)。
  • 类与外部类强相关,脱离它就没有意义。
  • 希望对包内其他类隐藏实现细节。

不要为了“赶时髦”而使用内部类! 如果该类在其他地方也可能有用,最好将其提升为顶层类。

内部类与 this

在内部类中,可以通过 OuterClassName.this 访问外部类的字段和方法。

class Car {
    private String model = "Tesla";

    class Engine {
        void printModel() {
            // 显式访问外部对象
            System.out.println("型号:" + Car.this.model);
        }
    }
}

当内外部类有同名字段时,这种语法尤其有用。

何时不应使用内部类

在以下情况下不要使用:

  • 内部类不访问外部类的字段/方法
  • 该类在程序的其他地方也可能有用
  • 外部类已经变得过大且复杂

这时更好的选择是:

  • 将其改为静态嵌套类(static class)
  • 将类抽到单独文件
  • 用普通方法替代整个类

6. 使用内部类时的常见错误

错误 № 1:试图在没有外部类对象的情况下创建内部类。
如果直接写 new Inner(),编译器会报错:"No enclosing instance of type Outer is accessible"。务必通过外部类对象来创建内部类:outer.new Inner()

错误 № 2:试图在内部类中声明静态字段或方法。
内部类不能包含静态成员(常量除外)。如果需要——请使用静态嵌套类,也就是用 static 修饰符来声明该类。

错误 № 3:过度使用内部类。
如果内部类没有使用外部类的字段/方法,它很可能应该是 static,或者干脆移到外部。不要“以防万一”就创建内部类。

错误 № 4:混淆对外部类字段的访问。
如果内外部类有同名字段,请使用 OuterClassName.this.field 来显式访问外部类的字段。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION