一、面向对象基础:类与对象初探

1. 核心概念区分

  • 类(Class):抽象模板,是一类事物共同特征与行为的集合,相当于图纸。比如“学生”类,会提炼出所有学生共有的属性(姓名、年龄)、行为(上课、考试)。
  • 对象(Object):类的具体实例,是模板制造出的真实个体,相当于按照图纸造出的房子。例如“张三、18岁”就是学生类的一个对象。

面向过程侧重“怎么做步骤”,而面向对象侧重“谁来做这件事”,把数据和操作数据的方法封装在类中,代码复用性、可读性更强。

二、定义类:搭建面向对象模板

一个完整的Java类由成员变量、成员方法两大部分组成。

1. 定义成员变量

成员变量用来描述事物的属性,写在类内部、方法外部,作用域覆盖整个类。

// 学生类
public class Student {
    // 成员变量:学生属性
    String name;
    int age;
}

2. 定义成员方法

成员方法用来描述事物的行为,负责处理类内数据,可读取、修改成员变量。

public class Student {
    String name;
    int age;
    
    // 成员方法:学生上课行为
    public void study() {
        System.out.println(name + "正在认真上课,今年" + age + "岁");
    }
}

三、对象:类的实例化与完整操作

有了类模板后,需要实例化对象才能使用属性和方法,完整对象操作分为实例化、初始化、调用对象、静态对象四部分。

3.1 实例化对象

使用new关键字创建对象,格式:类名 对象名 = new 类名();

// 创建学生对象
Student stu1 = new Student();

3.2 初始化对象

给对象的成员变量赋值,完成对象初始化:

stu1.name = "李明";
stu1.age = 19;

3.3 使用对象:调用成员变量与方法

通过.运算符访问对象属性、执行对象行为:

// 访问成员变量
System.out.println(stu1.name);
// 调用成员方法
stu1.study();

3.4 使用静态变量和静态方法

被static修饰的成员属于类本身,不依赖对象,可直接通过类名调用,常用于通用工具属性/功能:

public class Student {
    // 静态变量:全校学生总数
    static int totalStu = 0;
    
    // 静态方法:打印招生提示
    public static void showRule() {
        System.out.println("当前在校学生总数:" + totalStu);
    }
}

// 直接通过类名调用静态内容
Student.showRule();

3.5 清除对象

Java依靠垃圾回收机制(GC)自动回收无引用对象内存;手动清空引用即可标记对象待回收:

stu1 = null; // 切断对象引用,GC会自动清理该对象内存

3.6 应用程序与命令行参数

main方法是程序入口,String[] args接收运行时传入的命令行参数,可通过参数动态初始化对象数据。

四、方法重载:同一类中同名方法拓展

4.1 什么是方法重载

在同一个类中,方法名相同、参数列表(参数个数/类型/顺序)不同,即为方法重载,与返回值无关。作用:用统一方法名完成同类功能,简化调用逻辑。

public class Student {
    // 重载1:无参study
    public void study() {
        System.out.println("学生自习");
    }
    
    // 重载2:带参study,传入课程名
    public void study(String course) {
        System.out.println("学生学习:" + course);
    }
}

五、包:管理类文件,解决类名冲突

5.1 包的定义

使用package关键字放在代码第一行,声明当前类所属包,分层管理项目所有类。

package com.student.demo; // 定义包
public class Student {}

5.2 包的引入

使用import导入其他包下的类,否则无法跨包访问类:

import java.util.Scanner; // 导入工具包Scanner类

5.3 模块

模块化是高版本Java特性,通过module-info.java管理包对外可见范围,控制类的跨模块访问权限。

六、类及成员修饰符:控制访问权限

6.1 四类访问修饰符(权限从大到小)

  1. public(公共):所有包、所有类均可访问
  2. protected(受保护):同包、不同包子类可访问
  3. 默认(不写修饰符,包私有):仅同包内类可访问
  4. private(私有):仅当前类内部可访问,封装核心手段

6.2 封装设计思想

