Java面向对象进阶全解:从static到内部类,吃透OOP核心

6 阅读28分钟

Java面向对象进阶全解:从static到内部类,吃透OOP核心

本文系统梳理Java面向对象进阶核心知识点,覆盖static、继承、多态、包与权限、抽象类、接口、内部类等内容,结合源码解析、实战案例与避坑技巧,衔接前文集合与Stream流知识点,夯实Java面向对象编程能力。

一、static关键字:静态成员全解析

static是Java中用于修饰类成员的关键字,核心作用是让成员属于类本身,而非对象实例,是面向对象进阶的第一个核心知识点,也是工具类、单例模式等设计的基础。static可修饰变量、方法、代码块,不同修饰场景的核心逻辑一致,但用法和注意事项各有侧重。

1.1 static静态变量

静态变量(类变量)由static修饰,存储在方法区的静态常量池,被类的所有对象共享,不属于某个具体对象,无需创建对象即可访问。

核心特性

  1. 加载时机:随着类的加载而加载,优先于对象存在(类加载时初始化静态变量,对象创建时仅初始化非静态成员);

  2. 共享性:被类的所有对象共享,一个对象修改静态变量的值,其他所有对象访问到的都是修改后的值;

  3. 访问方式:推荐通过类名.静态变量直接访问,也可通过对象访问(不推荐,易混淆静态与非静态的归属);

  4. 访问限制:静态变量不能访问非静态成员(非静态成员属于对象,需对象实例才能访问,而静态成员加载时无对象),但非静态成员可以访问静态成员。

实战案例:静态变量的使用

public class StaticVariableDemo {
    // 静态变量(类变量):共享的班级名称
    public static String className = "Java进阶班";
    // 非静态变量(对象变量):每个学生的姓名
    public String name;

    public static void main(String[] args) {
        // 1. 直接通过类名访问静态变量(推荐)
        System.out.println("班级名称:" + StaticVariableDemo.className);
        
        // 2. 创建对象,访问静态变量(不推荐)
        StaticVariableDemo student1 = new StaticVariableDemo();
        student1.name = "张三";
        System.out.println(student1.name + "的班级:" + student1.className);
        
        // 3. 修改静态变量,所有对象共享修改后的值
        StaticVariableDemo.className = "Java高级班";
        StaticVariableDemo student2 = new StaticVariableDemo();
        student2.name = "李四";
        System.out.println(student2.name + "的班级:" + student2.className);
        System.out.println(student1.name + "的班级(修改后):" + student1.className);
        
        // 错误示例:静态方法中访问非静态变量(编译报错)
        // System.out.println(name);
    }
}

避坑点

  1. 静态变量不要用于存储对象独有的数据(如学生的姓名、年龄),否则会导致数据混乱;

  2. 静态变量的初始化顺序:默认初始化(与普通变量一致)→ 显式初始化 → 静态代码块初始化。

1.2 static静态方法

静态方法(类方法)由static修饰,属于类本身,无需创建对象即可调用,常用来定义工具方法(如Arrays工具类、Math工具类中的方法)。

核心特性

  1. 加载时机:与类同时加载,优先于对象存在;

  2. 调用方式:推荐通过类名.静态方法调用,也可通过对象调用(不推荐);

  3. 访问限制:静态方法不能访问非静态成员(变量/方法),不能使用this关键字(this代表当前对象,静态方法无对象关联);

  4. 继承特性:静态方法可以被继承,但不能被重写(子类重写静态方法会隐藏父类静态方法,而非真正重写)。

实战案例:静态工具方法的实现

// 静态工具类:封装常用的工具方法(工具类通常私有化构造方法,避免创建对象)
public class MathUtil {
    // 私有化构造方法,禁止创建对象(工具类无需实例化)
    private MathUtil() {}

    // 静态工具方法:计算两个整数的最大值
    public static int max(int a, int b) {
        return a > b ? a : b;
    }

    // 静态工具方法:计算两个整数的最小值
    public static int min(int a, int b) {
        return a < b ? a : b;
    }

    // 错误示例:静态方法访问非静态成员(编译报错)
    // public static void test() {
    //     System.out.println(num); // num是非静态变量
    // }

    // 非静态方法可以访问静态成员
    public void show() {
        System.out.println("最大值:" + max(10, 20));
    }
}

// 测试类
public class StaticMethodDemo {
    public static void main(String[] args) {
        // 直接通过类名调用静态工具方法(推荐)
        int max = MathUtil.max(15, 25);
        int min = MathUtil.min(15, 25);
        System.out.println("最大值:" + max);
        System.out.println("最小值:" + min);

        // 错误示例:工具类创建对象(无意义,且构造方法私有化后无法创建)
        // MathUtil util = new MathUtil();
    }
}

