08 内部类(嵌套类)

简介: 目前,见到的类、接口和枚举类型都定义为顶层类型。也就是说,都是包的直接成员,独立于其他类型。不过,类型还可以嵌套在其他类型中定义。这种类型是嵌套类型(nested type),一般称为“内部类”,是 Java 语言的一个强大功能。

目前,见到的类、接口和枚举类型都定义为顶层类型。也就是说,都是包的直接成员,独立于其他类型。不过,类型还可以嵌套在其他类型中定义。这种类型是嵌套类型(nested type),一般称为“内部类”,是 Java 语言的一个强大功能。


嵌套类型的使用目的,都和封装有关。


内部类只是 Java 编译器的概念,对于Java虚拟机而言,它是不知道内部类这回事的,每个内部类最后都会被编译为一个独立的类,生成一个独立的字节码文件。也就是说,每个内部类其实都可以被替换为一个独立的类。当然,这是单纯就技术实现而言。


内部类可以方便地访问外部类的私有变量,可以声明为 private 从而实现对外完全隐藏,相关代码写在一起,写法也更为简洁,这些都是内部类的好处。


嵌套类型也可以理解为通过某种方式和其他类型绑定在一起的类型,不作为完全独立的实体真实存在。类型能通过四种不同的方式嵌套在其他类型中。


提示 内部类编译成功后生成的字节码文件是“外部类$内部类.class”。


1. 静态成员类型 / 静态内嵌类


静态成员类型是定义为其他类型静态成员的类型。嵌套的接口、枚举和注解始终都是静态成员类型(就算不使用 static 关键字也是)。


2. 非静态成员类/成员内部类


“非静态成员类型”就是没使用 static 声明的成员类型。只有类才能作为非静态成员类型。


3. 局部类/方法内部类


局部类是在 Java 代码块中定义的类,只在这个块中可见。接口、枚举和注解不能定义为局部类型。


4. 匿名局部类/匿名内部类


匿名类也是一种局部类,但对 Java 语言来说没有有意义的名称。因此没有名字。接口、枚举和注解不能定义为匿名类型。


“嵌套类型”这个术语虽然正确且准确,但开发者并没有普遍使用,大多数 Java 程序员使用的是一个意义模糊的术语——“内部类”。根据语境的不同,这个术语可以指代非静态成员类、局部类或匿名类,但不能指代静态成员类型,因此使用“内部类”这个术语时无法区分指代的是哪种嵌套类型。虽然表示各种嵌套类型的术语并不总是那么明确,但幸运的是,从语境中一般都能确定应该使用哪种句法。


静态成员类型


语法上,静态内部类除了位置放在其他类内部外,它与一个独立的类差别不大,可以有静态变量、静态方法、成员方法、成员变量、构造方法等。


image.png


我们也可以看一些在 Java API 中使用静态内部类的例子:


❑ Integer 类内部有一个私有静态内部类 IntegerCache,用于支持整数的自动装箱。


❑ 表示链表的 LinkedList 类内部有一个私有静态内部类 Node,表示链表中的每个节点。


❑ Character 类内部有一个 public 静态内部类 UnicodeBlock,用于表示一个 Unicode block。


非静态成员类


与静态内部类不同,除了静态变量和方法,成员内部类还可以直接访问外部类的实例变量和方法,如 innerMethod 直接访问外部类私有实例变量a。成员内部类还可以通过“外部类.this.xxx”的方式引用外部类的实例变量和方法,如 Outer.this. action(),这种写法一般在重名的情况下使用,如果没有重名,那么“外部类.this. ”是多余的。

创建内部类对象的语法是“外部类对象.new 内部类()


public static void main(String arg[]) {
    OuterOne.InnerOne i = new OuterOne().new InnerOne();
    i.innerMethod();
}


成员内部类有哪些应用场景呢?如果内部类与外部类关系密切,需要访问外部类的实例变量或方法,则可以考虑定义为成员内部类。外部类的一些方法的返回值可能是某个接口,为了返回这个接口,外部类方法可能使用内部类实现这个接口,这个内部类可以被设为 private,对外完全隐藏。


比如,在 Java API 的类LinkedList中,它的两个方法 listIterator 和 descendingIterator 的返回值都是接口 Iterator,调用者可以通过 Iterator 接口对链表遍历,listIterator 和 descend-ingIterator 内部分别使用了成员内部类 ListItr 和 DescendingIterator,这两个内部类都实现了接口 Iterator。


方法局部类


局部类在一个 Java 代码块中声明,不是类的成员。只有类才能局部定义,接口、枚举类型和注解类型都必须是顶层类型或静态成员类型。局部类往往在方法中定义,但也可以在类的静态初始化程序或实例初始化程序中定义。