将成员变量用private私有化,对外提供get/set方法读写数据,避免外部随意篡改属性,保证数据安全,是面向对象三大特性之一。

public class Student {
    // 私有成员变量,外部无法直接访问
    private String name;
    
    // get方法:读取姓名
    public String getName() {
        return name;
    }
    
    // set方法:修改姓名
    public void setName(String name) {
        this.name = name;
    }
}

七、实用案例:完整学生类演示

下面整合本章所有核心知识点,给出可直接运行的完整案例:

下面是该案例的完整执行流程图:

定义Student类
包含成员变量、构造方法、
成员方法、静态变量/方法

实例化对象
Student s1 = new Student('小红', 18)

调用构造方法初始化
设置name='小红', age=18, count++

调用成员方法
s1.study()

输出: 小红自主学习

调用重载方法
s2.study('Java面向对象')

输出: 小王正在学习Java面向对象

调用静态方法
Student.showTotal()

输出: 学生总人数:2

程序结束

package com.demo;

public class Student {
    // 私有成员变量(封装)
    private String name;
    private int age;
    
    // 静态变量
    public static int count = 0;
    
    // 构造方法初始化对象
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
        count++;
    }
    
    // 方法重载
    public void study() {
        System.out.println(name + "自主学习");
    }
    
    public void study(String course) {
        System.out.println(name + "正在学习" + course);
    }
    
    // get/set访问私有变量
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    // 静态方法
    public static void showTotal() {
        System.out.println("学生总人数:" + count);
    }
    
    // 程序入口
    public static void main(String[] args) {
        // 实例化对象
        Student s1 = new Student("小红", 18);
        Student s2 = new Student("小王", 19);
        
        // 调用成员方法
        s1.study();
        s2.study("Java面向对象");
        
        // 调用静态方法
        Student.showTotal();
    }
}

下面整合本章所有核心知识点,给出可直接运行的完整案例:

package com.demo;

public class Student {
    // 私有成员变量(封装)
    private String name;
    private int age;
    
    // 静态变量
    public static int count = 0;
    
    // 构造方法初始化对象
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
        count++;
    }
    
    // 方法重载
    public void study() {
        System.out.println(name + "自主学习");
    }
    
    public void study(String course) {
        System.out.println(name + "正在学习" + course);
    }
    
    // get/set访问私有变量
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    // 静态方法
    public static void showTotal() {
        System.out.println("学生总人数:" + count);
    }
    
    // 程序入口
    public static void main(String[] args) {
        // 实例化对象
        Student s1 = new Student("小红", 18);
        Student s2 = new Student("小王", 19);
        
        // 调用成员方法
        s1.study();
        s2.study("Java面向对象");
        
        // 调用静态方法
        Student.showTotal();
    }
}

八、常见错误与排查

初学者在学习面向对象编程时,常常会遇到一些典型的错误。本节列举几个常见的错误场景,并提供错误代码示例与修正方法。

1. 未实例化对象就调用方法

错误示例:

public class Student {
    private String name;
    
    public void study() {
        System.out.println(name + "正在学习");
    }
    
    public static void main(String[] args) {
        // 错误:没有创建对象就直接调用实例方法
        study();  // 编译错误
    }
}

错误原因: study() 是实例方法,必须通过对象来调用。

正确写法:

public static void main(String[] args) {
    // 先实例化对象
    Student student = new Student("小明", 18);
    // 通过对象调用方法
    student.study();
}

2. 在静态方法中访问实例变量

错误示例:

public class Student {
    private String name;  // 实例变量
    
    public static void showInfo() {
        // 错误:静态方法中不能直接访问实例变量
        System.out.println("学生姓名:" + name);  // 编译错误
    }
}

错误原因: 静态方法属于类,在类加载时就存在,而实例变量需要对象创建后才存在。

正确写法:

public class Student {
    private String name;
    
    // 方法1:将方法改为实例方法
    public void showInfo() {
        System.out.println("学生姓名:" + name);
    }
    
    // 方法2:通过参数传递实例
    public static void showInfo(Student student) {
        System.out.println("学生姓名:" + student.name);
    }
}

