04-Java面向对象基础

Java面向对象基础:类、对象、属性与内存分析

引言

学完变量、控制流、数组和方法之后,我们已经能用Java写出不少"能跑"的程序。但代码一多,就会发现问题:所有逻辑都堆在main里,函数和函数之间靠参数传来传去,数据和行为被强行割裂。

面向对象(Object-Oriented,OO)就是用来解决这个痛点的。它把"数据"和"操作数据的方法"打包成一个整体,让代码组织更贴近现实世界的结构。本篇博客面向Java初学者,带你从面向过程平滑过渡到面向对象。


一、面向过程 vs 面向对象

1.1 两种思想的对比

面向过程关注的是"怎么做":把一个大问题拆成若干步骤,每一步用一个函数实现,按顺序调用即可。

面向对象关注的是"谁来做":先分析问题里有哪些"角色"(对象),每个角色有哪些属性和行为,再让它们协作完成任务。

经典例子:把大象装进冰箱。

  • 面向过程的写法:打开门 → 装大象 → 关上门。
  • 面向对象的写法:先设计PersonFridgeElephant三个类,让人去操作冰箱和大象。
java 复制代码
public class Main {
    public static void main(String[] args) {
        Person p = new Person("张三");
        Fridge f = new Fridge();
        Elephant e = new Elephant();

        p.pull(f);   // 人拉开冰箱
        p.pull(e);   // 人拉大象过来
        p.push(e);   // 人把大象推进去
        p.push(f);   // 人关上冰箱
    }
}

1.2 何时用哪种思想?

面向过程执行效率高、思路直接,适合小型脚本、单片机程序等场景。面向对象更适合大型软件:模块清晰、易于维护和扩展。Java是纯面向对象语言,一切代码都必须写在类里。

小贴士:面向对象并非"高级"于面向过程,二者是不同维度的思想。Java中依然大量使用方法(过程),只是这些方法被组织到了类和对象里。


二、面向对象的三大特征

面向对象有三大核心特征,本篇只做简介,后续博客会逐一深入。

1. 封装(Encapsulation):把属性和行为封装进对象,隐藏内部实现细节,只暴露必要的接口。就像汽车,你只需要知道踩油门能加速,不需要懂发动机内部结构。

2. 继承(Inheritance) :子类可以复用父类的属性和方法,避免重复造轮子。比如Dog继承Animal,自动拥有"吃""睡"等行为。

3. 多态(Polymorphism) :同一个方法调用,不同对象表现出不同行为。比如同样调用animal.cry(),狗叫"汪汪",猫叫"喵喵"。

理解了三大特征的整体轮廓后,我们就能从最基础的"类与对象"开始动手了。


三、类与对象的概念

3.1 生活化比喻

类(Class)是设计图,对象(Object)是按图生产的产品。

对象
楼房的设计图纸 一栋真正盖起来的楼房
汽车的车型设计 一辆停在楼下的真车
学生类的描述 张三这个具体的学生

一张图纸可以盖出很多栋楼,但每栋楼都有自己的门牌号、住户、装修。同样,一个类可以创建多个对象,每个对象的属性值各自独立。

3.2 概念定义

  • :是一类具有共同特征 的事物的抽象描述
  • 对象:是这类事物的一个具体的个体、实体、实例。

以"学生类"为例:

  • 属性:姓名、学号、性别、专业、成绩......
  • 行为:注册、学习、考试......

类只描述"有哪些属性和行为",具体值要由对象来确定。


四、声明类的格式

4.1 基本语法

java 复制代码
【修饰符】 class 类名 {
    // 成员变量(属性)
    // 成员方法(行为)
}
  • 修饰符 :可以是publicfinalabstract等,初学阶段先用public
  • 类名 :使用大驼峰命名法(每个单词首字母大写),如HelloWorldStudent
  • 一份.java文件中只能有一个public类,且类名要和文件名一致。

4.2 第一个自定义类

java 复制代码
package com.atguigu.oop;

public class Student {
    public String name;   // 姓名
    public int age;       // 年龄
    public String tel;    // 电话
    public int score;     // 成绩
}

注意package语句声明了类所属的包(即文件夹),用于避免类名冲突并组织代码。包名习惯用公司域名倒置,例如com.atguigu.xxx


