什么是类和对象
一句话理解:类是图纸,对象是按图纸造出来的房子
你画了一份”别墅设计图”(类),然后按照这张图可以盖出很多栋别墅(对象),每栋别墅的颜色、门牌号可以不同,但结构一样
类定义了”有什么”(属性/字段)和”能干什么”(方法/行为),对象是类的一个具体实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Dog { String name; int age;
void bark() { System.out.println(name + "在汪汪叫!"); } }
Dog myDog = new Dog(); myDog.name = "旺财"; myDog.age = 3; myDog.bark();
|
类的定义:字段、方法、构造器
字段(Field)= 属性:描述这个东西”有什么”,比如狗有名字、年龄
方法(Method)= 行为:描述这个东西”能干什么”,比如狗能叫、能跑
构造器(Constructor):创建对象时自动调用的特殊方法,用来做”初始化”工作,后面详细讲
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class Student {
String name; int age; String school;
public Student(String name, int age) { this.name = name; this.age = age; }
public void introduce() { System.out.println("我叫" + name + ",今年" + age + "岁"); } }
|
创建对象:new 关键字
new 就像在工厂里按下”开工”按钮,JVM 会在堆内存里给你造一个新对象出来
语法:类名 变量名 = new 类名();
1 2 3 4 5 6 7 8 9 10 11
| @Test public void testNewObject() { Student s1 = new Student("小明", 18); Student s2 = new Student("小红", 17);
s1.introduce(); s2.introduce();
System.out.println(s1 == s2); }
|
this 关键字
**this 就是”我自己”**——在方法内部,this 指向当前正在调用这个方法的那个对象
最常见的用途:区分字段和参数同名的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Cat { String name;
public Cat(String name) { this.name = name; }
public void sayHi() { System.out.println("我是" + this.name); } }
|
this 还能用来调用本类的其他构造器:this(参数)(必须放在构造器第一行)
构造器:无参构造、有参构造、构造器重载
构造器就是”出厂设置”——对象被 new 出来的那一刻,自动执行
无参构造器:如果你什么构造器都不写,Java 会偷偷送你一个空的无参构造器;但只要你写了任何一个构造器,这个”赠品”就没了
有参构造器:创建对象时直接传值,省得一个个赋值
构造器重载:同一个类可以有多个构造器,参数不同就行(这就是重载)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Test public void testConstructor() {
Phone p1 = new Phone(); p1.brand = "华为"; p1.price = 5999;
Phone p2 = new Phone("苹果", 8999);
Phone p3 = new Phone("小米");
p1.showInfo(); p2.showInfo(); p3.showInfo(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class Phone { String brand; int price;
public Phone() { }
public Phone(String brand, int price) { this.brand = brand; this.price = price; }
public Phone(String brand) { this.brand = brand; this.price = 0; }
public void showInfo() { System.out.println(brand + "," + price + "元"); } }
|
常见坑:写了有参构造后忘了补无参构造,然后 new Phone() 报错
方法重载(Overload)
同一个类里,方法名相同,参数列表不同(参数个数不同、类型不同、顺序不同),就叫重载
跟返回值无关!只看方法名 + 参数列表
你可以理解为:同一个动作的不同版本,比如”打印”可以打印整数、打印字符串、打印浮点数
1 2 3 4 5 6 7
| @Test public void testOverload() { Calculator calc = new Calculator(); System.out.println(calc.add(1, 2)); System.out.println(calc.add(1.5, 2.5)); System.out.println(calc.add(1, 2, 3)); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Calculator {
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, int c) { return a + b + c; } }
|
| 对比项 |
方法重载(Overload) |
| 发生位置 |
同一个类里 |
| 方法名 |
必须相同 |
| 参数列表 |
必须不同(个数/类型/顺序) |
| 返回值 |
无要求,不参与判断 |
| 关键词 |
无特殊关键词 |
重载 vs 重写的完整对比见 封装继承多态
栈 vs 堆内存
一句话理解:栈是桌面(放遥控器/便签),堆是仓库(放大家电)
栈(Stack):存放基本类型的值、方法调用、局部变量、对象的引用(地址)
堆(Heap):存放 new 出来的对象本身
引用就像一张写着仓库地址的便签条,贴在桌面上(栈),指向仓库里的真正货物(堆)
文字版内存图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| ┌──────────────── 栈内存 (Stack) ────────────────┐ │ │ │ int age = 18; → age: [18] │ │ double score = 95.5; → score: [95.5] │ │ Student s1 = new ...; → s1: [0x001] ──────┐ │ │ Student s2 = new ...; → s2: [0x002] ────┐ │ │ │ │ │ │ └─────────────────────────────────────────────│─│──┘ │ │ ┌──────────────── 堆内存 (Heap) ────────────────┐ │ │ │ │ │ [0x002] Student对象 ◄────────────┘ │ │ │ name: "小红" │ │ │ age: 17 │ │ │ │ │ │ [0x001] Student对象 ◄──────────────┘ │ │ name: "小明" │ │ age: 18 │ │ │ └────────────────────────────────────────────────┘
|
关键点:
基本类型(int, double, boolean 等)→ 值直接存在栈上,参考 变量与数据类型
对象 → 栈上存引用(地址),堆上存对象本身
s1 == s2 比较的是栈上的地址,不是对象的内容!
1 2 3 4 5 6 7 8 9 10 11
| @Test public void testMemory() { int a = 10; int b = 10; System.out.println(a == b);
Student s1 = new Student("小明", 18); Student s2 = new Student("小明", 18); System.out.println(s1 == s2);
}
|
static 关键字:静态变量、静态方法
一句话理解:static 的东西属于”类本身”,不属于某个具体对象
类比:学校的”校训”是 static 的(属于学校,所有学生共享),而”姓名”不是 static 的(每个学生不同)
静态变量:所有对象共享同一份,改了一个全都变
静态方法:不需要创建对象就能调用,用 类名.方法名() 直接调
还记得 [hello world](/2026/04/04/hello world/) 里的 public static void main 吗?
main 方法是 static 的,因为程序启动时还没有任何对象存在,JVM 必须通过类名直接调用它
public:公开的,JVM 能访问
static:静态的,不需要 new 对象就能调用
void:没有返回值
main:固定的方法名,JVM 约定的程序入口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Test public void testStatic() {
Chinese.nation = "中国";
Chinese c1 = new Chinese("小明"); Chinese c2 = new Chinese("小红");
System.out.println(c1.name + " - " + Chinese.nation); System.out.println(c2.name + " - " + Chinese.nation);
Chinese.showNation(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Chinese { String name; static String nation;
public Chinese(String name) { this.name = name; }
public static void showNation() { System.out.println("我们都是" + nation + "人"); } }
|
| 对比项 |
实例成员 |
静态成员(static) |
| 归属 |
属于对象 |
属于类 |
| 内存 |
每个对象各一份 |
全局只有一份 |
| 访问方式 |
对象名.xxx |
类名.xxx(推荐) |
| 能否访问实例成员 |
能 |
不能(没有 this) |
| 何时加载 |
new 对象时 |
类加载时(更早) |
常见坑:在 static 方法里写 this.xxx,编译直接报错,因为 static 方法不属于任何对象,没有”自己”
包(package)的概念和 import
一句话理解:package 就是文件夹,用来给类分门别类,避免重名冲突
就像你电脑里有”工作”文件夹和”生活”文件夹,里面都可以有一个叫 记录.txt 的文件,但不会搞混
包名规范:公司域名倒写 + 项目名 + 模块名,全小写
例如:com.taobao.order.service
import:要用别的包里的类,就得先 import 导入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.example.demo;
import java.util.ArrayList; import java.util.Scanner;
public class MyApp { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); Scanner sc = new Scanner(System.in); } }
|
注意:java.lang 包下的类(String, System, Math 等)不需要 import,Java 自动导入
在 IDE使用(IDEA) 里,通常 IDE 会帮你自动 import,按 Alt + Enter 即可
常见坑汇总
| 坑 |
说明 |
正确做法 |
| 忘写无参构造器 |
写了有参构造后,Java 不再赠送无参构造 |
手动补上无参构造 |
== 比较对象 |
比较的是地址,不是内容 |
用 .equals() 比较内容 |
| static 方法用 this |
static 没有”自己”,编译报错 |
去掉 this,或改成实例方法 |
| 重载只改返回值 |
只改返回值不算重载,编译报错 |
必须改参数列表 |
| 包名大写 |
不规范,虽然能跑但同事会打你 |
全小写,用点分隔 |
练习题
练习1:定义一个 Book 类
属性:书名(String)、价格(double)、作者(String)
构造器:无参 + 全参
方法:showInfo() 打印书籍信息
创建 2 个 Book 对象并调用 showInfo()
练习2:给上面的 Book 类加一个 static 变量 bookCount,每创建一个 Book 对象,bookCount 就 +1,最后打印总共创建了几本书
练习3(思考题):下面的代码输出什么?为什么?
1 2 3 4
| Student s1 = new Student("小明", 18); Student s2 = s1; s2.name = "小红"; System.out.println(s1.name);
|
提示:画一下栈和堆的内存图,想想 s1 和 s2 指向的是同一个对象还是两个对象
学完这篇,接下来去看 封装继承多态,了解面向对象的三大核心思想