15. 内部类

发布时间:2024年01月15日

内部类

内部类是指定义在其他类内部的类。

它是Java中一种特殊的类,可以访问所在外部类的成员变量和方法,包括私有的成员。

内部类可以分为四种类型:成员内部类局部内部类匿名内部类静态内部类

1. 成员内部类

成员内部类是定义在其他类中的类。

1.1 特点

成员内部类具有以下特点:

  1. 访问外部类的成员:成员内部类可以直接访问外部类的成员变量和方法,包括私有成员。

    这是因为成员内部类持有一个对外部类对象的引用,可以通过该引用访问外部类的成员。

  2. 与外部类实例相关:创建成员内部类的实例时,需要先创建外部类的实例,然后通过外部类实例来创建内部类的实例。

    每个内部类实例都与外部类的实例相关联,即每个内部类实例都有一个隐式的引用指向创建它的外部类实例。

  3. 可访问权限限制:成员内部类可以使用外部类的私有成员,包括私有变量和私有方法。

    这为封装和数据隐藏提供了更多的灵活性。

  4. 名称空间:成员内部类的命名空间限定于所在的外部类。

    即在外部类中可以直接使用内部类的简单名称来引用它,而外部类之间无法直接引用彼此的内部类。

  5. 非静态上下文:成员内部类是非静态的,它依赖于外部类的实例。

    因此,成员内部类可以访问和操作外部类的成员,但不能声明静态成员(包括方法、变量和嵌套类内部的静态接口)。

1.2 定义格式

成员内部类的使用格式如下:

public class OuterClass {
    private int outerVar;

    public class InnerClass {
        private int innerVar;

        public void method() {
            outerVar = 10; // 访问外部类的成员变量
        }
    }
}

其中,OuterClass是外部类,InnerClass是成员内部类。成员内部类的定义在外部类的内部,并且在类的成员位置,可以定义自己的成员变量和方法。

1.3 获取对象的方式

1.3.1 通过外部类的实例创建内部类对象

通过外部类的实例创建内部类对象:

OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = outer.new InnerClass();

例如:

public class OuterClass {
    private int outerVar;

    public class InnerClass {
        private int innerVar;

        public InnerClass(int innerVar) {
            this.innerVar = innerVar;
        }

        public void method() {
            System.out.println("InnerClass outerVar: " + outerVar);
            System.out.println("InnerClass innerVar: " + innerVar);
        }
    }

    public static void main(String[] args) {
        OuterClass outer = new OuterClass(); // 创建外部类实例
        OuterClass.InnerClass inner = outer.new InnerClass(20); // 通过外部类实例创建内部类对象
        inner.method(); // 调用内部类对象的方法
    }
}

在上述代码中,我们首先创建了外部类 OuterClass 的实例 outer,然后调用 outernew 方法来创建内部类 InnerClass 的对象 inner,同时通过构造函数给 inner 的成员变量 innerVar 赋值为 20

最后,我们调用 innermethod() 方法,输出以下结果:

InnerClass outerVar: 0
InnerClass innerVar: 20

这表明我们成功创建了成员内部类对象,并可以访问外部类的成员变量 outerVar 和内部类的成员变量 innerVar

1.3.2 在外部类的方法内直接创建内部类对象

在外部类的方法内直接创建内部类对象:

public class OuterClass {
    // ...

    public void createInnerObject() {
        InnerClass inner = new InnerClass();
    }

    // ...

    public class InnerClass {
        // ...
    }
}

在外部类的方法内部,可以直接创建内部类的实例,无需先创建外部类的实例。在上述代码中,我们在外部类的 createInnerObject() 方法内创建了内部类的对象 inner

1.3.3 代码示例

下面是一个完整的例子,演示了这两种方式:

public class OuterClass {
    private int outerVar;

    public class InnerClass {
        private int innerVar;

        public InnerClass(int innerVar) {
            this.innerVar = innerVar;
        }

        public void method() {
            System.out.println("InnerClass outerVar: " + outerVar);
            System.out.println("InnerClass innerVar: " + innerVar);
        }
    }

    public void createInnerObject() {
        InnerClass inner = new InnerClass(20); // 直接在外部类方法内创建内部类对象
        inner.method();
    }