1.3 static静态代码块

静态代码块由static修饰,用{}包裹,属于类本身,仅在类加载时执行一次,且优先于构造方法执行,常用于初始化静态变量、加载资源(如配置文件、驱动)。

核心特性

  1. 执行时机:类加载时执行,仅执行一次(无论创建多少个对象,静态代码块都只执行一次);

  2. 执行顺序:静态代码块 → 构造代码块 → 构造方法;

  3. 作用:初始化静态变量、加载全局资源,避免重复初始化(如数据库驱动加载)。

实战案例:静态代码块的使用

public class StaticBlockDemo {
    // 静态变量
    public static String configInfo;

    // 静态代码块:初始化静态变量、加载资源
    static {
        System.out.println("静态代码块执行(类加载时)");
        // 模拟加载配置文件,初始化静态变量
        configInfo = "数据库地址:jdbc:mysql://localhost:3306/java_db";
    }

    // 构造代码块(非静态,每次创建对象都执行)
    {
        System.out.println("构造代码块执行(创建对象时)");
    }

    // 构造方法
    public StaticBlockDemo() {
        System.out.println("构造方法执行(创建对象时)");
    }

    public static void main(String[] args) {
        System.out.println("main方法执行");
        // 第一次创建对象,触发类加载(静态代码块执行)
        StaticBlockDemo demo1 = new StaticBlockDemo();
        System.out.println("配置信息:" + demo1.configInfo);
        
        // 第二次创建对象,静态代码块不再执行
        StaticBlockDemo demo2 = new StaticBlockDemo();
    }
}

执行结果分析

  1. 程序启动时,main方法执行前,类加载,静态代码块执行(仅一次);

  2. 创建第一个对象时,先执行构造代码块,再执行构造方法;

  3. 创建第二个对象时,仅执行构造代码块和构造方法,静态代码块不再执行。

二、继承:代码复用的核心机制

继承是面向对象三大特性(封装、继承、多态)之一,核心作用是代码复用,允许子类继承父类的非私有成员(变量、方法),同时子类可以扩展自己的独有成员,实现“子类 is a 父类”的关系(如学生 is a 人、猫 is a 动物)。

Java中继承的关键字是extends,且支持单继承(一个子类只能有一个父类),但支持多层继承(子类→父类→祖父类...)。

2.1 继承的基本语法与使用

语法格式

// 父类(基类)
class 父类名 {
    // 父类的成员(变量、方法)
}

// 子类(派生类),继承父类
class 子类名 extends 父类名 {
    // 子类的独有成员(可扩展)
}

实战案例:继承的代码复用

// 父类:Person(人),封装共性成员
class Person {
    // 父类的非私有成员(子类可继承)
    String name;
    int age;

    // 父类的方法
    public void eat() {
        System.out.println(name + "在吃饭");
    }

    public void sleep() {
        System.out.println(name + "在睡觉");
    }
}

// 子类:Student(学生),继承Person,扩展独有成员
class Student extends Person {
    // 子类的独有成员(父类没有)
    String studentId; // 学号

    // 子类的独有方法
    public void study() {
        System.out.println(name + "(学号:" + studentId + ")在学习Java");
    }
}

// 测试类
public class InheritanceDemo {
    public static void main(String[] args) {
        // 创建子类对象
        Student student = new Student();
        
        // 访问父类继承的成员
        student.name = "张三";
        student.age = 20;
        student.eat(); // 继承父类的方法
        student.sleep(); // 继承父类的方法
        
        // 访问子类的独有成员
        student.studentId = "2024001";
        student.study(); // 子类的独有方法
    }
}

2.2 继承的核心规则

  1. 成员访问规则:子类可以访问父类的非私有成员(public、protected修饰),不能访问父类的私有成员(private修饰);若子类有与父类同名的成员,优先访问子类自身的成员(成员隐藏);

  2. 构造方法规则:子类构造方法执行前,会先执行父类的无参构造方法(默认调用super());若父类没有无参构造方法,子类必须通过super()手动调用父类的有参构造方法;

  3. 单继承限制:Java不支持多继承(一个子类不能继承多个父类),但可以通过接口实现多继承的效果;

  4. 继承传递性:若A继承B,B继承C,则A会继承B和C的非私有成员。

重点:super关键字的使用

super关键字用于访问父类的成员,核心作用:

  1. 调用父类的构造方法:super(参数),必须放在子类构造方法的第一行;

  2. 访问父类的成员变量:super.父类变量名,当子类与父类变量同名时使用;

  3. 访问父类的成员方法:super.父类方法名(参数),当子类重写父类方法后,需要访问父类原方法时使用。

