Java类与对象及面向对象基础核心详细笔记

一、类与对象

1.1 类的定义

是对现实世界中同一类事物的抽象描述,是抽取事物共性特征(属性)和行为(方法)后的模板,不代表具体实物。比如现实中的鸟类、人类、书籍,都是对一类事物的统称,而非某一个具体的个体。类封装了对象的属性和行为,是创建对象的唯一依据,其中属性用成员变量表示,行为用成员方法表示。

通俗比喻:类就像是汽车的设计图纸,图纸上规定了汽车的颜色、品牌、排量(属性),也规定了汽车能跑、能鸣笛、能刹车(行为),但图纸本身不是真正的汽车,只是一个制作标准;再比如"人类"这个类,抽象了所有人的共性------有姓名、年龄(属性),能说话、能走路(行为),但"人类"不是具体的某个人。

1.1.1 类的完整语法格式
java 复制代码
// 基础类定义,权限修饰符可选,常用public
[权限修饰符] class 类名 {
    // 一、成员变量(属性):描述事物的特征,对应类的静态属性
    [权限修饰符] 数据类型 变量名;
    
    // 二、成员方法(行为):描述事物的动作,对应类的动态行为
    [权限修饰符] 返回值类型 方法名(参数列表) {
        // 方法体:具体执行逻辑,实现行为的具体功能
        return 返回值; // 无返回值则用void,省略return语句(或写return;)
    }
}
(1)成员变量

成员变量是类的属性,作用范围覆盖整个类,在类的所有成员方法中都可以直接调用,无需额外声明。其数据类型可以是基本数据类型(int、double、char、boolean等),也可以是引用数据类型(类、数组、接口等)。

成员变量必须遵循权限修饰符的约束,常用权限修饰符:public(所有类可见)、protected(同包及子类可见)、默认(无修饰符,同包可见)、private(仅本类可见),其中private最常用,用于隐藏内部属性,避免外部直接修改,保证数据安全。

(2)成员方法

成员方法对应类对象的行为,是对象可以执行的操作。如果方法无返回值,必须用void关键字修饰,方法体中可以省略return语句,若写return,只需写return;(无返回值);如果有返回值,必须指定返回值类型(与return后的返回值类型一致),且方法体中必须有return语句,返回对应类型的数据。

如果方法内定义了和成员变量同名的局部变量,方法中优先访问局部变量,遵循"就近原则"------即谁离代码更近,就访问谁。此时若想访问成员变量,必须使用this关键字区分。

(3)类的完整代码示例
java 复制代码
// 声明公开的Book类,类名首字母大写,遵循驼峰命名法
public class Book{    
    // 私有成员变量:书籍名称,外部无法直接访问(封装思想)
    private String name;
    // 私有成员变量:书籍价格,外部无法直接访问
    private double price;
    
    // 成员方法:获取书籍名称(Getter方法,对外提供访问接口)
    public String getName(){    
        return name;
    }
    
    // 成员方法:修改书籍名称(Setter方法,对外提供修改接口,可加校验)
    public void setName(String name){    
        // this关键字区分成员变量(this.name)和局部变量(name)
        this.name = name;
    }
    
    // 成员方法:获取书籍价格(Getter方法)
    public double getPrice() {
        return price;
    }
    
    // 成员方法:修改书籍价格,可加校验逻辑(避免非法值)
    public void setPrice(double price) {
        if(price > 0){ // 校验:价格必须为正数
            this.price = price;
        } else {
            System.out.println("书籍价格不能为负数!");
        }
    }
}
1.1.2 局部变量

局部变量是定义在成员方法内部、方法参数列表、代码块(如for循环、if语句块)中的变量,仅在当前作用域内有效,作用范围从变量声明开始,到所在大括号({})结束为止。

比如上述Book类中setName方法的参数name,就是局部变量,只能在setName方法内部使用;再比如方法体中定义的int temp = 10;,temp也属于局部变量,方法执行完毕后,局部变量会被JVM自动销毁,占用的内存空间释放,再次调用方法时会重新创建。

新手易错点:局部变量没有默认值,必须先声明、再赋值,才能使用;而成员变量有默认值(基本数据类型默认0、false等,引用数据类型默认null),即使不赋值也能使用(不推荐)。

1.1.3 this关键字

this关键字表示当前类的对象引用,指向当前正在操作的本类对象,只能在本类内部使用,不能在类外直接调用。

核心作用:区分同名的成员变量和局部变量,解决"就近原则"导致的变量访问冲突。当方法内局部变量与成员变量同名时,this.变量名 代表成员变量,直接写变量名代表局部变量;此外,this还可以调用本类的其他构造方法(this(参数列表)),后续构造方法部分会详细说明。

新手易错点:不要误以为this指向某个new出来的具体对象,它代表的是"当前类的对象引用"------哪个对象调用方法,this就指向哪个对象。比如new Book()调用setName方法,this就指向这个新创建的Book对象。

1.2 对象的定义

对象是类的实例化结果,是类对应的具体实体,拥有类中定义的所有属性和行为,是现实世界中真实存在的事物。比如根据汽车图纸(类)造出来的某一辆特斯拉汽车(对象),根据Book类创建的某一本《Java编程思想》书籍(对象),根据"人类"类创建的"张三"(对象)。

核心特点:每个对象在内存中都有独立的存储空间(堆内存),拥有独立的属性值,对象之间互不干扰;比如两个Book对象,一个name是《Java入门》,一个name是《Python入门》,二者属性互不影响;当对象生命周期结束(没有引用指向它),会被JVM的垃圾回收机制自动回收,无法再使用。

1.2.1 对象的创建与使用

创建对象分为"声明对象"和"实例化对象"两步,也可以合并为一步完成,实例化的核心是使用new关键字------new的作用是为对象分配堆内存空间,并调用类的构造方法完成初始化。

(1)对象创建语法
java 复制代码
// 方式一:先声明,后实例化(分步执行,清晰易懂)
// 1. 声明对象:指定对象类型和名称,仅在栈内存开辟空间,存储对象引用(地址)
类名 对象名;
// 2. 实例化对象:new关键字创建对象(堆内存分配空间),赋值引用地址给对象名
对象名 = new 类名();

// 方式二:声明+实例化一步完成(日常开发最常用,简洁高效)
类名 对象名 = new 类名();
(2)对象的属性和方法调用
java 复制代码
// 访问对象属性(私有属性需通过Getter/Setter调用,不能直接访问,体现封装)
对象名.属性; // 仅适用于public修饰的属性
// 调用对象的成员方法
对象名.方法名(参数列表);
// 为对象属性赋值(私有属性通过Setter赋值,可做校验)
对象名.方法名(属性值);
(3)对象使用完整示例
java 复制代码
public class TestBook {
    public static void main(String[] args) {
        // 创建Book类的对象,实例化(new关键字分配内存,调用无参构造)
        Book book = new Book();
        // 通过Setter方法为对象属性赋值(避免直接修改私有属性)
        book.setName("Java从入门到精通");
        book.setPrice(59.8);
        // 通过Getter方法获取对象属性(安全访问私有属性)
        String name = book.getName();
        double price = book.getPrice();
        // 输出对象属性值
        System.out.println("书籍名称:" + name + ",书籍价格:" + price);
    }
}

1.3 值传递与引用传递

Java中方法调用的参数传递只有两种形式,分别对应基本数据类型和引用数据类型,二者的传递本质完全不同,是新手最容易混淆的知识点,核心区别在于"传递的是数据副本"还是"对象的内存地址"。

1.3.1 值传递(基本数据类型)

传递本质:传递的是数据的副本,方法内部修改的是副本的值,不会影响原始数据(原始变量的值不变)。

通俗比喻:就像复印文件,把原件(原始变量)复印一份给别人(传入方法),别人在复印件上涂改、修改,原件不会有任何变化------原件和复印件是两个独立的个体,互不影响。

适用类型:int、double、float、char、boolean、byte、short、long八大基本数据类型。

java 复制代码
public class TestValueTransfer {
    public static void main(String[] args) {
        // 定义基本数据类型变量(原始数据,存在栈内存)
        int num = 10;
        // 调用方法,传递变量副本(不是原始数据本身)
        changeNum(num);
        // 原始值不变,依旧输出10(方法内修改的是副本)
        System.out.println(num);
    }
    
