Java面向对象核心知识点随堂笔记
前言
本次笔记完整复盘了Java面向对象入门的核心授课内容,同时补充了课堂提及的多线程经典问题的底层原理,涵盖多线程原子性基础、封装、继承、方法重写与重载等核心知识点,修正了示例代码的语法问题,适配Java入门新手的复盘与巩固需求。
一、多线程基础:经典count++原子性问题
课堂开篇先讲解了CPU的线程执行特性,以及Java多线程中最经典的count++数据安全问题,这里做完整梳理与原理补充。
1. 线程的执行本质
- 宏观层面:CPU呈现多任务并行执行的效果,我们可以同时运行多个软件、执行多个任务,无明显卡顿感知。
- 微观层面:CPU是以纳秒(ns)级的速度,高速交替执行不同线程的任务,同一时刻单个CPU核心只会执行一个线程的指令,只是切换速度极快,人眼无法感知。
2. 问题场景与现象
- 初始变量:
int count = 0; - 线程A:循环执行
count++一百万次 - 线程B:循环执行
count++一百万次 - 预期结果:count最终值为200万
- 实际结果:最终值远小于200万,甚至会出现几万、十几万的大幅偏差
3. 核心原因:数据覆盖与非原子性操作
这里补充课堂未展开的底层执行逻辑:count++ 看似是一行代码,在CPU执行时会被拆分为3步独立的指令,不具备原子性(原子性指一个操作要么一次性全部执行完成,要么完全不执行,中间不能被打断):
- 读取:从主内存中读取count的当前值,加载到当前线程的工作内存
- 计算:在工作内存中,对读取到的值执行+1操作
- 回写:将计算后的新值,写回主内存
正是因为CPU会高速交替执行两个线程,就会出现临界问题:线程A读取了count=0,还没来得及回写新值,CPU就切换到了线程B,线程B也读取到了count=0。两个线程都对0做了+1操作,最终都回写了1,本该两次+1让count变成2,结果只增加了1,这就是数据覆盖问题,也是最终结果不符合预期的根源。
二、面向对象三大特性之:封装
1. 封装的核心定义
封装的核心思想,是将类中的成员变量(属性)私有化隐藏,禁止外部类直接访问和修改 ,只对外暴露统一的get/set方法,供外部安全地操作属性。
2. 封装的核心实现规则
- 权限控制:使用
private(私有)权限修饰符修饰成员变量,被private修饰的内容,仅能在当前类内部访问,外部类无法直接调用。 - 对外接口:为每个private修饰的成员变量,提供对应的
getXxx()(获取属性值)和setXxx()(设置属性值)方法,方法使用public修饰,对外公开访问。
3. 封装的完整代码示例(修正课堂代码语法问题)
java
package com.qcby;
/**
* 封装示例:Person实体类
*/
public class Person {
// 私有成员变量,外部无法直接访问
private String name = "admin";
public int age;
private String sex;
private String address;
private String phone;
// 对外提供name属性的get方法:获取属性值
public String getName() {
return name;
}
// 对外提供name属性的set方法:设置属性值
public void setName(String name) {
// 封装的核心优势:可添加数据校验,避免非法数据赋值
if (name != null && !name.isEmpty()) {
this.name = name;
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
// 年龄合法性校验,保障数据安全
if (age >= 0 && age <= 120) {
this.age = age;
}
}
// 其余私有属性的get/set方法,可按照相同格式补充
}
java
package com.qcby;
/**
* 封装测试类
*/
public class Test {
public static void main(String[] args) {
Person p1 = new Person();
// 通过get方法获取初始值
System.out.println(p1.getName());
// 通过set方法修改name属性值
p1.setName("张三");
// 获取修改后的值
System.out.println(p1.getName());
Person p2 = new Person();
p2.setAge(20);
System.out.println(p2.getAge());
}
}
4. 封装的核心优势
- 数据安全:可在set方法中添加数据校验逻辑,避免非法数据赋值,从源头保障属性的合法性。
- 隐藏实现细节:外部仅需调用get/set方法,无需关注类内部的实现逻辑,降低代码耦合度。
- 提升可维护性:属性的修改规则仅需在类内部的get/set方法中调整,无需修改所有调用处的代码。
三、面向对象三大特性之:继承
1. 继承的核心本质
继承的核心是代码复用。通过继承,子类可以直接使用父类中非private修饰的成员变量和成员方法,无需重复编写相同的逻辑代码,大幅减少代码冗余。
2. 继承的核心语法与规则
- 关键字:通过
extends关键字实现类的继承 - 基础格式:
public class 子类名 extends 父类名 { } - 别称对应:父类也叫超类、基类;子类也叫派生类
- 单继承限制:Java只支持单继承,一个子类只能有一个直接父类,但一个父类可以拥有多个子类。
3. 继承的核心调用规则
- 子类可以直接调用父类中非private修饰的方法和属性。
- 父类无法调用子类中独有的方法和属性。
- 继承具有传递性:A类继承B类,B类继承C类,则A类可直接使用B类和C类中非private的内容。
4. 继承的完整代码示例(修正课堂代码命名规范)
java
package qcby;
/**
* 父类:动物类,提取所有动物的通用行为
*/
public class Animal {
public void run() {
System.out.println("running...");
}
public void eat() {
System.out.println("eating...");
}
public void jump() {
System.out.println("jumping...");
}
}
java
package qcby;
/**
* 子类:猫类,继承自动物类
*/
public class Cat extends Animal {
// 子类独有的方法,父类无法调用
public void catchMouse() {
System.out.println("小猫抓老鼠...");
}
}
java
package qcby;
/**
* 继承测试类
*/
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
// 调用继承自父类的通用方法
cat.jump();
cat.run();
cat.eat();
// 调用子类自身独有的方法
cat.catchMouse();
}
}
四、方法的重写(Override)与重载(Overload)
方法重写与重载是Java入门最容易混淆的两个知识点,这里结合课堂内容做清晰区分、规则补充与示例演示。
1. 方法的重写(Override)
核心定义
方法重写发生在父子类之间 ,子类重写父类中已有的方法,要求方法名、参数列表必须和父类完全一致。子类对象调用该方法时,会优先执行子类重写后的逻辑,而非父类的原有逻辑。
完整代码示例
java
package com.qcby;
/**
* 父类
*/
public class Animal {
public void eat() {
System.out.println("动物在吃东西...");
}
}
java
package com.qcby;
/**
* 子类:猫类,重写父类的eat方法
*/
public class Cat extends Animal {
// @Override注解:标记该方法是重写父类的方法,编译时会自动校验语法合法性
@Override
public void eat() {
System.out.println("小猫吃的很多....");
}
}
java
package com.qcby;
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
// 执行子类重写后的方法,输出:小猫吃的很多....
cat.eat();
}
}
重写的核心规则(补充)
- 方法名、参数列表必须与父类完全一致。
- 子类方法的访问权限,不能比父类更严格(例如父类方法是public,子类重写时就不能用private修饰)。
- 父类中被private、final、static修饰的方法,无法被重写。
- 强烈建议添加
@Override注解,编译器会自动校验重写语法,避免因方法名、参数列表写错导致的隐性问题。
2. 方法的重载(Overload)
核心定义
方法重载发生在同一个类当中 ,多个方法的方法名完全相同,但参数列表不同 ,返回值、访问修饰符可以不同。
在Java中,判断两个方法是否重复的唯一标准是:方法名 + 参数列表,与返回值无关。
参数列表不同的判定标准
满足以下任意一种,即可判定为参数列表不同,构成方法重载:
- 参数的个数不同
- 参数的类型不同
- 参数的顺序不同
完整代码示例
java
public class Calculator {
// 两个int类型数值相加
public int add(int a, int b) {
return a + b;
}
// 参数个数不同,构成重载
public int add(int a, int b, int c) {
return a + b + c;
}
// 参数类型不同,构成重载
public double add(double a, double b) {
return a + b;
}
// 参数顺序不同,构成重载
public void show(String name, int age) {
System.out.println("姓名:" + name + ",年龄:" + age);
}
public void show(int age, String name) {
System.out.println("年龄:" + age + ",姓名:" + name);
}
}
3. 重写与重载的核心区别对照表
| 对比维度 | 方法重写(Override) | 方法重载(Overload) |
|---|---|---|
| 发生范围 | 父子类之间 | 同一个类当中 |
| 方法名 | 必须完全相同 | 必须完全相同 |
| 参数列表 | 必须完全相同 | 必须不同 |
| 返回值 | 必须与父类一致(或其子类) | 无强制要求 |
| 访问修饰符 | 不能比父类更严格 | 无强制要求 |
| 核心作用 | 子类自定义父类的方法逻辑 | 同一个方法名适配多种入参场景 |
五、笔记核心总结
- 多线程中
count++并非原子操作,会因CPU交替执行出现数据覆盖问题,这是多线程线程安全的入门核心场景。 - 封装的核心是私有化属性、对外提供get/set方法,核心价值是保障数据安全、隐藏实现细节。
- 继承的核心是代码复用,Java仅支持单继承,子类可使用父类非private的成员,父类无法访问子类独有内容。
- 方法重写发生在父子类之间,是子类对父类方法的重构;方法重载发生在同一个类中,是同名方法的多场景适配,二者核心判定标准均为方法名+参数列表。