实战案例:super关键字的使用

class Father {
    // 父类的成员变量
    String name = "父亲";

    // 父类的有参构造方法
    public Father(String name) {
        this.name = name;
        System.out.println("父类有参构造方法执行");
    }

    // 父类的方法
    public void show() {
        System.out.println("父类的show方法:" + name);
    }
}

class Son extends Father {
    // 子类与父类同名的成员变量
    String name = "儿子";

    // 子类的构造方法
    public Son() {
        // 手动调用父类的有参构造方法(父类无无参构造,必须调用)
        super("张三");
        System.out.println("子类无参构造方法执行");
    }

    // 子类重写父类的show方法
    @Override
    public void show() {
        super.show(); // 调用父类的show方法
        System.out.println("子类的show方法:" + name); // 访问子类自身的变量
        System.out.println("父类的name:" + super.name); // 访问父类的变量
    }
}

public class SuperDemo {
    public static void main(String[] args) {
        Son son = new Son();
        son.show();
    }
}

2.3 方法重写(Override):子类的个性化实现

方法重写是指子类继承父类后,定义与父类**方法名、参数列表、返回值类型(子类返回值可小于等于父类)**完全一致的方法,用于修改父类方法的实现逻辑,实现子类的个性化需求。

方法重写的核心规则(必须遵守)

  1. 方法签名一致:方法名、参数列表(参数个数、类型、顺序)必须与父类完全一致;

  2. 返回值规则:子类方法的返回值类型必须小于等于父类方法的返回值类型(如父类返回Object,子类可返回String);

  3. 访问权限规则:子类方法的访问权限必须大于等于父类方法的访问权限(如父类方法是protected,子类可改为public,不能改为private);

  4. 不能重写的方法:静态方法、final方法、private方法(父类private方法子类无法继承,更无法重写);

  5. 注解提示:重写方法时,建议添加@Override注解,编译器会校验重写规则,避免写错。

实战案例:方法重写的应用

// 父类:Animal(动物)
class Animal {
    public void cry() {
        System.out.println("动物在叫");
    }
}

// 子类:Dog(狗),重写cry方法
class Dog extends Animal {
    @Override
    public void cry() {
        System.out.println("小狗汪汪叫");
    }
}

// 子类:Cat(猫),重写cry方法
class Cat extends Animal {
    @Override
    public void cry() {
        System.out.println("小猫喵喵叫");
    }
}

// 测试类
public class OverrideDemo {
    public static void main(String[] args) {
        Animal dog = new Dog();
        Animal cat = new Cat();
        
        // 调用的是子类重写后的方法(多态的体现)
        dog.cry(); // 输出:小狗汪汪叫
        cat.cry(); // 输出:小猫喵喵叫
    }
}

三、多态:面向对象的灵魂

多态是面向对象三大特性的核心,核心定义:同一方法调用,根据对象的不同,执行不同的实现逻辑,本质是“父类引用指向子类对象”,实现代码的灵活性和扩展性,是后续框架设计(如Spring)的核心思想。

3.1 多态的实现条件

实现多态必须同时满足3个条件,缺一不可:

  1. 存在继承关系(子类继承父类);

  2. 子类重写父类的方法;

  3. 父类引用指向子类对象(父类名 引用名 = new 子类名();)。

实战案例:多态的基本使用

// 父类:Shape(图形)
class Shape {
    // 父类方法,子类重写
    public void draw() {
        System.out.println("绘制图形");
    }
}

// 子类:Circle(圆形)
class Circle extends Shape {
    @Override
    public void draw() {
        System.out.println("绘制圆形");
    }
}

// 子类:Rectangle(矩形)
class Rectangle extends Shape {
    @Override
    public void draw() {
        System.out.println("绘制矩形");
    }
}

// 子类:Triangle(三角形)
class Triangle extends Shape {
    @Override
    public void draw() {
        System.out.println("绘制三角形");
    }
}

// 测试类
public class PolymorphismDemo {
    public static void main(String[] args) {
        // 父类引用指向子类对象(多态的核心)
        Shape shape1 = new Circle();
        Shape shape2 = new Rectangle();
        Shape shape3 = new Triangle();
        
        // 同一方法调用,根据对象不同,执行不同逻辑
        shape1.draw(); // 输出:绘制圆形
        shape2.draw(); // 输出:绘制矩形
        shape3.draw(); // 输出:绘制三角形
        
        // 调用多态工具方法
        drawShape(shape1);
        drawShape(shape2);
        drawShape(shape3);
    }