    // 方法接收的是num的副本,形参n是副本变量
    public static void changeNum(int n) {
        // 仅修改副本的值,与原始变量num无关
        n = 20;
    }
}
1.3.2 引用传递(引用数据类型)

传递本质:传递的是对象的内存地址(引用),方法内部通过地址找到堆内存中的同一个对象,修改对象的属性值,会直接影响原始对象(因为原始对象和方法内的形参指向同一个堆内存空间)。

通俗比喻:就像分享储物箱地址,你有一个储物箱(对象,存在堆内存),里面放了东西(对象的属性),你把储物箱的"地址纸条"(对象引用,存在栈内存)交给同事(传入方法)。同事根据地址找到同一个储物箱,修改了里面的物品(对象属性),你再去看时,里面的物品也会同步改变------你们操作的是同一个储物箱,只是持有同一个地址的"纸条"。

适用类型:类、数组、接口等引用数据类型。

java 复制代码
// 定义Person类(引用数据类型)
class Person {
    String name; // 成员变量(对象的属性)
    // 构造方法(后续详细说明,用于初始化对象)
    Person(String name) {
        this.name = name;
    }
}

public class TestRefTransfer {
    public static void main(String[] args) {
        // 创建Person对象,获取对象引用(栈内存存地址,堆内存存对象本身)
        Person person = new Person("张三");
        // 传递对象引用(内存地址),形参p接收的是地址副本
        changeName(person);
        // 原始对象属性被修改,输出李四(方法内通过地址修改了堆内存中的对象)
        System.out.println(person.name);
    }
    
    // 方法接收对象引用(地址),p和person指向同一个堆内存对象
    public static void changeName(Person p) {
        // 通过引用(地址)找到对象,修改对象内部属性
        p.name = "李四";
    }
}

新手高频误区:如果在方法内重新new对象并赋值给形参,只是修改了方法内的引用副本(栈内存的地址),不会改变原始对象的指向,原始对象依旧不变。比如修改上述changeName方法:

java 复制代码
public static void changePerson(Person p) { 
p = new Person("王五"); // 重新new对象,形参p指向新的堆内存地址 
} // 调用后,person.name依旧是张三,因为原始对象的引用(地址)没变化

核心总结:值传递传"副本",修改副本不影响原始数据;引用传递传"地址",修改对象属性影响原始对象,但修改引用地址(重新new)不影响原始对象。

二、类的构造方法

2.1 构造方法定义

构造方法是类中用于创建对象、初始化对象属性的特殊方法,每当使用new关键字实例化对象时,JVM会自动调用对应的构造方法,完成对象的初始化(比如给成员变量赋值),无需手动调用。

通俗比喻:构造方法就像"对象的出生初始化程序",比如一个婴儿(对象)出生时,会自动执行"初始化身高、体重"的程序(构造方法),无需手动操作;对应到Book类,new Book()时,构造方法会自动初始化name、price等属性。

2.2 构造方法语法格式

java 复制代码
[权限修饰符] 类名(参数列表) {
    // 方法体:初始化对象属性的逻辑(比如给成员变量赋值)
}

2.3 构造方法核心特点(必记)

  1. 构造方法的方法名必须与类名完全一致,大小写也要完全相同(比如类名是Book,构造方法名必须是Book,不能是book或Book1);

  2. 构造方法没有返回值,不需要写void关键字,这是和普通成员方法最核心的区别(普通方法必须写返回值类型,无返回值写void);

  3. 一个类可以有多个构造方法,即构造方法重载------多个构造方法的方法名相同,参数列表(参数个数、参数类型、参数顺序)不同;

  4. 如果类中没有手动编写构造方法,JVM会自动生成一个无参的空构造方法(方法体为空),用于默认初始化;

  5. 如果手动编写了构造方法(无论有参还是无参),JVM不再自动生成无参构造,此时若需要无参构造,必须手动编写(否则new 类名()会报错);

  6. 构造方法可以调用本类的其他构造方法,使用this(参数列表),且this(...)必须放在构造方法的第一行(否则编译报错)。

新手易错点:1. 给构造方法写了void关键字,导致它变成普通方法,JVM会认为没有构造方法,new对象时报错;2. 手动写了有参构造,忘记写无参构造,后续用new 类名()创建对象时报错。

2.4 构造方法代码示例

java 复制代码
public class Student {
    private String name; // 成员变量:姓名
    private int age;     // 成员变量:年龄

    // 1. 无参构造方法:JVM默认会生成,手动编写可做初始化操作
    public Student() {
        System.out.println("无参构造方法被调用,对象初始化完成");
        // 可手动初始化属性,避免默认值(如name默认null,手动设为"未知")
        this.name = "未知";
        this.age = 0;
    }

    // 2. 有参构造方法:初始化姓名(构造重载,参数列表不同)
    public Student(String name) {
        this.name = name; // 初始化姓名
    }

    // 3. 有参构造方法:初始化姓名和年龄(构造重载)
    public Student(String name, int age) {
        // this(name); // 调用本类的单参构造方法(必须在第一行)
        this.name = name;
        this.age = age;
    }

    // Getter和Setter方法:对外提供属性访问/修改接口
    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

// 测试构造方法(调用不同构造方法创建对象)
class TestStudent {
    public static void main(String[] args) {
        // 调用无参构造,name=未知,age=0
        Student s1 = new Student();
        // 调用单参构造,name=小明,age默认0
        Student s2 = new Student("小明");
        // 调用双参构造,name=小红,age=18
        Student s3 = new Student("小红", 18);
        
        // 输出对象属性,验证初始化结果
        System.out.println(s1.getName() + "," + s1.getAge()); // 未知,0
        System.out.println(s2.getName() + "," + s2.getAge()); // 小明,0
        System.out.println(s3.getName() + "," + s3.getAge()); // 小红,18
    }
}

三、静态变量和静态方法

3.1 静态成员定义

static关键字修饰的成员变量和成员方法,称为静态变量(类变量)和静态方法(类方法),统称为静态成员。静态成员不属于单个对象,属于整个类,被该类的所有对象共享,随类的加载而加载(JVM加载类时就创建静态成员),优先于对象存在(对象创建前,静态成员就已经存在)。

通俗比喻:静态成员就像"班级的公共物品",比如班级的黑板、投影仪,属于整个班级(类),所有同学(对象)都可以使用,且共用一个------黑板上的内容被一个同学修改,其他同学看到的都是修改后的内容;而普通成员变量就像"同学的个人物品",每个人的都不一样,互不影响。

3.2 静态成员调用语法

静态成员可以通过类名.静态成员直接调用(推荐,符合静态成员"属于类"的定义),也可以通过对象调用(不推荐,容易混淆静态成员和普通成员),但本质都是调用类的静态成员。

java 复制代码
// 类名.静态变量(推荐调用方式)
类名.静态变量名;
// 类名.静态方法(推荐调用方式)
类名.静态方法名(参数列表);

// 不推荐:对象.静态成员(容易误以为静态成员属于对象)
对象名.静态变量名;
对象名.静态方法名(参数列表);

3.3 静态成员代码示例

java 复制代码
public class StaticDemo {
    // 静态变量:所有对象共享,随类加载而创建,存储在方法区
    static int x = 10;
    // 普通成员变量:每个对象独有,随对象创建而创建,存储在堆内存
    private String str = "test";
    
    // 静态方法:属于类,可直接通过类名调用
    public static void method(){
        System.out.println("静态方法示例");
        // 静态方法中不能直接调用普通成员(str)和普通方法
        // System.out.println(str); // 报错
        // 若要调用,需创建对象
        StaticDemo demo = new StaticDemo();
        System.out.println(demo.str); // 正确
    }
    
    // 普通方法:属于对象,需创建对象才能调用
    public void commonMethod() {
        System.out.println("普通方法示例");
        // 普通方法中可以直接调用静态成员(静态优先于对象存在)
        System.out.println(x);
        method();
    }
    
    public static void main(String[] args){
        // 直接通过类名调用静态变量和静态方法(推荐)
        System.out.println(StaticDemo.x); // 输出10
        StaticDemo.method(); // 输出静态方法示例
        
        // 通过对象调用静态成员(不推荐)
        StaticDemo demo = new StaticDemo();
        System.out.println(demo.x); // 输出10(依旧是类的静态变量)
        demo.method(); // 输出静态方法示例
    }
}

3.4 静态成员使用注意事项(必记)

