[Java提高篇]Java内部类的剖析

一、什么是内部类?

可以将一个类的定义放在另一个类的定义内部,这就是内部类
为什么要用内部类?
内部类方法可以访问该类中定义所在的作用域中的数据,包括私有数据
内部类可以对同一个包中的其他类隐藏起来。
当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。
什么时候用到内部类?
当描述事物时,若一个事物内部还包含其他事物,就可以使用内部类这种结构。如,汽车类Car中包含发动机类Engine使用内部类的好处:
内部类可以很好的实现隐藏,一般非内部类,时不允许有private与protected权限的,但内部类可以。
内部类拥有外围类的所有元素的访问权限。
可以实现多重继承。
可以避免修改接口而实现同一个类中两种同名方法的调用。
缺点: 内部类的结构复杂。

二、创建内部类

在Java中内部类主要分为成员内部类、局部内部类、匿名内部类、静态内部类。

///创建非静态内部类对象格式:
外部类名.内部类名 对象名 = new 外部类().new 内部类();
Car.Engine engine = new Car.new Engine();

///创建静态内部类的格式:
外部类名.内部类名 对象名 = new 外部类.内部类;
Car.Engine engine = c.Engine();

///在外部类的方法中创建内部类,就像普通对象一样直接创建
 Engine engine = new Engine();

2.1 成员内部类

成员内部类是最普通的内部类,它是外围类的一个成员,所以它是可以无限制的访问外围类的所有属性和方法,即使是private。但是外围类要访问内部类的成员属性和方法需要通过内部类实例来访问。

注意:
在成员内部类中,不能存在任何static的变量和方法;
成员内部类是依附于外部类的,所以只有先创建外围类才能构创建内部类。

class Car{
      private boolean run = true;
      class Engine{
          public void runEmgine(){
              if(run) System.out.println("发动机启动!");
              else System.out.println("发动机坏了!");
          }
      }
      ///当内部类的构造方法无参数时,推荐使用getXxx()来获取成员内部类
      public Engine getEngine(){
          return new Engine();
      } 
}
public class Main {
    public static void main(String [] args){
       Car c = new Car();
      Car.Engine engine = c.new Engine();
     /// Car.Engine engine = c.getEngine();
       engine.runEmgine();
    }
}

2.2 局部内部类(方法内部类)

局部内部类,是嵌套在方法内,且只能作用于方法内的内部类。局部内部类不能使用public、protected、private和static修饰符(由于局部内部类不能在外围类的方法以外的地方使用

class Car{
      private boolean run = true;
      public void Method() {
          class Engine {
              public void runEmgine() {
                  if (run) System.out.println("发动机启动!");
                  else System.out.println("发动机坏了!");
              }
          }
          new Engine().runEmgine();
      }
}
public class Main {
    public static void main(String [] args){
       Car c = new Car();
       c.Method();
    }
}

2.3 静态内部类

使用static修饰的内部类为静态内部类,静态内部类与非静态内部类存在一个最大的区别,内部类编译后会隐含地保存着一个引用,该引用是指向创建它的外围类内,但是静态内部类没有。

静态内部类的创建是不需要依赖外围类的。
静态内部类不能使用外围类的非static成员变量和方法。

class Car{
      public static String name = "长安汽车" ;
          static class Engine {
              public void EngineMethod() {
                  System.out.println("这汽车发动机为" + name + "制造的");
              }
          }
          public  void CarMethod(){
              new Engine().EngineMethod();
          }
}
public class Main {
    public static void main(String [] args){
       Car c = new Car();
       c.CarMethod();
    }
}

2.4 匿名内部类

匿名内部类是内部类的简化写法。
它的本质是一个带具体实现的父类或者父接口的匿名的子类对象。在开发中,最常用的内部类就是匿名内部类了。
匿名内部类的作用:能简化接口的创建以及实现的步骤。
在这里插入图片描述

匿名内部类的前提:必须继承一个父类或者实现一个父接口。

实现方式一:

interface function{
    public abstract void run();
}

public class Main {
    public static void main(String [] args) {
        ///等号右边:定义并创建该接口的子类对象
        ///等号左边:多态,接口类型引用类型指向子类对象
        function f = new function() {
            @Override
            public void run() {
                System.out.println("发动机启动!");
            }
        };
        f.run();
    }

      public static void showRun(function f){
            f.run();
    }
}

实现方式二:

interface function{
    public abstract void run();
}

public class Main {
    public static void main(String [] args) {
    ///创建匿名内部类,直接传递给showRun(function f)
        showRun(new function() {
            @Override
            public void run() {
                System.out.println("发动机启动了!!!");
            }
        });
    }

      public static void showRun(function f){
            f.run();
    }
}

使用匿名内部类,我们要注意:
匿名内部类是没有访问修饰符的
当所在的方法的形参需要被匿名的内部类使用,那么这个形参就必须为final。
匿名内部类没有构造方法,因为本身没有名字。不能定义静态成员。

三、内部类与外围类之间的关系

内部类与外围类是两个独立完整的类,在运行是生成各自的.class文件。
内部类相当于外围类的一个成员变量的位置,可以使用任意访问控制符。
内部类可以随意访问外围类的成员方法与成员变量(不受访问控制符的影响),但内部类不可以访问内部类的成员变量与成员方法。

1. 编译器自动为内部类添加一个成员变量,这个成员变量的类型和外围类的类型相同,这个成员变量就是指向外围类对象(this)的引用。
2. 编译器自动为内部类的构造方法添加一个参数,参数的类型是外围类的类型,在构造方法内部使用这个参数为内部类中添加的成员变量赋值。
3. 在调用内部类的构造函数初始化内部类对象时,会默认传入外围类的引用

要调用内部类的方法只能通过实例内部类对象来调用。

通过.this来在内部类中引用其外围类
一个外围类中的内部类对象,是与这个外围类的对象存在着一个链接的依存关系的,那么我们是不是可以在内部类中显式地调起这个外围类地对象呢?答案是肯定的。如果我们在内部类中生成对于外围类对象的引用,可以使用外围类的名字后面紧跟.this,这样产生的引用自动对地具有正确的类型,这一点在编译期就会被知晓并受到检查,因此没有任何运行时的开销。

public class Car {
    void getName(){
      System.out.println("这是长安汽车!");
    }
    class Engine {
        public Car runEmgine() {
            return Car.this;///引用外围类
        }
    }
    public Engine Engine(){
        return new Engine();
    }
    public static void main(String [] args){
        Car car = new Car();
        Car.Engine engine = car.Engine();
        engine.runEmgine().getName();
    }
}

参考资料

1.https://www.cnblogs.com/chenssy/p/3388487.html
2.https://blog.csdn.net/qq_43646059/article/details/104441947?utm_source=app

发布了40 篇原创文章 · 获赞 6 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Honeycomb_1/article/details/104754024