欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java 之路 (十) -- 内部类(概念、分类、特性、意义、"多重继承"、继承)

程序员文章站 2022-05-07 10:50:13
...

1. 内部类基础

1.1 什么是内部类

内部类的定义如下:

  • 可以将一个类的定义放在另一个类的定义内部,这就是内部类

更具体一点,对于编程思想而言:

  • 内部类允许将逻辑相关的类组织在一起,并控制位于内部的类的可视性。
  • 内部类就像是一种代码隐藏机制:将类置于其他类的内部,它了解外围类,能与之通信

1.2 内部类的分类

内部类分为以下四类:

  • 成员内部类:最最普通的内部类,定义在另一个类的内部
  • 局部内部类:定义在一个方法或者一个作用域内
  • 匿名内部类:直观来讲就是没有名称的成员内部类
  • 静态内部类:使用 static 修饰的成员内部类(书中称之为 嵌套类

下面就具体分析每个类的语法及其特性。

1.2.1 成员内部类

将包含内部类的外层的类都称为 外围类

成员内部类是最普通的内部类,定义位于另一个类(外围类)的内部,并且与成员方法和属性平级,可以假象为外部类的非静态方法。

1.2.1.1 内部访问外围

其形式如下:

public class Outer {
    String name = "";
    static int tag = 1;
    private int type = 0;

    public Outer(String name) {
        this.name = name;
    }
    private String f(){
        System.out.println("Outer.f()")
    }

    class Inner {     //内部类
        private String name;

        public void showInfo() {
            System.out.println(name);//此处表现出特性:成员内部类拥有其外部类的所有元素访问权。
            System.out.println(""+tag);
            System.out.println(""+type);
        }

        //内部类通过 外部类.this 令生成对外部类对象的引用
        public Outer getOuter(){
            return Outer.this;
        }
    }

    public Inner getInner(){
        return new Inner();
    }

    public static void main(String[] args){
        Outer o = new Outer();
         //生成内部类对象
        //方式一:通过提供的 getInner() 方法获取内部类的对象
        Outer.Inner i = o.getInner();
         //方式二:通过 .new 获取内部类的对象
         Outer.Inner i = o.new Inner();

         //生成对外部类对象的引用,并访问方法
         i.getOuter().f();
    }
}

上述代码中,体现了如下要点:

  1. 成员内部类可以无条件访问外部类的所有成员属性和方法(包括 private 和 static )

    Inner 类中的 showName 方法直接访问了 Outer 类的 name

    也可以表示为:成员内部类自动拥有对其外围类所有成员的访问权

    这里解释一下为什么事情是这样的:当某个外围类的对象创建了一个成员内部类对象时,词内部类对象必定会持有一个指向外围类对象的引用,也就是说在我们访问外围类的成员时,就是通过该引用来访问外围类的成员。

    这一点编译器帮我们做好处理了,因此无需担心构建内部类对象时访问不到指向外部类的引用。

  2. 如何创建成员内部类的对象

    1. 首先构造外部类对象,这是必备前提。

      原因还在内部类对象持有外围类对象引用上,没有外围类对象,内部类对象去哪获得这个引用呢?

    2. 其次声明类型时必须具体指明内部类的类型 OuterClassName.InnerClassName

    3. 然后通过外部类对象 outer.new 或 实例方法 获取对象

      Outer o = new Outer();
             //生成内部类对象
          //方式一:通过提供的 getInner() 方法获取内部类的对象
          Outer.Inner i = o.getInner();
             //方式二:通过 .new 获取内部类的对象
             Outer.Inner i = o.new Inner();
  3. 内部类对象如何生成对外部类对象的引用

    1. 使用 .this

      • 外部类.this.成员变量
      • 外部类.this.成员方法
    2. 如:i.getOuter().f();

      此种方式直接避免了隐藏现象

      当内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,默认情况下访问的是成员内部类的成员/方法。

  4. (例子中未体现,作为补充)成员内部类可以设置访问修饰权限(public、protected、默认、private)

    1. private : 只能在外部类的内部访问

    2. public :任何地方都能访问

    3. protected:同一个包下或者外部类的子类中访问

    4. 默认:同一个包下访问

      作为对比:外围类只能是 public 或者 默认权限。

  5. 无论嵌套多少层,成员内部类都可以透明的访问所有包含它的外围类的所有成员。

1.2.2.2 外围访问内部

说了这么多,都是在说内部类访问外部类是如何如何方便,但是反过来,外部类想访问内部类的成员就没这么随意了。

外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问。

class Outer {
    private String name = "Outer";

    public Outer(String name) {
        this.name = name;
        getInstance().show();   //必须先创建成员内部类的对象,再进行访问
    }

    private Inner getInstance() {
        return new Inner();
    }

    class Inner {     //内部类
        public void show() {
            System.out.println(radius);  //外部类的private成员
        }
    }
}

1.2.2 局部内部类

局部内部类是在一个方法或者任意的作用域里定义的类,和成员内部类的区别在于局部内部类访问权限仅限于方法内或该作用域内

class People{
    //...
}

class Son{
    //...

    public People getParent(){
        class Parent extends People{   //局部内部类
            //...
        }
        return new Parent();
    }
}

显然,Parent 类并不是公共可用的,它仅仅是辅助我们实现具体的需求设计。

注意:

  1. 局部内部类不能用 public、protected、private 和 static 修饰的,就像一个局部变量一样
  2. 局部内部类只能访问局部 final 变量,但是能访问外围所有成员
  3. 限制的只是访问权限,但局部内部类的对象在方法或作用域外仍然可用

1.2.3 匿名内部类

在 Android 中匿名内部类用得很多,比如典型的 Button 的点击监听:

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {

    }
});