  1. 静态成员遵循public、private、protected权限修饰符的约束------private修饰的静态成员,只能在本类内部访问,外部无法通过类名调用;

  2. 静态方法中不能使用this关键字,因为this属于对象,而静态方法属于类,加载时还没有对象;

  3. 静态方法中不能直接调用非静态方法和非静态变量,需要创建对象后,通过对象调用(非静态成员属于对象,静态方法加载时无对象);

  4. 局部变量不能使用static修饰,只有类的成员变量、成员方法、内部类可以用static修饰;

  5. 程序的主方法(main方法)必须用static修饰,是程序的入口------JVM执行程序时,先加载主类,再调用静态的main方法,无需创建对象;

  6. 只有内部类可以使用static修饰(静态内部类),外部类不能用static修饰(外部类本身就是独立的类,无需静态修饰);

  7. 静态变量的生命周期与类一致,类加载时创建,类卸载时销毁;普通成员变量的生命周期与对象一致,对象销毁时,普通成员变量也随之销毁。

四、类的主方法

4.1 主方法定义

**主方法(main方法)**是Java程序的入口点,JVM执行程序时,会从main方法开始运行,控制整个程序的执行流程------没有main方法,Java程序无法运行(除了web程序、单元测试等特殊场景)。

4.2 主方法固定语法(必须死记)

java 复制代码
public static void main(String[] args){
    // 程序执行逻辑(代码从这里开始执行)
}

4.3 主方法核心特点(必记)

  1. 主方法必须用public static void修饰,固定格式,不能修改------public保证JVM能访问到,static保证JVM无需创建对象就能调用,void保证无返回值;

  2. 主方法没有返回值,必须用void修饰,不能写return语句(或仅写return;,无实际意义);

  3. 主方法的形参是字符串数组args(argument的缩写,意为"参数"),用于接收命令行传入的参数;

  4. args[0]到args[n]对应传入的第一个到第n个参数,args.length可获取参数的个数(命令行未传入参数时,args.length=0,不会报错);

  5. 主方法内直接调用的其他方法,必须也是静态方法------因为主方法是静态的,静态方法不能直接调用非静态方法,非静态方法需要创建对象后调用;

  6. 主方法只能定义在类中,不能定义在方法内部(主方法本身是类的成员方法);

  7. 一个类中可以有多个main方法吗?可以,但JVM只会执行"public static void main(String[] args)"这个固定格式的主方法,其他格式的main方法(如参数不同)会被当作普通静态方法。

4.4 主方法接收命令行参数示例

java 复制代码
public class MainDemo {
    public static void main(String[] args) {
        // 输出命令行传入的参数个数
        System.out.println("传入的参数个数:" + args.length);
        // 遍历输出所有参数
        for (int i = 0; i < args.length; i++) {
            System.out.println("第" + (i+1) + "个参数:" + args[i]);
        }
    }
}

运行方式:通过命令行进入该类的编译目录,执行"java MainDemo 张三 18 男",输出结果为:

java 复制代码
传入的参数个数:3
第1个参数:张三
第2个参数:18
第3个参数:男

五、面向对象的核心特性

面向对象编程(OOP)是Java的核心编程思想,主要依托三大核心特性实现,分别是封装、继承、多态,这三大特性是类与对象的延伸,也是区分面向过程和面向对象的关键------面向过程关注"步骤",面向对象关注"对象和交互"。

  1. 封装:核心是"隐藏对象的内部属性和实现细节,仅对外提供公共的访问接口"。就像手机的内部零件(属性和实现)被外壳包裹,用户不需要知道零件如何工作,只需通过屏幕、按键(公共接口)操作手机即可。封装的目的是保证数据安全,避免外部直接修改对象内部属性,同时降低代码的耦合度(修改内部实现,不影响外部调用)。 实现方式:用private修饰成员变量,通过public的Getter/Setter方法对外提供访问和修改接口,可在Setter方法中添加校验逻辑(如书籍价格不能为负数)。

  2. 继承:核心是"子类继承父类的属性和方法,实现代码复用,建立类与类之间的层级关系"。就像儿子继承父亲的财产和技能,儿子可以直接使用父亲的东西,还可以拥有自己的专属财产和技能。继承的目的是减少重复代码,提升开发效率,建立类的层级结构(如Animal是父类,Dog、Cat是子类)。 关键语法:用extends关键字实现继承,格式为"class 子类名 extends 父类名",Java支持单继承(一个子类只能有一个父类),但可以通过接口实现多继承的效果。

  3. 多态:核心是"父类引用指向子类对象,同一个方法调用,根据子类的不同实现,产生不同的执行结果"。就像一个遥控器(父类引用),可以控制电视、空调(子类对象),按下同一个"开机"按钮(方法),电视和空调的执行效果不同。多态的目的是提升代码的扩展性和灵活性,降低代码的耦合度。 实现条件:子类继承父类、子类重写父类方法、父类引用指向子类对象(向上转型)。

记忆技巧:封装是"藏起来,留接口",继承是"拿过来,再扩展",多态是"一个接口,多种实现",三者相辅相成,构成面向对象编程的核心。

三大特性并非孤立存在,而是相互配合、协同作用,共同构建出灵活、可复用、易维护的Java代码。其中封装是基础,为继承和多态提供了数据安全的保障;继承是核心,实现了代码的复用和类的层级划分;多态是延伸,提升了代码的扩展性和灵活性。接下来,我们将逐一详细拆解每一个特性的具体实现、语法规则、代码示例及新手易错点,帮助大家彻底掌握面向对象编程的核心逻辑,真正将三大特性运用到实际开发中。

5.1 封装(Encapsulation)

封装是面向对象的基础特性,核心思想是"隐藏内部细节,暴露公共接口",即将对象的属性和实现逻辑隐藏起来,不允许外部直接访问,仅通过预先定义的公共方法(Getter/Setter方法、业务方法)来访问和操作对象,以此保证数据的安全性和代码的可维护性。

通俗比喻:封装就像一个密封的保温杯,杯身(外部)只能看到杯盖、杯柄(公共接口),看不到内部的保温层、内胆(内部细节);用户只需通过杯盖(接口)加水、倒水,无需知道内胆如何保温,也不能直接触碰内胆------这样既保护了内胆(内部数据),也避免了用户操作不当导致的损坏(数据异常)。

5.1.1 封装的实现步骤(必会)
  1. 隐藏属性:使用private权限修饰符修饰成员变量,禁止外部直接访问(private修饰的成员,仅本类可见);

  2. 暴露接口:提供public修饰的Getter方法(获取属性值)和Setter方法(修改属性值),作为外部访问对象属性的唯一途径;

  3. 添加校验:在Setter方法中添加逻辑校验,防止非法值传入,保证数据的合法性(这是封装的核心价值之一)。

5.1.2 封装完整代码示例
java 复制代码
// 封装示例:User类,封装用户的姓名、年龄属性
public class User {
    // 1. 隐藏属性:private修饰,外部无法直接访问
    private String name; // 姓名
    private int age;     // 年龄

    // 2. 暴露接口:Getter方法(获取属性值)
    public String getName() {
        return name;
    }

    // 3. 暴露接口:Setter方法(修改属性值),添加校验逻辑
    public void setName(String name) {
        // 校验:姓名不能为null或空字符串
        if (name != null && !name.trim().isEmpty()) {
            this.name = name;
        } else {
            System.out.println("姓名不能为空!");
        }
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        // 校验:年龄必须在0-120之间,符合现实逻辑
        if (age >= 0 && age <= 120) {
            this.age = age;
        } else {
            System.out.println("年龄输入非法,需在0-120之间!");
        }
    }

    // 额外业务方法:封装对象的行为(也是接口的一部分)
    public void showInfo() {
        System.out.println("姓名:" + this.name + ",年龄:" + this.age);
    }
}

// 测试封装效果
class TestEncapsulation {
    public static void main(String[] args) {
        User user = new User();

        // 错误:无法直接访问private修饰的属性(编译报错)
        // user.name = "张三";
        // user.age = 150;

        // 正确:通过Setter方法访问和修改属性,自动触发校验
        user.setName("张三");
        user.setAge(150); // 输出:年龄输入非法,需在0-120之间!
        user.setAge(20);  // 校验通过,成功赋值

        // 通过Getter方法获取属性值
        System.out.println("获取姓名:" + user.getName()); // 输出:张三
        System.out.println("获取年龄:" + user.getAge());   // 输出:20

        // 调用业务方法
        user.showInfo(); // 输出:姓名:张三,年龄:20
    }
}
5.1.3 封装的核心价值与注意事项

核心价值