    // 工具方法:接收父类引用,实现多态调用
    public static void drawShape(Shape shape) {
        shape.draw(); // 调用的是子类重写后的方法
    }
}

3.2 多态的核心特性

3.2.1 向上转型(自动转型)

即“父类引用指向子类对象”,是多态的核心,自动完成,无需手动转换,例如:Shape shape = new Circle();

特点:父类引用只能访问父类的成员(变量、方法),不能访问子类的独有成员(除非强制向下转型)。

3.2.2 向下转型(强制转型)

当需要访问子类的独有成员时,需将父类引用强制转换为子类引用,语法:子类名 引用名 = (子类名) 父类引用;

注意:向下转型前,需通过instanceof关键字判断父类引用指向的对象是否是目标子类的实例,避免出现ClassCastException(类型转换异常)。

实战案例:向下转型与instanceof判断

public class DownCastDemo {
    public static void main(String[] args) {
        // 向上转型(自动)
        Shape shape = new Circle();
        shape.draw(); // 调用子类重写的方法
        
        // 错误示例:父类引用不能直接访问子类独有成员
        // shape.radius = 5;
        
        // 向下转型(强制),先判断类型
        if (shape instanceof Circle) {
            Circle circle = (Circle) shape;
            circle.setRadius(5); // 访问子类独有方法
            System.out.println("圆形半径:" + circle.getRadius());
        }
        
        // 错误示例:未判断类型,强制转型报错
        // if (shape instanceof Rectangle) {
        //     Rectangle rectangle = (Rectangle) shape;
        // }
    }
}

// 完善Circle类,添加独有成员和方法
class Circle extends Shape {
    private double radius; // 子类独有成员(半径)

    @Override
    public void draw() {
        System.out.println("绘制圆形");
    }

    // 子类独有方法
    public void setRadius(double radius) {
        this.radius = radius;
    }

    public double getRadius() {
        return radius;
    }
}

3.2.3 多态的优势

  1. 代码复用:通过父类引用统一管理子类对象,减少重复代码;

  2. 扩展性强:新增子类时,无需修改原有代码(如新增正方形子类,只需重写draw方法,工具方法无需修改);

  3. 解耦:降低代码之间的耦合度,符合“开闭原则”(对扩展开放,对修改关闭)。

四、包与访问权限修饰符

包(package)是Java中用于组织类的容器,核心作用是避免类名冲突、划分功能模块(如util包、service包);访问权限修饰符用于控制类、成员的访问范围,实现封装的核心需求,二者结合使用,让代码结构更清晰、更安全。

4.1 包的基本使用

4.1.1 包的定义

使用package关键字定义包,必须放在Java文件的第一行(注释除外),语法:package 包名;

包名命名规范:通常采用“域名反转”的方式,避免冲突,例如:com.xxx.util、com.xxx.service(xxx为公司/个人域名)。

4.1.2 包的导入

当需要使用其他包中的类时,需通过import关键字导入,语法:import 包名.类名;(导入单个类)或import 包名.*;(导入包中所有类)。

注意:java.lang包(如String、System)无需导入,JVM自动加载。

实战案例:包的定义与导入

// 定义包:com.xxx.oop.advanced(假设xxx为个人域名)
package com.xxx.oop.advanced;

// 导入其他包的类
import java.util.ArrayList; // 导入单个类
import java.util.*; // 导入java.util包下所有类

// 导入自定义包的类
import com.xxx.oop.basic.Person;

public class PackageDemo {
    public static void main(String[] args) {
        // 使用java.util包的类
        List<String> list = new ArrayList<>();
        
        // 使用自定义包的类
        Person person = new Person();
    }
}

4.2 访问权限修饰符

Java中有4种访问权限修饰符,从大到小依次为:public > protected > default(无修饰符) > private,用于控制类、成员(变量、方法)的访问范围。

4种访问权限对比

修饰符本类同包子类(不同包)其他包
private×××
default(无修饰)××
protected×
public

核心使用场景

  1. private:修饰类的私有成员(变量、方法),仅本类可访问,实现封装(隐藏内部细节);

  2. default:修饰类、成员,仅同包可访问,适合包内共享的成员;

  3. protected:修饰成员,同包和不同包子类可访问,适合子类需要继承的成员;

  4. public:修饰类、成员,所有包可访问,适合对外提供的接口、工具类。

实战案例:访问权限的使用

// 包1:com.xxx.oop.advanced
package com.xxx.oop.advanced;

public class PermissionDemo {
    // private:仅本类可访问
    private String privateStr = "private成员";
    