    public static void main(String[] args) {
        OuterClass outer = new OuterClass(); // 创建外部类实例
        OuterClass.InnerClass inner = outer.new InnerClass(); // 通过外部类实例创建内部类对象
        inner.method();

        outer.createInnerObject();
    }
}

在上例中,我们先通过外部类的实例 outer 创建了内部类的实例 inner,然后调用了 innermethod() 方法。同时,我们还在外部类的 createInnerObject() 方法内直接创建了内部类的实例,并调用了 method() 方法。

运行上例的输出如下:

InnerClass outerVar: 0
InnerClass innerVar: 0
InnerClass outerVar: 0
InnerClass innerVar: 20

1.4 注意事项

在使用成员内部类时,有几个注意事项需要注意:

  1. 创建成员内部类的对象需要先创建外部类的对象,然后使用外部类对象调用 new 关键字来实例化内部类对象,例如:OuterClass.InnerClass inner = outer.new InnerClass();

  2. 成员内部类可以访问外部类的所有成员,包括私有成员,即使是静态的私有成员也可以直接访问。

  3. 成员内部类拥有对外部类对象的引用,可以直接访问和修改外部类的成员变量。

  4. 在内部类中可以定义与外部类相同名称的成员变量和方法。在内部类方法中,使用 this 关键字****可以引用内部类对象自身,使用 OuterClass.this 可以引用外部类对象。

  5. 成员内部类可以被外部类以及其他类继承和实现,具有灵活性。

  6. 成员内部类可以是私有的,这样它只能在外部类中访问,对其他类不可见。

  7. 如果成员内部类是静态的,那么它可以直接通过外部类名字引用,例如:OuterClass.InnerClass inner = new OuterClass.InnerClass();,不需要先创建外部类对象。

2. 静态内部类

静态内部类是声明在外部类内部的内部类,并且使用 static 修饰符进行标记。

2.1 特点

它具有以下特点:

  1. 静态内部类可以单独使用,不需要依赖外部类的实例。

    它的创建方式是通过 外部类名.内部类名 来实例化,例如:OuterClass.InnerClass inner = new OuterClass.InnerClass();

  2. 静态内部类可以访问外部类的静态成员变量和方法,但不能直接访问外部类的非静态成员变量和方法。

    如果需要访问外部类的非静态成员,可以通过创建外部类的对象来间接访问。

  3. 静态内部类的对象不持有对外部类对象的引用,因此它的创建不会导致外部类对象的创建。

  4. 在静态内部类中,可以定义静态成员变量和方法,这些成员变量和方法可以直接通过内部类名引用

    例如:OuterClass.InnerClass.staticVar

  5. 静态内部类可以被其他类继承和实现。

  6. 静态内部类的作用域仅限于外部类,它对其他类是不可见的。

2.2 定义格式

静态内部类的定义格式如下:

public class OuterClass {
    // 外部类成员和方法

    public static class StaticInnerClass {
        // 静态内部类成员和方法
    }
}

在这个定义中,StaticInnerClass 是一个静态内部类,嵌套在 OuterClass 外部类中。注意到在 StaticInnerClass 的定义中,使用了 static 属性符,使得内部类可以直接访问外部类的静态属性和方法。

2.3 获取对象的方式

2.3.1 使用外部类的类名直接访问内部类

使用外部类的类名直接访问内部类:

>可以使用`外部类.内部类`的方式来获得静态内部类的对象。

例如,如果外部类的类名为`OuterClass`,内部类为`StaticInnerClass`,可以使用`OuterClass.StaticInnerClass`来获取对象。
OuterClass.StaticInnerClass inner = new OuterClass.StaticInnerClass();
2.3.2 在外部类中定义方法返回内部类对象

在外部类中定义一个方法来返回内部类对象:

>可以在外部类中定义一个方法,通过调用这个方法来获取静态内部类的对象。
public class OuterClass {
    // 外部类成员和方法

    public static class StaticInnerClass {
        // ...
    }

    public static StaticInnerClass getInstance() {
        return new StaticInnerClass();
    }
}

// 在其他类中获取内部类对象
OuterClass.StaticInnerClass inner = OuterClass.getInstance();