五、创建对象:new关键字

5.1 语法格式

java 复制代码
类名 对象名 = new 类名();          // 无参
类名 对象名 = new 类名(实参列表);   // 有参(需配合构造方法)

new关键字做了三件事:在堆中开辟内存、初始化属性默认值、返回对象的首地址。对象名本质是一个引用变量,存的是对象在堆中的地址。

5.2 命名对象与匿名对象

java 复制代码
public class CreateObjectDemo {
    public static void main(String[] args) {
        // 命名对象:用变量接收引用,可重复使用
        Student s = new Student();
        System.out.println(s);  // com.atguigu.oop.Student@34c45dca

        // 匿名对象:不赋给任何变量,用一次即丢
        System.out.println(new Student());
    }
}

5.3 匿名对象的使用场景

匿名对象没有名字,创建后无法再次访问,常见于以下场景:

  1. 只调用一次方法new Scanner(System.in).nextInt();
  2. 作为实参传递printInfo(new Student());
  3. 作为方法的返回值return new MyDate();

小贴士 :如果同一个对象要被使用多次,必须用命名对象。匿名对象每new一次就开辟一块新内存,滥用会浪费资源。


六、类的成员之一:属性(成员变量)

6.1 声明格式

java 复制代码
【修饰符】 class 类名 {
    【修饰符】 数据类型 成员变量名;
}

成员变量也叫属性、字段。它写在类里、方法外。

6.2 访问方式

java 复制代码
对象名.属性

6.3 完整示例

java 复制代码
public class StudentTest {
    public static void main(String[] args) {
        Student s1 = new Student();

        // 赋值前:输出默认值
        System.out.println(s1.name);   // null
        System.out.println(s1.age);    // 0

        // 赋值
        s1.name = "张三";
        s1.age = 23;

        // 赋值后
        System.out.println(s1.name);   // 张三
        System.out.println(s1.age);    // 23

        // 新建一个对象,属性值独立
        Student s2 = new Student();
        System.out.println(s2.name);   // null
    }
}

小贴士:成员变量有默认值(整数0、浮点0.0、布尔false、字符'\u0000'、引用null),无需手动初始化即可使用。这是和局部变量最大的不同。


七、成员变量分类:静态 vs 非静态

7.1 何时加static?

判断原则非常简单:

  • 如果属性的值是每个对象独立的 → 不加static → 实例变量。
  • 如果属性的值是所有对象共享的 → 加static → 静态变量(类变量)。

7.2 代码对比

java 复制代码
public class Account {
    public static double rate;     // 利率:所有人共享 → 静态变量
    public double balance;         // 余额:每人独立 → 实例变量
    public String id;              // 账号:每人独立 → 实例变量
}

class AccountTest {
    public static void main(String[] args) {
        Account a1 = new Account();
        a1.id = "111";
        a1.balance = 5000;

        Account a2 = new Account();
        a2.id = "222";
        a2.balance = 10000;

        // 静态变量推荐用"类名."访问
        Account.rate = 0.035;

        System.out.println(a1.id + "," + a1.balance + "," + a1.rate);
        System.out.println(a2.id + "," + a2.balance + "," + a2.rate);
    }
}

注意a1.rate = 0.035语法上也合法,但不推荐。因为静态变量属于类而非某个对象,用类名.静态变量才能体现共享语义。

7.3 详细对比表

对比维度 静态成员变量(类变量) 非静态成员变量(实例变量)
是否加static
共享性 所有对象共享同一份 每个对象各自独立一份
内存存储位置 JDK6及以前:方法区;JDK7+:堆(Class对象的反射字段);JDK8+:堆 堆(随对象一起)
初始化时机 类加载时初始化 new对象时初始化
访问方式 推荐类名.变量,也可对象名.变量 只能对象名.变量
生命周期 类存在即存在,类卸载才销毁 对象创建时存在,对象被GC回收时销毁
默认值 有默认值 有默认值

JDK版本变化小贴士

  • JDK6及之前:静态变量存储在方法区(永久代)。
  • JDK7:静态变量的引用从方法区移到了中。
  • JDK8+:永久代被废除,方法区改名为元空间(Metaspace),使用本地内存;静态变量随Class对象存放在堆中。

八、类的成员之二:方法

8.1 声明格式