  • 数据安全:防止外部直接修改属性,避免非法数据传入(如年龄不能为负数、姓名不能为空);

  • 低耦合:外部只需关注接口的使用,无需关心内部实现逻辑------即使修改内部校验规则,外部调用方式不变;

  • 可维护性:集中管理属性的访问和修改逻辑,后续修改时只需修改Setter方法,无需修改所有调用处。

新手易错点:1. 只隐藏属性,不提供Getter/Setter方法,导致外部无法访问属性,封装失去意义;2. Setter方法不添加校验逻辑,仅简单赋值,违背封装"保证数据安全"的核心目的;3. 用public修饰成员变量,相当于没有封装,失去了封装的价值。

5.2 继承(Inheritance)

继承是面向对象的核心特性之一,核心思想是"子类继承父类的属性和方法,同时可以添加自己的专属属性和方法",建立类与类之间的层级关系(父类→子类),实现代码复用,减少重复代码,提升开发效率。

通俗比喻:继承就像家族遗传,父亲(父类)有身高、肤色等特征(属性),有说话、走路等行为(方法),儿子(子类)会继承父亲的这些特征和行为,同时还会有自己的专属特征(如近视)和专属行为(如弹钢琴);再比如,Animal(动物)类有eat(吃)、sleep(睡)方法,Dog(狗)类继承Animal类,就可以直接使用eat和sleep方法,无需重复编写,只需添加自己的bark(叫)方法即可。

5.2.1 继承的语法格式
java 复制代码
// 父类(基类、超类):被继承的类,封装子类的共性属性和方法
class 父类名 {
    // 父类的属性和方法
}

// 子类(派生类):继承父类的类,拥有父类的所有属性和方法,可添加自己的内容
class 子类名 extends 父类名 {
    // 子类的专属属性和方法(可选)
}

关键说明

  • Java只支持单继承:一个子类只能有一个父类(就像人只能有一个亲生父亲),但一个父类可以有多个子类(一个父亲可以有多个儿子);

  • 子类继承父类的所有非private成员(属性和方法):private修饰的成员被父类隐藏,子类无法直接访问,需通过父类的public方法间接访问;

  • 继承的传递性:如果A继承B,B继承C,那么A会继承B和C的所有非private成员(如爷爷→父亲→儿子,儿子继承爷爷和父亲的特征)。

5.2.2 继承的核心:super关键字

super关键字与this关键字类似,this代表"当前对象",super代表"父类对象的引用",主要用于在子类中访问父类的成员(属性、方法、构造方法),解决子类与父类成员同名的冲突。

super的三大作用

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

  2. 调用父类的成员方法:当子类重写父类方法后,用super.方法名(参数)调用父类的原方法;

  3. 调用父类的构造方法:用super(参数列表)调用父类的对应构造方法,必须放在子类构造方法的第一行(与this(参数)不能同时使用)。

5.2.3 方法重写(Override)------继承的核心延伸

方法重写是指子类继承父类后,对父类的某个方法进行重新实现(方法名、参数列表、返回值类型完全一致),实现子类自己的业务逻辑------父类的方法是"通用模板",子类根据自身需求修改模板,体现"子类的特殊性"。

通俗比喻:父亲(父类)会做饭(方法),做的是家常菜;儿子(子类)继承了父亲的做饭方法,但觉得家常菜不好吃,于是改进了做法(重写方法),做的是红烧肉------方法名(做饭)不变,但实现逻辑(做法)不同。

方法重写的核心规则(必记)

  1. 方法名、参数列表、返回值类型必须与父类完全一致(返回值可以是父类返回值的子类,新手可忽略,称为"协变返回值");

  2. 子类重写方法的访问权限不能比父类更严格:父类方法是public,子类重写后不能是private/protected;父类是protected,子类不能是private;

  3. 子类重写方法不能抛出比父类更多、更严重的异常(新手可暂时忽略);

  4. 用@Override注解标记重写方法(可选,但推荐):告诉编译器这是重写方法,若方法名、参数写错,编译器会报错,避免手写错误。

5.2.4 继承与方法重写完整代码示例
java 复制代码
// 父类:Animal,封装所有动物的共性属性和方法
class Animal {
    // 父类成员变量:姓名
    protected String name;

    // 父类构造方法
    public Animal(String name) {
        this.name = name;
        System.out.println("Animal的构造方法被调用");
    }

    // 父类成员方法:吃(通用方法)
    public void eat() {
        System.out.println(name + "在吃东西");
    }

    // 父类成员方法:睡(通用方法)
    public void sleep() {
        System.out.println(name + "在睡觉");
    }
}

// 子类:Dog,继承Animal类
class Dog extends Animal {
    // 子类专属成员变量:颜色
    private String color;

    // 子类构造方法:必须先调用父类构造方法(super关键字)
    public Dog(String name, String color) {
        super(name); // 调用父类的有参构造,必须在第一行
        this.color = color;
        System.out.println("Dog的构造方法被调用");
    }

    // 方法重写:重写父类的eat方法,实现狗的专属逻辑
    @Override
    public void eat() {
        super.eat(); // 调用父类的eat方法(先执行父类逻辑)
        System.out.println(color + "的" + name + "在吃骨头"); // 子类专属逻辑
    }

    // 子类专属方法:汪汪叫(父类没有的方法)
    public void bark() {
        System.out.println(color + "的" + name + "在汪汪叫");
    }
}

// 测试继承和方法重写
class TestInheritance {
    public static void main(String[] args) {
        // 创建子类对象:调用子类构造方法,会先调用父类构造方法
        Dog dog = new Dog("小黑", "黑色");

        // 调用继承自父类的方法
        dog.sleep(); // 输出:小黑在睡觉

        // 调用重写后的方法
        dog.eat();    // 输出:小黑在吃东西 → 黑色的小黑在吃骨头

        // 调用子类专属方法
        dog.bark();   // 输出:黑色的小黑在汪汪叫

        // 访问继承自父类的成员变量(protected修饰,子类可直接访问)
        System.out.println("狗的名字:" + dog.name); // 输出:小黑
    }
}
5.2.5 继承的注意事项(必记)

新手易错点

  1. 子类构造方法中,必须先调用父类构造方法(super(...)),若不手动调用,JVM会自动调用父类的无参构造;若父类没有无参构造(手动写了有参构造),子类必须手动调用父类的有参构造,否则编译报错;

  2. 不要为了复用代码盲目继承:继承的前提是"is-a"关系(子类是父类的一种),比如Dog is a Animal(狗是一种动物),合理;但Dog extends Car(狗继承汽车),逻辑错误,此时应使用组合而非继承;

  3. private修饰的父类成员,子类无法直接访问,需通过父类的public Getter/Setter方法间接访问;

  4. 方法重写时,不要修改方法名、参数列表,否则会变成方法重载,而非重写。

继承的核心价值:减少重复代码,建立类的层级关系,为多态奠定基础------没有继承,就没有多态。

5.3 多态(Polymorphism)

多态是面向对象的三大特性之一,也是Java编程中灵活性的核心来源,核心思想是"父类引用指向子类对象,同一个方法调用,根据子类的不同实现,产生不同的执行结果"------即"一个接口,多种实现"。

通俗比喻:多态就像一个多功能遥控器(父类引用),可以控制电视、空调、灯(子类对象);按下同一个"开机"按钮(方法),电视会开机播放节目,空调会开机制冷,灯会开机发光------同一个方法,不同对象有不同的执行效果;再比如,Animal类的makeSound(发声)方法,Dog类重写后是"汪汪叫",Cat类重写后是"喵喵叫",用Animal引用分别指向Dog和Cat对象,调用makeSound方法,会产生不同的声音。

5.3.1 多态的实现条件(必记)
  1. 存在继承关系:子类必须继承父类(或实现接口,接口多态后续讲解);

  2. 子类重写父类方法:子类对父类的某个方法进行重新实现,这是多态的核心;