这种方式通过定义一个静态方法,在方法中实例化内部类对象并返回它,以供外部类和其他类使用。

注意

  • 内部类的使用格式

    外部类.内部类。
    
  • 静态内部类对象的创建格式

    外部类.内部类  变量 = new  外部类.内部类构造器;
    
  • 调用方法的格式

    • 调用非静态方法的格式:先创建对象,用对象调用
    • 调用静态方法的格式:外部类名.内部类名.方法名();

2.4 注意事项

在使用静态内部类时,需要注意以下事项:

  1. 静态内部类无法访问外部类的非静态成员变量和非静态方法。如果需要访问非静态成员变量和非静态方法,需要先创建一个外部类实例,然后通过实例访问。

  2. 如果一个外部类与静态内部类拥有相同的方法名,可以通过添加外部类的类名或静态内部类的类名来调用不同的方法。

     例如,假设外部类和静态内部类都有一个方法叫做`method()`,可以使用`OuterClass.method()`或者`StaticInnerClass.method()`来调用不同的方法。
    
  3. 静态内部类可以有静态成员变量和静态方法,它们与外部类和其它内部类的静态成员变量和静态方法一样。

  4. 可以通过 外部类.静态内部类new 外部类.静态内部类() 来创建静态内部类的对象。静态内部类的实例化不需要先实例化外部类的对象。

  5. 静态内部类可以被其他类继承和实现,并且它的作用域限定在外部类中,不对外部的类造成影响。

3. 局部内部类

局部内部类是一个定义在方法内部的类。

3.1 特点

局部内部类的特点如下:

  1. 作用域仅限于所在的方法内部

    局部内部类的作用域被限制在定义它的方法内部,无法在方法外部或其他方法中访问它。

  2. 可以访问所在方法的局部变量

    局部内部类可以访问所在方法中的局部变量,但是该局部变量必须为 final 或者是事实上的 final(即不再改变),这是因为局部内部类的实例可以在方法的执行结束后仍然存在,这时局部变量可能已经消失。

  3. 可以访问所在方法的参数

    局部内部类可以访问所在方法的参数,同样也必须是 final 或者是事实上的 final。

  4. 可以访问外部类的成员变量和方法

    与静态内部类不同,局部内部类可以访问外部类的成员变量和方法,包括私有的成员。

  5. 无法包含静态成员或方法

    由于局部内部类的实例存在于方法执行的生命周期中,所以不能包含静态成员和方法。

  6. 可以实现接口或者继承抽象类

    局部内部类可以实现接口或者继承抽象类,从而具备相应的行为及特性。

3.2 定义格式

局部内部类的定义格式如下:

public class OuterClass {
    // 外部类的成员和方法
    
    public void method() {
        // 方法内部定义局部内部类
        class LocalInnerClass {
            // 局部内部类的成员和方法
        }

        // 在方法内可以使用局部内部类
        LocalInnerClass inner = new LocalInnerClass();
    }
}

在上述例子中,LocalInnerClass是一个定义在方法内部的局部内部类。它被定义在method方法内部,并且只能在该方法内部被访问和使用。

3.3 获取对象的方式

3.3.1 将局部内部类作为方法的返回值

在方法中创建局部内部类的实例,并返回该实例,外部类通过该方法获得局部内部类的对象。

例如:

public class OuterClass {
    public InnerClass getInnerInstance() {
        // 方法内部定义局部内部类
        class InnerClass {
            // 局部内部类的成员和方法
        }

        // 在方法内部创建局部内部类的实例并返回
        return new InnerClass();
    }
}

通过在外部类的方法getInnerInstance()中创建局部内部类InnerClass的实例,并将该实例返回给外部类的调用者,从而使外部类获得了局部内部类的对象。

需要注意的是,由于局部内部类的作用域仅限于所在方法内部,因此只能在该方法内部进行创建及访问。

3.3.2 在方法参数中传递局部内部类的实例

在方法内部将局部内部类的实例作为参数传递给其他方法。这样,在其他方法中就可以直接使用局部内部类的实例了。

public class OuterClass {
    public void method(InnerClass inner) {
        // 使用传递的局部内部类的实例
        inner.hello();
    }

