面向对象
介绍
面向对象三大特征: 封装, 继承, 多态.
类里面能写哪些东西:
public class 类名 { 1. 成员变量 (代表属性, 一般是名词) 2. 成员方法 (代表行为, 一般是动词) 3. 构造器 4. 代码块 5. 内部类 }
通过类得到对象:
类名 对象名 = new 类名();
使用对象:
对象名.成员变量; // 访问属性 对象名.方法名; // 访问行为
定义一个类:
public class Phone { // 属性 (成员变量) String brand; double price; // 行为 (方法) public void call() { System.out.println("手机在打电话"); } public void playGame() { System.out.println("手机在玩游戏"); } }
使用这个类:
public class PhoneTest { public static void main(String[] args) { // 创建手机对象 Phone p = new Phone(); // 给手机赋值 p.brand = "华为"; p.price = 6999.9; // 获取手机对象中的值 System.out.println(p.brand); System.out.println(p.price); // 调用手机的方法 p.call(); p.playGame(); Phone p2 = new Phone(); p2.brand = "小米"; p2.price = 1999.9; System.out.println(p2.brand); System.out.println(p2.price); p2.call(); p2.playGame(); } }
执行结果:
华为 6999.9 手机在打电话 手机在玩游戏 小米 1999.9 手机在打电话 手机在玩游戏
用来描述某一个事物的类称为 Javabean 类, 在 Javabean 类中, 是不写 main() 方法的.
带 main() 方法的类, 称为测试类.
可以在测试类中创建 Javabean 类的对象并赋值调用.
类名采用大驼峰命名规则.
一个 Java 源文件可以写多个类, 但是只能有一个 public 类, 且 public 类的类名必须和文件名相同. 实际开发中, 一般一个源文件只写一个类.
public是一个访问修饰符 (access modifier) , 访问修饰符用于控制程序的其他部分对这段代码的访问级别.
成员变量的完整定义格式为:
修饰符 数据类型 变量名称 = 初始值;
一般都不给成员变量以初始值, 因为各个对象的成员变量的值都可能不相等, 且如果不给成员变量赋值的话, 成员变量都有默认值.
默认值:
基本数据类型:
-
byte, short, int, long: 0
-
float, double: 0.0
-
char: 空格
-
boolean: false
引用类型:
- String, 类, 接口, 数组: null
程序示例:
Javabean 类:
public class Default { byte b; short s; int i; long L; char c; boolean boo; float f; double d; String st; }
测试类:
public class DefaultTest { public static void main(String[] args) { Default test = new Default(); // 如果此处写 Default test; 则会报错 java: 可能尚未初始化变量test System.out.println(test.b); System.out.println(test.s); System.out.println(test.i); System.out.println(test.L); System.out.println(test.c); System.out.println(test.boo); System.out.println(test.f); System.out.println(test.d); System.out.println(test.st); } }
执行结果:
0 0 0 0 false 0.0 0.0 null
封装
private 是一个修饰符, 可以修饰成员, 例如成员变量和成员方法等, 用 private 修饰的成员, 只能在本类中访问.
public 也是一个修饰符, 也可以修饰成员, 例如成员变量和成员方法等, 用 public 修饰的成员, 可以在所有的类中访问.
程序示例:
Javabean 类:
public class Girlfriend { String name; String sex; private int age; public void setAge(int a) { if (a < 18 || a > 50) System.out.println("非法数据. "); else age = a; } public int getAge() { return age; } }
测试类:
public class GirlfriendTest { public static void main(String[] args) { Girlfriend g1 = new Girlfriend(); g1.sex = "女"; g1.name = "小团团"; g1.setAge(20); System.out.println(g1.getAge()); System.out.println(g1.sex); System.out.println(g1.name); } }
执行结果:
20 女 小团团
用 public 或 private 修饰成员变量时不影响它的默认值.
private 可以防止给成员变量一个不恰当的值.
程序示例:
Javabean 类:
public class GF { // 成员变量 private int age; private String name; private String gender; // getter 和 setter public void setAge(int age) { if (age < 18 || age > 30) { System.out.println("年龄不合适"); } else { this.age = age; } } public int getAge() { return age; } public void setName(String name) { this.name = name; } public String getName() { return name; } public void setGender(String gender) { this.gender = gender; } public String getGender() { return gender; } // 成员方法 public void playGame() { System.out.println("GF is playing game."); } public void eat() { System.out.println("GF is eating."); } }
测试类:
public class GFtest { public static void main(String[] args) { GF fg = new GF(); fg.setName("Hello"); fg.setAge(20); fg.setGender("nv"); fg.eat(); fg.playGame(); System.out.println(fg.getAge()); System.out.println(fg.getGender()); System.out.println(fg.getName()); } }
执行结果:
GF is eating. GF is playing game. 20 nv Hello
Javabean 类的成员变量都应该被定义为私有的, 即被 private 修饰的, 同时提供对应的 get 和 set 方法, 外界对成员变量的操作 (包括赋值和获取值) 都应该通过 get 和 set 方法进行而不是直接操作这些私有的成员变量.
定义在方法里面的变量叫做局部变量 (包括定义在方法头的形参变量) , 定义在方法外面类里面的变量叫做成员变量. 遵循就近原则, 如果局部变量和成员变量同名, 则在方法内局部变量覆盖成员变量. 如果不想覆盖, 即在方法中使用同名的成员变量, 需要在成员变量前面加关键字 this.
this 的本质: 代表方法调用者的地址.



程序示例 1:
Javabean 类:
public class Girlfriend { private int age; // 成员变量 public int getAge() { int age = 10; // 局部变量 return age; // 返回局部变量 } public int getAge1() { int age = 10; // 局部变量 return this.age; // 返回成员变量 } }
测试类:
public class GirlfriendTest { public static void main(String[] args) { Girlfriend g1 = new Girlfriend(); System.out.println(g1.getAge()); // 10 System.out.println(g1.getAge1()); // 0, 0 是默认值 } }
程序示例 2:
Javabean 类:
public class Girlfriend { private String name; private int age; public void setName(String name) { name = name; } public void setName1(String name) { this.name = name; } public void setAge(int a) { age = a; } public String getName() { String name = "小团团"; return name; } public String getName1() { String name = "小张"; return this.name; } public int getAge() { int age = 10; return age; } }
测试类:
public class GirlfriendTest { public static void main(String[] args) { Girlfriend g1 = new Girlfriend(); g1.setName("小明"); g1.setName1("小李"); g1.setAge(20); System.out.println(g1.getAge()); // 10 System.out.println(g1.getName()); // 小团团 System.out.println(g1.getName1()); // 小李 } }
如果不发生成员变量和局部变量同名的情况, this 写不写都一样.
程序示例:
Javabean 类:
public class Girlfriend { private int age; public int getAge() { return age; } public int getAge1() { return this.age; } }
测试类:
public class GirlfriendTest { public static void main(String[] args) { Girlfriend g1 = new Girlfriend(); System.out.println(g1.getAge()); // 0 System.out.println(g1.getAge1()); // 0 } }
构造方法
构造方法也叫构造器或构造函数.
作用: 在创建对象时, 给成员变量进行初始化 (即赋值) .
构造方法的格式:
public class Student { // 构造方法 修饰符 类名(参数) { 方法体; } }
注意:
-
方法名必须和类名完全相同.
-
没有返回值类型, 连 void 也不能有.
-
没有返回值, 不能由 return 语句带回结果数据, 即使是
return;
也不能有.
使用空参构造方法时, 成员变量被初始化为默认值. 空参构造方法的方法体一般都是空着, 什么都不写.
可以自定义带参构造方法. 在方法体内, 可以给成员变量赋值.
构造方法在创建对象时由虚拟机调用, 不能手动调用构造方法.
每创建一次对象, 就会调用一次构造方法.
如果没有自定义的构造方法, 那么虚拟机会自动添加一个空参构造方法.
如果自定义了有参构造, 那么虚拟机将不再提供默认的无参构造, 在创建对象时, 将不能再使用无参构造, 如果还需要使用无参构造, 则需要我们自己手动书写无参构造.
因此, 建议如果写了自定义的有参构造, 那么不管是否会用到无参构造, 都再手动书写一个无参构造.
带参构造和无参构造是构造方法的重载.
带参构造一定是带全部参数.
如果在 Javabean 类中, 定义的类只有有参构造, 没有无参构造, 在测试类中, 定义类的对象时, 不传递参数, 想要调用无参构造, 这时会报错, 因为在 Javabean 类中, 已经没有无参构造了, 如果还需要无参构造, 就需要自己去写无参构造了.

程序示例:
Javabean 类:
public class Girlfriend { private int age; private String name; public void setAge(int age) { this.age = age; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public String getname() { return name; } // 空参构造, 虚拟机自动添加的构造方法就和这个长得一样. public Girlfriend() { } // 带参构造 public Girlfriend(String name, int age) { this.name = name; this.age = age; } }
测试类:
public class GirlfriendTest { public static void main(String[] args) { Girlfriend g1 = new Girlfriend(); System.out.println(g1.getAge()); // 0 System.out.println(g1.getname()); // null Girlfriend g2 = new Girlfriend("小张", 23); System.out.println(g2.getAge()); // 23 System.out.println(g2.getname()); // 小张 g2.setAge(25); // 进行修改 g2.setName("小王"); System.out.println(g2.getAge()); // 25 System.out.println(g2.getname()); // 小王 } }
标准的 Javabean 类
类名采用大驼峰.
所有成员变量都用 private 修饰.
提供至少两个构造方法: 无参构造方法和带全部参数的构造方法.
成员方法: 提供对应到每一个成员变量的 setXXX() / getXXX(). 如果有其他行为, 也需要写上.
快捷键: alt + insert
或者 alt + fn + insert
插件 PTG 可以 1 秒生成标准 Javabean, 快捷键: ctrl + shift + ,
按下 alt + insert
或者 alt + fn + insert
之后显示:

回车, 表示选择 Construcor, 显示:

点击 Select None, 表示空参构造, 如果不想点击鼠标, 可以按两下 tab, 再按下回车. 第一次按下 tab, 表示不选择任何成员变量, 再按下 tab, 表示选择 Select None, 然后按下回车. 这样就生成了空参的构造方法.
再次按快捷键 alt + insert
或者 alt + fn + insert
, 还是出现图 1, 回车, 表示选择 Construcor, 然后出现图 2, 然后按 Ctrl + a, 全选所有的成员变量, 再按下回车, 表示选择 OK 选项, 则创建出全参构造方法.
再次按快捷键 alt + insert
或者 alt + fn + insert
, 还是出现图 1, 选择 Getter and Setter, 回车, 得到图 3, 然后按 Ctrl + a, 全选所有的成员变量, 再按下回车, 表示选择 OK 选项, 则创建出全部成员变量的 getter 方法和 setter 方法.

对象内存图
对象是 new 出来的, 对象中存放的是地址.
程序示例:
Javabean 类:
public class Phone { // 属性 (成员变量) String brand; double price; // 行为 (方法) public void call() { System.out.println("手机在打电话"); } public void playGame() { System.out.println("手机在玩游戏"); } }
测试类:
public class PhoneTest { public static void main(String[] args) { // 创建手机对象 Phone p = new Phone(); System.out.println(p); // classes.Phone@776ec8df } }
new 一个对象要发生的事情至少包括以下七个步骤:
Student s = new Student(); // new 一个对象
-
加载 Student.class 字节码文件. 因为用到了这个类, 所以要加载这个类的字节码文件
-
申明等号左侧的局部变量
-
在堆内存中开辟一个空间
-
默认初始化
-
显示初始化 (在类的定义中给定初始值即为显式初始化)
-
构造方法初始化 (在类中定义了有参构造方法时执行这一步)
-
将堆内存中的地址值赋值给左边的局部变量
可以让两个变量指向同一个对象:
Javabean 类:
public class Student { String name; }
测试类:
public class StudentTest { public static void main(String[] args) { Student s1 = new Student(); s1.name = "小明"; Student s2 = s1; System.out.println(s1.name + " " + s2.name); // 小明 小明 s2.name = "小张"; System.out.println(s1.name + " " + s2.name); // 小张 小张 s1 = null; // System.out.println(s1.name); // Exception in thread "main" java.lang.NullPointerException: Cannot read field "name" because "s1" is null System.out.println(s2.name); // 小张 } }
一个对象的内存图 的分析过程:
下图 的代码中, 首先是将 TestStudent
类的字节码文件加载到方法区中, 并将 main()
方法在其中进行临时存储.

接下来, 虚拟机会自动调用程序的主入口 main()
方法, 于是, main()
方法被加载到栈里面.

然后开始执行 main()
方法里面的代码. 第一句是 Student s = new Student();
, 创建一个 Student
类的对象 s
.
创建这个对象时, 遵循上面所说的创建一个对象要经历的 7 个步骤.
第一步是将类 Student
的 class 文件加载到方法区, 且这个空间内有着这个类的全部信息, 比如所有的成员变量 name
, age
, 所有的成员方法 study()
等.

第二步是申明局部变量, 也就是创建对象这条语句 Student s = new Student();
左边的代码 Student s
, 在 main()
方法中就开辟了一个空间, 这个空间的名字就叫做 s. 这个空间可以存储 Student
这个类的对象的地址值.

第三步, 在堆内存中开辟一块空间, 也就是创建对象这条语句 Student s = new Student();
右边的代码 new Student()
. 堆内存里面的空间都是有地址值的, 假设这块空间的地址值为 001
, 这块空间里面会把 Student
这个类的所有的成员变量都拷贝一份放过来 (从方法区中拷贝, 因为这些信息存储在方法区中) . 除此之外 001
这块空间还存储有 Student
这个类的所有成员方法的地址, 这个地址是为了以后用对象调用方法的时候能找到对应的方法, 等于说并没有复制成员方法, 成员方法还是在方法区中, 只是复制了成员变量到堆内存中. 此时这个地址为 001
的空间就是我们所说的对象. 但是这个对象现在还没有创建完毕. 因为此时的成员变量 name
和 age
都还没有值. 赋值操作就是接下来的 4, 5, 6 三个步骤.

第四步, 首先是默认初始化, name
默认初始化为 null
, age
默认初始化为 0
.

第五步, 是显示初始化, 如果 Student
类的定义中, 成员变量时直接给定了值, 即 String name = "zhangsan";
和 int age = 10;
, 那么这就叫做显示初始化, 此时上一步里面默认初始化的 null
和 0
在这一步中就会被 zhangsan
和 10
代替, 但是这里 Student
类并没有写这样的显示初始化的代码, 那么显式初始化可以忽略.
第六步, 是构造方法初始化, 在创建实例 s
时, 即 Student s = new Student();
这条语句中, 小括号中什么都没写, 就表示现在调用的是空参构造, 且是虚拟机提供的默认的空参构造方法, 这个默认的空参构造方法的方法体是空的, 没有任何语句, 所以空参构造是可以忽略的. 但是如果此时用的是有参构造, 那么在构造方法初始化这一步, 就会有值代替默认初始化中的 null
和 0
. 从此也可以看出, 构造方法只是创建对象的多个步骤中的一个步骤而已.
第七步, 即把堆内存中的地址赋值给左边的局部变量. 即把地址 001
通过等号运算符即赋值运算符赋值给了左边的变量 s
. 此时 s
这个变量的空间里面就会存储地址值 001
. 于是变量 s
也可以通过地址值 001
找到堆内存中的空间.

至此, 创建对象的这条语句, 即 Student s = new Student();
, 就执行完了.
如果直接打印 s
, 就是打印 s
中记录的地址值 001
.
s.name
就表示 001
这个内存空间里面的 name
, 就找到了堆内存里面的 name
.
如果执行语句 s.name = "aqiang";
, 则改变的也是堆内存里面的 name
.
语句 s.study();
就是找 001
这个空间里面的 study()
, 但是找到的是这个成员方法的地址, 再用这个地址, 找到了方法区里面的成员方法 study()
. 此时 study()
方法就会被加载进栈. study()
方法执行完毕后, 就会出栈. 此时整个 main()
方法就执行完毕了, 于是 main()
方法也出栈. 既然 main()
方法都出栈了, 那么 main()
里面的变量自然也就没有了. 于是 s
也就没有了, 于是也就没有变量再使用堆内存中的这块原本地址值为 001
的这一块空间了. 专业点说就是没有变量指向这块空间了, 那么这块空间也会消失.


两个对象的内存图 的分析过程:
创建第一个对象和第二个对象的过程和上述分析一个对象的内存图 的过程是一样的.
执行 Student s2 = new Student();
语句时, Student.class
文件不需要再加载一次, 因为已经存在于方法区中了, 直接用就可以了.







【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术