    // default:同包可访问
    String defaultStr = "default成员";
    
    // protected:同包、不同包子类可访问
    protected String protectedStr = "protected成员";
    
    // public:所有包可访问
    public String publicStr = "public成员";
    
    // private方法
    private void privateMethod() {
        System.out.println(privateStr);
    }
    
    // public方法
    public void publicMethod() {
        privateMethod(); // 本类可访问private方法
        System.out.println(defaultStr + "、" + protectedStr + "、" + publicStr);
    }
}

// 同包的测试类
package com.xxx.oop.advanced;

public class SamePackageTest {
    public static void main(String[] args) {
        PermissionDemo demo = new PermissionDemo();
        // 可访问default、protected、public成员,不能访问private
        System.out.println(demo.defaultStr);
        System.out.println(demo.protectedStr);
        System.out.println(demo.publicStr);
        // demo.privateStr; // 编译报错
        
        demo.publicMethod(); // 可访问public方法
    }
}

// 不同包的子类
package com.xxx.oop.sub;

import com.xxx.oop.advanced.PermissionDemo;

public class DifferentPackageSub extends PermissionDemo {
    public void test() {
        // 可访问protected、public成员,不能访问private、default
        // System.out.println(defaultStr); // 编译报错
        System.out.println(protectedStr);
        System.out.println(publicStr);
        
        publicMethod(); // 可访问public方法
    }
}

// 不同包的非子类
package com.xxx.oop.other;

import com.xxx.oop.advanced.PermissionDemo;

public class DifferentPackageTest {
    public static void main(String[] args) {
        PermissionDemo demo = new PermissionDemo();
        // 仅可访问public成员
        System.out.println(demo.publicStr);
        // demo.defaultStr; // 编译报错
        // demo.protectedStr; // 编译报错
        
        demo.publicMethod(); // 可访问public方法
    }
}

五、抽象类与接口:面向抽象编程

抽象类和接口是面向对象进阶中用于实现“抽象编程”的核心工具,核心作用是定义规范、约束子类的实现,降低代码耦合度,是多态的重要应用场景,也是后续框架设计的基础。

5.1 抽象类(abstract class)

抽象类是“不完整的类”,由abstract关键字修饰,包含抽象方法(无方法体的方法)和非抽象方法,核心作用是定义子类的共性规范,子类必须重写抽象类中的所有抽象方法(除非子类也是抽象类)。

5.1.1 抽象类的基本语法

// 抽象类:由abstract修饰
abstract class 抽象类名 {
    // 非抽象方法(有方法体)
    public void 方法名() {
        // 方法实现
    }

    // 抽象方法(无方法体,由abstract修饰)
    public abstract void 抽象方法名();
}

5.1.2 抽象类的核心特性

  1. 抽象类不能实例化(不能创建对象),只能作为父类被继承;

  2. 抽象类中可以有抽象方法和非抽象方法,抽象方法必须无方法体;

  3. 子类继承抽象类后,必须重写所有抽象方法(除非子类也是抽象类);

  4. 抽象类可以有构造方法(用于子类初始化时调用);

  5. 抽象类可以包含静态成员(static变量、static方法)。

实战案例:抽象类的使用

// 抽象类:Animal(动物),定义共性规范
abstract class Animal {
    String name;

    // 构造方法(抽象类可以有构造方法)
    public Animal(String name) {
        this.name = name;
    }

    // 非抽象方法(共性方法,有方法体)
    public void eat() {
        System.out.println(name + "在吃饭");
    }

    // 抽象方法(无方法体,子类必须重写)
    public abstract void cry();
}

// 子类:Dog,重写抽象方法
class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void cry() {
        System.out.println(name + "汪汪叫");
    }
}

// 子类:Cat,重写抽象方法
class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }

    @Override
    public void cry() {
        System.out.println(name + "喵喵叫");
    }
}

// 测试类
public class AbstractDemo {
    public static void main(String[] args) {
        // 错误示例:抽象类不能实例化
        // Animal animal = new Animal("动物");
        
        // 父类引用指向子类对象(多态)
        Animal dog = new Dog("小狗");
        Animal cat = new Cat("小猫");
        
        dog.eat();
        dog.cry();
        
        cat.eat();
        cat.cry();
    }
}

5.2 接口(interface)

接口是“纯粹的抽象规范”,由interface关键字修饰,仅包含抽象方法(JDK 1.8+后可包含默认方法、静态方法),核心作用是定义“行为规范”,实现多继承的效果(一个类可以实现多个接口)。

5.2.1 接口的基本语法

// 接口:由interface修饰
interface 接口名 {
    // 常量(默认public static final,可省略)
    数据类型 常量名 = 值;