java 复制代码
【修饰符】 class 类名 {
    【修饰符】 static 返回值类型 方法名(【形参列表】) {  // 静态方法
        方法体语句;
    }

    【修饰符】 返回值类型 方法名(【形参列表】) {         // 实例方法
        方法体语句;
    }
}

8.2 静态方法 vs 实例方法

判断原则:

  • 方法体中需要访问本类的实例变量 → 必须是实例方法。
  • 方法体中不访问本类的实例变量 → 可以定义为静态方法,调用更方便(无需new对象)。

工具类的方法大多是静态的,比如Math.random()Arrays.sort(),调用时直接类名.方法名即可。

8.3 跨类调用

java 复制代码
类名.静态方法();      // 静态方法
对象名.实例方法();    // 实例方法

8.4 为什么要定义方法?

为了让代码可复用。下面的矩形类把"求面积""求周长""获取信息"分别封装成方法,每次需要时直接调用即可。

java 复制代码
public class Rectangle {
    public double length;
    public double width;

    // 求面积:实例方法,直接使用实例变量
    public double area() {
        return length * width;
    }

    // 求周长
    public double perimeter() {
        return 2 * (length + width);
    }

    // 获取信息:本类内部方法之间可以互相调用
    public String getInfo() {
        return "长:" + length + ",宽:" + width
             + ",面积:" + area() + ",周长:" + perimeter();
    }
}

class RectangleTest {
    public static void main(String[] args) {
        Rectangle r1 = new Rectangle();
        r1.length = 5;
        r1.width = 3;
        System.out.println("r1的" + r1.getInfo());
    }
}

8.5 同类中成员的互相访问

在同一类中:

  • 静态方法 :只能访问本类的静态变量和静态方法,不能直接访问实例变量和实例方法。
  • 实例方法:既能访问静态成员,也能访问实例成员。

小贴士:因为静态方法不依赖对象存在,而实例变量必须依附于对象。静态方法被调用时可能还没有任何对象,所以它无法直接使用实例变量。


九、static关键字详解

static可以修饰成员变量、成员方法、代码块、内部类。它修饰的成员属于"类级别",不属于某个对象。

核心特点

  1. 随着类的加载而加载,生命周期最长。
  2. 优先于对象存在,可以不new对象就直接通过类名访问。
  3. 所有对象共享同一份静态成员。
  4. 可以直接用类名调用类名.静态变量类名.静态方法()
java 复制代码
public class Chinese {
    public static String countryName;  // 国名:所有中国人共享
    public String name;                // 姓名:每人独立
    public int age;                    // 年龄:每人独立
}

class ChineseTest {
    public static void main(String[] args) {
        Chinese.countryName = "中国";   // 类名访问静态变量

        Chinese c1 = new Chinese();
        c1.name = "李涛";
        c1.age = 25;

        Chinese c2 = new Chinese();
        c2.name = "王芳";
        c2.age = 22;

        System.out.println(c1.name + "," + c1.countryName);  // 李涛,中国
        System.out.println(c2.name + "," + c2.countryName);  // 王芳,中国
    }
}

十、this与super简介

本篇源笔记未深入讲解thissuper,但它们在面向对象中至关重要,这里做简要介绍,后续博客会展开。

  • this :代表"当前对象"的引用。当实例方法的局部变量与成员变量同名时,用this.变量名区分;构造方法间互相调用也用this(...)
  • super:代表"父类对象"的引用,用于在子类中访问父类的成员,常用于方法重写后调用父类原版方法。
java 复制代码
public class Student {
    private String name;

    public void setName(String name) {
        this.name = name;  // this.name指成员变量,name指参数
    }
}

注意thissuper都是对象级别的引用,不能在静态方法中使用。


十一、对象的内存分析

理解对象在内存中如何存放,是真正掌握面向对象的关键。Java虚拟机内存主要涉及三块区域:

11.1 三大内存区域

  • 栈(Stack):存放方法的栈帧,方法内的局部变量、对象的引用(地址)都存在这里。方法执行完即出栈。
  • 堆(Heap) :存放new出来的对象本体,对象的成员变量(实例变量)也在这里。由GC自动回收。
  • 方法区/元空间(Method Area / Metaspace):存放类的信息(Class对象)、静态变量、常量、方法的字节码等。JDK8后改名为元空间,使用本地内存。