  3. 父类引用指向子类对象:声明一个父类类型的变量,赋值为子类对象(即"向上转型")。

5.3.2 多态的核心原则:编译看左边,运行看右边

多态的核心本质是"编译时类型"与"运行时类型不一致":

  • 编译时看左边:父类引用(左边)只能调用父类中存在的方法,不能调用子类的专属方法(编译器不知道引用指向的是哪个子类对象);

  • 运行时看右边:父类引用实际指向的是子类对象(右边),因此执行的是子类重写后的方法,而非父类的原方法。

5.3.3 多态完整代码示例
java 复制代码
// 父类:Animal
class Animal {
    protected String name;

    public Animal(String name) {
        this.name = name;
    }

    // 父类方法:发声(将被子类重写)
    public void makeSound() {
        System.out.println(name + "在发出声音");
    }
}

// 子类1:Dog,继承Animal,重写makeSound方法
class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

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

    // 子类专属方法:汪汪叫(父类没有)
    public void bark() {
        System.out.println(name + "在狂吠");
    }
}

// 子类2:Cat,继承Animal,重写makeSound方法
class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }

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

// 测试多态
class TestPolymorphism {
    public static void main(String[] args) {
        // 1. 父类引用指向子类对象(向上转型,多态的核心)
        Animal animal1 = new Dog("小黑");
        Animal animal2 = new Cat("小白");

        // 2. 调用方法:编译看左边(Animal有makeSound方法,编译通过)
        // 运行看右边(实际执行子类重写后的方法)
        animal1.makeSound(); // 输出:小黑汪汪叫(执行Dog的方法)
        animal2.makeSound(); // 输出:小白喵喵叫(执行Cat的方法)

        // 错误:父类引用不能调用子类专属方法(编译报错)
        // animal1.bark(); 

        // 3. 若要调用子类专属方法,需进行向下转型(强制转换)
        if (animal1 instanceof Dog) { // 先判断引用指向的对象类型,避免转换异常
            Dog dog = (Dog) animal1; // 向下转型:父类引用转子类对象
            dog.bark(); // 调用子类专属方法,输出:小黑在狂吠
        }
    }
}
5.3.4 向上转型与向下转型(多态的核心操作)

多态的实现依赖向上转型,而调用子类专属方法则需要向下转型,二者是多态中最常用的操作,也是新手最容易出错的地方。

  1. 向上转型(自动转型,安全): 语法:父类类型 变量名 = new 子类类型();(如Animal animal = new Dog();)

  2. 特点:自动完成,无需强制转换,安全可靠;转型后只能调用父类的方法,不能调用子类专属方法;

  3. 通俗比喻:把"狗"当成"动物"看,没问题(狗是动物的一种)。

  4. 向下转型(强制转型,不安全): 语法:子类类型 变量名 = (子类类型) 父类引用;(如Dog dog = (Dog) animal;)

  5. 特点:需要手动强制转换,不安全,容易报ClassCastException(类型转换异常);

  6. 安全保障:转型前必须用instanceof关键字判断父类引用指向的对象类型,确认是目标子类类型后,再进行转型;

  7. 通俗比喻:把"动物"硬说成"狗",万一这个动物是猫,就会出错,所以需要先确认"动物是不是狗"(instanceof判断)。

新手易错点:不使用instanceof判断,直接进行向下转型,会导致类型转换异常。比如将指向Cat对象的Animal引用,强制转换为Dog类型,编译通过,但运行时报错。

java 复制代码
// 错误示例:无instanceof判断,直接向下转型
Animal animal = new Cat("小白");
Dog dog = (Dog) animal; // 运行时报错:ClassCastException(猫不能转成狗)
5.3.5 多态的核心价值

多态的核心价值是"解耦"和"扩展":

  • 降低耦合度:代码只依赖父类接口,不依赖具体子类,修改子类实现,无需修改调用代码;

  • 提升扩展性:新增子类时,无需修改原有代码,只需让子类继承父类、重写方法,即可通过父类引用调用,符合"开闭原则"(对扩展开放,对修改关闭)。

六、抽象类与接口

抽象类和接口是Java中实现抽象编程的核心载体,二者都用于定义"模板"或"契约",不能直接实例化,主要用于规范子类的实现,为多态提供更灵活的支持。抽象类是"半抽象模板",接口是"完全抽象契约",二者既有联系,又有明显区别。

6.1 抽象类(Abstract Class)

抽象类是"半完成的模板",它包含抽象方法(没有方法体,需要子类实现)和普通方法(有方法体,子类可直接复用),不能直接实例化,只能作为父类被子类继承,子类必须实现抽象类中的所有抽象方法(除非子类也是抽象类)。

通俗比喻:抽象类就像一份"填色画模板",模板上已经画好了轮廓(普通方法,有实现),但颜色需要自己填(抽象方法,无实现);比如"交通工具"抽象类,有"行驶"的轮廓(普通方法),但汽车、飞机的行驶方式不同(抽象方法,子类实现)。

6.1.1 抽象类的语法格式
java 复制代码
// 抽象类:用abstract关键字修饰
abstract class 抽象类名 {
    // 1. 普通成员变量(可选)
    数据类型 变量名;

    // 2. 普通成员方法(有方法体,可选)
    [权限修饰符] 返回值类型 方法名(参数列表) {
        // 方法体
    }

    // 3. 抽象方法(无方法体,用abstract修饰,必须由子类实现)
    [权限修饰符] abstract 返回值类型 方法名(参数列表); // 无方法体,结尾用分号
}

// 子类:继承抽象类,必须实现所有抽象方法(除非子类也是抽象类)
class 子类名 extends 抽象类名 {
    // 实现抽象类中的所有抽象方法
    @Override
    返回值类型 抽象方法名(参数列表) {
        // 方法体(子类的具体实现)
    }
}
6.1.2 抽象类的核心特点(必记)
  1. 用abstract关键字修饰类,称为抽象类;用abstract修饰方法,称为抽象方法;

  2. 抽象方法没有方法体,只有方法声明,结尾用分号;

  3. 抽象类不能直接实例化(不能用new关键字创建对象),只能作为父类被子类继承;

  4. 子类继承抽象类后,必须实现抽象类中的所有抽象方法,否则子类必须也用abstract修饰(成为抽象子类);

  5. 抽象类中可以有普通方法、构造方法、成员变量(构造方法用于子类初始化时调用,不能用于实例化抽象类);

  6. 抽象类不能用final修饰(final类不能被继承,而抽象类必须被继承才能使用,矛盾)。

6.1.3 抽象类完整代码示例
java 复制代码
// 抽象类:Transport(交通工具),定义模板
abstract class Transport {
    protected String name;

    // 抽象类的构造方法(用于子类初始化)
    public Transport(String name) {
        this.name = name;
    }

    // 普通方法(有方法体,子类可直接复用)
    public void start() {
        System.out.println(name + "开始运行");
    }

    // 抽象方法(无方法体,子类必须实现)
    public abstract void run();
}

// 子类1:Car(汽车),继承抽象类,实现抽象方法
class Car extends Transport {
    public Car(String name) {
        super(name); // 调用抽象类的构造方法
    }

    @Override
    public void run() {
        // 子类实现抽象方法:汽车的行驶方式
        System.out.println(name + "在公路上行驶,靠车轮转动");
    }
}

// 子类2:Plane(飞机),继承抽象类,实现抽象方法
class Plane extends Transport {
    public Plane(String name) {
        super(name);
    }

    @Override
    public void run() {
        // 子类实现抽象方法:飞机的行驶方式
        System.out.println(name + "在天空中飞行,靠机翼升力");
    }
}

// 测试抽象类
class TestAbstract {
    public static void main(String[] args) {
        // 错误:抽象类不能实例化
        // Transport transport = new Transport("交通工具");

        // 正确:父类(抽象类)引用指向子类对象,实现多态
        Transport car = new Car("宝马");
        Transport plane = new Plane("波音747");

        car.start(); // 调用抽象类的普通方法,输出:宝马开始运行
        car.run();   // 调用子类实现的抽象方法,输出:宝马在公路上行驶,靠车轮转动

        plane.start(); // 输出:波音747开始运行
        plane.run();   // 输出:波音747在天空中飞行,靠机翼升力
    }
}

6.2 接口(Interface)

