📖 所属阶段 :W1 · Java 语言基础(共 3 个模块:M01 ✅ + M02 ✅ + M03)
1. 模块概览
| 项目 | 内容 |
|---|---|
| 一句话总结 | 掌握 Java OOP 四大支柱------封装、继承、多态、抽象,以及访问修饰符、static、this/super |
| 前置依赖 | M02(Java 语法基础) |
| 预计耗时 | 2 小时 |
| 评估标准 | 能设计一个包含继承和接口的小型类体系,正确使用访问修饰符 |
1.1 概念映射速查表
| 前端概念 | Java 对应 | 说明 |
|---|---|---|
class / constructor |
class / 构造器 |
语法几乎一样,但 Java 更严格 |
this |
this |
Java 的 this 不会丢失(不像 JS 会绑定问题) |
extends |
extends |
语法一模一样! 但 Java 只能单继承 |
#private 字段 |
private 修饰符 |
Java 强制私有,必须通过 getter/setter 访问 |
static 方法/属性 |
static |
JS 和 Java 都支持,含义相同 |
TS interface |
Java interface |
区别大!Java 接口可以有 default 方法 |
| TS 泛型接口 | Java 接口 + 多实现 | Java 可以 implements A, B, C 多个接口 |
super |
super |
父类构造器 / 父类方法调用 |
| 没有等价 | protected |
Java 独有------子类可访问,外部不行 |
1.2 JS class vs Java class 差异速查
| 特性 | JavaScript | Java |
|---|---|---|
| 类声明 | class A { } |
public class A { }(必须加修饰符) |
| 构造器 | constructor() { } |
与类同名的方法 |
| 私有字段 | #name(提案)或 _name(约定) |
private String name(强制) |
| 继承 | extends(单继承) |
extends(单继承)+ implements(多接口) |
| 接口 | 没有(TS 有,但只是类型) | interface(运行时存在) |
| 方法重写 | 直接写同名方法 | 加 @Override 注解 |
| 静态成员 | static prop / static method() |
完全一样 |
| this 绑定 | 可能丢失(箭头函数解决) | 永远指向当前对象,不会丢 |
| 默认值 | 字段会自动赋值 undefined |
字段会自动赋值 0/false/null |
2. 核心知识点
知识点 1:访问修饰符 ------ Java 的"权限四象限"
前端类比
typescript
// TypeScript 的访问修饰符(编译后消失!)
class User {
public name: string; // 默认 public,谁都能访问
private password: string; // 只有类内部能访问(编译时检查)
}
Java 原理
java
// Java 的访问修饰符(运行时生效!)
public class User {
public String name; // 公开:谁都能访问
private String password; // 私有:只有 User 类内部能访问
protected String email; // 保护:子类 + 同包能访问(JS 没有!)
// 默认(不加修饰符):同包能访问
}
四种修饰符可见性:
| 修饰符 | 同一个类 | 同包 | 子类(不同包) | 全局 |
|---|---|---|---|---|
public |
✅ | ✅ | ✅ | ✅ |
protected |
✅ | ✅ | ✅ | ❌ |
| 默认(无) | ✅ | ✅ | ❌ | ❌ |
private |
✅ | ❌ | ❌ | ❌ |
💡 实战建议 :字段一律用
private,通过public的 getter/setter 暴露。这就是封装。
知识点 2:构造器(Constructor)------ 和 JS 的 constructor 不一样
前端类比
typescript
// JS 的 constructor 是 class 里的一个特殊方法
class Person {
constructor(public name: string, public age: number) {
// 构造器完成后对象就创建好了
}
}
Java 原理
java
// Java 的构造器:和类同名,没有返回值类型(连 void 都不能写!)
public class Person {
private String name;
private int age;
// 无参构造器
public Person() { }
// 有参构造器(重载)
public Person(String name, int age) {
this.name = name; // this 区分字段和参数
this.age = age;
}
// 构造器可以互相调用(用 this()),但必须放在第一行
public Person(String name) {
this(name, 0); // 调用上面的两参数构造器
}
}
构造器规则:
- 类没有写任何构造器 → 编译器自动加一个无参构造器
- 自己写了有参构造器 → 编译器不再提供默认无参构造器!
- 构造器不能被
static、final、abstract修饰 - 子类构造器第一行默认调用
super()(父类无参构造器)
🚨 坑预警 :父类如果没有无参构造器,子类必须显式写
super(...)调用父类的有参构造器。
知识点 3:static 关键字 ------ 属于类,不属于对象
前端类比
javascript
// JS 的 static 方法
class MathUtil {
static PI = 3.14;
static add(a, b) { return a + b; }
}
// 调用:MathUtil.add(1, 2) ------ 不需要 new MathUtil()
Java 原理
java
public class MathUtil {
// static 变量:所有对象共享一份
public static final double PI = 3.14; // static + final = 常量
// static 方法:不需要 new 对象就能调用
public static int add(int a, int b) {
return a + b;
}
// 普通变量:每个对象各有一份
private int instanceCount;
}
// 调用
MathUtil.add(1, 2); // ✅ 直接用类名调用
System.out.println(MathUtil.PI); // ✅ 直接访问
// new MathUtil().add(1, 2); // ❌ 编译警告(不推荐)
static 的限制:
- static 方法里不能用
this(因为没有对象) - static 方法里不能直接访问非 static 字段
- static 方法不能被重写(但可以被继承)
知识点 4:继承 extends ------ Java 单继承
前端类比
typescript
class Person { name: string; }
class Student extends Person { studentId: string; }
// 语法完全一样!
Java 原理
java
// Java 单继承:一个类只能有一个父类
public class Student extends Person {
private String studentId;
// 子类构造器第一行必须调用父类构造器
public Student(String name, int age, String studentId) {
super(name, age); // ✅ 调用父类 Person(name, age)
this.studentId = studentId;
}
}
继承规则:
- Java 只有单继承 :
class A extends B,B 还可以 extends C,形成继承链 - 子类继承父类的
public和protected成员(private不继承) - 子类可以**重写(Override)**父类方法,加
@Override注解 - 子类用
super.method()调用父类被重写的方法
java
// 方法重写示例
@Override // 编译器帮你检查是不是真的在重写
public void sayHello() {
super.sayHello(); // 先执行父类的逻辑
System.out.println("我是学生!"); // 再加自己的逻辑
}
知识点 5:接口 interface ------ Java 和 TS 最大的区别之一
前端类比
typescript
// TS interface 只是类型约定,编译后消失
interface Flyable {
fly(): void;
// ❌ 不能有实现!
}
class Bird implements Flyable {
fly() { console.log("飞"); }
}
Java 原理
java
// Java interface 是运行时存在的,可以有 default 方法
public interface Flyable {
// 抽象方法(默认 public abstract)------ 实现类必须实现
void fly();
// default 方法(Java 8+)------ 接口可以有默认实现!
default void land() {
System.out.println("平稳降落...");
}
// static 方法(Java 8+)------ 通过接口名调用
static void info() {
System.out.println("这是飞行接口");
}
}
// 一个类可以 implements 多个接口(弥补单继承的不足)
public class Bird implements Flyable {
@Override
public void fly() { System.out.println("鸟儿飞"); }
// 可选:重写 default 方法
@Override
public void land() { System.out.println("轻盈落下"); }
}
// 调用
Flyable.info(); // ✅ 静态方法通过接口名调用
Bird b = new Bird();
b.fly(); // ✅ 自己的实现
b.land(); // ✅ 重写后的实现
接口 vs 抽象类(先有印象,M08 讲 Spring 时再深入):
| 特性 | interface | abstract class |
|---|---|---|
| 多实现 | ✅ 可以多个 | ❌ 只能一个 |
| 构造器 | ❌ 没有 | ✅ 有 |
| 字段 | 只能是 public static final |
任意 |
| 方法实现 | default / static 可以 |
任意 |
| 设计意图 | "能做什么"(能力) | "是什么"(身份) |
知识点 6:多态(Polymorphism)------ 同一接口,不同实现
前端类比
typescript
// TS 的多态------父类引用指向子类对象
const animals: Animal[] = [
new Dog("旺财"),
new Cat("咪咪"),
];
animals.forEach(a => a.speak()); // 各自有不同的实现
Java 原理
java
// 父类/接口引用可以指向子类对象
Person p = new Student("小明", 20, "001");
p.sayHello(); // 调用的是 Student 重写的 sayHello(动态绑定)
// 多态数组
Flyable[] flyers = {
new Bird("麻雀", "灰色"),
new Bird("鹦鹉", "彩色"),
};
for (Flyable f : flyers) {
f.fly(); // 各自的 fly() 实现
}
// 向下转型 + instanceof
if (p instanceof Student) {
Student s = (Student) p; // 强转
s.study(); // 现在可以调用 Student 独有方法了
}
3. 完整示例代码
文件清单
| 文件 | 说明 |
|---|---|
| Person.java | 父类:构造器重载、getter/setter、static、toString、equals |
| Student.java | 子类:extends Person、super、方法重写 |
| Flyable.java | 接口:抽象方法 + default 方法 + static 方法 |
| Bird.java | 实现类:implements Flyable |
| OOPDemo.java | 主程序:综合演示所有特性 |
运行方式
bash
cd M03-OOP
# 编译(编译所有 .java 文件)
javac -encoding UTF-8 -d out src/com/example/learn/*.java
# 运行主程序
java -cp out com.example.learn.OOPDemo
💡 如果控制台中文乱码,加上
-Dfile.encoding=UTF-8:java -Dfile.encoding=UTF-8 -cp out com.example.learn.OOPDemo
预期输出
csharp
========================================
M03 面向对象编程(OOP)综合演示
========================================
===== 1. 创建 Person 对象 =====
👋 你好,我是 张三,今年 25 岁
👋 你好,我是 李四,今年 30 岁
===== 2. getter / setter 访问 private 字段 =====
⚠️ 年龄不合法: -5,已忽略
张三 现在 26 岁
===== 3. static 静态变量(所有对象共享) =====
总共创建了 3 个 Person 对象
...
===== 10. equals() 比较对象内容 =====
pa == pb ? false
pa.equals(pb) ? true
🚨 记住:对象比较永远用 .equals(),不要用 ==
4. 练习题
题目 1:设计 Shape 接口(基础)
要求:
- 定义一个
Shape接口,包含:double getArea()------ 计算面积default方法String getInfo()------ 返回 "这是一个形状"
- 实现
Rectangle类(矩形)------ 实现 Shape 接口,有 width 和 height - 实现
Circle类(圆形)------ 实现 Shape 接口,有 radius(π 用 3.14) - 在主方法中创建 Rectangle(5, 3) 和 Circle(2),打印它们的面积
参考答案:
java
// Shape.java
package com.example.learn;
public interface Shape {
double getArea();
default String getInfo() {
return "这是一个形状";
}
}
// Rectangle.java
package com.example.learn;
public class Rectangle implements Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double getArea() {
return width * height;
}
}
// Circle.java
package com.example.learn;
public class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double getArea() {
return 3.14 * radius * radius;
}
}
// 主方法
public class ShapeTest {
public static void main(String[] args) {
Shape[] shapes = {
new Rectangle(5, 3),
new Circle(2),
};
for (Shape s : shapes) {
System.out.println(s.getInfo() + ",面积 = " + s.getArea());
}
}
}
运行结果:
这是一个形状,面积 = 15.0
这是一个形状,面积 = 12.56
题目 2:Animal 继承体系(综合)
要求:
- 创建抽象类
Animal(用abstract关键字),包含:protected String name- 抽象方法
abstract void speak() - 普通方法
void eat()打印 "xxx 正在吃东西"
- 创建
Dog extends Animal------ speak() 打印 "汪汪" - 创建
Cat extends Animal------ speak() 打印 "喵喵" - 创建
Robot类,实现Flyable接口 + 有work()方法 - 主方法演示多态:Animal 数组 + Flyable 数组
参考答案:
java
// Animal.java(抽象类)
package com.example.learn;
public abstract class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
// 抽象方法:没有实现,子类必须实现
public abstract void speak();
// 普通方法:抽象类可以有实现
public void eat() {
System.out.println(name + " 正在吃东西 🍖");
}
}
// Dog.java
public class Dog extends Animal {
public Dog(String name) { super(name); }
@Override
public void speak() {
System.out.println("🐕 " + name + ":汪汪!");
}
}
// Cat.java
public class Cat extends Animal {
public Cat(String name) { super(name); }
@Override
public void speak() {
System.out.println("🐱 " + name + ":喵喵~");
}
}
// Robot.java
public class Robot implements Flyable {
private String model;
public Robot(String model) { this.model = model; }
@Override
public void fly() {
System.out.println("🤖 " + model + " 启动喷射推进器,飞起来了!");
}
public void work() {
System.out.println("🤖 " + model + " 正在工作 ⚙️");
}
}
// 主方法
public class AnimalTest {
public static void main(String[] args) {
// 多态 1:抽象类引用指向具体子类
Animal[] animals = {
new Dog("旺财"),
new Cat("咪咪"),
new Dog("小黑"),
};
for (Animal a : animals) {
a.speak(); // 动态绑定到子类的 speak()
a.eat(); // 抽象类的普通方法
}
// 多态 2:接口引用指向实现类
Flyable[] flyables = {
new Bird("雄鹰", "棕色"),
new Robot("X-7"),
};
for (Flyable f : flyables) {
f.fly();
}
}
}
题目 3:final 关键字的三种用法(理解)
要求: 说明 final 在以下三种场景中的作用,并写代码验证:
final修饰变量final修饰方法final修饰类
参考答案:
java
public class FinalDemo {
public static void main(String[] args) {
// 1. final 变量 = 常量
final int MAX_AGE = 150;
// MAX_AGE = 200; ← ❌ 编译报错,不能修改
System.out.println("MAX_AGE = " + MAX_AGE);
// final 修饰引用类型------引用不能变,但内容可以变
final List<String> names = new ArrayList<>();
names.add("张三"); // ✅ 可以!内容可变
// names = new ArrayList<>(); ← ❌ 编译报错,引用不能变
}
// 2. final 方法------子类不能重写
public final void importantMethod() {
System.out.println("这个方法不允许子类重写");
}
// 3. final 类------不能被继承(如 String 类就是 final!)
// public final class Utility { ... }
}
// ❌ 编译报错:无法继承 final 类
// class Bad extends FinalDemo { }
final 三种用法总结:
| 修饰对象 | 效果 |
|---|---|
| 变量 | 值/引用不可变(= 常量) |
| 方法 | 子类不能重写 |
| 类 | 不能被继承 |
💡
String类是final的------防止你继承它改变字符串的不可变性。
5. 常见坑
坑 1:子类没有显式调用 super(),但父类没有无参构造器
java
// ❌ 编译报错
class Parent {
public Parent(String name) { } // 只有有参构造器
}
class Child extends Parent {
// 编译器默认加 Child() { super(); }
// 但 Parent 没有无参构造器!→ 编译报错
}
// ✅ 修复方案:子类显式调用父类有参构造器
class Child extends Parent {
public Child() {
super("默认名字"); // 显式调用
}
}
💡 最佳实践 :如果类有子类倾向,要么保留无参构造器,要么在子类中显式
super(...)。
坑 2:private 字段没有 getter/setter 导致子类访问不到
java
public class Person {
private String name; // private:子类也访问不到!
}
public class Student extends Person {
public void test() {
// this.name = "小明"; ← ❌ 编译报错!private 子类也看不到
}
}
// ✅ 方案 1:用 protected
protected String name; // 子类可以直接访问
// ✅ 方案 2:保留 private + getter/setter
// 在 Person 里加:
public String getName() { return name; }
public void setName(String name) { this.name = name; }
坑 3:Java 单继承,一个类不能 extends 两个类
java
// ❌ 编译报错
class A { }
class B { }
class C extends A, B { } // Java 不支持多继承!
// ✅ 解决方案:用接口弥补
interface BInterface { }
class C extends A implements BInterface { }
// 一个类可以 implements 多个接口
class MultiImpl implements A, B, C { } // ✅
坑 4:== 比较对象永远是 false
java
Person p1 = new Person("张三", 25);
Person p2 = new Person("张三", 25);
// ❌ 永远是 false(不同对象,地址不同)
if (p1 == p2) { }
// ✅ 比较内容用 equals()
if (p1.equals(p2)) { }
// 记得:自己写的类要重写 equals(),否则默认和 == 一样
6. JS class vs Java class 完整对照表
| 能力 | JavaScript | Java | 备注 |
|---|---|---|---|
| 定义类 | class A {} |
public class A {} |
Java 必须加 public/package-private |
| 构造器 | constructor(x) {} |
A(x) {} |
Java 构造器 = 类同名方法 |
| 重载构造器 | ❌ 不支持 | ✅ 支持多个 | Java 可以写多个构造器 |
| 继承 | extends |
extends |
语法完全一样 |
| 多实现 | ❌ | ✅ implements A, B |
Java 用接口弥补单继承 |
| private 字段 | #field |
private |
Java 强制,JS 是提案 |
| protected | ❌ | ✅ | 子类可见,外部不可 |
| this | 可能丢失 | 不会丢 | JS 的 this 绑定是出了名的坑 |
| super | super() / super.method() |
一样 | |
| static | static |
static |
含义相同 |
| final 类 | ❌ | final class |
不能被继承 |
| 抽象类 | ❌ | abstract class |
TS 有 abstract class |
| 接口 | ❌(TS 有) | interface |
Java 接口可有 default 方法 |
| toString | 无原生([object Object]) |
✅ toString() |
打印对象自动调用 |
7. 知识小结
java
M03 你学到了什么?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ 访问修饰符:public / protected / 默认 / private
→ private 字段必须通过 getter/setter 访问(封装)
→ protected 子类可见(JS 没有!)
✅ 构造器:和类同名、无返回值、可以重载
→ 子类默认调用 super()
→ 父类没有无参构造器时,子类必须显式 super(...)
✅ static:属于类,不属于对象
→ static 变量所有对象共享
→ static 方法不用 new 就能调用
→ static 里不能用 this
✅ 继承:extends,Java 单继承
→ 重写方法加 @Override 注解
→ super() 调父类构造器,super.method() 调父类方法
✅ 接口:interface + implements,可多实现
→ Java 接口可有 default / static 方法
→ TS 接口只是类型约定,Java 接口运行时存在
✅ 多态:父类/接口引用指向子类对象
→ 动态绑定:调用子类重写后的方法
→ instanceof 判断类型 + 向下转型
✅ final 三种用法:变量(常量)、方法(不能重写)、类(不能继承)
下一个模块 → M04:集合框架
相关文件:JAVA-LEARN-SPEC.md | M01 笔记 | M02 笔记