因为所有 Java 代码块都在类中,所以局部类都嵌套在外层类中。因此,局部类和成员类有很多共同的特性。局部类往往更适合看成完全不同的嵌套类型。


  1. 局部类的特性
    局部类有如下两个有趣的特性:
    和成员类一样,局部类和外层实例关联,而且能访问外层类的任何成员,包括私有成员
    除了能访问外层类定义的字段之外,局部类还能访问局部方法的作用域中声明为 final 的任何局部变量、方法参数和异常参数


  1. 局部类的限制


  • 局部类的名称只存在于定义它的块中,在块的外部不能使用。(但是要注意,在类的作用域中创建的局部类实例,在这个作用域之外仍能使用。稍后本节会详细说明这种情况。)


  • 局部类不能声明为 public、protected、private 或 static。


  • 与成员类的原因一样,局部类不能包含静态字段、方法或类。唯一的例外是同时使用 static 和 final 声明的常量。


  • 接口、枚举类型和注解类型不能局部定义。


  • 局部类和成员类一样,不能与任何外层类同名。


  • 前面说过,局部类能使用同一个作用域中的局部变量、方法参数和异常参数,但这些变量或参数必须声明为 final。这是因为,局部类实例的生命周期可能比定义它的方法的执行时间长很多。


局部类用到的每个局部变量都有一个私有内部副本(这些副本由 javac 自动生成)。只有把局部变量声明为 final 才能保证局部变量和私有副本始终保持一致。


这一点从下述代码中可以看出:

public class Weird {
  // 静态成员接口,下面会用到
  public static interface IntHolder { public int getValue(); }
  public static void main(String[] args) {
    IntHolder[] holders = new IntHolder[10];
    for(int i = 0; i < 10; i++) {
      final int fi = i;
      // 局部类
      class MyIntHolder implements IntHolder {
        // 使用前面定义的final变量
        public int getValue() { return fi; }
      }
      holders[i] = new MyIntHolder();
    }
    // 局部类不在作用域中了,因此不能使用
    // 但是在数组中保存有这个类的 10 个有效实例
    // 局部变量fi现在已经不在作用域中了
    // 但仍然在那10个对象 getValue()方法的作用域中
    // 因此,可以在每个对象上调用getValue()方法,打印fi的值
    // 下述代码打印数字 0 到 9
    for(int i = 0; i < 10; i++) {
      System.out.println(holders[i].getValue());
    }
  }
}


方法内部类可以用成员内部类代替,至于方法参数,也可以作为参数传递给成员内部类。不过,如果类只在某个方法内被使用,使用方法内部类,可以实现更好的封装。


匿名局部类 / 匿名内部类


匿名内部类只能被使用一次,用来创建一个对象。它没有名字,没有构造方法,但可以根据参数列表,调用对应的父类构造方法。它可以定义实例变量和方法,可以有初始化代码块,初始化代码块可以起到构造方法的作用,只是构造方法可以有多个,而初始化代码块只能有一份。因为没有构造方法,它自己无法接受参数,如果必须要参数,则应该使用其他内部类。与方法内部类一样,匿名内部类也可以访问外部类的所有变量和方法,可以访问方法中的 final 参数和局部变量。


网络异常,图片无法展示
|


匿名内部类能做的,方法内部类都能做。但如果对象只会创建一次,且不需要构造方法来接受参数,则可以使用匿名内部类,这样代码书写上更为简洁。


总结



内部类本质上都会被转换为独立的类,但一般而言,它们可以实现更好的封装,代码实现上也更为简洁。


参考



  • 丁振凡编著,《Java 语言程序设计(第2版)》华东交大版,2014.9


  • Java语言程序设计_中国大学MOOC(慕课)


https://www.icourse163.org/learn/ECJTU-1206089803?tid=1451269475#/learn/content?type=detail&id=1218983526&cid=1227151005


  • Java 编程的逻辑-微信读书


https://weread.qq.com/web/reader/b51320f05e159eb51b29226kc81322c012c81e728d9d180




目录
相关文章
|
3月前
|
Java 编译器
内部类14
内部类14
19 2
|
7月前
内部类
内部类
26 1
|
7月前
|
Java 编译器
内部类详解
内部类详解
|
Java 编译器
你真的了解四种内部类吗
你真的了解四种内部类吗
73 0
|
编译器
神奇的内部类
神奇的内部类
49 0
|
Java
内部类(上)成员内部类,局部内部类的使用
内部类(上)成员内部类,局部内部类的使用
64 0
JavaN种内部类
内部类的使用场景、作用: 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构可以选择使用内部类来设计。 内部类通常可以方便访问外部类的成员,包括私有的成员。
85 0
|
Java
四种内部类你都了解了吗?
四种内部类你都了解了吗?
79 0
内部类和匿名内部类
一.内部类 1.什么是内部类? 将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类 2.成员内部类 (1)成员内部类:定义在类中方法外的类 (2)定义格式:
93 0