接口是"完全抽象的契约",JDK8之前,接口中只有抽象方法(无方法体)和常量(public static final修饰);JDK8及之后,接口中可以添加默认方法(default修饰,有方法体)和静态方法(static修饰,有方法体),但依旧不能直接实例化,只能被类实现(implements)。

通俗比喻:接口就像一份"劳动合同",契约中规定了员工必须做的事情(抽象方法),可选做的事情(默认方法),以及公司的公共规则(静态方法);任何签订合同的员工(实现接口的类),都必须遵守契约,完成必须做的事情,可选做的事情可以直接使用,也可以修改。

接口的核心作用是"弥补Java单继承的不足"------一个类可以实现多个接口(多实现),从而拥有多个接口的功能,实现"多继承"的效果。

6.2.1 接口的语法格式
java 复制代码
// 接口:用interface关键字修饰
interface 接口名 {
    // 1. 常量(默认public static final,可省略不写)
    数据类型 常量名 = 值;

    // 2. 抽象方法(JDK8前,默认public abstract,可省略不写)
    返回值类型 方法名(参数列表);

    // 3. JDK8+ 默认方法(default修饰,有方法体,实现类可直接使用或重写)
    default 返回值类型 方法名(参数列表) {
        // 方法体
    }

    // 4. JDK8+ 静态方法(static修饰,有方法体,通过接口名调用)
    static 返回值类型 方法名(参数列表) {
        // 方法体
    }
}

// 类实现接口:用implements关键字,可实现多个接口(用逗号分隔)
class 类名 implements 接口名1, 接口名2 {
    // 必须实现所有接口中的抽象方法
    @Override
    接口1的抽象方法名(参数列表) {
        // 方法体
    }

    @Override
    接口2的抽象方法名(参数列表) {
        // 方法体
    }

    // 可选:重写接口的默认方法(不重写则使用接口的默认实现)
    @Override
    default 返回值类型 接口的默认方法名(参数列表) {
        // 方法体
    }
}
6.2.2 接口的核心特点(必记)
  1. 用interface关键字修饰接口,接口名通常以I开头(规范,如ISwimmable);

  2. 接口中的常量默认是public static final,即使不写,也会自动添加,且必须初始化(不能只声明不赋值);

  3. 接口中的抽象方法默认是public abstract,JDK8及之后可省略不写,无方法体;

  4. 默认方法(default):有方法体,实现类可直接使用,也可重写;重写时无需加default关键字;

  5. 静态方法(static):有方法体,只能通过接口名调用(不能通过实现类对象调用);

  6. 接口不能直接实例化,只能被类实现(implements),一个类可以实现多个接口(多实现);

  7. 实现接口的类,必须实现所有接口中的抽象方法,否则该类必须用abstract修饰;

  8. 接口之间可以继承(extends),且一个接口可以继承多个接口(多继承),无需实现方法。

6.2.3 接口完整代码示例
java 复制代码
// 接口1:ISwimmable(会游泳的),定义游泳的契约
interface ISwimmable {
    // 常量:游泳的速度单位(默认public static final)
    String SPEED_UNIT = "米/秒";

    // 抽象方法:游泳(必须实现)
    void swim();

    // 默认方法:呼吸(可选重写,有默认实现)
    default void breath() {
        System.out.println("在水中呼吸");
    }

    // 静态方法:游泳规则(通过接口名调用)
    static void swimRule() {
        System.out.println("游泳时注意安全,避免溺水");
    }
}

// 接口2:IRunnable(会跑的),定义跑步的契约
interface IRunnable {
    void run(); // 抽象方法:跑步
}

// 类:Fish(鱼),实现ISwimmable接口
class Fish implements ISwimmable {
    private String name;

    public Fish(String name) {
        this.name = name;
    }

    // 必须实现接口的抽象方法swim
    @Override
    public void swim() {
        System.out.println(name + "在水中快速游泳,速度单位:" + SPEED_UNIT);
    }

    // 可选:重写默认方法breath
    @Override
    public void breath() {
        System.out.println(name + "用鳃在水中呼吸");
    }
}

// 类:Duck(鸭子),实现ISwimmable和IRunnable两个接口(多实现)
class Duck implements ISwimmable, IRunnable {
    private String name;

    public Duck(String name) {
        this.name = name;
    }

    // 实现ISwimmable的抽象方法
    @Override
    public void swim() {
        System.out.println(name + "在水面上游动");
    }

    // 实现IRunnable的抽象方法
    @Override
    public void run() {
        System.out.println(name + "在陆地上跑步");
    }
}

// 测试接口
class TestInterface {
    public static void main(String[] args) {
        // 测试Fish类
        ISwimmable fish = new Fish("金鱼");
        fish.swim();    // 输出:金鱼在水中快速游泳,速度单位:米/秒
        fish.breath();  // 输出:金鱼用鳃在水中呼吸(重写后的默认方法)

        // 测试Duck类(多实现)
        ISwimmable duck1 = new Duck("北京鸭");
        IRunnable duck2 = new Duck("北京鸭");
        duck1.swim();   // 输出:北京鸭在水面上游动
        duck2.run();    // 输出:北京鸭在陆地上跑步

        // 调用接口的静态方法(通过接口名调用)
        ISwimmable.swimRule(); // 输出:游泳时注意安全,避免溺水
    }
}
6.3 抽象类与接口的核心区别(必记)

抽象类和接口都是抽象编程的载体,容易混淆,核心区别如下表所示:

对比维度 抽象类(Abstract Class) 接口(Interface)
关键字 abstract class interface
继承/实现方式 子类用extends继承,单继承 类用implements实现,多实现
方法类型 抽象方法、普通方法、默认方法(JDK8+)、静态方法 抽象方法、默认方法、静态方法(JDK8+),无普通方法
成员变量 可包含任意权限的成员变量,非默认常量 只能是public static final常量,必须初始化
构造方法 有构造方法(用于子类初始化) 无构造方法(不能实例化)
核心关系 is-a关系(子类是父类的一种,如Dog is a Animal) has-a关系(类拥有接口的能力,如Fish has a Swimmable能力)
使用场景 子类有共性属性和方法,需要复用代码 定义类的能力,弥补单继承不足,实现多态扩展

新手易错点:混淆抽象类和接口的使用场景------如果子类之间有共性的属性和方法,需要复用,用抽象类;如果只是需要给类添加某种能力(如"会游泳""会飞"),且类可能需要多种能力,用接口。

七、内部类

内部类是"定义在类内部的类",它依赖于外部类存在,不能独立存在,内部类可以访问外部类的所有成员(包括private成员),而外部类访问内部类的成员,需要通过内部类对象。内部类主要分为4种:成员内部类、静态内部类、局部内部类、匿名内部类,其中匿名内部类是开发中最常用的。

通俗比喻:内部类就像"房子里的房间",外部类是房子,内部类是房子里的房间------房间依赖房子存在(没有房子就没有房间),房间可以直接使用房子里的设施(如水电,对应外部类成员),而房子要使用房间里的东西(如家具),需要进入房间(创建内部类对象)。

7.1 成员内部类(最基础的内部类)

成员内部类定义在外部类的成员位置(与外部类的成员变量、成员方法同级),没有static修饰,依赖于外部类对象存在,能访问外部类的所有成员(包括private)。

7.1.1 语法与代码示例
java 复制代码
// 外部类
class Outer {
    // 外部类成员变量(private)
    private int num = 10;

    // 成员内部类(定义在外部类成员位置,无static)
    public class Inner {
        // 内部类成员变量
        private int innerNum = 20;

        // 内部类成员方法
        public void show() {
            // 访问外部类的private成员变量
            System.out.println("外部类的num:" + num);
            // 访问内部类的成员变量
            System.out.println("内部类的innerNum:" + innerNum);
        }
    }

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

// 测试成员内部类
class TestInner {
    public static void main(String[] args) {
        // 1. 先创建外部类对象(成员内部类依赖外部类对象)
        Outer outer = new Outer();

        // 2. 创建成员内部类对象(外部类对象. new 内部类名())
        Outer.Inner inner = outer.new Inner();

        // 3. 调用内部类方法
        inner.show(); // 输出:外部类的num:10 → 内部类的innerNum:20

        // 4. 调用外部类方法(间接访问内部类)
        outer.outerMethod();
    }
}
7.1.2 核心特点
  • 无static修饰,依赖外部类对象存在,不能直接创建内部类对象(必须先创建外部类对象);