3. 混淆静态变量与实例变量的使用

错误示例:

public class Student {
    public static int count = 0;  // 静态变量
    
    public Student(String name) {
        count++;
    }
    
    public void printCount() {
        // 错误:通过对象访问静态变量(虽然语法允许,但容易引起混淆)
        System.out.println("当前学生数:" + this.count);
    }
}

错误原因: 静态变量属于类,应该通过类名访问,而不是通过对象引用。

正确写法:

public class Student {
    public static int count = 0;
    
    public Student(String name) {
        count++;
    }
    
    public void printCount() {
        // 正确:通过类名访问静态变量
        System.out.println("当前学生数:" + Student.count);
    }
    
    // 或者在静态方法中访问
    public static void showTotal() {
        System.out.println("学生总数:" + Student.count);
    }
}

排查技巧

  1. 编译错误优先查看: IDE 会提示具体的错误信息,如 “non-static method cannot be referenced from a static context”。
  2. 理解变量作用域: 明确区分哪些是实例成员(需要对象),哪些是静态成员(属于类)。
  3. 使用调试工具: 在 IDE 中设置断点,观察对象的创建过程和成员变量的值变化。
  4. 阅读错误堆栈: 运行时异常会显示详细的调用栈,帮助定位问题源头。

记住:实例成员需要通过对象访问,静态成员可以通过类名直接访问。掌握这个基本原则,就能避免大部分常见的面向对象编程错误。

八、本章总结

  1. 类是模板,对象是实例,面向对象核心是封装数据与行为;
  2. 类核心组成:私有成员变量、成员方法、静态成员、构造方法;
  3. 对象操作流程:new实例化→赋值初始化→调用属性方法;
  4. 方法重载依靠参数列表区分,实现同名多态简化调用;
  5. 包用于分层管理类,访问修饰符控制类与成员可见范围,private配合get/set实现封装;
  6. static修饰内容归属类,无需实例对象即可直接调用。

课后思考习题

请思考并回答以下问题,每个问题下方已预留答题空间:

1. 简述类和对象的区别,举一个生活中的例子

2. 方法重载能否仅依靠返回值不同实现?为什么?

3. private修饰的成员变量如何在类外完成读写?

4. 静态方法中能否直接调用普通成员方法?说明原因


参考答案

1. 类和对象的区别及生活实例

核心概念对比

维度 类(Class) 对象(Object)
本质 抽象模板/蓝图 具体实例
内存 不占用实际内存空间 占用内存空间
存在时机 编译时定义 运行时创建
数量关系 一个类可创建多个对象 一个对象属于一个类

生活实例解析

  • 类的例子:汽车设计图

    • 定义了汽车的属性(颜色、品牌、型号)
    • 定义了汽车的行为(启动、加速、刹车)
    • 是抽象概念,不占用物理空间
  • 对象的例子:一辆红色特斯拉 Model 3

    • 具体属性值:颜色=红色,品牌=特斯拉,型号=Model 3
    • 可执行行为:实际启动、加速、刹车
    • 是物理实体,占用实际空间

关键区别总结

  1. 抽象 vs 具体:类是抽象概念,对象是具体实体
  2. 设计 vs 实现:类定义在编译时,对象创建在运行时
  3. 内存占用:类不占内存,对象占用内存
  4. 数量关系:一个类可对应多个对象

2. 方法重载能否仅依靠返回值不同实现?

答案:不能

原因分析

  1. 编译器无法区分调用

    • 调用语句如 method(); 无法确定应该调用返回 int 还是返回 String 的版本
    • 编译器根据方法名 + 参数列表确定调用目标,返回值类型不在匹配范围内
  2. Java语法规定

    • 方法重载要求参数签名不同(参数类型、个数或顺序)
    • 返回值类型不属于方法签名的一部分
  3. 实际场景矛盾

    • 调用者可能忽略返回值(如调用 void 方法)
    • 仅靠返回值无法提供足够信息让编译器做出正确选择

正确的方法重载示例