    // 抽象方法(JDK 1.8前,默认public abstract,可省略)
    void 抽象方法名();

    // 默认方法(JDK 1.8+,由default修饰,有方法体,子类可重写)
    default void 默认方法名() {
        // 方法实现
    }

    // 静态方法(JDK 1.8+,由static修饰,有方法体,通过接口名调用)
    static void 静态方法名() {
        // 方法实现
    }
}

5.2.2 接口的核心特性

  1. 接口不能实例化,只能被类实现(implements关键字);

  2. 一个类可以实现多个接口(class 类名 implements 接口1, 接口2,...),实现多继承效果;

  3. 接口中的抽象方法,实现类必须重写所有抽象方法(除非实现类是抽象类);

  4. 接口中的成员默认修饰符:常量(public static final)、抽象方法(public abstract);

  5. 默认方法(default):有方法体,子类可重写,也可直接继承;静态方法(static):有方法体,只能通过接口名调用,子类不能重写。

实战案例:接口的使用与多实现

// 接口1:Swim(游泳行为规范)
interface Swim {
    // 抽象方法(默认public abstract)
    void swim();

    // 默认方法
    default void showSwim() {
        System.out.println("会游泳的生物");
    }

    // 静态方法
    static void swimRule() {
        System.out.println("游泳规则:保持呼吸,手脚协调");
    }
}

// 接口2:Fly(飞行行为规范)
interface Fly {
    void fly();
}

// 实现类:Duck(鸭子),实现两个接口
class Duck implements Swim, Fly {
    @Override
    public void swim() {
        System.out.println("鸭子在水里游泳");
    }

    @Override
    public void fly() {
        System.out.println("鸭子低空飞行");
    }

    // 重写接口的默认方法(可选)
    @Override
    public void showSwim() {
        System.out.println("鸭子会游泳,也会飞");
    }
}

// 测试类
public class InterfaceDemo {
    public static void main(String[] args) {
        Duck duck = new Duck();
        duck.swim();
        duck.fly();
        duck.showSwim();

        // 调用接口的静态方法(通过接口名调用)
        Swim.swimRule();

        // 多态:接口引用指向实现类对象
        Swim swim = new Duck();
        swim.swim();

        Fly fly = new Duck();
        fly.fly();
    }
}

5.3 抽象类与接口的区别(核心考点)

对比维度抽象类(abstract class)接口(interface)
关键字abstract classinterface
继承/实现方式子类extends继承,单继承类implements实现,多实现
成员方法可包含抽象方法、非抽象方法JDK1.8前仅抽象方法;JDK1.8+可包含默认、静态方法
成员变量可包含各种类型变量(静态、非静态)仅能包含常量(public static final)
构造方法有构造方法(用于子类初始化)无构造方法
核心作用定义子类的共性,实现代码复用定义行为规范,实现多继承、解耦

六、内部类:类中的类

内部类是定义在另一个类(外部类)内部的类,核心作用是隐藏类的实现细节、实现类的复用,内部类可以访问外部类的所有成员(包括private成员),根据定义方式的不同,分为成员内部类、局部内部类、匿名内部类、静态内部类。

6.1 成员内部类(最常用)

成员内部类定义在外部类的成员位置(与外部类的变量、方法同级),无static修饰,属于外部类的对象,需通过外部类对象才能创建内部类对象。

核心特性

  1. 成员内部类可以访问外部类的所有成员(包括private);

  2. 外部类访问成员内部类的成员,需创建内部类对象;

  3. 创建内部类对象的语法:外部类对象.new 内部类名();

  4. 成员内部类不能定义静态成员(除非是静态常量)。

实战案例:成员内部类的使用

// 外部类
class Outer {
    // 外部类的成员变量(private)
    private String outerName = "外部类";

    // 成员内部类(无static修饰)
    class Inner {
        // 内部类的成员变量
        private String innerName = "内部类";

        // 内部类的方法
        public void show() {
            // 访问外部类的成员(包括private)
            System.out.println("外部类名称:" + outerName);
            System.out.println("内部类名称:" + innerName);
        }
    }

    // 外部类的方法,访问内部类成员
    public void outerMethod() {
        // 创建内部类对象
        Inner inner = new Inner();
        inner.show();
        System.out.println("访问内部类的成员:" + inner.innerName);
    }
}

// 测试类
public class MemberInnerDemo {
    public static void main(String[] args) {
        // 1. 先创建外部类对象
        Outer outer = new Outer();
        
        // 2. 通过外部类对象创建内部类对象
        Outer.Inner inner = outer.new Inner();
        
        // 3. 调用内部类的方法
        inner.show();
        
        // 4. 调用外部类的方法(间接访问内部类)
        outer.outerMethod();
    }
}