11.2 new对象的过程

执行Student s = new Student();时,发生了什么?

  1. 类加载:JVM检测到Student类,把它的Class信息加载到方法区。
  2. 堆中开辟空间:在堆中为对象分配内存。
  3. 默认初始化:实例变量被赋默认值(int→0,引用→null等)。
  4. 显式初始化:执行成员变量的显式赋值语句。
  5. 构造方法执行:执行构造方法体(如果有的话)。
  6. 返回地址:把对象在堆中的首地址赋给栈中的引用变量s

11.3 内存图示意

以如下代码为例:

java 复制代码
Student s1 = new Student();
s1.name = "张三";
s1.age = 23;

Student s2 = new Student();
s2.name = "李四";
复制代码
栈                          堆                         方法区/元空间
+-----------------+        +---------------------+    +------------------+
| main栈帧        |        | Student对象 @a1     |    | Student类信息    |
|   s1 = @a1      | -----> |   name = "张三"     |    |   字节码          |
|   s2 = @a2      |        |   age  = 23         |    |   静态变量        |
+-----------------+        |   tel  = null       |    +------------------+
                           |   score= 0          |
                           +---------------------+
                           | Student对象 @a2     |
                           |   name = "李四"     |
                           |   age  = 0          |
                           |   ...               |
                           +---------------------+

可以看到:每个对象在堆中独立占用一块内存,但它们都指向方法区里同一份类信息。多个对象通过各自的引用变量区分。

11.4 引用类型成员变量的内存分析

当成员变量是引用类型时,对象内部存的是另一个对象的地址,形成"对象套对象"的结构。

java 复制代码
public class Employee {
    public String name;
    public MyDate birthday;   // 引用类型成员变量
}

class EmployeeTest {
    public static void main(String[] args) {
        Employee e = new Employee();
        e.name = "张三";
        e.birthday = new MyDate();      // 堆中再开一块MyDate对象
        e.birthday.year = 2000;         // 通过引用逐级访问
        e.birthday.month = 5;
        e.birthday.day = 1;
    }
}

小贴士e.birthday.year的访问路径是 栈中e → 堆中Employee对象 → 堆中MyDate对象 → year。任何一环为null都会抛NullPointerException


十二、成员变量与局部变量的区别

这是初学者最容易混淆的点,下面用表格梳理。

对比维度 成员变量 局部变量
声明位置 类中、方法外 方法中、方法的形参、代码块中
作用域 整个类 从声明处到所在{}结束
默认值 有默认值,可直接使用 无默认值,必须先赋值再使用
存储位置 实例变量在堆中;静态变量在堆中(JDK8+) 栈中(方法栈帧内)
生命周期 实例变量随对象生灭;静态变量随类生灭 随方法调用而生,方法结束即亡
修饰符 可用public/private/static/final 只能用final,不能用访问修饰符和static
java 复制代码
public class Demo {
    public int memberVar = 10;   // 成员变量,有默认值

    public void test() {
        int localVar;            // 局部变量,无默认值
        // System.out.println(localVar);  // 编译报错:可能尚未初始化
        localVar = 20;
        System.out.println(localVar);
    }
}

小贴士 :如果局部变量和成员变量同名,方法内优先使用局部变量(就近原则)。若要访问被遮蔽的成员变量,需用this.变量名


十三、基本类型 vs 引用类型作为成员变量

Java的数据类型分两大类:

  • 8种基本类型byteshortintlongfloatdoublecharboolean
  • 引用类型:数组、类、接口等。

成员变量无论是基本类型还是引用类型,都存在堆中。但存储内容不同

  • 基本类型成员变量:直接存值。
  • 引用类型成员变量:存的是另一个对象的地址(引用)。

13.1 引用类型成员变量示例

java 复制代码
public class MyDate {
    public int year;
    public int month;
    public int day;

    public String getDateInfo() {
        return year + "年" + month + "月" + day + "日";
    }
}

public class Employee {
    public String name;        // String是引用类型
    public MyDate birthday;    // 自定义类也是引用类型

    public String getEmployeeInfo() {
        return "姓名:" + name + ",生日:" + birthday.getDateInfo();
    }
}

