概述

  • 把类定义在其他类的内部,这个类就被称为内部类。
    • 举例:在类A中定义了一个类B,类B就是内部类。
  • 内部类访问特点
    • 内部类可以直接访问外部类的成员,包括私有。
    • 外部类要访问内部类的成员,必须创建对象。
  • 内部类作用
    • 部类作用主要实现功能的隐藏、减少内存开销,提高程序的运行速度
    • 内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两类。
      • 对于一个名为outer的外部类和其内部定义的名为inner的内部类。
      • 编译完成后出现outer.class和outer$inner.class两类。

内部类分类

  • Java中的内部类共分为四种:
    • 静态内部类static inner class (also called nested class)
    • 成员内部类member inner class
    • 局部内部类local inner class
    • 匿名内部类anonymous inner class
  • 按照内部类位置分类
    • 成员位置:在成员位置定义的类,被称为成员内部类。
    • 局部位置:在局部位置定义的类,被称为局部内部类。

成员内部类

  • 作为外部类的一个成员存在,与外部类的属性、方法并列。可通过外部类对象进行访问

  • 定义格式

    1
    2
    3
    4
    5
    6
    7
    8
    // 外部类
    修饰符 class 类名称{
    // 内部类
    修饰符 class 类名称{
    ...
    }
    ...
    }
  • 外部类静态方法中不能直接访问实例内部类数据,要用对象调用

  • 访问内部类的成员:

    • 格式: 外部类名.内部类名 对象名 = 外部类对象.内部类对象;

特点

  • 成员内部类可以访问外围类的所有成员,包括私有成员;

  • 只有内部类可以是私有类,而常规类只可以是protected或public

  • 成员内部类是不可以声明静态成员(包括静态变量、静态方法、静态成员类、嵌套接口)

    • 但有个例外—可以声明 static final的变量, 这是因为编译器对final类型的特殊处理,是直接将值写入字节码;
    • static final int N=22;编译期能识别,编译后所有N用22替代了
  • 成员内部类对象都隐式地保存了一个引用,指向创建它的外部类对象;或者说,成员内部类的入口是由外围类的对象保持着(静态内部类的入口,则直接由外围类保持着),通过这个引用,内部类就可以无限制的访问外部类的成员了。

  • 成员内部类可以继续包含成员内部类,而且不管一个内部类被嵌套了多少层,它都能透明地访问它的所有外部类所有成员;

  • 成员内部可以继续嵌套多层的成员内部类,但无法嵌套静态内部类;静态内部类则都可以继续嵌套这两种内部类。

  • 成员内部类中的 this,new关键字:

    • 获取外部类对象:OuterClass.this
    • 明确指定使用外部类的成员(当内部类与外部类的名字冲突时):OuterClass.this.成员名

    • 创建内部类对象的new:外围类对象.new

      1
      2
      3
      4
      //先创建外围类对象
      OuterClass outer=new OuterClass();
      //创建成员内部类对象
      OuterClass.InnerClass inner=outer.new InnerClass();

使用

  • 定义类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Body {//外部类,身体
    private boolean life= true; //生命状态
    public class Heart { //内部类,心脏
    public void jump() {
    System.out.println("心脏噗通噗通的跳");
    System.out.println("生命状态" + life); //访问外部类成员变量
    }
    }
    }

创建内部类对象

  • 创建对象:外部类名.内部类名 变量名 = new 外部类名().new 内部类名();
1
2
3
4
5
6
public static void main(String[] args) {
//创建内部类对象
Body.Heart bh = new Body().new Heart();
//调用内部类中的方法
bh.jump();
}

外部内中创建内部类对象

  • 创建内部类的对象时,必须由此外围类的对象来创建其内部类的一个对象

    • 内部类的对象被创建的时候,内部类的对象会悄悄地链接到创建它地外部类地对象,从而获得它地一个隐式引用。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class OutterClass {

    private String name;
    private int age;

    //外部类的非静态方法访问成员内部类
    public void getInnerClass(){
    InnerClass innerClass = new InnerClass();
    System.out.println(innerClass);
    }

    //外部类的静态方法访问成员内部类,和外部类的外部访问内部类的方法一样
    public static void get(){
    OutterClass outterClass = new OutterClass();
    InnerClass innerClass = outterClass.new InnerClass();
    }

    //一个内部类
    class InnerClass{

    private String nickName;
    private String name;

    }
    }

