面向对象 10:Java 内部类相关知识,四种不同内部类的使用方式和异同(成员 / 静态 / 局部 / 匿名内部类)

一、前言

记录时间 [2024-05-18]

系列文章简摘:
面向对象 04:三大特性之——封装,封装的含义和作用,以及在 Java 中的使用方式,附完整的测试案例代码
面向对象 07:抽象类相关知识,抽象类的基本概念,使用方式,以及一些注意事项
面向对象 08:接口的相关知识,接口的基本特性、作用、使用方法等,以及普通类 / 抽象类 / 接口三者的区分
面向对象 09:static 关键字的使用——静态变量 / 静态方法 / 静态代码块 / 静态导入包 / 静态内部类

更多 Java 相关文章,请参考专栏哦。

本文讲述 Java 内部类相关知识,通过案例分析,整理了四种不同内部类的使用方式和异同点。分别是成员内部类、静态内部类、局部内部类,以及匿名内部类。


在 Java 中,内部类是指在一个类的内部定义的类

这种结构让内部类可以访问外部类的成员,即使这些成员是私有的。

Java 支持几种类型的内部类,包括成员内部类、静态内部类、局部内部类,以及匿名内部类。

比如,A 类中定义一个 B 类:

  • B 类相对 A 类来说就称为内部类
  • A 类相对 B 类来说就是外部类

二、成员内部类

1. 基本概念

成员内部类,也称为非静态内部类(non-static nested class),是定义在另一个类(称为外部类)的内部的类。

成员内部类的主要特点和用法如下:

  • 访问权限:成员内部类可以访问外部类的所有成员,包括私有成员。这使得内部类能够操作外部类的属性和方法,增强了代码的封装性。
  • 实例化:创建成员内部类的实例需要通过外部类的实例来完成。需要先有外部类的一个对象,然后通过这个对象来创建内部类的对象。
  • 内存模型每个内部类实例都包含一个对外部类实例的隐式引用,这影响了内存的使用。因此,如果内部类持有大量数据或生命周期较长,可能会影响性能。
  • 继承与实现:成员内部类可以有自己的构造方法、属性和方法,也可以继承其他类或实现接口。
  • 访问外部类实例:在内部类中,可以通过 OuterClass.this 来明确地引用外部类的实例。

2. 相关使用

在这个例子中,InnerClassOuterClass 的一个成员内部类,它能够访问外部类的 outerField 私有变量。

public class OuterClass {

    // 外部类的私有属性
    private int outerField = 10;
    
    public void out() {
        System.out.println("这是外部类的方法");
    }

    // 成员内部类
    class InnerClass {

        public void display() {
            // 访问外部类的私有属性
            System.out.println("OuterField Value: " + outerField);
        }

        public void in() {
            System.out.println("这是内部类的方法");
        }

    }

    public void createAndUseInner() {
        // 创建内部类实例,需要先有外部类的实例
        InnerClass inner = new InnerClass();
        inner.display();
    }

}

需要通过外部类来实例化内部类,内部类实例调用内部类的方法,外部类实例调用外部类的方法。

  • 通过 OuterClass.InnerClass inner = outer.new InnerClass(); 创建内部类实例;
  • 通过 outer.createAndUseInner() 方法,创建内部类的实例并调用了其 display 方法,展示了内部类访问外部类成员的能力。
public static void main(String[] args) {

    // 外部类的实例化
    OuterClass outer = new OuterClass();
    outer.out();

    // 通过外部类来实例化内部类
    OuterClass.InnerClass inner = outer.new InnerClass();
    inner.in();

    // 另一种通过外部类实例化内部类的方法
    outer.createAndUseInner(); // 输出: OuterField Value: 10
}

三、静态内部类

1. 基本概念

静态内部类(Static Nested Class),也称作静态嵌套类,是在外部类中使用 static 关键字定义的类。

