面向对象_其他 OOP 特性

99 阅读7分钟

内部类

在 Java 中,内部类是定义在另一个类(外部类)内部的类。内部类可以访问外部类的成员,同时也能实现代码的封装和隐藏。根据定义位置和修饰符的不同,内部类分为四类。

成员内部类(Member Inner Class)

定义在外部类的成员位置(与成员变量、方法同级),无 static 修饰。

特点:

  • 可访问外部类的所有成员(包括 private 修饰的成员)。
  • 依赖外部类对象存在,需先创建外部类对象才能实例化内部类。
  • 内部类中不能定义静态成员(静态变量、静态方法)。

示例:

public class MemberInnerClassDemo {
    public static void main(String[] args) {
        // 1. 创建外部类对象
        Outer outer = new Outer();
        
        // 2. 通过外部类对象创建内部类对象
        Outer.Inner inner = outer.new Inner();
        
        // 3. 调用内部类方法
        inner.printOuterField(); // 输出:外部类的私有变量:10
    }
}

// 外部类
class Outer {
    private int outerField = 10; // 外部类私有成员

    // 成员内部类(定义在外部类成员位置)
    class Inner {
        // 内部类方法:访问外部类私有成员
        public void printOuterField() {
            System.out.println("外部类的私有变量:" + outerField);
        }
    }
}

静态内部类(Static Nested Class)

定义在外部类的成员位置,用 static 修饰(也称为 “嵌套类”)。

特点

  • 不依赖外部类对象,可直接通过 外部类名.内部类名 实例化。
  • 只能访问外部类的静态成员(静态变量、静态方法),不能访问非静态成员。
  • 内部类中可以定义静态成员和非静态成员。

示例:

public class StaticInnerClassDemo {
    public static void main(String[] args) {
        // 直接通过外部类名创建静态内部类对象(无需外部类对象)
        Outer.StaticInner inner = new Outer.StaticInner();
        inner.printOuterStaticField(); // 输出:外部类的静态变量:20
    }
}

// 外部类
class Outer {
    private static int staticField = 20; // 外部类静态成员
    private int nonStaticField = 30;     // 外部类非静态成员

    // 静态内部类(用static修饰)
    static class StaticInner {
        public void printOuterStaticField() {
            // 只能访问外部类的静态成员
            System.out.println("外部类的静态变量:" + staticField);
            
            // 不能访问外部类的非静态成员(编译报错)
            // System.out.println(nonStaticField);
        }
    }
}

局部内部类(Local Inner Class)

定义在外部类的方法或代码块内部(局部位置),作用域仅限于当前方法 / 代码块。

特点:

  • 作用域有限,仅在定义它的方法 / 代码块内可见。
  • 可访问外部类的所有成员,但只能访问方法中被 final 修饰的局部变量(Java 8+ 可省略 final,但变量本质仍是常量)。
  • 不能用访问修饰符(public/private 等)修饰。

示例:

public class LocalInnerClassDemo {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.outerMethod(100); // 调用外部类方法,触发局部内部类
    }
}

// 外部类
class Outer {
    private int outerField = 30;

    public void outerMethod(final int param) { // 方法参数隐式为final
        final int localVar = 50; // 局部变量(本质是final)

        // 局部内部类(定义在方法内部)
        class LocalInner {
            public void print() {
                System.out.println("外部类成员:" + outerField);
                System.out.println("方法参数:" + param);
                System.out.println("局部变量:" + localVar);
            }
        }

        // 只能在当前方法内使用局部内部类
        LocalInner inner = new LocalInner();
        inner.print();
    }
}

输出结果:

外部类成员:30
方法参数:100
局部变量:50

匿名内部类(Anonymous Inner Class)

没有类名的局部内部类,通常用于快速创建接口或抽象类的实现对象,定义后立即使用。

特点:

  • 无类名,只能创建一次对象(无法重复使用)。
  • 必须继承一个类或实现一个接口。
  • 语法紧凑,常用于简化代码(如事件监听、线程创建)。

示例:

public class AnonymousInnerClassDemo {
    public static void main(String[] args) {
        // 1. 实现接口的匿名内部类
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("线程执行:通过匿名内部类实现Runnable");
            }
        };
        new Thread(runnable).start();

        // 2. 继承类的匿名内部类(简化写法)
        Animal animal = new Animal() {
            @Override
            public void makeSound() {
                System.out.println("猫叫:喵喵喵");
            }
        };
        animal.makeSound();
    }
}

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

输出结果:

线程执行:通过匿名内部类实现Runnable
猫叫:喵喵喵

内部类的核心作用

  1. 封装性:内部类可隐藏在外部类中,仅外部类可见,减少对其他类的暴露。
  2. 访问便利性:非静态内部类可直接访问外部类的私有成员,无需通过 getter 方法。
  3. 代码组织:将逻辑相关的类放在一起,增强代码的内聚性(如 Map.Entry 是 Map 的内部类)。
  4. 简化代码:匿名内部类可快速实现接口或抽象类,避免单独定义类的繁琐(如 GUI 事件处理)。