内部类生成对外部类对象的引用

  • 使用外部类.this,这样就能够产生一个正确引用外部类的引用了

  • 非静态内部类持有外部类的引用 使用不慎会造成内存溢出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class App {
    private String name;
    public int step = 3;

    public class InnerClass {
    private String nickName;
    private String name;
    public App getOuterClass() {
    //在内部类中,访问自己的变量时直接使用变量名,也可以使用this.变量名
    System.out.println(nickName);
    System.out.println(this.nickName);
    //在内部类中访问外部类中与内部类同名的实例变量:外部类名.this.变量名
    System.out.println(OutterClass.this.name);
    //如果内部类中没有与外部类同名的变量,则可以直接用变量名访问外部类变量
    System.out.println(step);
    return App.this;
    }
    }

    public static void main(String[] args) {
    App. InnerClass innerClass = new App().new InnerClass();
    App outerClass = innerClass.getOuterClass();
    System.out.println(outerClass.step);
    }
    }

继承成员内部类

  • 在内部类的访问权限允许的情况下,成员内部类也是可以被继承的

    • 由于成员内部类的对象依赖于外围类的对象

    • 成员内部类的构造器入口由外围类的对象把持着。

    • 继承了成员内部类的子类必须要与一个外围类对象关联起来。

    • 子类的构造器是必须要调用父类的构造器方法,所以也只能通过父类的外围类对象来调用父类构造器。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      class ClassA{

      class ClassB{ //内部类
      private String name;//内部类的私有属性
      public ClassB(String name){//内部类的构造方法
      this.name = name;
      };

      public String info(){//内部类的成员方法
      return name;
      }
      } //内部类定义完毕

      }

      public class OutputInnerClass extends ClassA.ClassB{
      public OutputInnerClass(ClassA a, String s){
      //new ClassA().super();
      // 第一行,必须是调用父类的构造方法
      // a 是 类 A (class A) 的一个实体(对象)。
      a.super(s); // 首先调用父类(ClassA 的内部类)的构造方法
      // 执行 a.super(s), 将使用字符串 s 作为参数来调用类A (class A) 的内部类 B (class B) 的要求一个字符串参数的构造方法,
      // 也就是调用类OutputInnerClass的超类(class B)的、要求一个字符串参数的构造方法,以便首先创建父类对象。
      }
      public static void main(String[] args){
      // 创建子类对象时一定要先创建父类对象吗
      ClassA a = new ClassA();
      OutputInnerClass oi = new OutputInnerClass(a,"顺应天意了却凡尘" );
      System.out.println(oi.info());
      }
      }

外部类不可以直接访问非静态内部类的成员

  • 非静态内部类可以直接访问外部类的成员,

  • 而外部类不可以直接访问非静态内部类的成员

    • 非静态内部类没有被static修饰,所以这个内部类就不是类相关的,也就说不是类的,是实例的
    • 但是我们非静态内部类要创建实例,外部类一定会先创建一个外部类的实例,非静态内部类的实例就是寄生在外部类的实例上的。
    • 所以,非静态内部类的实例可以直接访问外部类的成员,因为,外部类已经创建一个实例的,内部类保留了外部类创建的实例的引用
  • 静态内部类是被static修饰的,所以是类的一员。

  • 根据静态成员不能访问非静态成员的原则,静态内部类是不能访问外部类的非静态成员的

  • 示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    class Outer {
    public String name = "MAH";
    private static int num = 100;

    //非静态内部类
    public class Inner {
    //非静态内部类的Feild
    public int Num = 4;
    //例外:这里的变量a相当于是一个常量
    static final int a=1;
    public void acc() {
    System.out.println(num);
    System.out.println(name);//非静态内部类可以直接访问外部类的成员
    }
    }

    public void accessInner() {
    // 不可以直接访问非静态内部类的成员
    System.out.println("内部类的Num值是:" + new Inner().Num);
    System.out.println("内部类的静态变量的值是"+ Inner.a);
    new Inner().acc();
    }

    public static void main(String[] args) {
    Outer b = new Outer();
    //外部类通过调用accessInner方法产生一个非静态内部类的实例
    b.accessInner();
    }
    }