静态内部类的主要特征和用途如下:

  • 静态属性:静态内部类不依赖于外部类的实例,它可以被看作是外部类的一个静态成员。因此,无需创建外部类的实例就可以创建静态内部类的实例
  • 访问权限:静态内部类可以直接访问外部类的静态成员(包括私有的静态成员),但不能直接访问外部类的非静态成员(因为非静态成员依赖于外部类的具体实例)。
  • 内存模型静态内部类不持有对外部类实例的引用,它存在于堆内存中,与普通的静态成员一样,只有一份实例,属于类级别而非对象级别。
  • 实例化:创建静态内部类的实例不需要外部类的实例,可以直接通过 外部类.内部类 的方式实例化。
  • 用途:静态内部类常用于工具类、工厂设计模式、单例模式等场景,因为它不绑定到外部类的实例上,适用于那些逻辑上与外部类相关联但又独立存在的类。

2. 相关使用

在这个例子中,StaticInnerClass 是一个静态内部类,它可以访问外部类的静态属性 staticField,但不能访问非静态属性。

public class StaticOuterClass {

    // 外部类的私有静态属性
    private static int staticField = 20;

    // 外部类的私有非静态属性
    private int nonStaticField = 30;

    // 静态内部类
    static class StaticInnerClass {
        
        void display() {
            // 可以访问外部类的私有静态属性
            System.out.println("Static Field from OuterClass: " + staticField);

            // 不能直接访问外部类的私有非静态属性,以下代码会编译错误
            // System.out.println("Non-static Field from OuterClass: " + nonStaticField);
        }
        
    }

}

无需创建外部类 StaticOuterClass 的实例,通过 StaticOuterClass.StaticInnerClass 直接创建内部类的实例。

public static void main(String[] args) {

    // 直接创建静态内部类的实例,无需外部类实例
    StaticInnerClass inner = new StaticOuterClass.StaticInnerClass();

    inner.display(); // 输出: Static Field from OuterClass: 20

}

四、局部内部类

1. 基本概念

局部内部类是定义在方法、代码块内部的类,它的作用域局限于该方法或代码块。

局部内部类的特点和用法如下:

  • 定义位置:局部内部类定义在方法、构造器、或者任何类型的代码块中,而不是直接在类的顶层
  • 访问权限:局部内部类可以直接访问外部类的所有成员,包括私有成员,同时也能够访问所在方法或代码块的局部变量,但这些局部变量必须是 final实际上不可改变的(从 Java 8 开始,虽然不需要显式声明 final,但局部变量在内部类中被视为 final,即不可修改)。
  • 生命周期:局部内部类的生命周期依赖于包含它的方法或代码块,当方法或代码块执行完毕后,局部内部类的实例将不再可用,除非该实例被某个作用域更广的变量所引用。
  • 实例化:局部内部类的实例通常在定义它的方法或代码块内部创建,不能在外部类的其他地方创建。
  • 作用域:局部内部类的作用域仅限于定义它的方法或代码块内,外部类的其他部分无法直接访问它。
  • 访问外部变量:如果局部内部类需要访问外部方法的局部变量,那么这个变量必须是 finaleffectively final(即初始化后不再改变的变量)。

2. 相关使用

在此示例中,LocalInnerClass 是在 someMethod 方法内部定义的局部内部类,它可以访问该方法中的局部变量 localVar

局部内部类的实例 inner 被创建并调用了 display 方法来展示对外部局部变量的访问。

public class PartOuterClass {
    
    void someMethod() {

        // 从 Java 8 起,即使不声明 final,此处局部变量也视为不可变
        final int localVar = 40;

        // 局部内部类
        class LocalInnerClass {
            void display() {
                System.out.println("Accessing local variable: " + localVar);
            }
        }

        // 创建并使用局部内部类的实例
        LocalInnerClass inner = new LocalInnerClass();
        inner.display(); // 输出: Accessing local variable: 40
    }

}

由于作用域限制,无法在 main 方法或其他外部位置访问或实例化 LocalInnerClass

public static void main(String[] args) {

    // 不能在这里创建 LocalInnerClass 的实例,因为它的作用域限制在 someMethod 方法内

    PartOuterClass outer = new PartOuterClass();
    outer.someMethod();

}