6.2 静态内部类

静态内部类定义在外部类的成员位置,由static修饰,属于外部类本身,无需创建外部类对象,可直接通过外部类名创建内部类对象。

核心特性

  1. 静态内部类不能访问外部类的非静态成员(只能访问外部类的静态成员);

  2. 创建静态内部类对象的语法:外部类名.内部类名 变量名 = new 外部类名.内部类名();

  3. 静态内部类可以定义静态成员和非静态成员;

  4. 静态内部类与外部类的非静态成员无关,独立性更强。

实战案例:静态内部类的使用

// 外部类
class OuterStatic {
    // 外部类的静态变量
    private static String outerStaticName = "外部类静态变量";
    
    // 外部类的非静态变量
    private String outerNonStaticName = "外部类非静态变量";

    // 静态内部类(static修饰)
    static class InnerStatic {
        // 静态内部类的静态成员
        public static String innerStaticName = "静态内部类静态变量";
        
        // 静态内部类的非静态成员
        public String innerNonStaticName = "静态内部类非静态变量";

        // 静态内部类的方法
        public void show() {
            // 可访问外部类的静态成员
            System.out.println("外部类静态变量:" + outerStaticName);
            // 不能访问外部类的非静态成员(编译报错)
            // System.out.println(outerNonStaticName);
            
            System.out.println("静态内部类静态变量:" + innerStaticName);
            System.out.println("静态内部类非静态变量:" + innerNonStaticName);
        }

        // 静态内部类的静态方法
        public static void staticShow() {
            System.out.println("静态内部类的静态方法");
        }
    }
}

// 测试类
public class StaticInnerDemo {
    public static void main(String[] args) {
        // 1. 直接创建静态内部类对象(无需外部类对象)
        OuterStatic.InnerStatic inner = new OuterStatic.InnerStatic();
        
        // 2. 调用静态内部类的非静态方法
        inner.show();
        
        // 3. 调用静态内部类的静态方法(直接通过内部类名)
        OuterStatic.InnerStatic.staticShow();
        
        // 4. 访问静态内部类的静态变量
        System.out.println(OuterStatic.InnerStatic.innerStaticName);
    }
}

6.3 局部内部类

局部内部类定义在外部类的方法、代码块等局部位置,无static修饰,仅在当前局部范围内有效(如方法内),访问权限仅限于当前局部范围。

核心特性

  1. 局部内部类仅能在定义它的局部范围内使用(如方法内),外部无法访问;

  2. 局部内部类可以访问外部类的所有成员,也可以访问局部范围内的final变量(JDK 1.8+后final可省略,但本质还是final);

  3. 局部内部类不能定义静态成员。

实战案例:局部内部类的使用

// 外部类
class OuterLocal {
    private String outerName = "外部类";

    // 外部类的方法
    public void outerMethod() {
        // 局部变量(JDK1.8+后可省略final,但本质是final)
        String localStr = "局部变量";

        // 局部内部类(定义在方法内)
        class InnerLocal {
            public void show() {
                // 访问外部类成员
                System.out.println("外部类名称:" + outerName);
                // 访问局部变量(本质是final)
                System.out.println("局部变量:" + localStr);
            }
        }

        // 仅能在当前方法内创建局部内部类对象并使用
        InnerLocal inner = new InnerLocal();
        inner.show();
    }
}

// 测试类
public class LocalInnerDemo {
    public static void main(String[] args) {
        OuterLocal outer = new OuterLocal();
        outer.outerMethod(); // 间接调用局部内部类的方法
    }
}

6.4 匿名内部类(重点,高频使用)

匿名内部类是“没有类名的局部内部类”,是局部内部类的简化形式,核心作用是简化代码,常用于实现接口、继承抽象类,仅使用一次的场景(如匿名实现接口、匿名子类)。

核心特性

  1. 匿名内部类没有类名,只能创建一个对象(仅使用一次);

  2. 匿名内部类必须继承一个类或实现一个接口;

  3. 匿名内部类的语法:new 父类/接口() { 重写方法; }

  4. 匿名内部类没有类名,只能创建一个对象(仅使用一次);

  5. 匿名内部类必须继承一个类或实现一个接口;

  6. 匿名内部类的语法:new 父类/接口() { 重写方法; }

  7. 匿名内部类可以访问外部类的所有成员,也可以访问局部范围内的final变量(JDK 1.8+后final可省略);

  8. 匿名内部类不能定义构造方法(无类名,无法定义),也不能定义静态成员。