局部内部类

  • 方法中定义的内部类称为局部内部类。
  • 局部类不能用访问修饰符进行声明,它的作用域被限定在声明这个局部类的块(方法)中。
  • 局部类有一个优势,即是对外部世界可以完全地隐藏起来。即是是OutterClass类中的其他代码也不能访问它。

访问限制

  • 对于局部变量,局部内部类只能访问final的局部变量。

    • 在JDK8中如果我们在匿名内部类中需要访问局部变量,那么这个局部变量不需要用final修饰符修饰。因为在生成的匿名内部类中维护着外部类实例的引用, 在内部类调用该变量时, 实际上是通过外部类的实例来调用的, 也就是两个实例引用着同一个内存地址, 保证了数据一致性
  • 局部类不仅能够访问包含它们的外部类,还可以访问局部变量。但是局部变量必须事实上为final。用final修饰实际上就是为了保护数据的一致性(对引用变量来说是引用地址的一致性,对基本类型来说就是值的一致性)。在内部类中,局部变量是被当成了参数传递给匿名对象的构造器,作为成员变量在堆上存在。只需要保证这个变量代表的值或地址不变就行了。

  • 对于类的全局成员,局部内部类定义在实例环境中(构造器、对象成员方法、实例初始化块),则可以访问外围类的所有成员

  • 但如果内部类定义在静态环境中(静态初始化块、静态方法),则只能访问外围类的静态成员

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    public class OuterClass {
    private int a = 21;
    static {//静态域中的局部内部类
    class LocalClass1{
    // int z = a; //错误,在静态的作用域中无法访问对象成员
    }
    }
    {//实例初始化块中的局部内部类
    class localClass2{
    }
    }
    public OuterClass(){
    int x = 2;
    final int y = 3;
    // x = 3;//若放开此行注释,编译无法通过,因为局部变量x已经是final类型
    //构造器中的局部内部类
    class localClass3{
    int z = y; //可以访问final的局部变量
    int b = a;//可以访问类的所有成员
    //1.8后可以访问没有用final修饰的局部变量
    int c = x;
    }
    }
    public void createRunnable() {
    final int x = 4;
    //方法中的局部内部类
    class LocalClass4 implements Runnable {//
    @Override
    public void run() {
    System.out.println("局部final变量:"+x);
    System.out.println("对象成员变量:"+a);
    }
    }
    }
    }

访问

  • 外部类方法中,创建内部类对象,进行访问

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Party {//外部类,聚会
    public void puffBall(){// 吹气球方法
    class Ball {// 内部类,气球
    public void puff(){
    System.out.println("气球膨胀了");
    }
    }
    //创建内部类对象,调用puff方法
    new Ball().puff();
    }
    }
  • 方法外:创建外部类对象访问方法,方法中调用内部类的对象

    1
    2
    3
    4
    5
    6
    public static void main(String[] args) {
    //创建外部类对象
    Party p = new Party();
    //调用外部类中的puffBall方法
    p.puffBall();
    }

匿名内部类

匿名内部类(anonymous inner class),简单地说:匿名内部类就是没有名字的内部类。什么情况下需要使用匿名内部类?如果满足下面的一些条件,使用匿名内部类是比较合适的:

  • 匿名内部类就是实现接口的类 或 继承了该类 的 对象

  • 类在定义后马上用到。

  • 类非常小(SUN推荐是在4行代码以下)

  • 给类命名并不会导致你的代码更容易被理解。

  • 格式

    1
    2
    3
    new 父类(参数列表) | 实现接口(){
    //匿名内部类的类体部分
    }