五、匿名内部类

1. 基本概念

匿名内部类是 Java 中一种没有名字的内部类,它允许在创建对象的同时定义类的结构和实现

匿名内部类主要用于快速创建一次性使用的类实例,常见于实现接口、继承抽象类或具体类的情况,尤其是在事件监听器、线程创建等场景下。

匿名内部类的特点和使用方法包括:

  • 无显式类名:匿名内部类没有自己的类名,它的定义和实例化过程在同一行完成。
  • 简洁性:由于没有类名,匿名内部类简化了代码,特别适合于只需要使用一次的类实例。
  • 实现接口或继承:匿名内部类可以实现一个接口的所有方法,或者继承一个抽象类并覆盖其所有抽象方法,也可以扩展一个具体的类并覆盖其方法。
  • 访问权限:匿名内部类可以访问外部类的成员,包括私有成员。同时,它还可以访问局部变量,但这些局部变量必须是 finaleffectively final 的。
  • 对象创建:匿名内部类通过 new 接口名/抽象类名/具体类名(){...} 的语法创建实例,大括号内定义类的具体实现。
  • 作用域限制:匿名内部类的实例化和使用通常局限在定义它的代码块中,外部无法直接访问。

2. 相关使用

匿名实例化

匿名初始化类,不用将实例保存到变量中。

  • 一般情况下,我们使用 new 关键字创建对象,会给实例对象命名,如 Apple apple = new Apple();
  • 但匿名初始化类,直接通过 new Apple() 就可以调用方法,如,new Apple().eat();
public class NoNameClass {
    
    public static void main(String[] args) {
        
        // 没有名字初始化类,不用将实例保存到变量中
        new Apple().eat();
    }
}

class Apple {
    public void eat() {
        System.out.println("eat apple...");
    }
    
}

接口实现

在 Java 中,匿名内部类实现接口是一种常见的做法,用于快速创建一个只使用一次的类实例,同时实现给定接口的所有方法。

这种方式简化了代码,特别是在需要传入接口实例作为参数或创建简单功能对象时非常有用。

例如,定义一个接口:

interface Printable {
    // 抽象方法
    void print();
}

用匿名内部类方式实现接口:

public static void main(String[] args) {

    // 接口实现
    // 使用匿名内部类实现 Printable 接口
    Printable printer = new Printable() {
        @Override
        public void print() {
            System.out.println("Hello from anonymous class!");
        }
    };

    printer.print(); // 输出: Hello from anonymous class!
}

继承抽象类

在 Java 中,匿名内部类不仅可以实现接口,还可以继承抽象类。

在如下情况下,使用匿名内部类非常有用:

  • 需要创建一个类来继承一个抽象类,并且只需要这个类的一个实例。
  • 或者这个类的具体实现只在某一个特定上下文中使用。

例如,定义一个抽象类:

// 抽象类
abstract class Animal {
    abstract void sound();
}

用匿名内部类方式继承抽象类:

public static void main(String[] args) {

    // 继承抽象类
    // 使用匿名内部类继承 Animal 抽象类
    Animal dog = new Animal() {
        @Override
        void sound() {
            System.out.println("Dog barks!");
        }
    };

    dog.sound(); // 输出: Dog barks!
}

六、总结

本文讲述 Java 内部类相关知识,通过案例分析,整理了四种不同内部类的使用方式和异同点。分别是成员内部类、静态内部类、局部内部类,以及匿名内部类


一些参考资料

狂神说 Java 零基础:https://www.bilibili.com/video/BV12J41137hu/
TIOBE 编程语言走势: https://www.tiobe.com/tiobe-index/
Typora 官网:https://www.typoraio.cn/
Oracle 官网:https://www.oracle.com/
Notepad++ 下载地址:https://notepad-plus.en.softonic.com/
IDEA 官网:https://www.jetbrains.com.cn/idea/
Java 开发手册:https://developer.aliyun.com/ebook/394
Java 8 帮助文档:https://docs.oracle.com/javase/8/docs/api/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值