内部类类型定义位置访问外部类成员实例化方式典型场景
成员内部类外部类成员位置所有成员(包括私有)外部类对象.new 内部类 ()与外部类紧密关联的辅助类
静态内部类外部类成员位置(static) 仅静态成员仅静态成员外部类名.new 内部类 ()独立于外部类实例的工具类
局部内部类方法 / 代码块内部所有成员 +final局部变量方法内直接 new方法内部临时使用的类
匿名内部类方法 / 代码块内部所有成员 +final局部变量定义时直接 new(无类名)快速实现接口 / 抽象类(如回调)

枚举(enum)和注解(@Annotation)

枚举(enum)

枚举是一种特殊的类,用于定义固定数量的常量集合(如季节、星期、状态等),使代码更具可读性和安全性。

枚举的基本定义

使用 enum 关键字定义枚举,常量之间用逗号分隔,末尾可省略分号。

// 定义季节枚举
enum Season {
    SPRING, SUMMER, AUTUMN, WINTER // 枚举常量(默认public static final)
}

枚举的特性

  • 常量不可变:枚举常量是枚举类的实例,默认被 public static final 修饰,一旦定义无法修改。
  • 不能实例化:枚举类的构造方法默认为 private,无法通过 new 创建对象。
  • 可包含成员:枚举类可以有成员变量、方法、构造方法(必须 private)。
  • 实现接口:枚举类可以实现接口,但不能继承其他类(默认继承 java.lang.Enum)。

示例

public class EnumDemo {
    public static void main(String[] args) {
        // 使用枚举常量
        Day today = Day.MONDAY;
        System.out.println("今天是:" + today); // 输出:MONDAY
        System.out.println("是否工作日:" + today.isWorkDay()); // 输出:true

        // 遍历所有枚举常量
        for (Day day : Day.values()) {
            System.out.println(day + " -> " + day.getDesc());
        }
    }
}

// 带成员的枚举类
enum Day {
    // 枚举常量(必须在最前面,参数对应构造方法)
    MONDAY("星期一", true),
    TUESDAY("星期二", true),
    SUNDAY("星期日", false);

    // 成员变量
    private String desc; // 描述
    private boolean isWorkDay; // 是否工作日

    // 构造方法(必须private)
    private Day(String desc, boolean isWorkDay) {
        this.desc = desc;
        this.isWorkDay = isWorkDay;
    }

    // 成员方法
    public String getDesc() {
        return desc;
    }

    public boolean isWorkDay() {
        return isWorkDay;
    }
}

输出结果:

今天是:MONDAY
是否工作日:true
MONDAY -> 星期一
TUESDAY -> 星期二
SUNDAY -> 星期日

枚举的应用场景

  1. 表示固定范围的选项(如性别 MALE/FEMALE、状态 SUCCESS/ERROR)。
  2. 替代 public static final 常量(类型更安全,避免传入无效值)。
  3. 用于 switch 语句(Java 7+ 支持)。

注解(@Annotation)

注解是一种标记性语法,用于为代码(类、方法、变量等)添加元数据(描述数据的数据),可被编译器、框架或工具读取并处理。

注解的基本概念

  • 语法:以 @ 开头,如 @Override@Deprecated
  • 作用:
    • 编译期检查(如 @Override 确保方法正确重写)。
    • 运行时处理(如 Spring@Autowired 实现依赖注入)。
    • 生成文档(如 @param@return 用于 Javadoc)。

常见内置注解

注解作用
@Override标记方法重写父类方法,编译器会校验正确性
@Deprecated标记元素以过时,使用时编译器会警告
@SuppressWarnings抑制编译器警告(如 unchecked
@FunctionalInterface标记函数式接口(仅一个抽象方法)

元注解(修饰注解的注解)

元注解用于定义自定义注解的行为,常用元注解:

  • @Target:指定注解可修饰的元素(如 TYPE 类、METHOD 方法)。
  • @Retention:指定注解的保留策略(SOURCE 源码期、CLASS 编译期、RUNTIME 运行期)。
  • @Documented:注解将被包含在 Javadoc 文档中。
  • @Inherited:注解可被子类继承。.

自定义注解示例

import java.lang.annotation.*;
import java.lang.reflect.Method;

// 自定义注解
@Target(ElementType.METHOD) // 仅可修饰方法
@Retention(RetentionPolicy.RUNTIME) // 运行时保留,可通过反射获取
public @interface Log {
    // 注解属性(默认值可选)
    String value() default "执行方法";
    boolean needLog() default true;
}

// 使用自定义注解
class UserService {
    @Log(value = "用户登录", needLog = true)
    public void login(String username) {
        System.out.println(username + "登录成功");
    }
}

// 解析注解(通过反射)
public class AnnotationDemo {
    public static void main(String[] args) throws Exception {
        // 获取方法
        Method method = UserService.class.getMethod("login", String.class);
        
        // 判断是否有@Log注解
        if (method.isAnnotationPresent(Log.class)) {
            Log logAnnotation = method.getAnnotation(Log.class);
            // 读取注解属性
            System.out.println("日志描述:" + logAnnotation.value());
            System.out.println("是否需要日志:" + logAnnotation.needLog());
        }
        
        // 调用方法
        new UserService().login("张三");
    }
}

运行结果:

日志描述:用户登录
是否需要日志:true
张三登录成功

注解的应用场景

  • 框架开发(如 Spring@ControllerMyBatis@Select)。
  • 代码检查(如 @NonNull 标记非空参数)。
  • 测试框架(如 JUnit@Test 标记测试方法)。