跳到主要内容
从 C 到 Java:面向对象编程核心概念实战 | 极客日志
Java java
从 C 到 Java:面向对象编程核心概念实战 Java 面向对象编程核心概念实战教程。针对有 C 语言基础的开发者,对比过程式与面向对象思维差异。涵盖类与对象、封装(访问修饰符、getter/setter)、继承(extends、super、重写)、多态(向上转型、接口、抽象类)。通过银行账号、图形类等实例演示代码复用与扩展性,强调构造方法规则及 final 关键字应用,帮助快速掌握 Java 核心特性。
第一课:从'过程'到'对象'
1.1 回顾:C 语言的过程式思维
对于有 C 语言基础的开发者,理解 Java 的第一步是意识到编程思维的转变。在 C 中,我们关注的是步骤和流程。
#include <stdio.h>
#include <string.h>
struct Student {
char name[20 ];
int age;
float score;
};
void printStudentInfo (struct Student s) {
printf ("姓名:%s,年龄:%d,成绩:%.1f\n" , s.name, s.age, s.score);
}
void updateScore (struct Student* s, float newScore) {
s->score = newScore;
}
int main () {
struct Student stu1 ;
strcpy (stu1.name, "张三" );
stu1.age = 20 ;
stu1.score = 85.5 ;
printStudentInfo(stu1);
updateScore(&stu1, 90.0 );
printStudentInfo(stu1);
return 0 ;
}
C 语言的特点 :
数据(struct)是被动 的,像一堆零件
函数是主动 的,像工人去操作这些零件
数据和函数分离 ,需要你手动'传递'数据给函数
适合解决线性、步骤清晰 的问题
1.2 走进:Java 的面向对象世界 同样的需求,在 Java 中是如何处理的?我们把数据和操作打包在一起。
public class Student {
String name;
int age;
double score;
public void printInfo () {
System.out.println("姓名:" + this .name + ",年龄:" + this .age + ",成绩:" + this .score);
}
public void updateScore (double newScore) {
this .score = newScore;
}
public static void main (String[] args) {
Student stu1 = new Student ();
stu1.name = "张三" ;
stu1.age = 20 ;
stu1.score = 85.5 ;
stu1.printInfo();
stu1.updateScore(90.0 );
stu1.printInfo();
}
}
1.3 核心比喻:从'菜谱'到'餐厅' C 语言(过程式) Java(面向对象) 现实比喻 数据(结构体) 属性(成员变量) 食材 函数 方法 厨师的技能 main() 函数调用所有对象调用自己的方法 顾客点餐,厨师做菜 编程思维 编程思维 管理思维 '第一步做什么,第二步做什么' '这个对象有什么能力,那个对象有什么能力' '服务员负责接待,厨师负责烹饪,收银员负责结账'
在 Java 中,Student stu1 = new Student(); 就像:
在 C 语言中:struct Student* stu1 = (struct Student*)malloc(sizeof(struct Student));
但 Java 帮你管理内存,不需要 free()!
stu1.printInfo() 就像:
在 C 语言中:printStudentInfo(stu1);
但现在是'让 stu1 自己打印自己的信息',更自然!
1.4 为什么要有这种转变?
C 语言方式 :你需要管理很多函数:addStudent(), addTeacher(), addCourse(), printStudent(), printTeacher()... 函数越来越多,关系越来越乱。
Java 方式 :
Student 类:负责所有与学生相关的数据和操作
Teacher 类:负责所有与老师相关的数据和操作
Course 类:负责所有与课程相关的数据和操作
每个对象都是一个小型的'自治系统' ,知道自己该做什么。你的 main() 方法只需要协调这些对象合作即可。
实践小练习 public class BankAccount {
String owner;
double balance;
public void deposit (double money) {
balance = balance + money;
System.out.println(owner + "存入" + money + "元,当前余额:" + balance);
}
public void withdraw (double money) {
if (money <= balance) {
balance = balance - money;
System.out.println(owner + "取出" + money + "元,当前余额:" + balance);
} else {
System.out.println("余额不足!" );
}
}
public void checkBalance () {
System.out.println(owner + "的账户余额为:" + balance + "元" );
}
public static void main (String[] args) {
BankAccount account1 = new BankAccount ();
account1.owner = "小明" ;
account1.balance = 1000.0 ;
BankAccount account2 = new BankAccount ();
account2.owner = "小红" ;
account2.balance = 500.0 ;
account1.deposit(500 );
account1.withdraw(200 );
account1.checkBalance();
account2.deposit(1000 );
account2.checkBalance();
}
}
输出结果 :
小明存入 500.0 元,当前余额:1500.0 小明取出 200.0 元,当前余额:1300.0 小明的账户余额为:1300.0 元 小红存入 1000.0 元,当前余额:1500.0 小红的账户余额为:1500.0 元
account1 和 account2 是两个独立的对象
它们有相同的结构 (都有 owner、balance,都能 deposit、withdraw)
但数据完全独立 (小明的操作不会影响小红的余额)
每个对象都封装了自己的状态和行为
💡 本课要点总结
类(Class) :是'蓝图'或'模板',定义了一类对象应该有什么属性(数据)和方法(操作)。
对象(Object) :是根据类创建出来的具体实例 。new 关键字就是创建对象的'魔法咒语'。
属性(Field) :对象内部的数据,相当于 C 语言结构体的成员。
方法(Method) :对象能执行的操作,相当于 C 语言的函数,但属于某个对象。
核心思想 :把相关的数据和对这些数据的操作打包在一起 ,形成一个独立的、自包含的单元。
第二课:封装 —— 给你的数据穿上'防护服' 上节课我们从'做菜步骤'(C 语言)进入了'开餐厅'(Java)的世界,建立了'类'和'对象'的基本概念。今天我们要学习封装(Encapsulation) ,这是面向对象的第一个重要特性,也是实际编程中最实用的技能。
2.1 从现实问题说起 先看一个上节课的例子,但这次我们发现了一个安全隐患 :
public class BankAccount {
public String owner;
public double balance;
public static void main (String[] args) {
BankAccount account = new BankAccount ();
account.owner = "小明" ;
account.balance = 1000.0 ;
account.balance = -5000.0 ;
account.balance = 9999999.0 ;
System.out.println("余额:" + account.balance);
}
}
任何人都能随意修改 balance
可以设置为负数(现实世界不允许)
可以设置为天文数字(不真实)
这就像 :把你的钱包放在桌子上,任何人都可以随便拿钱或放钱,没有密码、没有验证、没有记录。
2.2 封装的解决方案:黑盒子理论 封装就像给你的数据穿上防护服 ,或者给你的对象加上操作手册 :
public class SafeBankAccount {
private String owner;
private double balance;
public String getOwner () {
return owner;
}
public void setOwner (String owner) {
this .owner = owner;
}
public double getBalance () {
return balance;
}
public void deposit (double money) {
if (money > 0 ) {
balance += money;
System.out.println("成功存款:" + money + "元" );
} else {
System.out.println("存款金额必须大于 0!" );
}
}
public void withdraw (double money) {
if (money <= 0 ) {
System.out.println("取款金额必须大于 0!" );
} else if (money <= balance) {
balance -= money;
System.out.println("成功取款:" + money + "元" );
} else {
System.out.println("余额不足!当前余额:" + balance);
}
}
public void displayAccountInfo () {
System.out.println("账户持有人:" + owner);
System.out.println("账户余额:" + balance + "元" );
}
public static void main (String[] args) {
SafeBankAccount account = new SafeBankAccount ();
account.setOwner("小明" );
account.deposit(1000 );
account.deposit(500 );
account.withdraw(300 );
account.withdraw(2000 );
System.out.println("当前余额:" + account.getBalance());
account.displayAccountInfo();
}
}
运行结果 :
成功存款:1000.0 元 成功存款:500.0 元 取款 300.0 元 余额不足!当前余额:1200.0 当前余额:1200.0 账户持有人:小明 账户余额:1200.0 元
2.3 封装的核心概念:访问修饰符 Java 提供了 4 种访问级别,从最严格到最宽松:
public class AccessExample {
private String secret = "这是秘密,只有本类能看到" ;
String packagePrivate = "同包内的类可以看到我" ;
protected String familySecret = "家人和同住的人可以看到" ;
public String publicInfo = "我是公开信息,谁都能看" ;
public String getSecret () {
return secret;
}
}
class TestAccess {
public void test () {
AccessExample example = new AccessExample ();
System.out.println(example.publicInfo);
System.out.println(example.getSecret());
}
}
private(私有):像你的日记,只有自己能看
默认:像家庭内部通知,家里人能看到
protected(受保护):像家族秘密,家人和亲戚能看到
public(公开):像公告栏,所有人都能看到
2.4 封装的好处:为什么我们要这么做?
好处 1:安全性
account.balance = -10000 ;
account.withdraw(10000 );
好处 2:可控性 public void setAge (int age) {
if (age >= 0 && age <= 150 ) {
this .age = age;
} else {
System.out.println("年龄不合法!" );
}
}
public void setName (String name) {
if (name != null && !name.trim().isEmpty()) {
this .name = name.trim();
}
}
好处 3:维护性 如果需要修改内部实现,只要保持接口不变,外部代码完全不受影响:
private double balance;
private double getBalanceFromDatabase () {
return dbBalance;
}
public double getBalance () {
return getBalanceFromDatabase();
}
好处 4:隐藏复杂度
public boolean transfer (BankAccount target, double amount) {
if (this .withdraw(amount)) {
target.deposit(amount);
logTransaction(this , target, amount);
return true ;
}
return false ;
}
2.5 标准做法:getter 和 setter 在 Java 中,为私有属性提供 getter 和 setter 是标准做法。IDE(如 Eclipse、IntelliJ)可以自动生成:
public class Student {
private String name;
private int age;
private String id;
public String getName () {
return name;
}
public void setName (String name) {
this .name = name;
}
public int getAge () {
return age;
}
public void setAge (int age) {
if (age >= 6 && age <= 60 ) {
this .age = age;
}
}
public String getId () {
return id;
}
public void setId (String id) {
if (this .id == null ) {
this .id = id;
}
}
}
this 代表'当前对象自己'
this.name 指的是类的属性 name
不加 this 的 name 指的是方法的参数 name
当参数名和属性名相同时,必须用 this 来区分
2.6 实践练习:改造一个 C 语言风格的程序 struct Rectangle {
double width;
double height;
};
double calculateArea (struct Rectangle r) {
return r.width * r.height;
}
void setSize (struct Rectangle* r, double w, double h) {
r->width = w;
r->height = h;
}
public class Rectangle {
private double width;
private double height;
public Rectangle (double width, double height) {
setWidth(width);
setHeight(height);
}
public double getWidth () {
return width;
}
public double getHeight () {
return height;
}
public void setWidth (double width) {
if (width > 0 ) {
this .width = width;
} else {
System.out.println("宽度必须大于 0!" );
}
}
public void setHeight (double height) {
if (height > 0 ) {
this .height = height;
} else {
System.out.println("高度必须大于 0!" );
}
}
public double calculateArea () {
return width * height;
}
public double calculatePerimeter () {
return 2 * (width + height);
}
public static void main (String[] args) {
Rectangle rect = new Rectangle (5.0 , 3.0 );
System.out.println("宽度:" + rect.getWidth());
System.out.println("高度:" + rect.getHeight());
System.out.println("面积:" + rect.calculateArea());
System.out.println("周长:" + rect.calculatePerimeter());
rect.setWidth(10.0 );
rect.setHeight(5.0 );
System.out.println("新面积:" + rect.calculateArea());
rect.setWidth(-5.0 );
}
}
💡 本课要点总结
封装是什么 :把对象的属性和方法包装在一起,隐藏内部细节,只暴露必要的接口。
如何实现 :
使用 private 关键字保护属性
提供 public 的 getter 和 setter 方法
在方法中加入业务逻辑验证
访问修饰符 :
private:只有本类
默认:同包内
protected:同包内 + 子类
public:所有地方
封装的好处 :
安全性:防止不合理的数据
可控性:可以加入验证逻辑
维护性:内部修改不影响外部
简洁性:隐藏复杂实现
第三课:继承 —— 家族的智慧传承 同学真棒!已经坚持到第三课了。封装就像给每个对象穿上防护服,让它们安全又自律。今天我们要学习继承(Inheritance) ,这是面向对象最强大的特性之一,它能让我们写出优雅、简洁、可复用的代码。
📚 本章学习目标
理解继承的概念和意义
掌握 extends 关键字的用法
理解 super 关键字的三种用法
掌握方法重写(Override)的规则
掌握构造方法的继承规则(考试重点!)
了解 final 关键字的作用
3.1 从现实问题说起:代码重复的烦恼
class Student {
private String name;
private int age;
private String studentId;
private double gpa;
public void study () {
System.out.println(name + "正在学习..." );
}
public void takeExam () {
System.out.println(name + "正在参加考试" );
}
}
class Teacher {
private String name;
private int age;
private String teacherId;
private String subject;
public void teach () {
System.out.println(name + "正在教授" + subject);
}
public void gradeExam () {
System.out.println(name + "正在批改试卷" );
}
}
Student 和 Teacher 都有 name、age、id
都有相似的 getter/setter
都要写 displayInfo() 方法
这就像 :每次造汽车,都要重新发明轮子、方向盘、发动机...
3.2 继承的解决方案:家族的智慧 在现实世界中:孩子会继承父母的特征(眼睛颜色、身高...),也会继承父母的一些能力(语言、文化...),还可以发展自己的独特能力。
在 Java 中,我们可以创建'父类'(基类/超类)来存放公共特征:
class Person {
private String name;
private int age;
private String id;
public String getName () { return name; }
public void setName (String name) { this .name = name; }
public int getAge () { return age; }
public void setAge (int age) {
if (age > 0 && age < 150 ) { this .age = age; }
}
public String getId () { return id; }
public void setId (String id) { this .id = id; }
public void introduce () {
System.out.println("大家好,我是" + name + ",今年" + age + "岁" );
}
public void eat () { System.out.println(name + "正在吃饭" ); }
public void sleep () { System.out.println(name + "正在睡觉" ); }
}
class Student extends Person {
private String studentId;
private double gpa;
private String major;
public String getStudentId () { return studentId; }
public void setStudentId (String studentId) { this .studentId = studentId; }
public double getGpa () { return gpa; }
public void setGpa (double gpa) {
if (gpa >= 0 && gpa <= 4.0 ) { this .gpa = gpa; }
}
public String getMajor () { return major; }
public void setMajor (String major) { this .major = major; }
public void study () {
System.out.println(getName() + "正在学习" + major);
}
public void takeExam () {
System.out.println(getName() + "正在参加" + major + "考试" );
}
@Override
public void introduce () {
super .introduce();
System.out.println("我是" + major + "专业的学生,学号:" + studentId);
}
}
class Teacher extends Person {
private String teacherId;
private String subject;
private String title;
public String getTeacherId () { return teacherId; }
public void setTeacherId (String teacherId) { this .teacherId = teacherId; }
public String getSubject () { return subject; }
public void setSubject (String subject) { this .subject = subject; }
public String getTitle () { return title; }
public void setTitle (String title) { this .title = title; }
public void teach () {
System.out.println(getName() + "(" + title + ") 正在教授" + subject);
}
public void gradeExam () {
System.out.println(getName() + "正在批改" + subject + "试卷" );
}
@Override
public void introduce () {
super .introduce();
System.out.println("我是" + subject + "老师,职称:" + title);
}
}
测试类 public class InheritanceDemo {
public static void main (String[] args) {
Student stu = new Student ();
stu.setName("张三" );
stu.setAge(20 );
stu.setId("110101200001011234" );
stu.setStudentId("20230001" );
stu.setMajor("计算机科学" );
stu.setGpa(3.8 );
stu.eat();
stu.sleep();
stu.introduce();
stu.study();
stu.takeExam();
System.out.println("-------------------" );
Teacher tea = new Teacher ();
tea.setName("李教授" );
tea.setAge(45 );
tea.setId("110101197801011234" );
tea.setTeacherId("T001" );
tea.setSubject("面向对象程序设计" );
tea.setTitle("教授" );
tea.introduce();
tea.teach();
tea.gradeExam();
}
}
运行结果 :
张三正在吃饭 张三正在睡觉 大家好,我是张三,今年 20 岁 我是计算机科学专业的学生,学号:20230001 张三正在学习计算机科学 张三正在参加计算机科学考试 ------------------- 大家好,我是李教授,今年 45 岁 我是面向对象程序设计老师,职称:教授 李教授 (教授) 正在教授面向对象程序设计 李教授正在批改面向对象程序设计试卷
3.3 继承的核心概念详解
3.3.1 继承的语法
3.3.2 三个重要关键字
extends :表示继承关系
super :指代父类对象
super.属性:访问父类属性
super.方法 ():调用父类方法
super():调用父类构造方法(重要!)
@Override :注解,表示重写父类方法(可选但建议)
3.3.3 继承了什么?不能继承什么?
父类的 public 和 protected 属性和方法
父类的默认访问权限属性和方法(同包时)
父类的 private 属性和方法
父类的构造方法(但可以通过 super() 调用)
父类中被 final 修饰的方法
3.4 方法重写(Override)vs 方法重载(Overload)
方法重写(Override):子类重新定义父类的方法 class Animal {
public void makeSound () {
System.out.println("动物发出声音" );
}
}
class Dog extends Animal {
@Override
public void makeSound () {
System.out.println("汪汪汪!" );
}
}
class Cat extends Animal {
@Override
public void makeSound () {
System.out.println("喵喵喵!" );
}
}
方法重载(Overload):同一个类中有多个同名但参数不同的方法 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; }
}
特性 重写(Override) 重载(Overload) 发生位置 父子类之间 同一个类内部 方法名 必须相同 必须相同 参数列表 必须相同 必须不同 返回类型 必须相同或兼容 可以不同 访问权限 不能比父类更严格 可以不同 异常声明 不能抛出更宽泛的异常 可以不同
3.5 构造方法的继承规则(考试重点!)
规则 1:子类构造方法必须先调用父类构造方法 class Animal {
public Animal () {
System.out.println("Animal 构造方法" );
}
}
class Dog extends Animal {
public Dog () {
System.out.println("Dog 构造方法" );
}
}
public class Test1 {
public static void main (String[] args) {
Dog d = new Dog ();
}
}
规则 2:super() 必须在构造方法的第一行 class Animal {
private String name;
public Animal (String name) {
this .name = name;
System.out.println("Animal: " + name);
}
}
class Dog extends Animal {
private String breed;
public Dog (String name, String breed) {
super (name);
this .breed = breed;
System.out.println("Dog 品种:" + breed);
}
}
规则 3:如果父类没有无参构造,子类必须显式调用 super(参数) class Parent {
public Parent (String name) {
System.out.println("Parent: " + name);
}
}
class Child extends Parent {
public Child (String name) {
super (name);
System.out.println("Child: " + name);
}
public Child () {
super ("默认名字" );
System.out.println("Child 无参构造" );
}
}
规则 4:this() 和 super() 不能同时存在 class A {
public A () {
System.out.println("A()" );
}
public A (String msg) {
System.out.println("A: " + msg);
}
}
class B extends A {
public B () {
this ("调用本类其他构造" );
System.out.println("B()" );
}
public B (String msg) {
super (msg);
System.out.println("B: " + msg);
}
}
public class Test4 {
public static void main (String[] args) {
B b = new B ();
}
}
输出 :
A: 调用本类其他构造 B: 调用本类其他构造 B()
3.6 final 关键字在继承中的应用
1. final 类:不能被继承 final class FinalClass {
public void show () {
System.out.println("这是 final 类" );
}
}
2. final 方法:不能被重写 class Parent {
public final void cannotOverride () {
System.out.println("这是 final 方法" );
}
public void canOverride () {
System.out.println("这个方法可以重写" );
}
}
class Child extends Parent {
@Override
public void canOverride () {
System.out.println("子类重写了这个方法" );
}
}
3. final 属性:常量 class Constants {
public final int MAX_VALUE = 100 ;
public final String NAME;
public Constants (String name) {
this .NAME = name;
}
}
3.7 继承的好处与注意事项
好处:
代码复用 :不用重复写相同的代码
易于维护 :修改公共代码只需改父类
逻辑清晰 :类之间的关系明确
为多态打下基础
注意事项:
不要滥用继承 :只有'是一种'关系时才用继承
正确:Dog 是一种 Animal
错误:Student 是一种 Teacher
继承层次不宜过深 :一般不超过 3 层
3.8 实践:设计图形类家族
class Shape {
private String color;
public Shape (String color) {
this .color = color;
}
public double getArea () {
return 0 ;
}
public void display () {
System.out.print(color + "的图形" );
}
}
class Circle extends Shape {
private double radius;
public Circle (String color, double radius) {
super (color);
this .radius = radius;
}
@Override
public double getArea () {
return Math.PI * radius * radius;
}
@Override
public void display () {
super .display();
System.out.println(" - 圆形,半径:" + radius + ",面积:" + getArea());
}
}
class Rectangle extends Shape {
private double width;
private double height;
public Rectangle (String color, double width, double height) {
super (color);
this .width = width;
this .height = height;
}
@Override
public double getArea () {
return width * height;
}
@Override
public void display () {
super .display();
System.out.println(" - 矩形,宽:" + width + ",高:" + height + ",面积:" + getArea());
}
}
public class ShapeDemo {
public static void main (String[] args) {
Shape[] shapes = new Shape [3 ];
shapes[0 ] = new Circle ("红色" , 5.0 );
shapes[1 ] = new Rectangle ("蓝色" , 4.0 , 6.0 );
shapes[2 ] = new Circle ("绿色" , 3.0 );
for (Shape shape : shapes) {
shape.display();
}
}
}
💡 本课要点总结
继承是什么 :子类自动获得父类的属性和方法
extends 关键字 :建立继承关系
super 关键字 :
super.属性/方法 ():访问父类成员
super():调用父类构造方法
方法重写(Override) :子类重新实现父类方法
构造方法规则(考试重点!) :
子类构造方法必须先调用父类构造方法
super() 必须在第一行
父类没有无参构造时,必须显式调用 super(参数)
final 关键字 :
final class:不能被继承
final method:不能被重写
final field:常量
补充:构造方法(Constructor)是什么?
1. 什么是构造方法? 构造方法是创建对象时自动调用的特殊方法 ,用于初始化对象的属性 。
2. 构造方法的特点 public class Student {
private String name;
private int age;
public Student (String name, int age) {
this .name = name;
this .age = age;
System.out.println("Student 对象被创建了!" );
}
public static void main (String[] args) {
Student stu = new Student ("张三" , 20 );
}
}
3. 构造方法的几种形式
形式 1:无参构造方法 class Person {
private String name;
public Person () {
name = "未知" ;
System.out.println("无参构造方法被调用" );
}
}
Person p = new Person ();
形式 2:有参构造方法 class Person {
private String name;
private int age;
public Person (String name, int age) {
this .name = name;
this .age = age;
System.out.println("有参构造方法被调用:" + name);
}
}
Person p = new Person ("张三" , 20 );
形式 3:多个构造方法(重载) class Person {
private String name;
private int age;
public Person () {
this ("未知" , 0 );
}
public Person (String name) {
this (name, 0 );
}
public Person (String name, int age) {
this .name = name;
this .age = age;
}
}
Person p1 = new Person ();
Person p2 = new Person ("张三" );
Person p3 = new Person ("张三" , 20 );
4. 默认构造方法 如果你不写任何构造方法 ,Java 会自动提供一个无参构造方法 :
class Person {
private String name;
}
Person p = new Person ();
class Person {
private String name;
public Person () {
}
}
但是! 如果你写了任何一个构造方法 ,Java 就不再提供默认构造方法 :
class Person {
private String name;
public Person (String name) {
this .name = name;
}
}
Person p = new Person ("张三" );
📝 构造方法 vs 普通方法 特性 构造方法 普通方法 方法名 必须与类名相同 任意合法标识符 返回值 没有返回值类型 有返回值类型(或 void) 调用时机 创建对象时自动调用 通过对象显式调用 作用 初始化对象属性 执行具体功能 重载 可以重载 可以重载 继承 不能继承 可以继承
第四课:多态 —— 同一指令,不同反应
封装 :给数据穿上防护服
继承 :建立家族关系,代码复用
今天我们要学习面向对象的第三大支柱:多态(Polymorphism) 。这是最灵活、最神奇的特性,也是面向对象编程的精髓 !
📚 本章学习目标
理解多态的概念和意义
掌握多态的两种实现方式
理解向上转型和向下转型
掌握 instanceof 运算符的使用
理解抽象类和接口
4.1 什么是多态? 多态 :同一个方法调用,由于对象不同,可能有不同的行为。
现实世界的多态例子:
动物叫声 :都是'叫',但狗叫是'汪汪',猫叫是'喵喵'
交通工具 :都是'启动',但汽车是'引擎启动',电动车是'静音启动'
绘画 :都是'画',但画家画的是'油画',程序员画的是'流程图'
代码示例:没有多态的世界 class Dog {
public void makeSound () {
System.out.println("汪汪汪!" );
}
}
class Cat {
public void makeSound () {
System.out.println("喵喵喵!" );
}
}
public class NoPolymorphism {
public static void main (String[] args) {
Dog dog = new Dog ();
Cat cat = new Cat ();
dog.makeSound();
cat.makeSound();
}
}
代码示例:有多态的世界
class Animal {
public void makeSound () {
System.out.println("动物发出声音" );
}
}
class Dog extends Animal {
@Override
public void makeSound () {
System.out.println("汪汪汪!" );
}
}
class Cat extends Animal {
@Override
public void makeSound () {
System.out.println("喵喵喵!" );
}
}
class Bird extends Animal {
@Override
public void makeSound () {
System.out.println("叽叽喳喳!" );
}
}
public class WithPolymorphism {
public static void letAnimalSound (Animal animal) {
animal.makeSound();
}
public static void main (String[] args) {
Animal dog = new Dog ();
Animal cat = new Cat ();
Animal bird = new Bird ();
letAnimalSound(dog);
letAnimalSound(cat);
letAnimalSound(bird);
}
}
神奇之处 :letAnimalSound() 方法接收 Animal 类型,但实际传入的是 Dog、Cat、Bird 对象,却能调用它们各自重写的 makeSound() 方法!
4.2 多态的实现原理
核心概念:向上转型 Animal animal = new Dog ();
编译时类型 (左边):Animal
运行时类型 (右边):Dog
编译看左边,运行看右边
详细示例: class Animal {
public void eat () {
System.out.println("动物吃东西" );
}
}
class Dog extends Animal {
@Override
public void eat () {
System.out.println("狗吃骨头" );
}
public void watchHouse () {
System.out.println("狗看家" );
}
}
public class PolymorphismDemo {
public static void main (String[] args) {
Dog dog1 = new Dog ();
dog1.eat();
dog1.watchHouse();
Animal animal = new Dog ();
animal.eat();
}
}
编译时 :Java 编译器只看引用类型 (左边的类型)
运行时 :JVM 实际调用的是对象类型 (右边的类型)的方法
4.3 多态的两大体现
体现 1:方法重写(Override) class Shape {
public void draw () {
System.out.println("绘制形状" );
}
}
class Circle extends Shape {
@Override
public void draw () {
System.out.println("绘制圆形" );
}
}
class Rectangle extends Shape {
@Override
public void draw () {
System.out.println("绘制矩形" );
}
}
public class DrawingBoard {
public static void main (String[] args) {
Shape[] shapes = new Shape [3 ];
shapes[0 ] = new Circle ();
shapes[1 ] = new Rectangle ();
shapes[2 ] = new Circle ();
for (Shape shape : shapes) {
shape.draw();
}
}
}
体现 2:父类作为方法参数 class Employee {
public void work () {
System.out.println("员工工作" );
}
}
class Programmer extends Employee {
@Override
public void work () {
System.out.println("程序员写代码" );
}
}
class Manager extends Employee {
@Override
public void work () {
System.out.println("经理管理团队" );
}
}
class Company {
public void letWork (Employee emp) {
emp.work();
}
}
public class CompanyDemo {
public static void main (String[] args) {
Company company = new Company ();
Employee emp1 = new Programmer ();
Employee emp2 = new Manager ();
Employee emp3 = new Employee ();
company.letWork(emp1);
company.letWork(emp2);
company.letWork(emp3);
}
}
4.4 向下转型与 instanceof 运算符
问题:向上转型后,如何调用子类特有方法? Animal animal = new Dog ();
解决方案 1:向下转型(有风险) Animal animal = new Dog ();
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.watchHouse();
}
Animal animal2 = new Cat ();
解决方案 2:instanceof 运算符 public class TypeCheckDemo {
public static void main (String[] args) {
Animal animal1 = new Dog ();
Animal animal2 = new Cat ();
Animal animal3 = new Animal ();
System.out.println(animal1 instanceof Dog);
System.out.println(animal1 instanceof Animal);
System.out.println(animal1 instanceof Cat);
System.out.println(animal2 instanceof Cat);
System.out.println(animal2 instanceof Animal);
System.out.println(animal3 instanceof Dog);
System.out.println(animal3 instanceof Animal);
if (animal1 instanceof Dog) {
Dog dog = (Dog) animal1;
dog.watchHouse();
}
if (animal1 instanceof Dog) {
System.out.println("这是一只狗" );
} else if (animal1 instanceof Cat) {
System.out.println("这是一只猫" );
} else {
System.out.println("这是普通动物" );
}
}
}
4.5 多态的应用:抽象类
问题:Animal 类的 makeSound() 方法应该怎么实现? class Animal {
public void makeSound () {
}
}
解决方案:抽象类
abstract class Animal {
public abstract void makeSound () ;
public void eat () {
System.out.println("动物吃东西" );
}
}
class Dog extends Animal {
@Override
public void makeSound () {
System.out.println("汪汪汪!" );
}
}
class Cat extends Animal {
@Override
public void makeSound () {
System.out.println("喵喵喵!" );
}
}
public class AbstractDemo {
public static void main (String[] args) {
Animal dog = new Dog ();
Animal cat = new Cat ();
dog.makeSound();
cat.makeSound();
Animal[] animals = {new Dog (), new Cat ()};
for (Animal animal : animals) {
animal.makeSound();
}
}
}
抽象类的特点:
用 abstract 修饰 :abstract class 类名
可以包含抽象方法 :public abstract 返回类型 方法名 ();
不能创建对象 :只能被继承
子类必须实现所有抽象方法 (除非子类也是抽象类)
可以有构造方法 :用于子类初始化
4.6 多态的终极形式:接口
接口是什么? 接口是纯粹的抽象 ,定义了一组规范(方法声明),但不提供实现。
为什么需要接口? Java 只支持单继承,但可以通过接口实现'多继承'的效果。
interface Flyable {
void fly () ;
default void takeoff () {
System.out.println("准备起飞" );
}
static void showInfo () {
System.out.println("这是一个飞行接口" );
}
}
interface Swimmable {
void swim () ;
}
class Duck extends Animal implements Flyable , Swimmable {
@Override
public void makeSound () {
System.out.println("嘎嘎嘎!" );
}
@Override
public void fly () {
System.out.println("鸭子飞" );
}
@Override
public void swim () {
System.out.println("鸭子游泳" );
}
}
class Airplane implements Flyable {
@Override
public void fly () {
System.out.println("飞机飞行" );
}
}
public class InterfaceDemo {
public static void main (String[] args) {
Flyable flyable1 = new Duck ();
Flyable flyable2 = new Airplane ();
flyable1.fly();
flyable2.fly();
flyable1.takeoff();
Flyable.showInfo();
Duck duck = new Duck ();
duck.swim();
}
}
接口 vs 抽象类 特性 接口 抽象类 定义关键字 interfaceabstract class方法 只有抽象方法(JDK8 前) 可以有抽象和具体方法 变量 只能是常量(public static final) 可以是普通变量 继承 可以实现多个接口 只能继承一个类 构造方法 没有 有 设计理念 '能做什么'(功能) '是什么'(本质)
4.7 多态的好处
好处 1:提高代码的可扩展性
class PaymentSystem {
public void processPayment (Payment payment) {
payment.pay();
}
}
interface Payment {
void pay () ;
}
class CreditCardPayment implements Payment {
@Override
public void pay () {
System.out.println("信用卡支付" );
}
}
class WeChatPayment implements Payment {
@Override
public void pay () {
System.out.println("微信支付" );
}
}
class AlipayPayment implements Payment {
@Override
public void pay () {
System.out.println("支付宝支付" );
}
}
好处 2:提高代码的灵活性
public void drawShapes (List<Shape> shapes) {
for (Shape shape : shapes) {
shape.draw();
}
}
List<Shape> shapes = new ArrayList <>();
shapes.add(new Circle ());
shapes.add(new Rectangle ());
shapes.add(new Triangle ());
drawShapes(shapes);
好处 3:降低耦合度
class Computer {
public void connectUSB (USB usb) {
usb.connect();
}
}
interface USB {
void connect () ;
}
class Mouse implements USB { }
class Keyboard implements USB { }
class Printer implements USB { }
💡 本课要点总结
多态是什么 :同一方法调用,不同对象有不同行为
多态的实现 :
方法重写(Override)
父类引用指向子类对象(向上转型)
向上转型 :父类 引用 = new 子类 ();
向下转型 :子类 引用 = (子类) 父类引用 ;
抽象类 :
用 abstract 修饰
不能创建对象
可以有抽象方法和具体方法
接口 :
用 interface 定义
实现多继承效果
定义规范,不关注实现
多态的好处 :
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online