    public void invokeInner() {
        // 方法内部定义局部内部类
        class InnerClass {
            public void hello() {
                System.out.println("Hello, World!");
            }
        }

        // 在方法内部创建局部内部类的实例并传递
        InnerClass inner = new InnerClass();
        method(inner);
    }
}

在上述例子中,通过在invokeInner方法中定义局部内部类InnerClass,然后在该方法内部创建该类的实例inner并将其传递给method方法。method方法中就可以直接使用局部内部类InnerClass的实例,并调用其方法。

3.4 注意事项

在使用局部内部类时,有一些注意事项需要注意:

  1. 作用域限制:局部内部类的作用域仅限于定义它的方法或代码块内部。

     在定义它的方法或代码块外部是无法直接使用局部内部类的,包括在方法外部创建它的实例。
    
  2. 可访问的变量限制:局部内部类只能访问被声明为 final 或实质上为 final 的局部变量。

     这是因为局部内部类的生命周期可能超过其外部方法的生命周期,为了保证引用的正确性,访问的变量必须是被 final 修饰,或者保证它们不会被修改。
    
  3. 内部类可访问外部类成员:方法内部的局部内部类可以直接访问外部类的成员(包括实例变量和方法),无需额外的限定符或引用,就像访问自己的成员一样。

  4. 生命周期和内存管理:局部内部类的生命周期与方法的调用周期相关,在方法调用结束后,局部内部类的对象会随着方法的结束被销毁。

     当不再需要局部内部类的实例时,要确保适时释放它的引用,以便垃圾回收器能够回收对应的内存空间。
    
  5. 嵌套层级限制:虽然可以在方法内部定义局部内部类,但不允许在局部内部类中再定义内部类。

     也就是说,局部内部类不能再次嵌套声明其他内部类。
    

4. 匿名内部类(重点)

匿名内部类是一种在声明的同时实例化的内部类,没有显式的名称。

4.1 特点

匿名内部类具有以下特点:

  1. 没有显式的类名

    匿名内部类没有类名,它是在声明的同时进行实例化,通过实例化的对象使用。

  2. 以接口或抽象类为基础

    匿名内部类通常是基于接口或抽象类来创建的。它可以实现接口的方法或继承抽象类并覆盖其中的抽象方法。

  3. 声明和实例化同时完成

    匿名内部类的声明和实例化是一步完成的,通常在需要使用该内部类的地方直接创建,并且可以在创建时指定其特定的行为。

  4. 作用域限制

    匿名内部类的作用域与它所在的方法或代码块相同,只能在其声明的范围内使用。超出此范围,匿名内部类将不再可见。

  5. 可以访问外部类的成员

    匿名内部类可以访问外部类的方法、字段和实例变量,就像普通的内部类一样。

  6. 可以访问 final 或实质上是 final 的局部变量

    匿名内部类可以访问所在方法的 final 或实质上是 final 的局部变量。这是因为匿名内部类生命周期可能超过方法的生命周期,需要保证访问的变量不会改变。

  7. 适用于单次使用

    匿名内部类通常用于只需要一次使用的场景。由于没有类名,无法再次引用该类,所以适合于只需要创建临时对象的场景。

4.2 定义格式

4.2.1 使用前提

匿名内部类适用于以下情况:

  1. 必须继承一个父类或实现一个接口

     由于匿名内部类本身没有名称,所以必须以某种方式继承一个父类或实现一个接口。
    
  2. 只使用一次

     匿名内部类通常只使用一次,因为它没有名称,因此没有办法再次使用相同类的实例。
    
  3. 不需要访问外部类的非 final 变量

     如果需要访问非 final 变量,那么这个变量必须被拷贝为 final 或实质上是 final 的变量,否则变量在匿名内部类被创建之后被修改后,将会导致内部类得到的变量与期望值不一致。
    
  4. 简单逻辑代码

     匿名内部类通常适用于相对简单的逻辑代码。对于较复杂或结构复杂的代码,最好明确地定义一个独立的内部类。
    
4.2.2 使用格式

匿名内部类的基本格式如下:

父类或接口名称 对象名 = new 父类或接口名称(){
    //匿名内部类的类体部分
};