使用原则

  • 匿名内部类不能有构造方法。

    • 构造器和类名一样
    • 匿名就是没有名字
  • 不能定义任何静态成员,方法和类。

    • 只有静态内部类才能定义静态
    • static final 除外
  • 匿名内部类不能是public,protected,private,static。

  • 必须实现一个类或一个接口。

    • 返回的是子类 或 实现类 的子类对象
  • 因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //已经存在的父类:
    public abstract class Person{
    public abstract void eat();
    }

    //定义并创建该父类的子类对象,并用多态的方式赋值给父类引用变量
    Person p = new Person(){
    public void eat() {
    System.out.println("我吃了");
    }
    };
    //调用eat方法
    p.eat();
  • 匿名内部类如果不定义变量引用,则也是匿名对象

    1
    2
    3
    4
    5
    6
    // 将定义子类与创建子类对象两个步骤由一个格式一次完成
    new Person(){
    public void eat() {
    System.out.println(“我吃了”);
    }
    }.eat();
  • 传递参数

    • 匿名内部类不能有构造器,但是我们需要一个有参构造器的时候应该怎么办?
      • 只需简单地传递合适的参数给基类的构造器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Test01 {
    //1.8之前编译器会要求其参数引用是final型的。
    public Person fun1(final String str) {
    return new Person() {
    public void eat() {
    System.out.println("我吃了"+str);
    }
    };
    }

    public static void main(String[] args) {
    Person person = new Test01().fun1("西瓜");
    person.eat();
    }
    }

匿名内部类实现抽象类

  • 抽象类定义

    1
    2
    3
    4
    5
    6
    7
    8
    abstract class Base1 {
    Base1(int n) {
    System.out.println(n);
    }

    public void f() {
    }
    }
  • 匿名内部类实现抽象类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // 匿名内部类
    Base1 base1 = new Base1(22) {
    {
    System.out.println("构造块");
    }

    public void f() {
    System.out.println("sub_f");
    }

    public void method() {
    }// 调用不到,Base1没有,多态
    };

    //匿名内部类调用方法
    //匿名内部类是对象,所以能调用
    new Base1(44) {
    public void f() {
    System.out.println("sub_f");
    }

    public void m() {
    System.out.println("自己添加的m()");
    }
    }.m();

示例

  • 按照要求,补齐代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //要求在控制台输出”HelloWorld”
    interface Inter { void show(); }
    class Outer {
    //补齐代码
    }

    class OuterDemo {
    public static void main(String[] args) {
    // 1.类能直接调用method方法,有静态方法method()
    // 2.能有show()方法,说明method返回的是Inter
    // 3.使用匿名内部类重写show()方法
    Outer.method().show();
    }
    }
  • 答案:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //补齐代码
    public static Inter method() {
    //匿名内部类
    return new Inter() {
    public void show(){
    System.out.println("HelloWorld") ;
    } ;
    }
    }

静态内部类

  • 如果你不需要内部类对象与其外围类对象之间有联系,那你可以将内部类声明为static。这通常称为嵌套类(nested class)。

  • 想要理解static应用于内部类时的含义,你就必须记住,普通的内部类对象隐含地保存了一个引用,指向创建它的外围类对象。

  • 当内部类是static的时,就不依赖于外围类对象实例而独立存在。嵌套类意味着:

    • 要创建嵌套类的对象,并不需要其外围类的对象。
    • 静态内部类的可以访问外围类中的所有静态成员,包括private的静态成员,但是它不能使用任何外围类的非static成员变量和方法。
  • 其创建对象、继承(实现接口)、扩展子类等使用方式与外围类并没有多大的区别。