// 有效重载:参数类型不同
public int add(int a, int b) { return a + b; }
public double add(double a, double b) { return a + b; }

// 有效重载:参数个数不同  
public int add(int a, int b) { return a + b; }
public int add(int a, int b, int c) { return a + b + c; }

// 无效重载:仅返回值不同(编译错误)
public int getValue() { return 1; }
public String getValue() { return "1"; } // ❌ 编译错误

3. private修饰的成员变量如何在类外完成读写?

核心机制:通过公共方法间接访问

private成员变量在类外不能直接访问,必须通过类提供的公共方法间接操作,这是封装思想的具体体现。

读写操作实现

操作类型 方法类型 示例代码 外部调用方式
读取(读) Getter方法 public String getName() { return name; } obj.getName()
写入(写) Setter方法 public void setName(String n) { name = n; } obj.setName("张三")

封装的优势与价值

  1. 数据验证与控制

    // Setter中添加验证逻辑
    public void setAge(int age) {
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("年龄无效");
        }
        this.age = age;
    }
    
  2. 数据格式化与保护

    // Getter中控制返回形式
    public String getFormattedSalary() {
        return String.format("¥%,.2f", salary); // 格式化为货币形式
    }
    
  3. 内部实现隐藏

    • 可随时修改private变量的存储方式而不影响外部调用
    • 例如:将 String name 改为 String firstName + String lastName

完整示例

public class Student {
    private String name;  // private成员变量
    private int age;
    
    // Getter方法(读)
    public String getName() {
        return name;
    }
    
    public int getAge() {
        return age;
    }
    
    // Setter方法(写)
    public void setName(String name) {
        if (name == null || name.trim().isEmpty()) {
            throw new IllegalArgumentException("姓名不能为空");
        }
        this.name = name;
    }
    
    public void setAge(int age) {
        if (age < 0 || age > 120) {
            throw new IllegalArgumentException("年龄无效");
        }
        this.age = age;
    }
}

4. 静态方法中能否直接调用普通成员方法?

答案:不能直接调用

根本原因分析

对比维度 静态方法(Static Method) 普通成员方法(Instance Method)
所属主体 属于 属于对象
存在时机 类加载时即存在 对象实例化后才存在
隐含参数 this 引用 隐含 this 参数指向当前对象
访问权限 只能访问静态成员 可访问静态和实例成员

具体原因详解

  1. 生命周期不匹配

    • 静态方法在类加载时就已存在
    • 普通成员方法需要对象实例化后才存在
    • 静态方法调用时,可能还没有任何对象被创建
  2. 缺少this引用

    public class Example {
        private int value;  // 实例变量
        
        // 普通成员方法:隐含this参数
        public void showValue() {
            System.out.println(this.value);  // 需要this访问实例变量
        }
        
        // 静态方法:没有this
        public static void staticMethod() {
            // showValue();  // ❌ 错误:不知道操作哪个对象的value
        }
    }
    
  3. 设计原则冲突

    • 静态方法:用于不依赖对象状态的工具类、工厂方法等
    • 普通成员方法:用于操作对象具体状态的业务逻辑

正确的调用方式

public class MyClass {
    // 普通成员方法
    public void instanceMethod() {
        System.out.println("实例方法");
    }
    
    // 静态方法
    public static void staticMethod() {
        // 错误:直接调用 ❌
        // instanceMethod();
        
        // 正确:先创建实例 ✅
        MyClass obj = new MyClass();
        obj.instanceMethod();
        
        // 或者通过参数传入实例
        // public static void callInstance(MyClass obj) {
        //     obj.instanceMethod();
        // }
    }
}

使用建议

  1. 工具类设计:将不依赖对象状态的方法声明为static
  2. 工厂模式:使用静态方法创建对象实例
  3. 常量/配置:与静态变量配合使用的工具方法
  4. 避免滥用:不要为了"方便"而将所有方法都声明为static

实战编程题

题目:学生成绩管理系统扩展

题目描述

