• Java 面向对象(九)内部类


    一、概述

      1、引入

        类的成员包括:

    1、属性:成员变量
    2、方法:成员方法
    3、构造器
    4、代码块
    5、内部类:成员内部类

        其中 1、2是代表这类事物的特征

         其中3、4是初始化类和对象用的

         其中5协助完成2的功能的实现,表现

      2、定义

        内部类:将一个类 A 定义在另一个类 B 里面,里面的那个类 A 就称为 内部类,类 B 则称为 外部类

      3、分类

        根据内部类的所在的位置不同:

        (1)成员内部类:外部类中方法外;

        (2)局部内部类:方法体内(也可以在代码块内)

    二、成员内部类

      1、概述及分类

        成员内部类:定义在 类中方法外 的类。

        (1)有 static 修饰的:静态成员内部类,简称为 静态内部类。

        (2)没有static修饰的,非静态成员内部类,简称 成员内部类。

         扩展什么情况下会用到成员内部类(方法外声明的)?

    当描述一个事物时,发现它的内部还有一个完整的结构需要用一个类来描述;
    并且发现这内部的结构,如果独立存在是没有意义的,必须在这个外部类中才有意义。而且这个内部结构只为这个外部类服务。

    例如:Body身体,发现它内部还有完整的结果,如:心脏,发现心脏单独创建对象没有意义,只有在 Body 对象中才有意义,而且只为 Body 对象服务。

      2、静态成员内部类(静态内部类)

        (1)语法格式:

    【修饰符】 class 外部类  【 extends 父类】 【implements 父接口们】{
      【其他修饰符】 static class 内部类  【 extends 父类】 【implements 父接口们】{
       }
     }
    

                   注意

            ① 只有成员内部类才能用 static 修饰,其他的外部类,局部内部类等够不可以用 static 修饰。

            ② 外部类,内部类的父类,父接口都没有关系,各是各自的。

        (2)特点

            静态内部类中,可以出现原本类中能够定义的所有的成员

              属性:可以有静态属性和非静态属性;

              方法:可以有静态方法和非静态方法,如果静态内部类是抽象类,还可定义抽象方法;

              构造方法:有参方法、无参方法;

              代码块:可以有静态代码块和非静态代码块;

              内部类:允许,但很少再写内部类(臃肿)

            静态内部类中不能使用外部类的非静态的成员;

           ③ 在外部类中,使用静态内部类,和使用其他的类一样的原则;

               如果使用静态内部类的静态成员,直接“静态内部类名.”

               如果使用静态内部类的非静态成员,直接“静态内部类对象名.”

           ④ 在外部类的外面,使用静态内部类

               如果使用静态内部类的静态成员,直接“类名”

                 使用 外部类名.静态内部类名.静态方法;

                 使用 import  包.外部类名.静态内部类名;然后在代码中使用 “静态内部类名

           ⑤ 静态内部类不会随着外部类的初始化一起初始化,而是要在使用到这个静态内部类才会初始化;

        (3)总结

            ① 同级的来说静态的不能直接使用非静态的;

            ② 访问一个类的静态成员,用 “类名.” 即可;访问一个类的非静态成员,用 “对象名.” 即可。

            ③ 当使用到这个类时,这个类才会进行初始化;

          Demo:

     1 package com.java.test;
     2 //import com.java.test.Outer.Inner;
     3 
     4 public class TestStaticInner {
     5     public static void main(String[] args) {
     6        // 访问内部类的静态方法
     7                // Inner.test();//上面有导包语句,import 包.外部类名.静态内部类名;
     8         
     9         Outer.Inner.test();//外部类名.静态内部类名.静态方法(不使用导包语句)
    10         
    11        // 访问内部类的非静态方法
    12                 //  Inner in = new Inner();//上面有导包语句,import 包.外部类名.静态内部类名;
    13         Outer.Inner in = new Outer.Inner();
    14         in.method();
    15         
    16         Outer out = new Outer();  // 内部类不会初始化
    17         out.outMethod();              // 该方法中用到内部类,内部类会进行初始化
    18     }
    19 }
    20 class Outer{
    21     private int i = 1;
    22     private static int j = 2;
    23     
    24     static{
    25         System.out.println("外部类的静态代码块");
    26     }
    27     
    28     static class Inner{
    29         static{
    30             System.out.println("静态内部类的代码块");
    31         }
    32         
    33         public void method(){
    34             System.out.println("静态内部类的非静态方法");
    35             //System.out.println(i);//错误,不能访问非静态的
    36             System.out.println(j);
    37         }
    38         
    39         public static void test(){
    40             System.out.println("静态内部类的静态方法");
    41         }
    42     }
    43     // 外部类访问内部类
    44     public void outMethod(){
    45         Inner in = new Inner();
    46         in.method();//非静态方法,用对象名.访问
    47         
    48         Inner.test();//静态方法,用类名.访问
    49     }
    50 }

      3、非静态成员内部类

        (1)语法格式:

    【修饰符】 class 外部类 【 extends 父类】 【implements 父接口们】{
      【其他修饰符】  class 内部类  【 extends 父类】 【implements 父接口们】{
       }
     }

        (2)特点

           ① 在非静态内部类中,不能出现任何和 static 有关的声明;

           ② 在非静态内部类中,可以随意访问外部类的所有的成员,包括静态的和非静态的;

           ③ 在外部类的静态成员中,不能使用非静态的成员内部类

           ④ 在外部类的外面使用

              第一步:先创建外部类的对象

              第二步:要么通过外部类的对象,去创建内部类的对象

    Outer out = new Outer();
    Outer.Inner in = out.new Inner();

                  要么通过外部类的对象,去获取内部类的对象

    Outer out = new Outer();
    Outer.Inner in  = out.getInner();
    

      

         Demo:

     1 public class TestNonStaticInner {
     2     public static void main(String[] args) {
     3         //Outer.Inner in = new Outer.Inner();//错误的
     4         
     5         //在这里使用Inner,因为此时的Inner是Outer的非静态成员,所以需要用到Outer的对象
     6         Outer out = new Outer();
     7         //Outer.Inner in = out.new Inner();
     8         Outer.Inner in  = out.getInner();
     9         in.method();
    10     }
    11 }
    12 class Outer{
    13     private int i = 1;
    14     private static int j = 2;
    15     
    16     class Inner{
    17         public void method(){
    18             System.out.println("非静态内部类的非静态方法");
    19             System.out.println(i);
    20             System.out.println(j);
    21         }
    22     }
    23     
    24     public static void outTest(){
    25         //Inner in = new Inner();//静态的方法不能访问非静态的成员
    26     }
    27     public void outMethod(){
    28         Inner in = new Inner();
    29         in.method();
    30     }
    31     
    32     public Inner getInner(){
    33         return new Inner();
    34     }
    35 }

      案例:创建一个外部类,里面有一个抽象内部类,并有一个抽象方法,要求写一个类去继承该内部类。

      代码

     1 public class Test {
     2     public static void main(String[] args) {
     3         MySub my = new MySub(new Outer());
     4         my.test();
     5     }
     6 }
     7 class Outer{
     8     abstract class Inner{
     9         public abstract void test();
    10     }
    11 }
    12 class MySub extends Outer.Inner{
    13     MySub(Outer out){
    14         out.super();//需要外部类的对象,才能调用非静态内部类的构造器
    15     }
    16 
    17     @Override
    18     public void test() {
    19         System.out.println("hello");
    20     }
    21 }

        说明:

        ① 使用非静态内部类名时,可以使用:import 包.外部类名.内部类名; 或 外部类名.内部类名

        ② 要调用非静态内部类的构造器,需要用到外部类的对象

        ③ 因为子类的构造器的首行一定要调用父类的构造器,默认调用父类的无参构造(需要借助外部类对象)

        ④ 继承抽象类,要重写抽象的抽象方法

      注意:内部类仍然是一个独立的类,在编译之后会内部类会被编译成独立的 .class 文件,但是前面冠以外部类的类名和 $ 符号。如:Person$Heart.class

    三、局部内部类

      1、概述

        局部内部类:定义在 类中方法内 的类,就是局部内部类。即只有当前所属的方法才能使用它,出了这个方法外面就不能使用。

        (1)有名字的局部内部类:简称 局部内部类;

        (2)没名字的局部内部类:简称 匿名内部类。

      2、定义格式

        局部内部类定义格式

    【修饰符】 class 外部类名称 {
        【修饰符】 返回值类型 外部类方法名称(参数列表) {
            【修饰符】 class 有名字的局部内部类名称 {
                // ...
            }
        }
    }

              说明:如果子类调用的是父类的无参构造,那么()中实参列表不用写;如果子类调用的是父类的有参构造,那么就在()中传入实参列表

         定义一个类的时候,权限修饰符规则:

        (1)外部类: public / (default)

        (2)成员内部类:public / protected / (default) / private 

        (3)局部内部类:不加任何修饰符

      3、特点

        (1)局部内部类的修饰符,只能有 abstract 或 final

        (2)该类有作用域

        (3)如果局部内部类在静态方法中,不能使用外部类的非静态成员

        (4)在局部内部类中,可以使用当前局部内部类所在方法的局部变量,但是要求,这个局部变量必须是 final的常量

          在Java8时,如果某个局部变量被局部内部类使用了,会自动添加final变为常量,一旦变为常量,它的值就不能修改了。

          为什么它要这么要求?加final

          避免局部内部类对象被返回到外部类的外面使用时,访问不到这个局部变量,所以要把这个局部变量变为final的常量。(常量在方法区中

           原因:

            ① new出来的对象在堆内存当中。

            ② 局部变量是跟着方法走的,在栈内存当中。

            ③ 方法运行结束之后,立刻出栈,局部变量就会立刻消失。

            ④ 但是new出来的对象会在堆当中持续存在,直到垃圾回收消失。

        Demo:

     1 public class TestLocalInner {
     2     public static void main(String[] args) {
     3         Outer out = new Outer();
     4         Father in = out.test();//在外部类的外面虽然不能使用局部内部类,但是可以得到它的对象
     5         System.out.println(in.getClass());
     6         in.method();//在这里仍然可以访问到这个a,那么这个a就不能存在栈中,得挪到方法区,变为常量
     7     }
     8 }
     9 abstract class Father{
    10     public abstract void method();
    11 }
    12 class Outer{
    13     private int i = 1;//成员变量,实例变量,非静态成员变量
    14     private static int j = 2;//成员变量,类变量,静态变量
    15     
    16     public Father test(){
    17         //Inner in = new Inner();   现在还没有声明该类,不能使用
    18         
    19         final int a = 10;//局部变量==>局部的常量
    20         
    21         //局部内部类
    22         class Inner extends Father{
    23             public void method(){
    24                 System.out.println(i);
    25                 System.out.println(j);
    26                 System.out.println(a);
    27             }
    28         }
    29         
    30         Inner in = new Inner();
    31         in.method();
    32         
    33         return in;
    34     }
    35     
    36     public void method(){
    37         //Inner in = new Inner();  超过了作用域
    38     }
    39     
    40     public static void fun(){
    41         //局部内部类
    42         class Inner{
    43             public void method(){
    44                 //System.out.println(i);//是因为fun方法是静态的
    45                 System.out.println(j);
    46             }
    47         }
    48     }
    49 }

    四、匿名内部类【重要】

      1、概述

        匿名内部类:是内部类的简化写法。它的本质是一个 带具体实现的  父类或者父接口的 匿名的 子类对象

        开发中,最常用到的内部类就是匿名内部类了,以接口为例,当你使用一个接口时,都得做如下几步操作:

        (1)定义子类

        (2)重写接口中的方法

        (3)创建子类对象

        (4)调用重写后的方法

        而匿名类就可以把以上四步合成一步。

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

      2、格式

        定义格式

    new 父类名或者接口名(){
        // 方法重写
        @Override
        public void method() {
            // 执行语句
        }
    };

         特殊:声明匿名内部类与创建它的对象是一起完成的,即匿名内部类只有唯一的对象。

      3、使用方式

        以接口为例,匿名内部类的使用。

       定义接口:

    1 public abstract class FlyAble{
    2     public abstract void fly();
    3 }

              创建匿名内部类,并调用:

     1 public class InnerDemo {
     2     public static void main(String[] args) {
     3         /*
     4         1.等号右边:是匿名内部类,定义并创建该接口的子类对象
     5         2.等号左边:是多态赋值,接口类型引用指向子类对象
     6        */
     7         FlyAble f = new FlyAble(){
     8           public void fly() {
     9               System.out.println("我飞了~~~");
    10           }
    11         };
    12         //调用 fly方法,执行重写后的方法
    13         f.fly();
    14     }
    15 }

      通常在方法的形式参数是接口或者抽象类时,也可以将匿名内部类作为参数传递。如下所示

     1 public class InnerDemo2 {
     2     public static void main(String[] args) {
     3         /*
     4         1.等号右边:定义并创建该接口的子类对象
     5         2.等号左边:是多态,接口类型引用指向子类对象
     6         */
     7         FlyAble f = new FlyAble(){
     8           public void fly() {
     9               System.out.println("我飞了~~~");
    10           }
    11     };
    12     // 将f传递给showFly方法中
    13         showFly(f);
    14     } 
    15     public static void showFly(FlyAble f) {
    16         f.fly();
    17     }
    18 }

      将以上两步,也可以简化为一步:

     1 public class InnerDemo3 {
     2     public static void main(String[] args) {
     3     /*
     4         创建匿名内部类,直接传递给showFly(FlyAble f)
     5     */
     6     showFly( new FlyAble(){
     7          public void fly() {
     8             System.out.println("我飞了~~~");
     9         }
    10     });
    11 } 
    12     public static void showFly(FlyAble f) {
    13         f.fly();
    14     }
    15 }
  • 相关阅读:
    文件下载的几种方式
    获取文件的后缀名(转为数组) 字符串和变量的拼接 HTML中字符串和变量的拼接
    小程序之选择拍照或者本地相册
    实时显示时间
    uni-app事件冒泡 如何解决事件冒泡 推荐tap事件
    Codeforces Global Round 7 C. Permutation Partitions(组合数学)
    Codeforces Global Round 7 B. Maximums(逻辑)
    Codeforces Global Round 7 A. Bad Ugly Numbers(数学)
    Codeforces Round #622 (Div. 2) C2. Skyscrapers (hard version)(单调栈,递推)
    Codeforces Round #622 (Div. 2) B. Different Rules(数学)
  • 原文地址:https://www.cnblogs.com/niujifei/p/11372479.html
Copyright © 2020-2023  润新知