访问

  • 创建静态内部类对象

    • 外部类名.静态内部类名 = new 外部类名.静态内部类名();
  • 外部类访问内部类

    • 静态成员/方法:内部类.静态成员,内部类名.静态方法名();
    • 非静态成员: 实例化内部类调用
    • 非静态方法:通过创建对象使用
  • 内部类访问外部类

    • 所有静态成员,包括private的静态成员
    • 不能使用任何外围类的非static成员变量和方法
  • 静态内部类的静态方法只能访问静态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    public class Outer {
    private static int i = 1;
    private int j = 10;

    public static void outerF1() {
    }

    public void outerF3() {
    // 外部类访问内部类的静态成员:内部类.静态成员
    System.out.println(Inner.inner_i);
    Inner.innerF1();
    // 外部类访问内部类的非静态成员:实例化内部类即可
    Inner inner = new Inner();
    inner.innerF2();
    }

    /**
    * 静态内部类可以用public,protected,private修饰
    * 静态内部类中可以定义静态或者非静态的成员
    */
    static class Inner {
    static int inner_i = 100;
    int innerJ = 200;

    static void innerF1() {
    // 静态内部类只能访问外部类的静态成员(包括静态变量和静态方法)
    System.out.println("Outer.i" + i);
    outerF1();
    }


    void innerF2() {
    // 静态内部类不能访问外部类的非静态成员(包括非静态变量和非静态方法)
    // System.out.println("Outer.i"+j);
    // outerF2();
    }
    }
    }

接口写嵌套类

  • 正常情况下,你不能在接口内部放置任何代码

  • 但嵌套类可以作为接口的一部分,因为它是static 的。

  • 只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。

  • 声明在接口中的内部类自动成为staticpublic

    • 与常规内部类不同,静态内部类可以有静态域和方法。
    • 此外,静态内部类只能访问外部类的静态成员。
  • 定义接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    interface Outer2 {
    void car();
    //因为接口中的类,方法自动有public&static
    //所以不用声明也是静态内部类,还可以继承本接口
    class staticinner implements Outer2 {
    public void car() {
    System.out.println("This is a car");
    }
    }
    //---静态内部类------------------------
    class A{
    int x = 11;
    public void f() {}
    public static void sf() {}
    }

    //---静态内部接口------------------------
    interface IA{
    int N = 45;
    void af();
    default void df() {}
    static void sf1() {}
    }
    }

    class Test02{
    public static void main(String[] args) {
    Outer2.staticinner staticinner = new Outer2.staticinner();
    staticinner.car();
    }
    }
  • 实现接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    class Outer2Impl implements  Outer2{
    @Override
    public void car() {
    }

    //class A是实现类的内部类,属于实现类的对象
    public void method() {
    // 访问内部类中的变量需要用对象
    System.out.println(new A().x);
    // 访问内部类中的方法需要用对象
    new A().f();
    // 静态方法属于类,可以直接调用
    A.sf();
    }

    //访问接口的内容
    public void method2() {
    // 接口变量:public static final 修饰 ,直接访问
    System.out.println(IA.N);
    IA.sf1();

    // 使用接口对象(匿名内部类)调用默认方法和抽象方法
    IA ia = new IA() {
    @Override
    public void af() {
    // TODO Auto-generated method stub
    }

    };
    ia.df();
    ia.af();
    }
    }
  • 实现接口中的接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class IAImpl implements Outer2.IA {
    @Override
    public void af() {

    }

    @Override
    public void df() {

    }

    public static void main(String[] args) {
    //默认方法用对象调用
    new IAImpl().df();
    new IAImpl().af();
    //静态的用接口名限定
    System.out.println(Outer2.IA.N);
    Outer2.IA.sf1();
    }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30





## 总结

- 成员内部类和局部内部类,在编译以后,都会生成字节码文件
- 成员内部类: 外部类$内部类名.class. 如: Cat$CatBody.class
- 局部内部类: 外部类$数字内部类名

- 访问控制修饰符
- 外部类: public (default)
- 成员内部类:public / protected / (default) / private
- 局部内部类: 什么都不能写

- 成员内部类

- 归该类的对象,内部类对象中都隐藏了一个外部类对象

- 不允许 定义静态内容,static final 除外

- 如果外部类成员变量,内部类成员变量,内部类方法中的变量同名

- 内部类方法中的变量> 内部类成员变量> 外部类成员变量
- 内部类的方法访问同名变量
- num:内部类**方法中**的变量
- this.num:**内部类成员**变量
- Other.this.num:内部类使用**外部类成员变量**
- Other.this.fun():内部类调用**外部类的成员方法**