基于当前文章中的 Student 类,设计一个扩展的 EnhancedStudent 类,要求实现以下功能:

  1. 对象创建与初始化

    • 提供至少两种构造方法(方法重载):
      • 默认构造方法:创建匿名学生,学号自动生成
      • 带参数构造方法:接收姓名、学号、年龄参数
    • 使用静态变量 totalStudents 记录已创建的学生总数
  2. 封装与数据验证

    • 为所有成员变量提供 getter 和 setter 方法
    • 在 setter 中添加数据验证逻辑:
      • 姓名不能为空或仅包含空格
      • 年龄必须在 15-60 岁之间
      • 学号必须为 10 位数字字符串
  3. 成绩管理功能

    • 添加 addScore(String course, double score) 方法,记录学生各科成绩
    • 添加 getAverageScore() 方法,计算平均分
    • 添加静态方法 getClassAverage(EnhancedStudent[] students),计算班级平均分
  4. 信息展示

    • 重写 toString() 方法,返回格式化的学生信息
    • 添加实例方法 showDetails(),显示学生详细信息(包括各科成绩)

输入输出示例

public class Main {
    public static void main(String[] args) {
        // 创建学生对象
        EnhancedStudent s1 = new EnhancedStudent("张三", "2023000001", 20);
        EnhancedStudent s2 = new EnhancedStudent("李四", "2023000002", 21);
        EnhancedStudent s3 = new EnhancedStudent(); // 匿名学生,学号自动生成
        
        // 设置成绩
        s1.addScore("数学", 85.5);
        s1.addScore("英语", 92.0);
        s2.addScore("数学", 78.0);
        s2.addScore("英语", 88.5);
        s3.addScore("数学", 90.0);
        s3.addScore("英语", 86.0);
        
        // 显示学生信息
        System.out.println(s1);
        s2.showDetails();
        
        // 计算平均分
        System.out.println("张三平均分: " + s1.getAverageScore());
        
        // 计算班级平均分
        EnhancedStudent[] students = {s1, s2, s3};
        System.out.println("班级平均分: " + EnhancedStudent.getClassAverage(students));
        
        // 显示学生总数
        System.out.println("学生总数: " + EnhancedStudent.getTotalStudents());
    }
}

预期输出示例

学生[张三, 学号:2023000001, 年龄:20, 平均分:88.75]
=== 学生详细信息 ===
姓名: 李四
学号: 2023000002
年龄: 21
成绩列表:
  数学: 78.0
  英语: 88.5
平均分: 83.25
张三平均分: 88.75
班级平均分: 84.0
学生总数: 3

参考答案

import java.util.HashMap;
import java.util.Map;

/**
 * 增强版学生类,综合演示面向对象核心概念
 */
public class EnhancedStudent {
    // 静态变量:记录学生总数(类级别)
    private static int totalStudents = 0;
    
    // 实例变量(对象级别)
    private String name;
    private String studentId;
    private int age;
    private Map<String, Double> scores; // 存储课程成绩
    
    // === 构造方法重载 ===
    
    /**
     * 默认构造方法(方法重载示例1)
     * 创建匿名学生,学号自动生成
     */
    public EnhancedStudent() {
        this.name = "匿名";
        this.studentId = generateStudentId();
        this.age = 18;
        this.scores = new HashMap<>();
        totalStudents++; // 每次创建对象时递增
    }
    
    /**
     * 带参数构造方法(方法重载示例2)
     * @param name 学生姓名
     * @param studentId 学号
     * @param age 年龄
     */
    public EnhancedStudent(String name, String studentId, int age) {
        setName(name);      // 通过setter进行验证
        setStudentId(studentId);
        setAge(age);
        this.scores = new HashMap<>();
        totalStudents++;    // 每次创建对象时递增
    }
    
    // === 静态方法 ===
    
    /**
     * 生成自动学号
     * @return 10位数字学号
     */
    private static String generateStudentId() {
        // 简单示例:实际中可能从数据库获取
        return String.format("AUTO%06d", totalStudents + 1);
    }
    