下面还是说回 Java,举个典型的 Java 的例子:

//使用匿名内部类
public class Outer{
    public AnonymousInner getInner(){
        return new AnonymousInner(/*此处可以添加参数*/){
            //...
        }
    }
}
//不使用匿名内部类
public class Outer{
    public AnonymousInner getInner(){
        return new AnonymousInner();
    }
    class AnonymousInner{
        //...
    } 
}

可以看出,匿名内部类和 普通内部类 最大的区别就是在生成对象处对类进行定义。

需要注意的要点:

  1. 如果定义匿名内部类时,需要使用在其外部定义的对象作为参数,那么参数引用必须是 final 的
  2. 匿名内部类内没有构造器,所以需要进行初始化时需要通过实例初始化。
  3. 匿名内部类前不能使用 访问修饰符 和 static。

1.2.4 静态内部类

静态内部类是定义在另一个类中的类,并且用 static 修饰。(书中也称之为 嵌套类)

如何创建静态内部类的对象:

  • OuterClassName.InnerClassName inner = new OuterClassName.InnerClassName();

静态内部类的特性:

  1. 创建静态内部类不需要外围类的对象

  2. 静态内部类没有 this 引用

    类似于静态成员,不依赖于类

  3. 静态内部类的对象不允许访问外围类的非静态成员/属性

    静态内部类不依赖于外围类,而非静态成员/属性依托于类。

  4. 静态内部类可以包含 static 数据/字段,也可以包含静态内部类

    非静态内部类不能包含以上内容。

  5. 静态内部类可以放在 interface 中

    前面我们学习过,接口 interface 是一种特殊的类


2. 内部类的意义

2.1 为什么需要内部类?

  1. 对于编程思想而言:
    1. 每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多重继承的解决方案变得完整。
    2. 有效的实现了“多重继承”,提供继承多个具体的或抽象的类的能力。
  2. 对于在 Android 中:
    1. 方便实现 事件驱动 的代码

2.2 内部类是如何实现“多重继承”的?

  • 对于一个类实现两个接口 – 单一类或内部类都可以

    interface A {}
    interface B {}
    class X implements A,B {} // 使用单一类
    
    class Y implements A {
      B makeB(){
          return new B() {}; //使用匿名内部类
      }
    }
  • 对于非接口类型(类或抽象类),则只能通过内部类才能实现”多重继承”

    class D {}
    abstract class E {}
    
    class Z extends D {//继承D
      E makeR() {
          return new E(){};//内部类实现 E
      }
    }

2.3 内部类的一些额外特性

  1. 内部类可以有多个实例,实例间互相独立,同时与外围类对象的信息也相互独立

  2. 单个外围类中,可以让多个内部类以不同方式实现同一个接口或继承同一个类

  3. 创建内部类对象的时刻并不依赖于外部类对象的时刻

    应该指的是 对于非静态内部类而言,创建之前只要有外部类对象即可;对于静态内部类而言,无需外部类对象。

  4. 内部类与外围类并非 is-a 关系,它是一个独立实体


3. 内部类的补充说明

3.1 内部类的具体应用

关于内部类的具体应用示例,书中给了三个例子

  • 提供 闭包
  • 实现 应用控制框架
  • 优化 “工厂方法”的实现

emmm….大家感兴趣自行查阅吧。

3.2 内部类的继承

当某个类继承内部类时,其构造方法中必须传入外围类的实例对象,并且在构造方法第一句中显式调用 “外围类对象名.super()”

class Outer {
    class Inner {}
}
public class InheritInner extends Outer.Inner{
    InheritInner(Outer o){
        o.super();
    }
    public static void main(String[] args){
        Outer o = new Outer();
        InheritInner i = new InneritInner(o);
    }

解释一下为什么事情是这样子的:
因为内部类的构造器必须能够连接到指向其外围类对象的引用,于是问题就在于这个引用必须被初始化,然而在导出类中不再存在可链接的默认对象。

由继承就引发了另一个问题:内部类的覆盖

class A {
    class B {}
}
  • 当某类 C 继承外围类 A 时,也定义一个内部类 B ,这时并不会覆盖内部类的方法,因为此时 A.B 和 C.B 是两个独立的实体,各自在各自的命名空间。
  • 如果 C 继承 A,C.D 继承 A.B,此时在 C.D 中会覆盖 A.B 中的方法

3.3 内部类标识符

每个类都会生成一个 .class 文件,内部类也不例外。

该文件包含如何创建该类型的对象的全部信息。此信息产生一个 “meta-class”,叫做 Class 对象

内部类类文件的命名规则:

  • 外围类类名$内部类类名。

  • 如果是匿名内部类,则编译器用一个简单的数字作为标识符

  • 例如:

    class A {
      class B {}
      C makeC{
          return new C() {};
      }
    }

    生成的全部类文件:

    • A.class
    • A$B.class
    • A$1.class

总结

本章围绕内部类,讲述了概念,分类,意义还有一些平时可能用不到的语法,完备的包含了内部类方方面面需要了解的知识。原书中,重点还是在编程思想上的讲述,涉及到了诸多示例,比如工厂方法实现的改进以及闭包的实现等等,如果感兴趣可以自行看一看原书中的讲解。

但是呢,我重学 Java 的目的在于补一下基础,以此让我在 Android 上走的更远,但是 Android 中涉及到内部类的貌似并不多,所以上面的示例我只是简单了解,并未整理。

好像说了点废话,就这样吧。共勉。