其中,父类或接口名称指的是匿名内部类继承的父类或实现的接口的类名,对象名指的是匿名内部类的对象名,可以随意命名。

注意】在匿名内部类中,用到父类或接口中的方法、变量时,使用 superinterface 关键字进行调用。

匿名内部类的类体部分是匿名内部类的主体,在其中完成具体的实现。它可以包含匿名内部类所需的变量、方法、构造函数等。在匿名内部类中,可以使用父类或接口中已经定义的方法或变量,也可以重写已经定义的方法,完成特定的逻辑操作。

4.2.3 代码示例

下面是一个基于抽象类实现一个匿名内部类的示例:

abstract class Shape {
    public abstract void draw(String color);
}

public class Main {
    public static void main(String[] args) {
        Shape circle = new Shape() {
            @Override
            public void draw(String color) {
                System.out.println("Draw a circle with "+color+" color.");
            }
        };

        circle.draw("red");
    }
}

在这个例子中,定义了一个抽象类 Shape,其中有一个抽象方法 draw。然后利用匿名内部类通过继承抽象类 Shape,实现了一个新的 Shape 子类,并实现了 draw 方法。在 main 方法中,创建了一个基于匿名内部类的新对象 circle,并通过调用 draw 方法完成其具体的业务逻辑。

4.3 获取对象的方式

要获取匿名内部类的对象,可以通过两种方式:

  1. 将匿名内部类赋值给一个变量:通过将匿名内部类实例赋值给一个变量,就可以得到该匿名内部类的对象,以便后续使用。

  2. 作为方法的返回值或参数:可以将匿名内部类作为方法的返回值或参数,从而获取匿名内部类的对象,并在其他地方使用。

4.3.1 将匿名内部类赋值给变量

将匿名内部类赋值给一个变量:

abstract class Shape {
    public abstract void draw();
}

public class Main {
    public static void main(String[] args) {
        Shape circle = new Shape() {
            @Override
            public void draw() {
                System.out.println("Drawing a circle");
            }
        };

        circle.draw(); // 调用匿名内部类的方法
    }
}

在这个例子中,通过匿名内部类的方式创建了一个继承自抽象类 Shape 的匿名内部类,并实现了 draw 方法。然后将这个匿名内部类的实例赋值给了一个 Shape 类型的变量 circle,从而获取了匿名内部类的对象。通过调用 circle.draw() 方法,可以执行匿名内部类中具体的逻辑。

4.3.2 作为方法的返回值或参数

作为方法的返回值或参数:

interface Message {
    void display();
}

public class Main {
    public static void main(String[] args) {
        Message message = createMessage();

        message.display(); // 调用匿名内部类的方法
    }

    public static Message createMessage() {
        return new Message() {
            @Override
            public void display() {
                System.out.println("Hello, World!");
            }
        };
    }
}

在这个例子中,定义了一个接口 Message,其中有一个方法 display。然后通过 createMessage 方法创建匿名内部类的对象,并将其作为 Message 类型的返回值。在 main 方法中,调用 createMessage 方法,将返回的匿名内部类对象赋值给一个 Message 类型的变量 message,从而获取了匿名内部类的对象。通过调用 message.display() 方法,可以执行匿名内部类中具体的逻辑。

4.4 注意事项

  1. 匿名内部类必须继承一个父类或实现一个接口。

  2. 匿名内部类没有构造方法,因为它没有类名。

  3. 由于匿名内部类没有类名,也不能定义静态成员。因此,匿名内部类中不能有静态块、静态方法、静态成员变量等。

  4. 匿名内部类实现的是一个接口或继承自一个抽象类,因此必须实现其中的抽象方法。

  5. 由于匿名内部类在定义时就创建了一个实例,因此无法重复使用,其生命周期也就受限于当前的代码块或方法。

  6. 匿名内部类中使用的变量必须为 final 或不可变变量,因为匿名内部类实例中所使用的变量在匿名内部类创建时就确定了,如果在匿名内部类创建之后修改了这些变量的值,可能会导致匿名内部类实例中所使用的变量与预期不一致。

文章来源:https://blog.csdn.net/kuxiaoyang/article/details/135589380
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。