    /**
     * 计算班级平均分(静态方法示例)
     * @param students 学生数组
     * @return 班级平均分
     */
    public static double getClassAverage(EnhancedStudent[] students) {
        if (students == null || students.length == 0) {
            return 0.0;
        }
        
        double total = 0.0;
        int count = 0;
        
        for (EnhancedStudent student : students) {
            double avg = student.getAverageScore();
            if (avg > 0) { // 只统计有成绩的学生
                total += avg;
                count++;
            }
        }
        
        return count > 0 ? total / count : 0.0;
    }
    
    /**
     * 获取学生总数(静态方法)
     * @return 已创建的学生总数
     */
    public static int getTotalStudents() {
        return totalStudents;
    }
    
    // === Getter和Setter(封装示例) ===
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        if (name == null || name.trim().isEmpty()) {
            throw new IllegalArgumentException("姓名不能为空");
        }
        this.name = name.trim();
    }
    
    public String getStudentId() {
        return studentId;
    }
    
    public void setStudentId(String studentId) {
        if (studentId == null || !studentId.matches("\\d{10}")) {
            throw new IllegalArgumentException("学号必须为10位数字");
        }
        this.studentId = studentId;
    }
    
    public int getAge() {
        return age;
    }
    
    public void setAge(int age) {
        if (age < 15 || age > 60) {
            throw new IllegalArgumentException("年龄必须在15-60岁之间");
        }
        this.age = age;
    }
    
    // === 成绩管理方法 ===
    
    /**
     * 添加课程成绩
     * @param course 课程名称
     * @param score 成绩
     */
    public void addScore(String course, double score) {
        if (course == null || course.trim().isEmpty()) {
            throw new IllegalArgumentException("课程名称不能为空");
        }
        if (score < 0 || score > 100) {
            throw new IllegalArgumentException("成绩必须在0-100之间");
        }
        scores.put(course.trim(), score);
    }
    
    /**
     * 获取指定课程成绩
     * @param course 课程名称
     * @return 成绩,如果不存在返回-1
     */
    public double getScore(String course) {
        return scores.getOrDefault(course, -1.0);
    }
    
    /**
     * 计算平均分
     * @return 平均分
     */
    public double getAverageScore() {
        if (scores.isEmpty()) {
            return 0.0;
        }
        
        double sum = 0.0;
        for (double score : scores.values()) {
            sum += score;
        }
        return sum / scores.size();
    }
    
    // === 信息展示方法 ===
    
    /**
     * 重写toString方法
     * @return 格式化的学生基本信息
     */
    @Override
    public String toString() {
        return String.format("学生[%s, 学号:%s, 年龄:%d, 平均分:%.2f]", 
                name, studentId, age, getAverageScore());
    }
    
    /**
     * 显示学生详细信息
     */
    public void showDetails() {
        System.out.println("=== 学生详细信息 ===");
        System.out.println("姓名: " + name);
        System.out.println("学号: " + studentId);
        System.out.println("年龄: " + age);
        
        if (!scores.isEmpty()) {
            System.out.println("成绩列表:");
            for (Map.Entry<String, Double> entry : scores.entrySet()) {
                System.out.printf("  %s: %.1f%n", entry.getKey(), entry.getValue());
            }
            System.out.printf("平均分: %.2f%n", getAverageScore());
        } else {
            System.out.println("暂无成绩记录");
        }
        System.out.println();
    }
    
    /**
     * 获取成绩映射(只读视图)
     * @return 不可修改的成绩映射
     */
    public Map<String, Double> getScores() {
        return new HashMap<>(scores); // 返回副本以保护内部数据
    }
}

关键知识点说明

  1. 构造方法重载:提供了两种构造方法,演示了方法重载的实际应用
  2. 静态变量与方法totalStudents 统计学生总数,getClassAverage() 演示静态方法操作对象数组
  3. 封装与数据验证:所有 setter 方法都包含数据验证逻辑,确保数据完整性
  4. Map集合使用:使用 HashMap 存储课程成绩,演示集合的基本操作
  5. 方法设计:实例方法与静态方法的合理分工,实例方法操作对象状态,静态方法提供工具功能

更多推荐