  • 能直接访问外部类的所有成员(包括private);

  • 外部类访问内部类成员,必须创建内部类对象;

  • 成员内部类中不能定义static成员(静态成员属于类,内部类依赖对象,矛盾)。

7.2 静态内部类(最常用的内部类之一)

静态内部类是用static修饰的成员内部类,定义在外部类的成员位置,不依赖于外部类对象存在,属于外部类本身,只能访问外部类的静态成员(static修饰的成员)。

7.2.1 语法与代码示例
java 复制代码
// 外部类
class Outer {
    // 外部类静态成员变量
    private static int staticNum = 100;
    // 外部类普通成员变量(静态内部类不能访问)
    private int num = 10;

    // 静态内部类(static修饰)
    public static class StaticInner {
        private int innerNum = 200;

        public void show() {
            // 只能访问外部类的静态成员
            System.out.println("外部类的静态num:" + staticNum);
            // 错误:不能访问外部类的非静态成员
            // System.out.println(num);

            // 访问内部类自身成员
            System.out.println("静态内部类的innerNum:" + innerNum);
        }

        // 静态内部类中可以定义静态成员
        public static void staticMethod() {
            System.out.println("静态内部类的静态方法");
        }
    }

    // 外部类方法访问静态内部类
    public void outerMethod() {
        // 无需创建外部类对象,直接创建静态内部类对象
        StaticInner inner = new StaticInner();
        inner.show();
    }
}

// 测试静态内部类
class TestStaticInner {
    public static void main(String[] args) {
        // 1. 直接创建静态内部类对象(无需创建外部类对象)
        Outer.StaticInner inner = new Outer.StaticInner();

        // 2. 调用内部类方法
        inner.show(); // 输出:外部类的静态num:100 → 静态内部类的innerNum:200

        // 3. 调用静态内部类的静态方法(通过内部类名调用)
        Outer.StaticInner.staticMethod(); // 输出:静态内部类的静态方法

        // 4. 外部类对象调用方法,间接访问静态内部类
        Outer outer = new Outer();
        outer.outerMethod();
    }
}
7.2.2 核心特点
  • 用static修饰,不依赖外部类对象,可直接通过"外部类名.内部类名"创建对象;

  • 只能访问外部类的静态成员(static变量、static方法),不能访问非静态成员;

  • 静态内部类中可以定义静态成员和非静态成员;

  • 开发中常用静态内部类封装与外部类相关但不依赖外部类对象的逻辑(如工具类、辅助类)。

7.3 局部内部类(使用频率较低)

局部内部类定义在外部类的方法内部或代码块内部(如for循环、if语句块),作用域仅限于当前方法或代码块,不能被访问修饰符修饰(public、private等),只能在当前作用域内使用。

7.3.1 语法与代码示例
java 复制代码
class Outer {
    private int num = 10;

    public void outerMethod() {
        // 方法内部的局部变量(JDK8+ 可省略final,但必须是effectively final,即值不能修改)
        final int localNum = 20;

        // 局部内部类(定义在方法内部,无访问修饰符)
        class LocalInner {
            public void show() {
                // 访问外部类成员
                System.out.println("外部类的num:" + num);
                // 访问方法内的局部变量(必须是final或effectively final)
                System.out.println("方法内的localNum:" + localNum);
            }
        }

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

// 测试局部内部类
class TestLocalInner {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.outerMethod(); // 输出:外部类的num:10 → 方法内的localNum:20
    }
}
7.3.2 核心特点
  • 定义在方法或代码块内部,作用域仅限于当前方法/代码块,方法执行完毕后,局部内部类被销毁;

  • 不能被访问修饰符(public、private)修饰,也不能用static修饰;

  • 能访问外部类的所有成员,能访问方法内的局部变量(JDK8+ 需是effectively final,即值不能修改);

  • 使用频率较低,仅在当前方法内需要一个临时类时使用。

7.4 匿名内部类(开发中最常用)

匿名内部类是"没有类名的局部内部类",定义在方法内部或代码块内部,本质是一个"匿名的子类对象"或"匿名的接口实现类对象",一次性使用,创建后无法重复调用,常用于简化代码(如简化接口、抽象类的实现)。

通俗比喻:匿名内部类就像"临时雇佣的临时工",不需要知道他的名字(类名),只需要他完成特定的任务(实现接口/抽象类的方法),任务完成后就不再使用,无需长期保留。比如按钮点击事件的监听,只需实现监听接口的方法,无需单独定义一个类。

7.4.1 语法与核心场景(必记)

匿名内部类不能单独定义,必须在创建对象时同时定义,语法格式分两种:实现接口、继承抽象类(或普通类)。

java 复制代码
// 场景1:实现接口的匿名内部类(最常用)
接口名 对象名 = new 接口名() {
    // 必须实现接口的所有抽象方法
    @Override
    抽象方法名(参数列表) {
        // 方法体
    }
};

// 场景2:继承抽象类的匿名内部类
抽象类名 对象名 = new 抽象类名(构造参数) {
    // 必须实现抽象类的所有抽象方法
    @Override
    抽象方法名(参数列表) {
        // 方法体
    }
};
7.4.2 匿名内部类完整代码示例
java 复制代码
// 示例1:实现接口的匿名内部类(模拟按钮点击监听)
// 定义监听接口
interface OnClickListener {
    void onClick(); // 抽象方法:点击事件
}

// 外部类:模拟按钮
class Button {
    // 方法:设置点击监听(参数是接口类型)
    public void setOnClickListener(OnClickListener listener) {
        listener.onClick(); // 调用接口方法
    }
}

// 测试匿名内部类
class TestAnonymousInner {
    public static void main(String[] args) {
        Button button = new Button();
        
        // 给按钮设置点击监听,使用匿名内部类(实现OnClickListener接口)
        button.setOnClickListener(new OnClickListener() {
            // 实现接口的抽象方法
            @Override
            public void onClick() {
                System.out.println("按钮被点击了(匿名内部类实现)");
            }
        });
        
        // 示例2:继承抽象类的匿名内部类
        abstract class Animal {
            abstract void makeSound();
        }
        
        // 创建抽象类的匿名子类对象
        Animal cat = new Animal() {
            @Override
            void makeSound() {
                System.out.println("喵喵叫(匿名子类实现)");
            }
        };
        cat.makeSound(); // 调用匿名内部类的方法
    }
}
7.4.3 核心特点(必记)
  1. 没有类名,不能单独定义,必须在创建对象时同步定义,一次性使用(不能重复创建该类对象);

  2. 本质是"子类对象"或"接口实现类对象",隐含继承了某个类(抽象类/普通类)或实现了某个接口;

  3. 能访问外部类的所有成员,能访问方法内的局部变量(JDK8+ 需是effectively final,值不能修改);

  4. 不能定义构造方法(没有类名,无法定义构造方法);

  5. 不能用static修饰,不能定义static成员;

  6. 开发中主要用于简化代码,尤其是接口、抽象类的临时实现(如监听器、回调函数)。

新手易错点:1. 匿名内部类必须实现接口/抽象类的所有抽象方法,否则编译报错;2. 匿名内部类不能重复使用,若需多次使用,需单独定义普通类/内部类;3. 误将匿名内部类当作独立类,试图单独创建对象。

7.5 内部类核心总结

内部类的核心价值是"封装"和"简化代码",四种内部类的适用场景不同,核心区别可通过表格快速区分,重点掌握静态内部类和匿名内部类(开发高频使用)。

|-------|-----------------------------|--------------------------|
| 内部类类型 | 核心特征 | 适用场景 |
| 成员内部类 | 无static,依赖外部类对象,可访问外部类所有成员 | 与外部类联系紧密,需访问外部类非静态成员 |
| 静态内部类 | 有static,不依赖外部类对象,仅访问外部类静态成员 | 封装与外部类相关但独立的逻辑(如工具类、辅助类) |
| 局部内部类 | 定义在方法内,作用域仅限方法,无访问修饰符 | 仅在当前方法内临时使用,使用频率低 |
| 匿名内部类 | 无类名,一次性使用,本质是子类/接口实现类对象 | 简化接口、抽象类的临时实现(如监听器、回调) |

八、OOP常见面试题(核心重点)

面向对象编程是Java面试的核心考点,以下整理高频面试题,结合前文知识点,给出清晰易懂的答案,帮助巩固核心内容,应对面试场景。

8.1 基础面试题(必背)

1. 什么是类?什么是对象?二者的关系是什么?

答:类是对现实世界中同一类事物的抽象描述,是包含属性(成员变量)和行为(成员方法)的模板,不代表具体实物;对象是类的实例化结果,是具体的实体,拥有类中定义的所有属性和行为。

关系:类是创建对象的唯一依据,对象是类的具体实现;一个类可以创建多个对象,多个对象共享类的模板,但拥有独立的属性值。

2. Java中值传递和引用传递的区别?

答:Java中只有值传递,引用传递本质是"值传递的特殊形式",核心区别在于传递的内容不同:

  • 值传递(基本数据类型):传递的是数据的副本,方法内部修改副本,不影响原始数据;

  • 引用传递(引用数据类型):传递的是对象的内存地址副本,方法内部通过地址修改对象的属性,会影响原始对象,但修改地址副本(重新new),不影响原始对象。

3. 构造方法的核心特点是什么?

答:① 方法名与类名完全一致,大小写相同;② 没有返回值,不需要写void关键字;③ 用于对象初始化,new对象时自动调用;④ 支持重载(参数列表不同);⑤ 未手动编写时,JVM自动生成无参空构造;⑥ 可通过this(...)调用本类其他构造方法(必须在第一行)。

4. this关键字和super关键字的区别?

答:二者均用于访问类的成员,核心区别在于指向的对象不同:

  • this:指向当前类的对象,用于区分成员变量和局部变量,调用本类的方法、构造方法;只能在本类内部使用。

  • super:指向父类的对象引用,用于访问父类的成员变量、成员方法、构造方法;只能在子类中使用,调用父类构造方法必须在子类构造方法第一行。

8.2 核心特性面试题(高频)

1. 面向对象的三大核心特性是什么?分别说明其作用。

答:三大核心特性是封装、继承、多态:

  • 封装:隐藏对象内部属性和实现细节,对外提供公共访问接口(Getter/Setter),保证数据安全,降低代码耦合度。

  • 继承:子类继承父类的非private成员,实现代码复用,建立类的层级关系,为多态奠定基础。

  • 多态:父类引用指向子类对象,同一方法调用产生不同执行结果,提升代码扩展性和灵活性,符合开闭原则。

2. 方法重写(Override)和方法重载(Overload)的区别?

答:二者是Java中两种不同的方法复用机制,核心区别如下:

|------|-------------------------------|-----------------------------|
| 对比维度 | 方法重写(Override) | 方法重载(Overload) |
| 定义 | 子类继承父类后,重写父类的方法(方法名、参数、返回值一致) | 同一类中,方法名相同,参数列表(个数、类型、顺序)不同 |
| 范围 | 子类与父类之间 | 同一类内部 |
| 返回值 | 必须与父类一致(或协变返回值) | 可不同(与参数列表无关) |
| 访问权限 | 子类重写方法权限不能比父类更严格 | 无要求,可任意修饰 |

3. 抽象类和接口的核心区别?(面试高频)

答:核心区别在于"模板 vs 契约",具体见前文6.3节表格,重点记忆3点核心差异:

  • 继承/实现方式:抽象类用extends单继承,接口用implements多实现;

  • 成员结构:抽象类可包含普通方法、构造方法、任意成员变量;接口只能有常量、抽象方法、默认方法、静态方法,无普通方法和构造方法;

  • 核心关系:抽象类是is-a关系(子类是父类的一种),接口是has-a关系(类拥有某种能力)。

4. 多态的实现条件是什么?"编译看左边,运行看右边"是什么意思?

答:多态的实现条件有3个:① 存在继承/实现关系;② 子类重写父类方法;③ 父类引用指向子类对象(向上转型)。

"编译看左边,运行看右边":编译时,编译器检查父类(左边)是否有该方法,有则编译通过,无则报错;运行时,实际执行的是子类(右边)重写后的方法,而非父类原方法。

8.3 进阶面试题(拓展)

1. 静态内部类和成员内部类的区别?

答:① 依赖关系:静态内部类不依赖外部类对象,成员内部类依赖外部类对象;② 访问权限:静态内部类仅能访问外部类静态成员,成员内部类可访问外部类所有成员;③ 创建方式:静态内部类直接通过"外部类名.内部类名"创建,成员内部类需通过"外部类对象.new 内部类名"创建;④ 静态成员:静态内部类可定义静态成员,成员内部类不能。

2. 匿名内部类为什么不能访问非final的局部变量?

答:因为匿名内部类是局部内部类的特殊形式,生命周期可能比所在方法长(方法执行完毕,局部变量被销毁,但匿名内部类对象可能还存在)。为了保证数据一致性,JVM会将局部变量复制一份到匿名内部类中,若局部变量非final,修改后会导致副本与原始变量不一致,因此要求局部变量必须是final或effectively final(JDK8+)。

3. Java为什么不支持多继承?如何弥补单继承的不足?

答:Java不支持多继承,是为了避免"菱形继承"(子类继承多个父类,多个父类有同名方法,子类无法确定调用哪个),降低代码复杂度。

弥补方式:① 接口多实现(一个类可实现多个接口);② 内部类;③ 继承+接口结合(子类继承一个父类,实现多个接口)。

九、OOP核心总结

面向对象编程(OOP)的核心是"以对象为中心",通过类与对象的封装、继承、多态三大特性,实现代码的复用、低耦合、高扩展,是Java开发的基础,也是后续学习框架、设计模式的核心前提。

核心梳理:

  1. 类与对象:类是模板,对象是实例,通过new关键字创建对象,调用属性和方法;

  2. 核心特性:封装(藏细节、留接口)→ 继承(复用代码、建层级)→ 多态(解耦、扩展),三者相辅相成;

  3. 关键关键字:this(当前对象)、super(父类引用)、static(静态成员)、abstract(抽象类/方法)、interface(接口);

  4. 内部类:重点掌握静态内部类和匿名内部类,核心是封装与简化代码;

  5. 核心原则:多态的"编译看左边,运行看右边"、方法重写/重载的规则、抽象类与接口的使用场景。

十,最佳实践与注意事项

  1. 遵循单一职责原则 :一个类只负责一件事(比如 User 类只管用户信息,不管订单)。
  2. 优先使用组合而非继承:比如 "汽车有发动机"(组合)比 "汽车继承发动机"(继承)更灵活。
  3. 面向接口编程,而非实现 :比如用 List 接口声明变量,而不是 ArrayList,方便切换实现。
  4. 合理使用访问修饰符 :能 private 就不 public,保护数据安全。
  5. 重写 equals() 时也要重写 hashCode() :不然在 HashMapHashSet 里会出问题。
  6. 使用 @Override 注解:明确表示重写,防止写错方法名。

记忆技巧

  • 封装:隐藏细节,提供接口(藏好钱包,留个小口取钱)。
  • 继承:is-a 关系,代码复用(儿子继承父亲的财产)。
  • 多态:同一接口,不同实现(同一个遥控器,控制不同电器)。
  • 抽象类:部分实现,模板设计(填色画模板)。
  • 接口:完全抽象,契约规范(劳动合同)。

学习建议:结合代码示例反复练习,重点掌握多态、抽象类与接口、内部类的使用,理解每个知识点的底层逻辑,而非死记硬背,同时结合面试题巩固核心内容,为后续Java进阶学习奠定基础。

相关推荐
xiaokangzhe2 小时前
MySQL故障排查与优化
数据库·mysql
2601_949818092 小时前
LangChain-08 Query SQL DB 通过GPT自动查询SQL
数据库·sql·langchain
ytttr8732 小时前
C# 读取数据库表结构工具设计与实现
开发语言·数据库·c#
白露与泡影2 小时前
从 BIO 到 epoll:高并发 I/O 模型演进与本质分析
java·服务器·数据库
学编程就要猛2 小时前
JavaEE进阶:Spring Boot快速上手
java·spring boot·java-ee
Jinuss2 小时前
源码分析之React中的useImperativeHandle
开发语言·前端·javascript
csdn2015_2 小时前
HashSet 和 LinkedHashSet 区别
java·开发语言
KoiHeng2 小时前
初识Maven
java·maven