实战案例1:匿名内部类实现接口(高频场景)

// 定义接口:Greet(问候行为规范)
interface Greet {
    void sayHello();
}

// 测试类
public class AnonymousInnerDemo1 {
    public static void main(String[] args) {
        // 匿名内部类:实现Greet接口,无类名,直接创建对象
        Greet greet = new Greet() {
            // 重写接口的抽象方法
            @Override
            public void sayHello() {
                System.out.println("你好,Java面向对象进阶!");
            }
        };
        
        // 调用匿名内部类的方法
        greet.sayHello();
        
        // 简化写法:匿名内部类直接作为方法参数(最常用)
        showGreet(new Greet() {
            @Override
            public void sayHello() {
                System.out.println("匿名内部类作为方法参数,简化代码");
            }
        });
    }
    
    // 方法:接收Greet接口类型的参数
    public static void showGreet(Greet greet) {
        greet.sayHello();
    }
}

实战案例2:匿名内部类继承抽象类

// 抽象类:Animal(动物)
abstract class Animal {
    private String name;
    
    // 抽象类的构造方法
    public Animal(String name) {
        this.name = name;
    }
    
    // 抽象方法
    public abstract void cry();
    
    // 非抽象方法
    public void eat() {
        System.out.println(name + "在吃饭");
    }
}

// 测试类
public class AnonymousInnerDemo2 {
    public static void main(String[] args) {
        // 匿名内部类:继承Animal抽象类,重写抽象方法
        Animal dog = new Animal("小狗") {
            @Override
            public void cry() {
                System.out.println(name + "汪汪叫");
            }
        };
        
        // 调用匿名内部类的方法(重写的抽象方法+继承的非抽象方法)
        dog.eat();
        dog.cry();
    }
}

避坑点

  1. 匿名内部类仅能使用一次,若需多次使用该类的逻辑,需定义普通的内部类或外部类,避免重复编写代码;

  2. 匿名内部类中不能使用break、continue语句(除非嵌套在循环/switch中,且作用于循环/switch内部);

  3. 匿名内部类访问局部变量时,变量本质是final的,即使省略final关键字,也不能在匿名内部类中修改该变量的值(编译报错)。

七、面向对象进阶核心总结与避坑指南

本文围绕Java面向对象进阶核心知识点展开,从static关键字到内部类,覆盖继承、多态、包与权限、抽象类与接口等核心内容,结合实战案例拆解用法,以下是核心总结与高频避坑点,帮助快速巩固知识点、规避开发中的常见错误。

7.1 核心知识点总结

  1. static关键字:修饰成员属于类本身,优先于对象存在,静态成员不能访问非静态成员,常用于工具类、静态常量定义;

  2. 继承:通过extends实现单继承,核心是代码复用,子类继承父类非私有成员,super关键字用于访问父类成员;

  3. 多态:核心是“父类引用指向子类对象”,需满足继承、方法重写、向上转型三个条件,实现代码灵活扩展;

  4. 包与权限:包用于组织类、避免类名冲突,4种访问权限控制成员访问范围,封装的核心实现手段;

  5. 抽象类与接口:均用于定义规范,抽象类侧重共性复用(可含非抽象方法),接口侧重行为规范(多实现,解耦);

  6. 内部类:分为4种,核心是隐藏实现细节,匿名内部类简化代码,常用于单次使用的接口实现/抽象类继承场景。

7.2 高频避坑指南(重点)

  1. static相关:静态方法不能用this,不能访问非静态成员;静态变量不要存储对象独有数据,避免数据混乱;

  2. 继承相关:子类构造方法默认调用父类无参构造,父类无无参构造时,子类必须手动用super()调用父类有参构造;

  3. 方法重写vs隐藏:静态方法不能重写,只能隐藏;重写必须满足方法签名、返回值、访问权限的核心规则,建议添加@Override注解;

  4. 多态相关:向下转型前必须用instanceof判断类型,避免ClassCastException;父类引用不能直接访问子类独有成员;

  5. 抽象类与接口:抽象类不能实例化,但有构造方法;接口不能有构造方法,JDK1.8+的默认方法可重写、静态方法只能通过接口名调用;

  6. 内部类相关:成员内部类需通过外部类对象创建,静态内部类不能访问外部类非静态成员;匿名内部类不能修改局部变量的值。

掌握以上知识点,可夯实Java面向对象进阶基础,应对日常开发、笔试面试中的核心考点,后续可结合单例模式、工厂模式等设计模式,进一步深化面向对象编程思维。

Java面向对象进阶全解:从static到内部类,吃透OOP核心