class EmployeeTest {
    public static void main(String[] args) {
        Employee e = new Employee();
        e.name = "张三";
        e.birthday = new MyDate();
        e.birthday.year = 2000;
        e.birthday.month = 5;
        e.birthday.day = 1;

        System.out.println(e.getEmployeeInfo());
        // 姓名:张三,生日:2000年5月1日
    }
}

13.2 链式访问

引用类型成员变量可以一直"点"下去:

java 复制代码
e.birthday.year  // 逐级解引用

每一级.都是一次从引用到对象的跳转。这就是面向对象程序中"对象协作"的体现:一个对象内部包含另一个对象,多个对象组合成复杂结构。

注意 :自己写的类也是数据类型,可以像intString一样作为成员变量、方法参数、返回值。这是面向对象编程的核心能力。


十四、匿名对象再深入

14.1 什么是匿名对象

java 复制代码
new Student();   // 这就是一个匿名对象,没有变量名接收

它有对象实体的内存,但没有引用变量指向它,只能使用一次。

14.2 典型使用场景

场景1:只调用一次方法

java 复制代码
new Scanner(System.in).nextInt();

调用完毕,Scanner对象立即成为垃圾,等待GC回收。

场景2:作为方法实参

java 复制代码
printInfo(new Student());

public static void printInfo(Student s) {
    System.out.println(s.name);
}

场景3:作为方法返回值

java 复制代码
public static MyDate createToday() {
    return new MyDate();   // 返回匿名对象
}

14.3 注意事项

java 复制代码
new Student().name = "张三";   // 赋值成功,但对象立刻丢弃,无意义
new Student().study();          // 调用方法OK
new Student().study();          // 又new了一个新对象,与上一行无关

小贴士:匿名对象一旦不使用就成垃圾。需要多次访问同一对象时,必须用命名对象接收引用。


十五、小结

本篇博客带大家从思想到代码、从语法到内存,建立了面向对象的基础认知。回顾要点:

  1. 思想转变:从"怎么做"(面向过程)转向"谁来做"(面向对象)。
  2. 三大特征:封装、继承、多态是面向对象的根基。
  3. 类与对象 :类是模板,对象是实例;用new创建对象。
  4. 属性:成员变量有默认值;区分静态变量(共享)和实例变量(独立)。
  5. 方法:静态方法属于类,实例方法属于对象;同类中静态不能直接访问实例成员。
  6. static:类级别成员,生命周期长,所有对象共享。
  7. 内存分析 :栈存引用、堆存对象、方法区存类信息;理解new的全过程。
  8. 变量对比:成员变量vs局部变量、基本类型vs引用类型成员变量,差别巨大。
  9. 匿名对象:用于一次性场景,避免冗余引用。

掌握了这些基础,下一步就可以进入封装(private + getter/setter)构造方法this关键字的深入学习了。面向对象的大门已经打开,欢迎继续探索。

写完这篇博客,建议动手敲一遍所有示例代码,并自己画一画内存图。代码会忘,但内存图一旦画明白,面向对象就再也不会忘。

相关推荐
阿pin1 小时前
Android随笔-View绘制流程
android·开发语言·view
Java面试题总结1 小时前
GatewayWorker 从零到一完整指南
java
脉动数据行情1 小时前
Java OkHttp 完整封装 GC COMEX 美黄金实时盘口定时拉取工具类
java·okhttp·comex 黄金·外盘贵金属
敖行客 Allthinker1 小时前
IM 融合专题:后端架构师的核心修炼
java·开发语言·数据库
进击的程序猿~2 小时前
Go Slice源码深度解析指南
开发语言·后端·golang
Hesionberger2 小时前
快速求解完全平方数的最少数量
开发语言·数据结构·python·算法·leetcode·c#
HAPPY酷2 小时前
【ROS2】16G 内存笔记本跑 ROS2 仿真?VMware 虚拟机“保姆级”配置指南 (R9 7940HX + RTX 4060)
java·linux·ide·windows·pycharm
折哥的程序人生 · 物流技术专研10 小时前
第4篇:Lambda 简化策略模式(Java 8+)
java·设计模式·策略模式·函数式编程·lambda·代码简化·扩充系列
researcher-Jiang11 小时前
高性能计算之OpenMP——超算习堂学习1
android·java·学习